ubuntu-push-0.2.1+14.04.20140423.1/0000755000015301777760000000000012325725172016553 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/Makefile0000644000015301777760000000353012325724711020212 0ustar pbusernogroup00000000000000GOPATH := $(shell cd ../../..; pwd) export GOPATH PROJECT = launchpad.net/ubuntu-push ifneq ($(CURDIR),$(GOPATH)/src/launchpad.net/ubuntu-push) $(error unexpected curdir and/or layout) endif GODEPS = launchpad.net/gocheck GODEPS += launchpad.net/go-dbus/v1 GODEPS += launchpad.net/go-xdg/v0 GODEPS += code.google.com/p/gosqlite/sqlite3 TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance|grep -v http13client ) bootstrap: mkdir -p $(GOPATH)/bin mkdir -p $(GOPATH)/pkg go get -u launchpad.net/godeps go get -d -u $(GODEPS) $(GOPATH)/bin/godeps -u dependencies.tsv go install $(GODEPS) check: go test $(TESTFLAGS) $(TOTEST) check-race: go test $(TESTFLAGS) -race $(TOTEST) acceptance: cd server/acceptance; ./acceptance.sh build-client: go build ubuntu-push-client.go build-server-dev: go build -o push-server-dev launchpad.net/ubuntu-push/server/dev run-server-dev: go run server/dev/*.go sampleconfigs/dev.json coverage-summary: go test $(TESTFLAGS) -a -cover $(TOTEST) coverage-html: mkdir -p coverhtml for pkg in $(TOTEST); do \ relname="$${pkg#$(PROJECT)/}" ; \ mkdir -p coverhtml/$$(dirname $${relname}) ; \ go test $(TESTFLAGS) -a -coverprofile=coverhtml/$${relname}.out $$pkg ; \ if [ -f coverhtml/$${relname}.out ] ; then \ go tool cover -html=coverhtml/$${relname}.out -o coverhtml/$${relname}.html ; \ go tool cover -func=coverhtml/$${relname}.out -o coverhtml/$${relname}.txt ; \ fi \ done format: go fmt $(PROJECT)/... check-format: scripts/check_fmt $(PROJECT) protocol-diagrams: protocol/state-diag-client.svg protocol/state-diag-session.svg %.svg: %.gv # requires graphviz installed dot -Tsvg $< > $@ .PHONY: bootstrap check check-race format check-format \ acceptance build-client build server-dev run-server-dev \ coverage-summary coverage-html protocol-diagrams ubuntu-push-0.2.1+14.04.20140423.1/util/0000755000015301777760000000000012325725172017530 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/util/redialer.go0000644000015301777760000000756312325724711021657 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package util contains the redialer. package util import ( "sync" "time" ) // A Dialer is an object that knows how to establish a connection, and // where you'd usually want some kind of backoff if that connection // fails. type Dialer interface { Dial() error } // A Jitterer is a Dialer that wants to vary the backoff a little (to avoid a // thundering herd, for example). type Jitterer interface { Dialer Jitter(time.Duration) time.Duration } // The timeouts used during backoff. var timeouts []time.Duration var trwlock sync.RWMutex // Retrieve the list of timeouts used for exponential backoff. func Timeouts() []time.Duration { trwlock.RLock() defer trwlock.RUnlock() return timeouts } // For testing: change the default timeouts with the provided ones, // returning the defaults (the idea being you reset them on test // teardown). func SwapTimeouts(newTimeouts []time.Duration) (oldTimeouts []time.Duration) { trwlock.Lock() defer trwlock.Unlock() oldTimeouts, timeouts = timeouts, newTimeouts return } // An AutoRedialer's Redial() method retries its dialer's Dial() method until // it stops returning an error. It does exponential backoff (optionally // jittered). type AutoRedialer interface { Redial() uint32 // Redial keeps on calling Dial until it stops returning an error. Stop() // Stop shuts down the given AutoRedialer, if it is still retrying. } type autoRedialer struct { stop chan bool lock sync.RWMutex dial func() error jitter func(time.Duration) time.Duration } func (ar *autoRedialer) Stop() { if ar != nil { ar.lock.RLock() defer ar.lock.RUnlock() if ar.stop != nil { ar.stop <- true } } } func (ar *autoRedialer) shutdown() { ar.lock.Lock() defer ar.lock.Unlock() close(ar.stop) ar.stop = nil } // Redial keeps on calling Dial until it stops returning an error. It does // exponential backoff, adding back the output of Jitter at each step. func (ar *autoRedialer) Redial() uint32 { if ar == nil { // at least it's better than a segfault... panic("you can't Redial a nil AutoRedialer") } if ar.stop == nil { panic("this AutoRedialer has already been shut down") } defer ar.shutdown() ar.lock.RLock() stop := ar.stop ar.lock.RUnlock() var timeout time.Duration var dialAttempts uint32 = 0 // unsigned so it can wrap safely ... timeouts := Timeouts() var numTimeouts uint32 = uint32(len(timeouts)) for { if ar.dial() == nil { return dialAttempts + 1 } if dialAttempts < numTimeouts { timeout = timeouts[dialAttempts] } else { timeout = timeouts[numTimeouts-1] } if ar.jitter != nil { timeout += ar.jitter(timeout) } dialAttempts++ select { case <-stop: return dialAttempts case <-time.After(timeout): } } } // Returns a stoppable AutoRedialer using the provided Dialer. If the Dialer // is also a Jitterer, the backoff will be jittered. func NewAutoRedialer(dialer Dialer) AutoRedialer { ar := &autoRedialer{stop: make(chan bool), dial: dialer.Dial} jitterer, ok := dialer.(Jitterer) if ok { ar.jitter = jitterer.Jitter } return ar } func init() { ps := []int{1, 2, 5, 11, 19, 37, 67, 113, 191} // 3 pₙ₊₁ ≥ 5 pₙ timeouts := make([]time.Duration, len(ps)) for i, n := range ps { timeouts[i] = time.Duration(n) * time.Second } SwapTimeouts(timeouts) } ubuntu-push-0.2.1+14.04.20140423.1/util/redialer_test.go0000644000015301777760000000574312325724711022714 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package util import ( . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/bus" testibus "launchpad.net/ubuntu-push/bus/testing" "launchpad.net/ubuntu-push/testing/condition" "testing" "time" ) // hook up gocheck func TestRedialer(t *testing.T) { TestingT(t) } type RedialerSuite struct { timeouts []time.Duration } var _ = Suite(&RedialerSuite{}) func (s *RedialerSuite) SetUpSuite(c *C) { s.timeouts = SwapTimeouts([]time.Duration{0, 0}) } func (s *RedialerSuite) TearDownSuite(c *C) { SwapTimeouts(s.timeouts) s.timeouts = nil } // Redial() tests func (s *RedialerSuite) TestWorks(c *C) { endp := testibus.NewTestingEndpoint(condition.Fail2Work(3), nil) ar := NewAutoRedialer(endp) c.Check(ar.(*autoRedialer).stop, NotNil) c.Check(ar.Redial(), Equals, uint32(4)) // and on success, the stopper goes away c.Check(ar.(*autoRedialer).stop, IsNil) } func (s *RedialerSuite) TestRetryNil(c *C) { var ar *autoRedialer c.Check(ar.Redial, Not(PanicMatches), ".* nil pointer dereference") } func (s *RedialerSuite) TestRetryTwice(c *C) { endp := testibus.NewTestingEndpoint(condition.Work(true), nil) ar := NewAutoRedialer(endp) c.Check(ar.Redial(), Equals, uint32(1)) c.Check(ar.Redial, PanicMatches, ".*shut.?down.*") } type JitteringEndpoint struct { bus.Endpoint jittered int } func (j *JitteringEndpoint) Jitter(time.Duration) time.Duration { j.jittered++ return 0 } func (s *RedialerSuite) TestJitterWorks(c *C) { endp := &JitteringEndpoint{ testibus.NewTestingEndpoint(condition.Fail2Work(3), nil), 0, } ar := NewAutoRedialer(endp) c.Check(ar.Redial(), Equals, uint32(4)) c.Check(endp.jittered, Equals, 3) } // Stop() tests func (s *RedialerSuite) TestStopWorksOnNil(c *C) { // as a convenience, Stop() should succeed on nil // (a nil retrier certainly isn't retrying!) var ar *autoRedialer c.Check(ar, IsNil) ar.Stop() // nothing happens } func (s *RedialerSuite) TestStopStops(c *C) { endp := testibus.NewTestingEndpoint(condition.Work(false), nil) countCh := make(chan uint32) ar := NewAutoRedialer(endp) go func() { countCh <- ar.Redial() }() ar.Stop() select { case n := <-countCh: c.Check(n, Equals, uint32(1)) case <-time.After(20 * time.Millisecond): c.Fatal("timed out waiting for redial") } // on Stop(), the stopper goes away too c.Check(ar.(*autoRedialer).stop, IsNil) // and the next Stop() doesn't panic nor block ar.Stop() } ubuntu-push-0.2.1+14.04.20140423.1/whoopsie/0000755000015301777760000000000012325725172020410 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/whoopsie/doc.go0000644000015301777760000000125512325724711021505 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package whoopsie wraps libwhoopsie. package whoopsie ubuntu-push-0.2.1+14.04.20140423.1/whoopsie/identifier/0000755000015301777760000000000012325725172022532 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/whoopsie/identifier/identifier.go0000644000015301777760000000413712325724720025206 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package identifier wraps libwhoopsie, and is thus the // source of an anonymous and stable system id used by the Ubuntu // error tracker and the Ubuntu push notifications service. package identifier /* #cgo pkg-config: libwhoopsie #include #include */ import "C" import "unsafe" import "errors" import "time" // an Id knows how to generate itself, and how to stringify itself. type Id interface { Generate() error String() string } // Identifier is the default Id implementation. type Identifier struct { value string generator func(**C.char, **C.GError) } func generator(csp **C.char, errp **C.GError) { C.whoopsie_identifier_generate(csp, errp) } // New creates an Identifier, but does not call Generate() on it. func New() Id { return &Identifier{generator: generator} } // Generate makes the Identifier create the identifier itself. func (id *Identifier) Generate() error { var gerr *C.GError var cs *C.char defer C.g_free((C.gpointer)(unsafe.Pointer(cs))) for i := 0; i < 200; i++ { id.generator(&cs, &gerr) if cs != nil || gerr != nil { goto SuccessMaybe } time.Sleep(600 * time.Millisecond) } return errors.New("whoopsie_identifier_generate still bad after 2m; giving up") SuccessMaybe: if gerr != nil { return errors.New(C.GoString((*C.char)(gerr.message))) } else { id.value = C.GoString(cs) return nil } } // String returns the system identifier as a string. func (id *Identifier) String() string { return id.value } ubuntu-push-0.2.1+14.04.20140423.1/whoopsie/identifier/testing/0000755000015301777760000000000012325725172024207 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/whoopsie/identifier/testing/testing_test.go0000644000015301777760000000401212325724711027245 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package testing import ( identifier ".." . "launchpad.net/gocheck" "testing" ) // hook up gocheck func Test(t *testing.T) { TestingT(t) } type IdentifierSuite struct{} var _ = Suite(&IdentifierSuite{}) // TestSettableDefaultValueVisible tests that SettableIdentifier's default // value is notable. func (s *IdentifierSuite) TestSettableDefaultValueVisible(c *C) { id := Settable() c.Check(id.String(), Equals, "") } // TestSettableSets tests that SettableIdentifier is settable. func (s *IdentifierSuite) TestSettableSets(c *C) { id := Settable() id.Set("hello") c.Check(id.String(), Equals, "hello") } // TestSettableGenerateDoesNotFail tests that SettableIdentifier's Generate // does not fail. func (s *IdentifierSuite) TestSettableGenerateDoesNotFail(c *C) { id := Settable() c.Check(id.Generate(), Equals, nil) } // TestFailingFails tests that FailingIdentifier fails. func (s *IdentifierSuite) TestFailingFails(c *C) { id := Failing() c.Check(id.Generate(), Not(Equals), nil) } // TestFailingStringNotEmpty tests that FailingIdentifier still has a // non-empty string. func (s *IdentifierSuite) TestFailingStringNotEmpty(c *C) { id := Failing() c.Check(id.String(), Equals, "") } // TestIdentifierInterface tests that FailingIdentifier and // SettableIdentifier implement Id. func (s *IdentifierSuite) TestIdentifierInterface(c *C) { _ = []identifier.Id{Failing(), Settable()} } ubuntu-push-0.2.1+14.04.20140423.1/whoopsie/identifier/testing/testing.go0000644000015301777760000000371012325724711026212 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package testing implements a couple of Ids that are useful // for testing things that use whoopsie/identifier. package testing import "errors" // SettableIdentifier is an Id that lets you set the value of the identifier. // // By default the identifier's value is "", so it's visible // if you're misusing it. type SettableIdentifier struct { value string } // Settable is the constructor for SettableIdentifier. func Settable() *SettableIdentifier { return &SettableIdentifier{""} } // Set is the method you use to set the identifier. func (sid *SettableIdentifier) Set(value string) { sid.value = value } // Generate does nothing. func (sid *SettableIdentifier) Generate() error { return nil } // String returns the string you set. func (sid *SettableIdentifier) String() string { return sid.value } // FailingIdentifier is an Id that always fails to generate. type FailingIdentifier struct{} // Failing is the constructor for FailingIdentifier. func Failing() *FailingIdentifier { return &FailingIdentifier{} } // Generate fails with an ubiquitous error. func (*FailingIdentifier) Generate() error { return errors.New("lp0 on fire") } // String returns "". // // The purpose of this is to make it easy to spot if you're using it // by accident. func (*FailingIdentifier) String() string { return "" } ubuntu-push-0.2.1+14.04.20140423.1/whoopsie/identifier/identifier_test.go0000644000015301777760000000310312325724720026235 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package identifier import ( . "launchpad.net/gocheck" "testing" ) // hook up gocheck func Test(t *testing.T) { TestingT(t) } type IdentifierSuite struct{} var _ = Suite(&IdentifierSuite{}) // TestGenerate checks that Generate() does not fail, and returns a // 128-byte string. func (s *IdentifierSuite) TestGenerate(c *C) { id := New() c.Check(id.Generate(), Equals, nil) c.Check(id.String(), HasLen, 128) } // TestIdentifierInterface checks that Identifier implements Id. func (s *IdentifierSuite) TestIdentifierInterface(c *C) { _ = []Id{New()} } // TestFailure checks that Identifier survives whoopsie shenanigans func (s *IdentifierSuite) TestIdentifierSurvivesShenanigans(c *C) { count := 0 // using _Ctype* as a workaround for gocheck also having a C gen := func(csp **_Ctype_char, errp **_Ctype_GError) { count++ if count > 3 { generator(csp, errp) } } id := &Identifier{generator: gen} id.Generate() c.Check(id.String(), HasLen, 128) } ubuntu-push-0.2.1+14.04.20140423.1/external/0000755000015301777760000000000012325725172020375 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/external/murmur3/0000755000015301777760000000000012325725172022007 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/external/murmur3/murmur128.go0000644000015301777760000000675612325724711024134 0ustar pbusernogroup00000000000000package murmur3 import ( //"encoding/binary" "hash" "unsafe" ) const ( c1_128 = 0x87c37b91114253d5 c2_128 = 0x4cf5ad432745937f ) // Make sure interfaces are correctly implemented. var ( _ hash.Hash = new(digest128) _ Hash128 = new(digest128) _ bmixer = new(digest128) ) // Hack: the standard api doesn't define any Hash128 interface. type Hash128 interface { hash.Hash Sum128() (uint64, uint64) } // digest128 represents a partial evaluation of a 128 bites hash. type digest128 struct { digest h1 uint64 // Unfinalized running hash part 1. h2 uint64 // Unfinalized running hash part 2. } func New128() Hash128 { d := new(digest128) d.bmixer = d d.Reset() return d } func (d *digest128) Size() int { return 16 } func (d *digest128) reset() { d.h1, d.h2 = 1, 1 } func (d *digest128) Sum(b []byte) []byte { h1, h2 := d.h1, d.h2 return append(b, byte(h1>>56), byte(h1>>48), byte(h1>>40), byte(h1>>32), byte(h1>>24), byte(h1>>16), byte(h1>>8), byte(h1), byte(h2>>56), byte(h2>>48), byte(h2>>40), byte(h2>>32), byte(h2>>24), byte(h2>>16), byte(h2>>8), byte(h2), ) } func (d *digest128) bmix(p []byte) (tail []byte) { h1, h2 := d.h1, d.h2 nblocks := len(p) / 16 for i := 0; i < nblocks; i++ { t := (*[2]uint64)(unsafe.Pointer(&p[i*16])) k1, k2 := t[0], t[1] k1 *= c1_128 k1 = (k1 << 31) | (k1 >> 33) // rotl64(k1, 31) k1 *= c2_128 h1 ^= k1 h1 = (h1 << 27) | (h1 >> 37) // rotl64(h1, 27) h1 += h2 h1 = h1*5 + 0x52dce729 k2 *= c2_128 k2 = (k2 << 33) | (k2 >> 31) // rotl64(k2, 33) k2 *= c1_128 h2 ^= k2 h2 = (h2 << 31) | (h2 >> 33) // rotl64(h2, 31) h2 += h1 h2 = h2*5 + 0x38495ab5 } d.h1, d.h2 = h1, h2 return p[nblocks*d.Size():] } func (d *digest128) Sum128() (h1, h2 uint64) { h1, h2 = d.h1, d.h2 var k1, k2 uint64 switch len(d.tail) & 15 { case 15: k2 ^= uint64(d.tail[14]) << 48 fallthrough case 14: k2 ^= uint64(d.tail[13]) << 40 fallthrough case 13: k2 ^= uint64(d.tail[12]) << 32 fallthrough case 12: k2 ^= uint64(d.tail[11]) << 24 fallthrough case 11: k2 ^= uint64(d.tail[10]) << 16 fallthrough case 10: k2 ^= uint64(d.tail[9]) << 8 fallthrough case 9: k2 ^= uint64(d.tail[8]) << 0 k2 *= c2_128 k2 = (k2 << 33) | (k2 >> 31) // rotl64(k2, 33) k2 *= c1_128 h2 ^= k2 fallthrough case 8: k1 ^= uint64(d.tail[7]) << 56 fallthrough case 7: k1 ^= uint64(d.tail[6]) << 48 fallthrough case 6: k1 ^= uint64(d.tail[5]) << 40 fallthrough case 5: k1 ^= uint64(d.tail[4]) << 32 fallthrough case 4: k1 ^= uint64(d.tail[3]) << 24 fallthrough case 3: k1 ^= uint64(d.tail[2]) << 16 fallthrough case 2: k1 ^= uint64(d.tail[1]) << 8 fallthrough case 1: k1 ^= uint64(d.tail[0]) << 0 k1 *= c1_128 k1 = (k1 << 31) | (k1 >> 33) // rotl64(k1, 31) k1 *= c2_128 h1 ^= k1 } h1 ^= uint64(d.clen) h2 ^= uint64(d.clen) h1 += h2 h2 += h1 h1 = fmix64(h1) h2 = fmix64(h2) h1 += h2 h2 += h1 return h1, h2 } func fmix64(k uint64) uint64 { k ^= k >> 33 k *= 0xff51afd7ed558ccd k ^= k >> 33 k *= 0xc4ceb9fe1a85ec53 k ^= k >> 33 return k } /* func rotl64(x uint64, r byte) uint64 { return (x << r) | (x >> (64 - r)) } */ // Sum128 returns the MurmurHash3 sum of data. It is equivalent to the // following sequence (without the extra burden and the extra allocation): // hasher := New128() // hasher.Write(data) // return hasher.Sum128() func Sum128(data []byte) (h1 uint64, h2 uint64) { d := &digest128{h1: 1, h2: 1} d.tail = d.bmix(data) d.clen = len(data) return d.Sum128() } ubuntu-push-0.2.1+14.04.20140423.1/external/murmur3/murmur64.go0000644000015301777760000000170612325724711024041 0ustar pbusernogroup00000000000000package murmur3 import ( "hash" ) // Make sure interfaces are correctly implemented. var ( _ hash.Hash = new(digest64) _ hash.Hash64 = new(digest64) _ bmixer = new(digest64) ) // digest64 is half a digest128. type digest64 digest128 func New64() hash.Hash64 { d := (*digest64)(New128().(*digest128)) return d } func (d *digest64) Sum(b []byte) []byte { h1 := d.h1 return append(b, byte(h1>>56), byte(h1>>48), byte(h1>>40), byte(h1>>32), byte(h1>>24), byte(h1>>16), byte(h1>>8), byte(h1)) } func (d *digest64) Sum64() uint64 { h1, _ := (*digest128)(d).Sum128() return h1 } // Sum64 returns the MurmurHash3 sum of data. It is equivalent to the // following sequence (without the extra burden and the extra allocation): // hasher := New64() // hasher.Write(data) // return hasher.Sum64() func Sum64(data []byte) uint64 { d := &digest128{h1: 1, h2: 1} d.tail = d.bmix(data) d.clen = len(data) h1, _ := d.Sum128() return h1 } ubuntu-push-0.2.1+14.04.20140423.1/external/murmur3/murmur.go0000644000015301777760000000303012325724711023657 0ustar pbusernogroup00000000000000// Copyright 2013, Sébastien Paolacci. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /* Native (and fast) implementation of Austin Appleby's MurmurHash3. Package murmur3 implements Austin Appleby's non-cryptographic MurmurHash3. Reference implementation: http://code.google.com/p/smhasher/wiki/MurmurHash3 History, characteristics and (legacy) perfs: https://sites.google.com/site/murmurhash/ https://sites.google.com/site/murmurhash/statistics */ package murmur3 type bmixer interface { bmix(p []byte) (tail []byte) Size() (n int) reset() } type digest struct { clen int // Digested input cumulative length. tail []byte // 0 to Size()-1 bytes view of `buf'. buf [16]byte // Expected (but not required) to be Size() large. bmixer } func (d *digest) BlockSize() int { return 1 } func (d *digest) Write(p []byte) (n int, err error) { n = len(p) d.clen += n if len(d.tail) > 0 { // Stick back pending bytes. nfree := d.Size() - len(d.tail) // nfree ∈ [1, d.Size()-1]. if nfree < len(p) { // One full block can be formed. block := append(d.tail, p[:nfree]...) p = p[nfree:] _ = d.bmix(block) // No tail. } else { // Tail's buf is large enough to prevent reallocs. p = append(d.tail, p...) } } d.tail = d.bmix(p) // Keep own copy of the 0 to Size()-1 pending bytes. nn := copy(d.buf[:], d.tail) d.tail = d.buf[:nn] return n, nil } func (d *digest) Reset() { d.clen = 0 d.tail = nil d.bmixer.reset() } ubuntu-push-0.2.1+14.04.20140423.1/external/murmur3/murmur32.go0000644000015301777760000000535012325724711024033 0ustar pbusernogroup00000000000000package murmur3 // http://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/hash/Murmur3_32HashFunction.java import ( "hash" "unsafe" ) // Make sure interfaces are correctly implemented. var ( _ hash.Hash = new(digest32) _ hash.Hash32 = new(digest32) ) const ( c1_32 uint32 = 0xcc9e2d51 c2_32 uint32 = 0x1b873593 ) // digest32 represents a partial evaluation of a 32 bites hash. type digest32 struct { digest h1 uint32 // Unfinalized running hash. } func New32() hash.Hash32 { d := new(digest32) d.bmixer = d d.Reset() return d } func (d *digest32) Size() int { return 4 } func (d *digest32) reset() { d.h1 = 1 } func (d *digest32) Sum(b []byte) []byte { h := d.h1 return append(b, byte(h>>24), byte(h>>16), byte(h>>8), byte(h)) } // Digest as many blocks as possible. func (d *digest32) bmix(p []byte) (tail []byte) { h1 := d.h1 nblocks := len(p) / 4 for i := 0; i < nblocks; i++ { k1 := *(*uint32)(unsafe.Pointer(&p[i*4])) k1 *= c1_32 k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) k1 *= c2_32 h1 ^= k1 h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13) h1 = h1*5 + 0xe6546b64 } d.h1 = h1 return p[nblocks*d.Size():] } func (d *digest32) Sum32() (h1 uint32) { h1 = d.h1 var k1 uint32 switch len(d.tail) & 3 { case 3: k1 ^= uint32(d.tail[2]) << 16 fallthrough case 2: k1 ^= uint32(d.tail[1]) << 8 fallthrough case 1: k1 ^= uint32(d.tail[0]) k1 *= c1_32 k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) k1 *= c2_32 h1 ^= k1 } h1 ^= uint32(d.clen) h1 ^= h1 >> 16 h1 *= 0x85ebca6b h1 ^= h1 >> 13 h1 *= 0xc2b2ae35 h1 ^= h1 >> 16 return h1 } /* func rotl32(x uint32, r byte) uint32 { return (x << r) | (x >> (32 - r)) } */ // Sum32 returns the MurmurHash3 sum of data. It is equivalent to the // following sequence (without the extra burden and the extra allocation): // hasher := New32() // hasher.Write(data) // return hasher.Sum32() func Sum32(data []byte) uint32 { var h1 uint32 = 1 nblocks := len(data) / 4 var p uintptr if len(data) > 0 { p = uintptr(unsafe.Pointer(&data[0])) } p1 := p + uintptr(4*nblocks) for ; p < p1; p += 4 { k1 := *(*uint32)(unsafe.Pointer(p)) k1 *= c1_32 k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) k1 *= c2_32 h1 ^= k1 h1 = (h1 << 13) | (h1 >> 19) // rotl32(h1, 13) h1 = h1*5 + 0xe6546b64 } tail := data[nblocks*4:] var k1 uint32 switch len(tail) & 3 { case 3: k1 ^= uint32(tail[2]) << 16 fallthrough case 2: k1 ^= uint32(tail[1]) << 8 fallthrough case 1: k1 ^= uint32(tail[0]) k1 *= c1_32 k1 = (k1 << 15) | (k1 >> 17) // rotl32(k1, 15) k1 *= c2_32 h1 ^= k1 } h1 ^= uint32(len(data)) h1 ^= h1 >> 16 h1 *= 0x85ebca6b h1 ^= h1 >> 13 h1 *= 0xc2b2ae35 h1 ^= h1 >> 16 return h1 } ubuntu-push-0.2.1+14.04.20140423.1/external/murmur3/README.md0000644000015301777760000000435212325724711023270 0ustar pbusernogroup00000000000000murmur3 ======= Native Go implementation of Austin Appleby's third MurmurHash revision (aka MurmurHash3). Reference algorithm has been slightly hacked as to support the streaming mode required by Go's standard [Hash interface](http://golang.org/pkg/hash/#Hash). Benchmarks ---------- Go tip as of 2013-03-11 (i.e almost go1.1), core i7 @ 3.4 Ghz. All runs include hasher instanciation and sequence finalization.

Benchmark32_1       200000000         8.4 ns/op       119.39 MB/s
Benchmark32_2       200000000         9.5 ns/op       211.69 MB/s
Benchmark32_4       500000000         7.9 ns/op       506.24 MB/s
Benchmark32_8       200000000         9.4 ns/op       853.40 MB/s
Benchmark32_16      100000000        12.1 ns/op      1324.19 MB/s
Benchmark32_32      100000000        18.2 ns/op      1760.81 MB/s
Benchmark32_64       50000000        31.2 ns/op      2051.59 MB/s
Benchmark32_128      50000000        58.7 ns/op      2180.34 MB/s
Benchmark32_256      20000000       116.0 ns/op      2194.85 MB/s
Benchmark32_512      10000000       227.0 ns/op      2247.43 MB/s
Benchmark32_1024      5000000       449.0 ns/op      2276.88 MB/s
Benchmark32_2048      2000000       894.0 ns/op      2289.87 MB/s
Benchmark32_4096      1000000      1792.0 ns/op      2284.64 MB/s
Benchmark32_8192       500000      3559.0 ns/op      2301.33 MB/s

Benchmark128_1       50000000        33.2 ns/op        30.15 MB/s
Benchmark128_2       50000000        33.3 ns/op        59.99 MB/s
Benchmark128_4       50000000        35.4 ns/op       112.88 MB/s
Benchmark128_8       50000000        36.6 ns/op       218.30 MB/s
Benchmark128_16      50000000        35.5 ns/op       450.86 MB/s
Benchmark128_32      50000000        35.3 ns/op       905.84 MB/s
Benchmark128_64      50000000        44.3 ns/op      1443.76 MB/s
Benchmark128_128     50000000        58.2 ns/op      2201.02 MB/s
Benchmark128_256     20000000        85.3 ns/op      2999.88 MB/s
Benchmark128_512     10000000       142.0 ns/op      3592.97 MB/s
Benchmark128_1024    10000000       258.0 ns/op      3963.74 MB/s
Benchmark128_2048     5000000       494.0 ns/op      4144.65 MB/s
Benchmark128_4096     2000000       955.0 ns/op      4285.80 MB/s
Benchmark128_8192     1000000      1884.0 ns/op      4347.12 MB/s

ubuntu-push-0.2.1+14.04.20140423.1/external/murmur3/murmur_test.go0000644000015301777760000001106712325724711024727 0ustar pbusernogroup00000000000000package murmur3 import ( "hash" "testing" ) var data = []struct { h32 uint32 h64_1 uint64 h64_2 uint64 s string }{ {0x514e28b7, 0x4610abe56eff5cb5, 0x51622daa78f83583, ""}, {0xbb4abcad, 0xa78ddff5adae8d10, 0x128900ef20900135, "hello"}, {0x6f5cb2e9, 0x8b95f808840725c6, 0x1597ed5422bd493b, "hello, world"}, {0xf50e1f30, 0x2a929de9c8f97b2f, 0x56a41d99af43a2db, "19 Jan 2038 at 3:14:07 AM"}, {0x846f6a36, 0xfb3325171f9744da, 0xaaf8b92a5f722952, "The quick brown fox jumps over the lazy dog."}, } func TestRef(t *testing.T) { for _, elem := range data { var h32 hash.Hash32 = New32() h32.Write([]byte(elem.s)) if v := h32.Sum32(); v != elem.h32 { t.Errorf("'%s': 0x%x (want 0x%x)", elem.s, v, elem.h32) } if v := Sum32([]byte(elem.s)); v != elem.h32 { t.Errorf("'%s': 0x%x (want 0x%x)", elem.s, v, elem.h32) } var h64 hash.Hash64 = New64() h64.Write([]byte(elem.s)) if v := h64.Sum64(); v != elem.h64_1 { t.Errorf("'%s': 0x%x (want 0x%x)", elem.s, v, elem.h64_1) } var h128 Hash128 = New128() h128.Write([]byte(elem.s)) if v1, v2 := h128.Sum128(); v1 != elem.h64_1 || v2 != elem.h64_2 { t.Errorf("'%s': 0x%x-0x%x (want 0x%x-0x%x)", elem.s, v1, v2, elem.h64_1, elem.h64_2) } if v1, v2 := Sum128([]byte(elem.s)); v1 != elem.h64_1 || v2 != elem.h64_2 { t.Errorf("'%s': 0x%x-0x%x (want 0x%x-0x%x)", elem.s, v1, v2, elem.h64_1, elem.h64_2) } } } func TestIncremental(t *testing.T) { for _, elem := range data { h32 := New32() h128 := New128() for i, j, k := 0, 0, len(elem.s); i < k; i = j { j = 2*i + 3 if j > k { j = k } s := elem.s[i:j] print(s + "|") h32.Write([]byte(s)) h128.Write([]byte(s)) } println() if v := h32.Sum32(); v != elem.h32 { t.Errorf("'%s': 0x%x (want 0x%x)", elem.s, v, elem.h32) } if v1, v2 := h128.Sum128(); v1 != elem.h64_1 || v2 != elem.h64_2 { t.Errorf("'%s': 0x%x-0x%x (want 0x%x-0x%x)", elem.s, v1, v2, elem.h64_1, elem.h64_2) } } } //--- func bench32(b *testing.B, length int) { buf := make([]byte, length) b.SetBytes(int64(length)) b.ResetTimer() for i := 0; i < b.N; i++ { Sum32(buf) } } func Benchmark32_1(b *testing.B) { bench32(b, 1) } func Benchmark32_2(b *testing.B) { bench32(b, 2) } func Benchmark32_4(b *testing.B) { bench32(b, 4) } func Benchmark32_8(b *testing.B) { bench32(b, 8) } func Benchmark32_16(b *testing.B) { bench32(b, 16) } func Benchmark32_32(b *testing.B) { bench32(b, 32) } func Benchmark32_64(b *testing.B) { bench32(b, 64) } func Benchmark32_128(b *testing.B) { bench32(b, 128) } func Benchmark32_256(b *testing.B) { bench32(b, 256) } func Benchmark32_512(b *testing.B) { bench32(b, 512) } func Benchmark32_1024(b *testing.B) { bench32(b, 1024) } func Benchmark32_2048(b *testing.B) { bench32(b, 2048) } func Benchmark32_4096(b *testing.B) { bench32(b, 4096) } func Benchmark32_8192(b *testing.B) { bench32(b, 8192) } //--- func benchPartial32(b *testing.B, length int) { buf := make([]byte, length) b.SetBytes(int64(length)) start := (32 / 8) / 2 chunks := 7 k := length / chunks tail := (length - start) % k b.ResetTimer() for i := 0; i < b.N; i++ { hasher := New32() hasher.Write(buf[0:start]) for j := start; j+k <= length; j += k { hasher.Write(buf[j : j+k]) } hasher.Write(buf[length-tail:]) hasher.Sum32() } } func BenchmarkPartial32_8(b *testing.B) { benchPartial32(b, 8) } func BenchmarkPartial32_16(b *testing.B) { benchPartial32(b, 16) } func BenchmarkPartial32_32(b *testing.B) { benchPartial32(b, 32) } func BenchmarkPartial32_64(b *testing.B) { benchPartial32(b, 64) } func BenchmarkPartial32_128(b *testing.B) { benchPartial32(b, 128) } //--- func bench128(b *testing.B, length int) { buf := make([]byte, length) b.SetBytes(int64(length)) b.ResetTimer() for i := 0; i < b.N; i++ { Sum128(buf) } } func Benchmark128_1(b *testing.B) { bench128(b, 1) } func Benchmark128_2(b *testing.B) { bench128(b, 2) } func Benchmark128_4(b *testing.B) { bench128(b, 4) } func Benchmark128_8(b *testing.B) { bench128(b, 8) } func Benchmark128_16(b *testing.B) { bench128(b, 16) } func Benchmark128_32(b *testing.B) { bench128(b, 32) } func Benchmark128_64(b *testing.B) { bench128(b, 64) } func Benchmark128_128(b *testing.B) { bench128(b, 128) } func Benchmark128_256(b *testing.B) { bench128(b, 256) } func Benchmark128_512(b *testing.B) { bench128(b, 512) } func Benchmark128_1024(b *testing.B) { bench128(b, 1024) } func Benchmark128_2048(b *testing.B) { bench128(b, 2048) } func Benchmark128_4096(b *testing.B) { bench128(b, 4096) } func Benchmark128_8192(b *testing.B) { bench128(b, 8192) } //--- ubuntu-push-0.2.1+14.04.20140423.1/external/murmur3/LICENSE0000644000015301777760000000273012325724711023014 0ustar pbusernogroup00000000000000Copyright 2013, Sébastien Paolacci. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the library nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 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. ubuntu-push-0.2.1+14.04.20140423.1/external/README0000644000015301777760000000020412325724711021247 0ustar pbusernogroup00000000000000Directly included vendorized small packages. * murmor3 comes from import at lp:~ubuntu-push-hackers/ubuntu-push/murmur at revno 10 ubuntu-push-0.2.1+14.04.20140423.1/dependencies.tsv0000644000015301777760000000051112325724711021732 0ustar pbusernogroup00000000000000code.google.com/p/gosqlite hg 74691fb6f83716190870cde1b658538dd4b18eb0 15 launchpad.net/go-dbus/v1 bzr james@jamesh.id.au-20140206110213-pbzcr6ucaz3rqmnw 125 launchpad.net/go-xdg/v0 bzr john.lenton@canonical.com-20140208094800-gubd5md7cro3mtxa 10 launchpad.net/gocheck bzr gustavo@niemeyer.net-20140127131816-zshobk1qqme626xw 86 ubuntu-push-0.2.1+14.04.20140423.1/ubuntu-push-client.go0000644000015301777760000000334212325724720022655 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "log" "launchpad.net/go-dbus/v1" "launchpad.net/go-xdg/v0" "launchpad.net/ubuntu-push/client" ) const NAME = "com.ubuntu.PushNotifications" // grabName grabs ownership of the dbus name, and bails the client as // soon as somebody else grabs it. func grabName() { conn, err := dbus.Connect(dbus.SessionBus) if err != nil { log.Fatalf("bus: %v", err) } flags := dbus.NameFlagAllowReplacement | dbus.NameFlagReplaceExisting n := conn.RequestName(NAME, flags) go func() { for err := range n.C { if err != nil { log.Fatalf("FATAL: name channel got: %v", err) } } }() } func main() { // XXX: this is a quick hack to ensure unicity grabName() cfgFname, err := xdg.Config.Find("ubuntu-push-client/config.json") if err != nil { log.Fatalf("unable to find a configuration file: %v", err) } lvlFname, err := xdg.Data.Ensure("ubuntu-push-client/levels.db") if err != nil { log.Fatalf("unable to open the levels database: %v", err) } cli := client.NewPushClient(cfgFname, lvlFname) err = cli.Start() if err != nil { log.Fatalf("unable to start: %v", err) } cli.Loop() } ubuntu-push-0.2.1+14.04.20140423.1/tarmac_tests.sh0000755000015301777760000000017012325724711021577 0ustar pbusernogroup00000000000000#!/bin/bash # For running tests in Jenkins by Tarmac set -e make bootstrap make check make check-race make check-format ubuntu-push-0.2.1+14.04.20140423.1/config/0000755000015301777760000000000012325725172020020 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/config/config.go0000644000015301777760000002216412325724720021617 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package config has helpers to parse and use JSON based configuration. package config import ( "encoding/json" "errors" "flag" "fmt" "io" "io/ioutil" "net" "os" "path/filepath" "reflect" "strconv" "strings" "time" ) func checkDestConfig(name string, destConfig interface{}) (reflect.Value, error) { destValue := reflect.ValueOf(destConfig) if destValue.Kind() != reflect.Ptr || destValue.Elem().Kind() != reflect.Struct { return reflect.Value{}, fmt.Errorf("%s not *struct", name) } return destValue, nil } type destField struct { fld reflect.StructField dest interface{} } func (f destField) configName() string { fld := f.fld configName := strings.Split(fld.Tag.Get("json"), ",")[0] if configName == "" { configName = strings.ToLower(fld.Name[:1]) + fld.Name[1:] } return configName } func traverseStruct(destStruct reflect.Value) <-chan destField { ch := make(chan destField) var traverse func(reflect.Value, chan<- destField) traverse = func(destStruct reflect.Value, ch chan<- destField) { structType := destStruct.Type() n := structType.NumField() for i := 0; i < n; i++ { fld := structType.Field(i) val := destStruct.Field(i) if fld.PkgPath != "" { // unexported continue } if fld.Anonymous { traverse(val, ch) continue } ch <- destField{ fld: fld, dest: val.Addr().Interface(), } } } go func() { traverse(destStruct, ch) close(ch) }() return ch } func fillDestConfig(destValue reflect.Value, p map[string]json.RawMessage) error { destStruct := destValue.Elem() for destField := range traverseStruct(destStruct) { configName := destField.configName() raw, found := p[configName] if !found { // assume all fields are mandatory for now return fmt.Errorf("missing %s", configName) } dest := destField.dest err := json.Unmarshal([]byte(raw), dest) if err != nil { return fmt.Errorf("%s: %v", configName, err) } } return nil } // ReadConfig reads a JSON configuration into destConfig which should // be a pointer to a structure. It does some more configuration // specific error checking than plain JSON decoding, and mentions // fields in errors. Configuration fields in the JSON object are // expected to start with lower case. func ReadConfig(r io.Reader, destConfig interface{}) error { destValue, err := checkDestConfig("destConfig", destConfig) if err != nil { return err } // do the parsing in two phases for better error handling var p1 map[string]json.RawMessage err = json.NewDecoder(r).Decode(&p1) if err != nil { return err } return fillDestConfig(destValue, p1) } // FromString are config holders that can be set by parsing a string. type FromString interface { SetFromString(enc string) error } // UnmarshalJSONViaString helps unmarshalling from JSON for FromString // supporting config holders. func UnmarshalJSONViaString(dest FromString, b []byte) error { var enc string err := json.Unmarshal(b, &enc) if err != nil { return err } return dest.SetFromString(enc) } // ConfigTimeDuration can hold a time.Duration in a configuration struct, // that is parsed from a string as supported by time.ParseDuration. type ConfigTimeDuration struct { time.Duration } func (ctd *ConfigTimeDuration) UnmarshalJSON(b []byte) error { return UnmarshalJSONViaString(ctd, b) } func (ctd *ConfigTimeDuration) SetFromString(enc string) error { v, err := time.ParseDuration(enc) if err != nil { return err } *ctd = ConfigTimeDuration{v} return nil } // TimeDuration returns the time.Duration held in ctd. func (ctd ConfigTimeDuration) TimeDuration() time.Duration { return ctd.Duration } // ConfigHostPort can hold a host:port string in a configuration struct. type ConfigHostPort string func (chp *ConfigHostPort) UnmarshalJSON(b []byte) error { return UnmarshalJSONViaString(chp, b) } func (chp *ConfigHostPort) SetFromString(enc string) error { _, _, err := net.SplitHostPort(enc) if err != nil { return err } *chp = ConfigHostPort(enc) return nil } // HostPort returns the host:port string held in chp. func (chp ConfigHostPort) HostPort() string { return string(chp) } // ConfigQueueSize can hold a queue size in a configuration struct. type ConfigQueueSize uint func (cqs *ConfigQueueSize) UnmarshalJSON(b []byte) error { var enc uint err := json.Unmarshal(b, &enc) if err != nil { return err } if enc == 0 { return errors.New("queue size should be > 0") } *cqs = ConfigQueueSize(enc) return nil } // QueueSize returns the queue size held in cqs. func (cqs ConfigQueueSize) QueueSize() uint { return uint(cqs) } // LoadFile reads a file possibly relative to a base dir. func LoadFile(p, baseDir string) ([]byte, error) { if p == "" { return nil, nil } if !filepath.IsAbs(p) { p = filepath.Join(baseDir, p) } return ioutil.ReadFile(p) } // used to implement getting config values with flag.Parse() type val struct { destField destField accu map[string]json.RawMessage } func (v *val) String() string { // used to show default return string(v.accu[v.destField.configName()]) } func (v *val) IsBoolFlag() bool { return v.destField.fld.Type.Kind() == reflect.Bool } func (v *val) marshalAsNeeded(s string) (json.RawMessage, error) { var toMarshal interface{} switch v.destField.dest.(type) { case *string, FromString: toMarshal = s case *bool: bit, err := strconv.ParseBool(s) if err != nil { return nil, err } toMarshal = bit default: return json.RawMessage(s), nil } return json.Marshal(toMarshal) } func (v *val) Set(s string) error { marshalled, err := v.marshalAsNeeded(s) if err != nil { return err } v.accu[v.destField.configName()] = marshalled return nil } func readOneConfig(accu map[string]json.RawMessage, cfgPath string) error { r, err := os.Open(cfgPath) if err != nil { return err } defer r.Close() err = json.NewDecoder(r).Decode(&accu) if err != nil { return err } return nil } // used to implement -cfg@= type readConfigAtVal struct { accu map[string]json.RawMessage } func (v *readConfigAtVal) String() string { return "" } func (v *readConfigAtVal) Set(path string) error { return readOneConfig(v.accu, path) } // readUsingFlags gets config values from command line flags. func readUsingFlags(accu map[string]json.RawMessage, destValue reflect.Value) error { if flag.Parsed() { if IgnoreParsedFlags { return nil } return fmt.Errorf("too late, flags already parsed") } destStruct := destValue.Elem() for destField := range traverseStruct(destStruct) { help := destField.fld.Tag.Get("help") flag.Var(&val{destField, accu}, destField.configName(), help) } flag.Var(&readConfigAtVal{accu}, "cfg@", "get config values from file") flag.Parse() return nil } // IgnoreParsedFlags will just have ReadFiles ignore if the // command line was already parsed. var IgnoreParsedFlags = false // ReadFiles reads configuration from a set of files. The string // "" can be used as a pseudo file-path, it will consider // command line flags, invoking flag.Parse(). Among those the flag // -cfg@=FILE can be used to get further config values from FILE. func ReadFiles(destConfig interface{}, cfgFpaths ...string) error { destValue, err := checkDestConfig("destConfig", destConfig) if err != nil { return err } // do the parsing in two phases for better error handling p1 := make(map[string]json.RawMessage) readOne := false for _, cfgPath := range cfgFpaths { if cfgPath == "" { err := readUsingFlags(p1, destValue) if err != nil { return err } readOne = true continue } if _, err := os.Stat(cfgPath); err == nil { err := readOneConfig(p1, cfgPath) if err != nil { return err } readOne = true } } if !readOne { return fmt.Errorf("no config to read") } return fillDestConfig(destValue, p1) } // CompareConfigs compares the two given configuration structures. It returns a list of differing fields or nil if the config contents are the same. func CompareConfig(config1, config2 interface{}) ([]string, error) { v1, err := checkDestConfig("config1", config1) if err != nil { return nil, err } v2, err := checkDestConfig("config2", config2) if err != nil { return nil, err } if v1.Type() != v2.Type() { return nil, errors.New("config1 and config2 don't have the same type") } fields1 := traverseStruct(v1.Elem()) fields2 := traverseStruct(v2.Elem()) diff := make([]string, 0) for { d1 := <-fields1 d2 := <-fields2 if d1.dest == nil { break } if !reflect.DeepEqual(d1.dest, d2.dest) { diff = append(diff, d1.configName()) } } if len(diff) != 0 { return diff, nil } return nil, nil } ubuntu-push-0.2.1+14.04.20140423.1/config/config_test.go0000644000015301777760000002257012325724720022657 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package config import ( "bytes" "encoding/json" "flag" "fmt" "io/ioutil" "os" "path/filepath" "reflect" "testing" "time" . "launchpad.net/gocheck" ) func TestConfig(t *testing.T) { TestingT(t) } type configSuite struct{} var _ = Suite(&configSuite{}) type testConfig1 struct { A int B string C []string `json:"c_list"` } func (s *configSuite) TestReadConfig(c *C) { buf := bytes.NewBufferString(`{"a": 1, "b": "foo", "c_list": ["c", "d", "e"]}`) var cfg testConfig1 err := ReadConfig(buf, &cfg) c.Check(err, IsNil) c.Check(cfg, DeepEquals, testConfig1{A: 1, B: "foo", C: []string{"c", "d", "e"}}) } func checkError(c *C, config string, dest interface{}, expectedError string) { buf := bytes.NewBufferString(config) err := ReadConfig(buf, dest) c.Check(err, ErrorMatches, expectedError) } func (s *configSuite) TestReadConfigErrors(c *C) { var cfg testConfig1 checkError(c, "", cfg, `destConfig not \*struct`) var i int checkError(c, "", &i, `destConfig not \*struct`) checkError(c, "", &cfg, `EOF`) checkError(c, `{"a": "1"}`, &cfg, `a: .*type int`) checkError(c, `{"b": "1"}`, &cfg, `missing a`) checkError(c, `{"A": "1"}`, &cfg, `missing a`) checkError(c, `{"a": 1, "b": "foo"}`, &cfg, `missing c_list`) } type testTimeDurationConfig struct { D ConfigTimeDuration } func (s *configSuite) TestReadConfigTimeDuration(c *C) { buf := bytes.NewBufferString(`{"d": "2s"}`) var cfg testTimeDurationConfig err := ReadConfig(buf, &cfg) c.Assert(err, IsNil) c.Check(cfg.D.TimeDuration(), Equals, 2*time.Second) } func (s *configSuite) TestReadConfigTimeDurationErrors(c *C) { var cfg testTimeDurationConfig checkError(c, `{"d": 1}`, &cfg, "d:.*type string") checkError(c, `{"d": "2"}`, &cfg, "d:.*missing unit.*") } type testHostPortConfig struct { H ConfigHostPort } func (s *configSuite) TestReadConfigHostPort(c *C) { buf := bytes.NewBufferString(`{"h": "127.0.0.1:9999"}`) var cfg testHostPortConfig err := ReadConfig(buf, &cfg) c.Assert(err, IsNil) c.Check(cfg.H.HostPort(), Equals, "127.0.0.1:9999") } func (s *configSuite) TestReadConfigHostPortErrors(c *C) { var cfg testHostPortConfig checkError(c, `{"h": 1}`, &cfg, "h:.*type string") checkError(c, `{"h": ""}`, &cfg, "h: missing port in address") } type testQueueSizeConfig struct { QS ConfigQueueSize } func (s *configSuite) TestReadConfigQueueSize(c *C) { buf := bytes.NewBufferString(`{"qS": 1}`) var cfg testQueueSizeConfig err := ReadConfig(buf, &cfg) c.Assert(err, IsNil) c.Check(cfg.QS.QueueSize(), Equals, uint(1)) } func (s *configSuite) TestReadConfigQueueSizeErrors(c *C) { var cfg testQueueSizeConfig checkError(c, `{"qS": "x"}`, &cfg, "qS: .*type uint") checkError(c, `{"qS": 0}`, &cfg, "qS: queue size should be > 0") } func (s *configSuite) TestLoadFile(c *C) { tmpDir := c.MkDir() d, err := LoadFile("", tmpDir) c.Check(err, IsNil) c.Check(d, IsNil) fullPath := filepath.Join(tmpDir, "example.file") err = ioutil.WriteFile(fullPath, []byte("Example"), os.ModePerm) c.Assert(err, IsNil) d, err = LoadFile("example.file", tmpDir) c.Check(err, IsNil) c.Check(string(d), Equals, "Example") d, err = LoadFile(fullPath, tmpDir) c.Check(err, IsNil) c.Check(string(d), Equals, "Example") } func (s *configSuite) TestReadFiles(c *C) { tmpDir := c.MkDir() cfg1Path := filepath.Join(tmpDir, "cfg1.json") err := ioutil.WriteFile(cfg1Path, []byte(`{"a": 42}`), os.ModePerm) c.Assert(err, IsNil) cfg2Path := filepath.Join(tmpDir, "cfg2.json") err = ioutil.WriteFile(cfg2Path, []byte(`{"b": "x", "c_list": ["y", "z"]}`), os.ModePerm) c.Assert(err, IsNil) var cfg testConfig1 err = ReadFiles(&cfg, cfg1Path, cfg2Path) c.Assert(err, IsNil) c.Check(cfg.A, Equals, 42) c.Check(cfg.B, Equals, "x") c.Check(cfg.C, DeepEquals, []string{"y", "z"}) } func (s *configSuite) TestReadFilesErrors(c *C) { var cfg testConfig1 err := ReadFiles(1) c.Check(err, ErrorMatches, `destConfig not \*struct`) err = ReadFiles(&cfg, "non-existent") c.Check(err, ErrorMatches, "no config to read") err = ReadFiles(&cfg, "/root") c.Check(err, ErrorMatches, ".*permission denied") tmpDir := c.MkDir() err = ReadFiles(&cfg, tmpDir) c.Check(err, ErrorMatches, ".*is a directory") brokenCfgPath := filepath.Join(tmpDir, "b.json") err = ioutil.WriteFile(brokenCfgPath, []byte(`{"a"-`), os.ModePerm) c.Assert(err, IsNil) err = ReadFiles(&cfg, brokenCfgPath) c.Check(err, NotNil) } type B struct { BFld int } type A struct { AFld int B private int } func (s *configSuite) TestTraverseStruct(c *C) { var a A var i = 1 for destField := range traverseStruct(reflect.ValueOf(&a).Elem()) { *(destField.dest.(*int)) = i i++ } c.Check(a, DeepEquals, A{1, B{2}, 0}) } type testConfig2 struct { A int B string C []string `json:"c_list"` D ConfigTimeDuration } func (s *configSuite) TestCompareConfig(c *C) { var cfg1 = testConfig2{ A: 1, B: "xyz", C: []string{"a", "b"}, D: ConfigTimeDuration{200 * time.Millisecond}, } var cfg2 = testConfig2{ A: 1, B: "xyz", C: []string{"a", "b"}, D: ConfigTimeDuration{200 * time.Millisecond}, } _, err := CompareConfig(cfg1, &cfg2) c.Check(err, ErrorMatches, `config1 not \*struct`) _, err = CompareConfig(&cfg1, cfg2) c.Check(err, ErrorMatches, `config2 not \*struct`) _, err = CompareConfig(&cfg1, &testConfig1{}) c.Check(err, ErrorMatches, `config1 and config2 don't have the same type`) res, err := CompareConfig(&cfg1, &cfg2) c.Assert(err, IsNil) c.Check(res, IsNil) cfg1.B = "zyx" cfg2.C = []string{"a", "B"} cfg2.D = ConfigTimeDuration{205 * time.Millisecond} res, err = CompareConfig(&cfg1, &cfg2) c.Assert(err, IsNil) c.Check(res, DeepEquals, []string{"b", "c_list", "d"}) } type testConfig3 struct { A bool B string C []string `json:"c_list"` D ConfigTimeDuration `help:"duration"` E ConfigHostPort F string } type configFlagsSuite struct{} var _ = Suite(&configFlagsSuite{}) func (s *configFlagsSuite) SetUpTest(c *C) { flag.CommandLine = flag.NewFlagSet("cmd", flag.PanicOnError) // supress outputs flag.Usage = func() { flag.PrintDefaults() } flag.CommandLine.SetOutput(ioutil.Discard) } func (s *configFlagsSuite) TestReadUsingFlags(c *C) { os.Args = []string{"cmd", "-a=1", "-b=foo", "-c_list", `["x","y"]`, "-d", "10s", "-e=localhost:80"} var cfg testConfig3 p := make(map[string]json.RawMessage) err := readUsingFlags(p, reflect.ValueOf(&cfg)) c.Assert(err, IsNil) c.Check(p, DeepEquals, map[string]json.RawMessage{ "a": json.RawMessage("true"), "b": json.RawMessage(`"foo"`), "c_list": json.RawMessage(`["x","y"]`), "d": json.RawMessage(`"10s"`), "e": json.RawMessage(`"localhost:80"`), }) } func (s *configFlagsSuite) TestReadUsingFlagsBoolError(c *C) { os.Args = []string{"cmd", "-a=zoo"} var cfg testConfig3 p := make(map[string]json.RawMessage) c.Check(func() { readUsingFlags(p, reflect.ValueOf(&cfg)) }, PanicMatches, ".*invalid boolean.*-a.*") } func (s *configFlagsSuite) TestReadFilesAndFlags(c *C) { // test pseudo file os.Args = []string{"cmd", "-b=x"} tmpDir := c.MkDir() cfgPath := filepath.Join(tmpDir, "cfg.json") err := ioutil.WriteFile(cfgPath, []byte(`{"a": 42, "c_list": ["y", "z"]}`), os.ModePerm) c.Assert(err, IsNil) var cfg testConfig1 err = ReadFiles(&cfg, cfgPath, "") c.Assert(err, IsNil) c.Check(cfg.A, Equals, 42) c.Check(cfg.B, Equals, "x") c.Check(cfg.C, DeepEquals, []string{"y", "z"}) } func (s *configFlagsSuite) TestReadFilesAndFlagsConfigAtSupport(c *C) { // test pseudo file tmpDir := c.MkDir() cfgPath := filepath.Join(tmpDir, "cfg.json") os.Args = []string{"cmd", "-a=42", fmt.Sprintf("-cfg@=%s", cfgPath)} err := ioutil.WriteFile(cfgPath, []byte(`{"b": "x", "c_list": ["y", "z"]}`), os.ModePerm) c.Assert(err, IsNil) var cfg testConfig1 err = ReadFiles(&cfg, "") c.Assert(err, IsNil) c.Check(cfg.A, Equals, 42) c.Check(cfg.B, Equals, "x") c.Check(cfg.C, DeepEquals, []string{"y", "z"}) } func (s *configFlagsSuite) TestReadUsingFlagsHelp(c *C) { os.Args = []string{"cmd", "-h"} buf := bytes.NewBufferString("") flag.CommandLine.Init("cmd", flag.ContinueOnError) flag.CommandLine.SetOutput(buf) var cfg testConfig3 p := map[string]json.RawMessage{ "d": json.RawMessage(`"2s"`), } readUsingFlags(p, reflect.ValueOf(&cfg)) c.Check(buf.String(), Matches, `(?s).*-cfg@=: get config values from file\n.*-d="2s": duration.*`) } func (s *configFlagsSuite) TestReadUsingFlagsAlreadyParsed(c *C) { os.Args = []string{"cmd"} flag.Parse() var cfg struct{} p := make(map[string]json.RawMessage) err := readUsingFlags(p, reflect.ValueOf(&cfg)) c.Assert(err, ErrorMatches, "too late, flags already parsed") err = ReadFiles(&cfg, "") c.Assert(err, ErrorMatches, "too late, flags already parsed") IgnoreParsedFlags = true defer func() { IgnoreParsedFlags = false }() err = ReadFiles(&cfg, "") c.Assert(err, IsNil) } ubuntu-push-0.2.1+14.04.20140423.1/sampleconfigs/0000755000015301777760000000000012325725172021405 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/sampleconfigs/dev.json0000644000015301777760000000063112325724711023054 0ustar pbusernogroup00000000000000{ "exchange_timeout": "5s", "ping_interval": "10s", "broker_queue_size": 10000, "session_queue_size": 10, "addr": "127.0.0.1:9090", "key_pem_file": "../server/acceptance/ssl/testing.key", "cert_pem_file": "../server/acceptance/ssl/testing.cert", "http_addr": "127.0.0.1:8080", "http_read_timeout": "5s", "http_write_timeout": "5s", "delivery_domain": "localhost" } ubuntu-push-0.2.1+14.04.20140423.1/server/0000755000015301777760000000000012325725172020061 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/runner_devices.go0000644000015301777760000000707412325724711023431 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package server import ( "fmt" "net" "syscall" "time" "launchpad.net/ubuntu-push/config" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/server/listener" ) // A DevicesParsedConfig holds and can be used to parse the device server config. type DevicesParsedConfig struct { // session configuration ParsedPingInterval config.ConfigTimeDuration `json:"ping_interval"` ParsedExchangeTimeout config.ConfigTimeDuration `json:"exchange_timeout"` // broker configuration ParsedSessionQueueSize config.ConfigQueueSize `json:"session_queue_size"` ParsedBrokerQueueSize config.ConfigQueueSize `json:"broker_queue_size"` // device listener configuration ParsedAddr config.ConfigHostPort `json:"addr"` ParsedKeyPEMFile string `json:"key_pem_file"` ParsedCertPEMFile string `json:"cert_pem_file"` // private post-processed config certPEMBlock []byte keyPEMBlock []byte } func (cfg *DevicesParsedConfig) FinishLoad(baseDir string) error { keyPEMBlock, err := config.LoadFile(cfg.ParsedKeyPEMFile, baseDir) if err != nil { return fmt.Errorf("reading key_pem_file: %v", err) } certPEMBlock, err := config.LoadFile(cfg.ParsedCertPEMFile, baseDir) if err != nil { return fmt.Errorf("reading cert_pem_file: %v", err) } cfg.keyPEMBlock = keyPEMBlock cfg.certPEMBlock = certPEMBlock return nil } func (cfg *DevicesParsedConfig) PingInterval() time.Duration { return cfg.ParsedPingInterval.TimeDuration() } func (cfg *DevicesParsedConfig) ExchangeTimeout() time.Duration { return cfg.ParsedExchangeTimeout.TimeDuration() } func (cfg *DevicesParsedConfig) SessionQueueSize() uint { return cfg.ParsedSessionQueueSize.QueueSize() } func (cfg *DevicesParsedConfig) BrokerQueueSize() uint { return cfg.ParsedBrokerQueueSize.QueueSize() } func (cfg *DevicesParsedConfig) Addr() string { return cfg.ParsedAddr.HostPort() } func (cfg *DevicesParsedConfig) KeyPEMBlock() []byte { return cfg.keyPEMBlock } func (cfg *DevicesParsedConfig) CertPEMBlock() []byte { return cfg.certPEMBlock } // DevicesRunner returns a function to accept device connections. // If adoptLst is not nil it will be used as the underlying listener, instead // of creating one, wrapped in a TLS layer. func DevicesRunner(adoptLst net.Listener, session func(net.Conn) error, logger logger.Logger, parsedCfg *DevicesParsedConfig) func() { BootLogger.Debugf("PingInterval: %s, ExchangeTimeout %s", parsedCfg.PingInterval(), parsedCfg.ExchangeTimeout()) var rlim syscall.Rlimit err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlim) if err != nil { BootLogFatalf("getrlimit failed: %v", err) } BootLogger.Debugf("nofile soft: %d hard: %d", rlim.Cur, rlim.Max) lst, err := listener.DeviceListen(adoptLst, parsedCfg) if err != nil { BootLogFatalf("start device listening: %v", err) } BootLogListener("devices", lst) return func() { err = lst.AcceptLoop(session, logger) if err != nil { BootLogFatalf("accepting device connections: %v", err) } } } ubuntu-push-0.2.1+14.04.20140423.1/server/doc.go0000644000015301777760000000133512325724711021155 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package server contains code to start server components hosted // by the subpackages. package server ubuntu-push-0.2.1+14.04.20140423.1/server/dev/0000755000015301777760000000000012325725172020637 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/dev/server.go0000644000015301777760000000542412325724711022477 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Dev is a simple development server. package main import ( "encoding/json" "net" "net/http" "os" "path/filepath" "launchpad.net/ubuntu-push/config" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/server" "launchpad.net/ubuntu-push/server/api" "launchpad.net/ubuntu-push/server/broker/simple" "launchpad.net/ubuntu-push/server/session" "launchpad.net/ubuntu-push/server/store" ) type configuration struct { // device server configuration server.DevicesParsedConfig // api http server configuration server.HTTPServeParsedConfig // delivery domain DeliveryDomain string `json:"delivery_domain"` } func main() { cfgFpaths := os.Args[1:] cfg := &configuration{} err := config.ReadFiles(cfg, cfgFpaths...) if err != nil { server.BootLogFatalf("reading config: %v", err) } err = cfg.DevicesParsedConfig.FinishLoad(filepath.Dir(cfgFpaths[len(cfgFpaths)-1])) if err != nil { server.BootLogFatalf("reading config: %v", err) } logger := logger.NewSimpleLogger(os.Stderr, "debug") // setup a pending store and start the broker sto := store.NewInMemoryPendingStore() broker := simple.NewSimpleBroker(sto, cfg, logger) broker.Start() defer broker.Stop() // serve the http api storeForRequest := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { return sto, nil } lst, err := net.Listen("tcp", cfg.Addr()) if err != nil { server.BootLogFatalf("start device listening: %v", err) } mux := api.MakeHandlersMux(storeForRequest, broker, logger) // & /delivery-hosts mux.HandleFunc("/delivery-hosts", func(w http.ResponseWriter, req *http.Request) { w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(w) enc.Encode(map[string]interface{}{ "hosts": []string{lst.Addr().String()}, "domain": cfg.DeliveryDomain, }) }) handler := api.PanicTo500Handler(mux, logger) go server.HTTPServeRunner(nil, handler, &cfg.HTTPServeParsedConfig)() // listen for device connections server.DevicesRunner(lst, func(conn net.Conn) error { track := session.NewTracker(logger) return session.Session(conn, broker, cfg, track) }, logger, &cfg.DevicesParsedConfig)() } ubuntu-push-0.2.1+14.04.20140423.1/server/broker/0000755000015301777760000000000012325725172021345 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/broker/exchanges_test.go0000644000015301777760000001743712325724711024712 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package broker_test // use a package test to avoid cyclic imports import ( "encoding/json" "fmt" "strings" stdtesting "testing" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/server/broker" "launchpad.net/ubuntu-push/server/broker/testing" "launchpad.net/ubuntu-push/server/store" ) func TestBroker(t *stdtesting.T) { TestingT(t) } type exchangesSuite struct{} var _ = Suite(&exchangesSuite{}) func (s *exchangesSuite) TestBroadcastExchangeInit(c *C) { exchg := &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 3, NotificationPayloads: []json.RawMessage{ json.RawMessage(`{"a":"x"}`), json.RawMessage(`[]`), json.RawMessage(`{"a":"y"}`), }, } exchg.Init() c.Check(exchg.Decoded, DeepEquals, []map[string]interface{}{ map[string]interface{}{"a": "x"}, nil, map[string]interface{}{"a": "y"}, }) } func (s *exchangesSuite) TestBroadcastExchange(c *C) { sess := &testing.TestBrokerSession{ LevelsMap: broker.LevelsMap(map[store.InternalChannelId]int64{}), Model: "m1", ImageChannel: "img1", } exchg := &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 3, NotificationPayloads: []json.RawMessage{ json.RawMessage(`{"img1/m1":100}`), json.RawMessage(`{"img2/m2":200}`), }, } exchg.Init() outMsg, inMsg, err := exchg.Prepare(sess) c.Assert(err, IsNil) // check marshalled, err := json.Marshal(outMsg) c.Assert(err, IsNil) c.Check(string(marshalled), Equals, `{"T":"broadcast","ChanId":"0","TopLevel":3,"Payloads":[{"img1/m1":100}]}`) err = json.Unmarshal([]byte(`{"T":"ack"}`), inMsg) c.Assert(err, IsNil) err = exchg.Acked(sess, true) c.Assert(err, IsNil) c.Check(sess.LevelsMap[store.SystemInternalChannelId], Equals, int64(3)) } func (s *exchangesSuite) TestBroadcastExchangeEmpty(c *C) { sess := &testing.TestBrokerSession{ LevelsMap: broker.LevelsMap(map[store.InternalChannelId]int64{}), Model: "m1", ImageChannel: "img1", } exchg := &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 3, NotificationPayloads: []json.RawMessage{}, } exchg.Init() outMsg, inMsg, err := exchg.Prepare(sess) c.Assert(err, Equals, broker.ErrNop) c.Check(outMsg, IsNil) c.Check(inMsg, IsNil) } func (s *exchangesSuite) TestBroadcastExchangeEmptyButAhead(c *C) { sess := &testing.TestBrokerSession{ LevelsMap: broker.LevelsMap(map[store.InternalChannelId]int64{ store.SystemInternalChannelId: 10, }), Model: "m1", ImageChannel: "img1", } exchg := &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 3, NotificationPayloads: []json.RawMessage{}, } exchg.Init() outMsg, inMsg, err := exchg.Prepare(sess) c.Assert(err, IsNil) c.Check(outMsg, NotNil) c.Check(inMsg, NotNil) } func (s *exchangesSuite) TestBroadcastExchangeReuseVsSplit(c *C) { sess := &testing.TestBrokerSession{ LevelsMap: broker.LevelsMap(map[store.InternalChannelId]int64{}), Model: "m1", ImageChannel: "img1", } payloadFmt := fmt.Sprintf(`{"img1/m1":%%d,"bloat":"%s"}`, strings.Repeat("x", 1024*2)) needsSplitting := make([]json.RawMessage, 32) for i := 0; i < 32; i++ { needsSplitting[i] = json.RawMessage(fmt.Sprintf(payloadFmt, i)) } topLevel := int64(len(needsSplitting)) exchg := &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: topLevel, NotificationPayloads: needsSplitting, } exchg.Init() outMsg, _, err := exchg.Prepare(sess) c.Assert(err, IsNil) parts := 0 for { done := outMsg.Split() parts++ if done { break } } c.Assert(parts, Equals, 2) exchg = &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: topLevel + 2, NotificationPayloads: []json.RawMessage{ json.RawMessage(`{"img1/m1":"x"}`), json.RawMessage(`{"img1/m1":"y"}`), }, } exchg.Init() outMsg, _, err = exchg.Prepare(sess) c.Assert(err, IsNil) done := outMsg.Split() // shouldn't panic c.Check(done, Equals, true) } func (s *exchangesSuite) TestBroadcastExchangeAckMismatch(c *C) { sess := &testing.TestBrokerSession{ LevelsMap: broker.LevelsMap(map[store.InternalChannelId]int64{}), Model: "m1", ImageChannel: "img2", } exchg := &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 3, NotificationPayloads: []json.RawMessage{ json.RawMessage(`{"img2/m1":1}`), }, } exchg.Init() outMsg, inMsg, err := exchg.Prepare(sess) c.Assert(err, IsNil) // check marshalled, err := json.Marshal(outMsg) c.Assert(err, IsNil) c.Check(string(marshalled), Equals, `{"T":"broadcast","ChanId":"0","TopLevel":3,"Payloads":[{"img2/m1":1}]}`) err = json.Unmarshal([]byte(`{}`), inMsg) c.Assert(err, IsNil) err = exchg.Acked(sess, true) c.Assert(err, Not(IsNil)) c.Check(sess.LevelsMap[store.SystemInternalChannelId], Equals, int64(0)) } func (s *exchangesSuite) TestBroadcastExchangeFilterByLevel(c *C) { sess := &testing.TestBrokerSession{ LevelsMap: broker.LevelsMap(map[store.InternalChannelId]int64{ store.SystemInternalChannelId: 2, }), Model: "m1", ImageChannel: "img1", } exchg := &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 3, NotificationPayloads: []json.RawMessage{ json.RawMessage(`{"img1/m1":100}`), json.RawMessage(`{"img1/m1":101}`), }, } exchg.Init() outMsg, inMsg, err := exchg.Prepare(sess) c.Assert(err, IsNil) // check marshalled, err := json.Marshal(outMsg) c.Assert(err, IsNil) c.Check(string(marshalled), Equals, `{"T":"broadcast","ChanId":"0","TopLevel":3,"Payloads":[{"img1/m1":101}]}`) err = json.Unmarshal([]byte(`{"T":"ack"}`), inMsg) c.Assert(err, IsNil) err = exchg.Acked(sess, true) c.Assert(err, IsNil) } func (s *exchangesSuite) TestBroadcastExchangeChannelFilter(c *C) { sess := &testing.TestBrokerSession{ LevelsMap: broker.LevelsMap(map[store.InternalChannelId]int64{}), Model: "m1", ImageChannel: "img1", } exchg := &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 5, NotificationPayloads: []json.RawMessage{ json.RawMessage(`{"img1/m1":100}`), json.RawMessage(`{"img2/m2":200}`), json.RawMessage(`{"img1/m1":101}`), }, } exchg.Init() outMsg, inMsg, err := exchg.Prepare(sess) c.Assert(err, IsNil) // check marshalled, err := json.Marshal(outMsg) c.Assert(err, IsNil) c.Check(string(marshalled), Equals, `{"T":"broadcast","ChanId":"0","TopLevel":5,"Payloads":[{"img1/m1":100},{"img1/m1":101}]}`) err = json.Unmarshal([]byte(`{"T":"ack"}`), inMsg) c.Assert(err, IsNil) err = exchg.Acked(sess, true) c.Assert(err, IsNil) c.Check(sess.LevelsMap[store.SystemInternalChannelId], Equals, int64(5)) } func (s *exchangesSuite) TestConnBrokenExchange(c *C) { sess := &testing.TestBrokerSession{} cbe := &broker.ConnBrokenExchange{"REASON"} outMsg, inMsg, err := cbe.Prepare(sess) c.Assert(err, IsNil) c.Check(inMsg, IsNil) // no answer is expected // check marshalled, err := json.Marshal(outMsg) c.Assert(err, IsNil) c.Check(string(marshalled), Equals, `{"T":"connbroken","Reason":"REASON"}`) c.Check(func() { cbe.Acked(nil, true) }, PanicMatches, "Acked should not get invoked on ConnBrokenExchange") } ubuntu-push-0.2.1+14.04.20140423.1/server/broker/exchg_impl_test.go0000644000015301777760000000533112325724711025052 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package broker import ( "encoding/json" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/server/store" ) type exchangesImplSuite struct{} var _ = Suite(&exchangesImplSuite{}) func (s *exchangesImplSuite) TestFilterByLevel(c *C) { payloads := []json.RawMessage{ json.RawMessage(`{"a": 3}`), json.RawMessage(`{"a": 4}`), json.RawMessage(`{"a": 5}`), } res := filterByLevel(5, 5, payloads) c.Check(len(res), Equals, 0) res = filterByLevel(4, 5, payloads) c.Check(len(res), Equals, 1) c.Check(res[0], DeepEquals, json.RawMessage(`{"a": 5}`)) res = filterByLevel(3, 5, payloads) c.Check(len(res), Equals, 2) c.Check(res[0], DeepEquals, json.RawMessage(`{"a": 4}`)) res = filterByLevel(2, 5, payloads) c.Check(len(res), Equals, 3) res = filterByLevel(1, 5, payloads) c.Check(len(res), Equals, 3) // too ahead, pick only last res = filterByLevel(10, 5, payloads) c.Check(len(res), Equals, 1) c.Check(res[0], DeepEquals, json.RawMessage(`{"a": 5}`)) } func (s *exchangesImplSuite) TestFilterByLevelEmpty(c *C) { res := filterByLevel(5, 0, nil) c.Check(len(res), Equals, 0) res = filterByLevel(5, 10, nil) c.Check(len(res), Equals, 0) } func (s *exchangesImplSuite) TestChannelFilter(c *C) { payloads := []json.RawMessage{ json.RawMessage(`{"a/x": 3}`), json.RawMessage(`{"b/x": 4}`), json.RawMessage(`{"a/y": 5}`), json.RawMessage(`{"a/x": 6}`), } decoded := make([]map[string]interface{}, 4) for i, p := range payloads { err := json.Unmarshal(p, &decoded[i]) c.Assert(err, IsNil) } other := store.InternalChannelId("1") c.Check(channelFilter("", store.SystemInternalChannelId, nil, nil), IsNil) c.Check(channelFilter("", other, payloads[1:], decoded), DeepEquals, payloads[1:]) // use tag when channel is the sytem channel c.Check(channelFilter("c/z", store.SystemInternalChannelId, payloads, decoded), HasLen, 0) c.Check(channelFilter("a/x", store.SystemInternalChannelId, payloads, decoded), DeepEquals, []json.RawMessage{payloads[0], payloads[3]}) c.Check(channelFilter("a/x", store.SystemInternalChannelId, payloads[1:], decoded), DeepEquals, []json.RawMessage{payloads[3]}) } ubuntu-push-0.2.1+14.04.20140423.1/server/broker/simple/0000755000015301777760000000000012325725172022636 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/broker/simple/simple_test.go0000644000015301777760000000504612325724711025520 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package simple import ( "encoding/json" stdtesting "testing" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/server/broker" "launchpad.net/ubuntu-push/server/broker/testing" "launchpad.net/ubuntu-push/server/store" ) func TestSimple(t *stdtesting.T) { TestingT(t) } type simpleSuite struct{} var _ = Suite(&simpleSuite{}) var testBrokerConfig = &testing.TestBrokerConfig{10, 5} func (s *simpleSuite) TestNew(c *C) { sto := store.NewInMemoryPendingStore() b := NewSimpleBroker(sto, testBrokerConfig, nil) c.Check(cap(b.sessionCh), Equals, 5) c.Check(len(b.registry), Equals, 0) c.Check(b.sto, Equals, sto) } func (s *simpleSuite) TestFeedPending(c *C) { sto := store.NewInMemoryPendingStore() muchLater := time.Now().Add(10 * time.Minute) notification1 := json.RawMessage(`{"m": "M"}`) decoded1 := map[string]interface{}{"m": "M"} sto.AppendToChannel(store.SystemInternalChannelId, notification1, muchLater) b := NewSimpleBroker(sto, testBrokerConfig, nil) sess := &simpleBrokerSession{ exchanges: make(chan broker.Exchange, 1), } b.feedPending(sess) c.Assert(len(sess.exchanges), Equals, 1) exchg1 := <-sess.exchanges c.Check(exchg1, DeepEquals, &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 1, NotificationPayloads: []json.RawMessage{notification1}, Decoded: []map[string]interface{}{decoded1}, }) } func (s *simpleSuite) TestFeedPendingNop(c *C) { sto := store.NewInMemoryPendingStore() muchLater := time.Now().Add(10 * time.Minute) notification1 := json.RawMessage(`{"m": "M"}`) sto.AppendToChannel(store.SystemInternalChannelId, notification1, muchLater) b := NewSimpleBroker(sto, testBrokerConfig, nil) sess := &simpleBrokerSession{ exchanges: make(chan broker.Exchange, 1), levels: map[store.InternalChannelId]int64{ store.SystemInternalChannelId: 1, }, } b.feedPending(sess) c.Assert(len(sess.exchanges), Equals, 0) } ubuntu-push-0.2.1+14.04.20140423.1/server/broker/simple/simple.go0000644000015301777760000001541212325724711024457 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package simple implements a simple broker for just one process. package simple import ( "sync" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/protocol" "launchpad.net/ubuntu-push/server/broker" "launchpad.net/ubuntu-push/server/store" ) // SimpleBroker implements broker.Broker/BrokerSending for everything // in just one process. type SimpleBroker struct { sto store.PendingStore logger logger.Logger // running state runMutex sync.Mutex running bool stop chan bool stopped chan bool // sessions sessionCh chan *simpleBrokerSession registry map[string]*simpleBrokerSession sessionQueueSize uint // delivery deliveryCh chan *delivery } // simpleBrokerSession represents a session in the broker. type simpleBrokerSession struct { registered bool deviceId string model string imageChannel string done chan bool exchanges chan broker.Exchange levels broker.LevelsMap // for exchanges exchgScratch broker.ExchangesScratchArea } type deliveryKind int const ( broadcastDelivery deliveryKind = iota ) // delivery holds all the information to request a delivery type delivery struct { kind deliveryKind chanId store.InternalChannelId } func (sess *simpleBrokerSession) SessionChannel() <-chan broker.Exchange { return sess.exchanges } func (sess *simpleBrokerSession) DeviceIdentifier() string { return sess.deviceId } func (sess *simpleBrokerSession) DeviceImageModel() string { return sess.model } func (sess *simpleBrokerSession) DeviceImageChannel() string { return sess.imageChannel } func (sess *simpleBrokerSession) Levels() broker.LevelsMap { return sess.levels } func (sess *simpleBrokerSession) ExchangeScratchArea() *broker.ExchangesScratchArea { return &sess.exchgScratch } // NewSimpleBroker makes a new SimpleBroker. func NewSimpleBroker(sto store.PendingStore, cfg broker.BrokerConfig, logger logger.Logger) *SimpleBroker { sessionCh := make(chan *simpleBrokerSession, cfg.BrokerQueueSize()) deliveryCh := make(chan *delivery, cfg.BrokerQueueSize()) registry := make(map[string]*simpleBrokerSession) return &SimpleBroker{ logger: logger, sto: sto, stop: make(chan bool), stopped: make(chan bool), registry: registry, sessionCh: sessionCh, deliveryCh: deliveryCh, sessionQueueSize: cfg.SessionQueueSize(), } } // Start starts the broker. func (b *SimpleBroker) Start() { b.runMutex.Lock() defer b.runMutex.Unlock() if b.running { return } b.running = true go b.run() } // Stop stops the broker. func (b *SimpleBroker) Stop() { b.runMutex.Lock() defer b.runMutex.Unlock() if !b.running { return } b.stop <- true <-b.stopped b.running = false } // Running returns whether ther broker is running. func (b *SimpleBroker) Running() bool { b.runMutex.Lock() defer b.runMutex.Unlock() return b.running } func (b *SimpleBroker) feedPending(sess *simpleBrokerSession) error { // find relevant channels, for now only system channels := []store.InternalChannelId{store.SystemInternalChannelId} for _, chanId := range channels { topLevel, payloads, err := b.sto.GetChannelSnapshot(chanId) if err != nil { // next broadcast will try again b.logger.Errorf("unsuccessful feed pending, get channel snapshot for %v: %v", chanId, err) continue } clientLevel := sess.levels[chanId] if clientLevel != topLevel { broadcastExchg := &broker.BroadcastExchange{ ChanId: chanId, TopLevel: topLevel, NotificationPayloads: payloads, } broadcastExchg.Init() sess.exchanges <- broadcastExchg } } return nil } // Register registers a session with the broker. It feeds the session // pending notifications as well. func (b *SimpleBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) { // xxx sanity check DeviceId model, err := broker.GetInfoString(connect, "device", "?") if err != nil { return nil, err } imageChannel, err := broker.GetInfoString(connect, "channel", "?") if err != nil { return nil, err } levels := map[store.InternalChannelId]int64{} for hexId, v := range connect.Levels { id, err := store.HexToInternalChannelId(hexId) if err != nil { return nil, &broker.ErrAbort{err.Error()} } levels[id] = v } sess := &simpleBrokerSession{ deviceId: connect.DeviceId, model: model, imageChannel: imageChannel, done: make(chan bool), exchanges: make(chan broker.Exchange, b.sessionQueueSize), levels: levels, } b.sessionCh <- sess <-sess.done err = b.feedPending(sess) if err != nil { return nil, err } return sess, nil } // Unregister unregisters a session with the broker. Doesn't wait. func (b *SimpleBroker) Unregister(s broker.BrokerSession) { sess := s.(*simpleBrokerSession) b.sessionCh <- sess } // run runs the agent logic of the broker. func (b *SimpleBroker) run() { Loop: for { select { case <-b.stop: b.stopped <- true break Loop case sess := <-b.sessionCh: if sess.registered { // unregister // unregister only current if b.registry[sess.deviceId] == sess { delete(b.registry, sess.deviceId) } } else { // register prev := b.registry[sess.deviceId] if prev != nil { // kick it close(prev.exchanges) } b.registry[sess.deviceId] = sess sess.registered = true sess.done <- true } case delivery := <-b.deliveryCh: switch delivery.kind { case broadcastDelivery: topLevel, payloads, err := b.sto.GetChannelSnapshot(delivery.chanId) if err != nil { // next broadcast will try again b.logger.Errorf("unsuccessful broadcast, get channel snapshot for %v: %v", delivery.chanId, err) continue Loop } broadcastExchg := &broker.BroadcastExchange{ ChanId: delivery.chanId, TopLevel: topLevel, NotificationPayloads: payloads, } broadcastExchg.Init() for _, sess := range b.registry { sess.exchanges <- broadcastExchg } } } } } // Broadcast requests the broadcast for a channel. func (b *SimpleBroker) Broadcast(chanId store.InternalChannelId) { b.deliveryCh <- &delivery{ kind: broadcastDelivery, chanId: chanId, } } ubuntu-push-0.2.1+14.04.20140423.1/server/broker/simple/suite_test.go0000644000015301777760000000276412325724711025364 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package simple import ( . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/server/broker" "launchpad.net/ubuntu-push/server/broker/testsuite" "launchpad.net/ubuntu-push/server/store" ) // run the common broker test suite against SimpleBroker // aliasing through embedding to get saner report names by gocheck type commonBrokerSuite struct { testsuite.CommonBrokerSuite } var _ = Suite(&commonBrokerSuite{testsuite.CommonBrokerSuite{ MakeBroker: func(sto store.PendingStore, cfg broker.BrokerConfig, log logger.Logger) testsuite.FullBroker { return NewSimpleBroker(sto, cfg, log) }, RevealSession: func(b broker.Broker, deviceId string) broker.BrokerSession { return b.(*SimpleBroker).registry[deviceId] }, RevealBroadcastExchange: func(exchg broker.Exchange) *broker.BroadcastExchange { return exchg.(*broker.BroadcastExchange) }, }}) ubuntu-push-0.2.1+14.04.20140423.1/server/broker/exchanges.go0000644000015301777760000001064612325724711023646 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package broker import ( "encoding/json" "fmt" "launchpad.net/ubuntu-push/protocol" "launchpad.net/ubuntu-push/server/store" ) // Exchanges // Scratch area for exchanges, sessions should hold one of these. type ExchangesScratchArea struct { broadcastMsg protocol.BroadcastMsg ackMsg protocol.AckMsg connBrokenMsg protocol.ConnBrokenMsg } // BroadcastExchange leads a session through delivering a BROADCAST. // For simplicity it is fully public. type BroadcastExchange struct { ChanId store.InternalChannelId TopLevel int64 NotificationPayloads []json.RawMessage Decoded []map[string]interface{} } // check interface already here var _ Exchange = (*BroadcastExchange)(nil) // Init ensures the BroadcastExchange is fully initialized for the sessions. func (sbe *BroadcastExchange) Init() { decoded := make([]map[string]interface{}, len(sbe.NotificationPayloads)) sbe.Decoded = decoded for i, p := range sbe.NotificationPayloads { err := json.Unmarshal(p, &decoded[i]) if err != nil { decoded[i] = nil } } } func filterByLevel(clientLevel, topLevel int64, payloads []json.RawMessage) []json.RawMessage { c := int64(len(payloads)) if c == 0 { return nil } delta := topLevel - clientLevel if delta < 0 { // means too ahead, send the last pending delta = 1 } if delta < c { return payloads[c-delta:] } else { return payloads } } func channelFilter(tag string, chanId store.InternalChannelId, payloads []json.RawMessage, decoded []map[string]interface{}) []json.RawMessage { if len(payloads) != 0 && chanId == store.SystemInternalChannelId { decoded := decoded[len(decoded)-len(payloads):] filtered := make([]json.RawMessage, 0) for i, decoded1 := range decoded { if _, ok := decoded1[tag]; ok { filtered = append(filtered, payloads[i]) } } payloads = filtered } return payloads } // Prepare session for a BROADCAST. func (sbe *BroadcastExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) { clientLevel := sess.Levels()[sbe.ChanId] payloads := filterByLevel(clientLevel, sbe.TopLevel, sbe.NotificationPayloads) tag := fmt.Sprintf("%s/%s", sess.DeviceImageChannel(), sess.DeviceImageModel()) payloads = channelFilter(tag, sbe.ChanId, payloads, sbe.Decoded) if len(payloads) == 0 && sbe.TopLevel >= clientLevel { // empty and don't need to force resync => do nothing return nil, nil, ErrNop } scratchArea := sess.ExchangeScratchArea() scratchArea.broadcastMsg.Reset() scratchArea.broadcastMsg.Type = "broadcast" // xxx need an AppId as well, later scratchArea.broadcastMsg.ChanId = store.InternalChannelIdToHex(sbe.ChanId) scratchArea.broadcastMsg.TopLevel = sbe.TopLevel scratchArea.broadcastMsg.Payloads = payloads return &scratchArea.broadcastMsg, &scratchArea.ackMsg, nil } // Acked deals with an ACK for a BROADCAST. func (sbe *BroadcastExchange) Acked(sess BrokerSession, done bool) error { scratchArea := sess.ExchangeScratchArea() if scratchArea.ackMsg.Type != "ack" { return &ErrAbort{"expected ACK message"} } // update levels sess.Levels()[sbe.ChanId] = sbe.TopLevel return nil } // ConnBrokenExchange breaks a session giving a reason. type ConnBrokenExchange struct { Reason string } // check interface already here var _ Exchange = (*ConnBrokenExchange)(nil) // Prepare session for a CONNBROKEN. func (cbe *ConnBrokenExchange) Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) { scratchArea := sess.ExchangeScratchArea() scratchArea.connBrokenMsg.Type = "connbroken" scratchArea.connBrokenMsg.Reason = cbe.Reason return &scratchArea.connBrokenMsg, nil, nil } // CONNBROKEN isn't acked func (cbe *ConnBrokenExchange) Acked(sess BrokerSession, done bool) error { panic("Acked should not get invoked on ConnBrokenExchange") } ubuntu-push-0.2.1+14.04.20140423.1/server/broker/broker_test.go0000644000015301777760000000257012325724711024221 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package broker import ( "fmt" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/protocol" ) type brokerSuite struct{} var _ = Suite(&brokerSuite{}) func (s *brokerSuite) TestErrAbort(c *C) { err := &ErrAbort{"expected FOO"} c.Check(fmt.Sprintf("%s", err), Equals, "session aborted (expected FOO)") } func (s *brokerSuite) TestGetInfoString(c *C) { connectMsg := &protocol.ConnectMsg{} v, err := GetInfoString(connectMsg, "foo", "?") c.Check(err, IsNil) c.Check(v, Equals, "?") connectMsg.Info = map[string]interface{}{"foo": "yay"} v, err = GetInfoString(connectMsg, "foo", "?") c.Check(err, IsNil) c.Check(v, Equals, "yay") connectMsg.Info["foo"] = 33 v, err = GetInfoString(connectMsg, "foo", "?") c.Check(err, Equals, ErrUnexpectedValue) } ubuntu-push-0.2.1+14.04.20140423.1/server/broker/testsuite/0000755000015301777760000000000012325725172023376 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/broker/testsuite/suite.go0000644000015301777760000002042612325724711025060 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package testsuite contains a common test suite for brokers. package testsuite import ( "encoding/json" "errors" // "log" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/protocol" "launchpad.net/ubuntu-push/server/broker" "launchpad.net/ubuntu-push/server/broker/testing" "launchpad.net/ubuntu-push/server/store" helpers "launchpad.net/ubuntu-push/testing" ) // The expected interface for tested brokers. type FullBroker interface { broker.Broker broker.BrokerSending Start() Stop() Running() bool } // The common brokers' test suite. type CommonBrokerSuite struct { // Build the broker for testing. MakeBroker func(store.PendingStore, broker.BrokerConfig, logger.Logger) FullBroker // Let us get to a session under the broker. RevealSession func(broker.Broker, string) broker.BrokerSession // Let us get to a broker.BroadcastExchange from an Exchange. RevealBroadcastExchange func(broker.Exchange) *broker.BroadcastExchange // private testlog *helpers.TestLogger } func (s *CommonBrokerSuite) SetUpTest(c *C) { s.testlog = helpers.NewTestLogger(c, "error") } var testBrokerConfig = &testing.TestBrokerConfig{10, 5} func (s *CommonBrokerSuite) TestSanity(c *C) { sto := store.NewInMemoryPendingStore() b := s.MakeBroker(sto, testBrokerConfig, nil) c.Check(s.RevealSession(b, "FOO"), IsNil) } func (s *CommonBrokerSuite) TestStartStop(c *C) { b := s.MakeBroker(nil, testBrokerConfig, nil) b.Start() c.Check(b.Running(), Equals, true) b.Start() b.Stop() c.Check(b.Running(), Equals, false) b.Stop() } func (s *CommonBrokerSuite) TestRegistration(c *C) { sto := store.NewInMemoryPendingStore() b := s.MakeBroker(sto, testBrokerConfig, nil) b.Start() defer b.Stop() sess, err := b.Register(&protocol.ConnectMsg{ Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"0": 5}, Info: map[string]interface{}{ "device": "model", "channel": "daily", }, }) c.Assert(err, IsNil) c.Assert(s.RevealSession(b, "dev-1"), Equals, sess) c.Assert(sess.DeviceIdentifier(), Equals, "dev-1") c.Check(sess.DeviceImageModel(), Equals, "model") c.Check(sess.DeviceImageChannel(), Equals, "daily") c.Assert(sess.ExchangeScratchArea(), Not(IsNil)) c.Check(sess.Levels(), DeepEquals, broker.LevelsMap(map[store.InternalChannelId]int64{ store.SystemInternalChannelId: 5, })) b.Unregister(sess) // just to make sure the unregister was processed _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}) c.Assert(err, IsNil) c.Check(s.RevealSession(b, "dev-1"), IsNil) } func (s *CommonBrokerSuite) TestRegistrationBrokenLevels(c *C) { sto := store.NewInMemoryPendingStore() b := s.MakeBroker(sto, testBrokerConfig, nil) b.Start() defer b.Stop() _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1", Levels: map[string]int64{"z": 5}}) c.Check(err, FitsTypeOf, &broker.ErrAbort{}) } func (s *CommonBrokerSuite) TestRegistrationInfoErrors(c *C) { sto := store.NewInMemoryPendingStore() b := s.MakeBroker(sto, testBrokerConfig, nil) b.Start() defer b.Stop() info := map[string]interface{}{ "device": -1, } _, err := b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}) c.Check(err, Equals, broker.ErrUnexpectedValue) info["device"] = "m" info["channel"] = -1 _, err = b.Register(&protocol.ConnectMsg{Type: "connect", Info: info}) c.Check(err, Equals, broker.ErrUnexpectedValue) } func (s *CommonBrokerSuite) TestRegistrationFeedPending(c *C) { sto := store.NewInMemoryPendingStore() notification1 := json.RawMessage(`{"m": "M"}`) muchLater := time.Now().Add(10 * time.Minute) sto.AppendToChannel(store.SystemInternalChannelId, notification1, muchLater) b := s.MakeBroker(sto, testBrokerConfig, nil) b.Start() defer b.Stop() sess, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) c.Assert(err, IsNil) c.Check(len(sess.SessionChannel()), Equals, 1) } func (s *CommonBrokerSuite) TestRegistrationFeedPendingError(c *C) { sto := &testFailingStore{} b := s.MakeBroker(sto, testBrokerConfig, s.testlog) b.Start() defer b.Stop() _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) c.Assert(err, IsNil) // but c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful feed pending, get channel snapshot for 0: get channel snapshot fail\n") } func (s *CommonBrokerSuite) TestRegistrationLastWins(c *C) { sto := store.NewInMemoryPendingStore() b := s.MakeBroker(sto, testBrokerConfig, nil) b.Start() defer b.Stop() sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) c.Assert(err, IsNil) sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) c.Assert(err, IsNil) checkAndFalse := false // previous session got signaled by closing its channel select { case _, ok := <-sess1.SessionChannel(): checkAndFalse = ok == false default: } c.Check(checkAndFalse, Equals, true) c.Assert(s.RevealSession(b, "dev-1"), Equals, sess2) b.Unregister(sess1) // just to make sure the unregister was processed _, err = b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: ""}) c.Assert(err, IsNil) c.Check(s.RevealSession(b, "dev-1"), Equals, sess2) } func (s *CommonBrokerSuite) TestBroadcast(c *C) { sto := store.NewInMemoryPendingStore() notification1 := json.RawMessage(`{"m": "M"}`) decoded1 := map[string]interface{}{"m": "M"} b := s.MakeBroker(sto, testBrokerConfig, nil) b.Start() defer b.Stop() sess1, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) c.Assert(err, IsNil) sess2, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-2"}) c.Assert(err, IsNil) // add notification to channel *after* the registrations muchLater := time.Now().Add(10 * time.Minute) sto.AppendToChannel(store.SystemInternalChannelId, notification1, muchLater) b.Broadcast(store.SystemInternalChannelId) select { case <-time.After(5 * time.Second): c.Fatal("taking too long to get broadcast exchange") case exchg1 := <-sess1.SessionChannel(): c.Check(s.RevealBroadcastExchange(exchg1), DeepEquals, &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 1, NotificationPayloads: []json.RawMessage{notification1}, Decoded: []map[string]interface{}{decoded1}, }) } select { case <-time.After(5 * time.Second): c.Fatal("taking too long to get broadcast exchange") case exchg2 := <-sess2.SessionChannel(): c.Check(s.RevealBroadcastExchange(exchg2), DeepEquals, &broker.BroadcastExchange{ ChanId: store.SystemInternalChannelId, TopLevel: 1, NotificationPayloads: []json.RawMessage{notification1}, Decoded: []map[string]interface{}{decoded1}, }) } } type testFailingStore struct { store.InMemoryPendingStore countdownToFail int } func (sto *testFailingStore) GetChannelSnapshot(chanId store.InternalChannelId) (int64, []json.RawMessage, error) { if sto.countdownToFail == 0 { return 0, nil, errors.New("get channel snapshot fail") } sto.countdownToFail-- return 0, nil, nil } func (s *CommonBrokerSuite) TestBroadcastFail(c *C) { logged := make(chan bool, 1) s.testlog.SetLogEventCb(func(string) { logged <- true }) sto := &testFailingStore{countdownToFail: 1} b := s.MakeBroker(sto, testBrokerConfig, s.testlog) b.Start() defer b.Stop() _, err := b.Register(&protocol.ConnectMsg{Type: "connect", DeviceId: "dev-1"}) c.Assert(err, IsNil) b.Broadcast(store.SystemInternalChannelId) select { case <-time.After(5 * time.Second): c.Fatal("taking too long to log error") case <-logged: } c.Check(s.testlog.Captured(), Matches, "ERROR unsuccessful broadcast, get channel snapshot for 0: get channel snapshot fail\n") } ubuntu-push-0.2.1+14.04.20140423.1/server/broker/broker.go0000644000015301777760000000622312325724711023161 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package broker handles session registrations and delivery of messages // through sessions. package broker import ( "errors" "fmt" "launchpad.net/ubuntu-push/protocol" "launchpad.net/ubuntu-push/server/store" ) // Broker is responsible for registring sessions and delivering messages // through them. type Broker interface { // Register the session. Register(*protocol.ConnectMsg) (BrokerSession, error) // Unregister the session. Unregister(BrokerSession) } // BrokerSending is the notification sending facet of the broker. type BrokerSending interface { // Broadcast channel. Broadcast(chanId store.InternalChannelId) } // Exchange leads the session through performing an exchange, typically delivery. type Exchange interface { Prepare(sess BrokerSession) (outMessage protocol.SplittableMsg, inMessage interface{}, err error) Acked(sess BrokerSession, done bool) error } // ErrNop returned by Prepare means nothing to do/send. var ErrNop = errors.New("nothing to send") // LevelsMap is the type for holding channel levels for session. type LevelsMap map[store.InternalChannelId]int64 // GetInfoString helps retrivieng a string out of a protocol.ConnectMsg.Info func GetInfoString(msg *protocol.ConnectMsg, name, defaultVal string) (string, error) { v, ok := msg.Info[name] if !ok { return defaultVal, nil } s, ok := v.(string) if !ok { return "", ErrUnexpectedValue } return s, nil } // BrokerSession holds broker session state. type BrokerSession interface { // SessionChannel returns the session control channel // on which the session gets exchanges to perform. SessionChannel() <-chan Exchange // DeviceIdentifier returns the device id string. DeviceIdentifier() string // DeviceImageModel returns the device model. DeviceImageModel() string // DeviceImageChannel returns the device system image channel. DeviceImageChannel() string // Levels returns the current channel levels for the session Levels() LevelsMap // ExchangeScratchArea returns the scratch area for exchanges. ExchangeScratchArea() *ExchangesScratchArea } // Session aborted error. type ErrAbort struct { Reason string } func (ea *ErrAbort) Error() string { return fmt.Sprintf("session aborted (%s)", ea.Reason) } // Unexpect value in message var ErrUnexpectedValue = &ErrAbort{"unexpected value in message"} // BrokerConfig gives access to the typical broker configuration. type BrokerConfig interface { // SessionQueueSize gives the session queue size. SessionQueueSize() uint // BrokerQueueSize gives the internal broker queue size. BrokerQueueSize() uint } ubuntu-push-0.2.1+14.04.20140423.1/server/broker/testing/0000755000015301777760000000000012325725172023022 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/broker/testing/impls.go0000644000015301777760000000353712325724711024503 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package testing contains simple test implementations of some broker interfaces. package testing import ( "launchpad.net/ubuntu-push/server/broker" ) // Test implementation of BrokerSession. type TestBrokerSession struct { DeviceId string Model string ImageChannel string Exchanges chan broker.Exchange LevelsMap broker.LevelsMap exchgScratch broker.ExchangesScratchArea } func (tbs *TestBrokerSession) DeviceIdentifier() string { return tbs.DeviceId } func (tbs *TestBrokerSession) DeviceImageModel() string { return tbs.Model } func (tbs *TestBrokerSession) DeviceImageChannel() string { return tbs.ImageChannel } func (tbs *TestBrokerSession) SessionChannel() <-chan broker.Exchange { return tbs.Exchanges } func (tbs *TestBrokerSession) Levels() broker.LevelsMap { return tbs.LevelsMap } func (tbs *TestBrokerSession) ExchangeScratchArea() *broker.ExchangesScratchArea { return &tbs.exchgScratch } // Test implementation of BrokerConfig. type TestBrokerConfig struct { ConfigSessionQueueSize uint ConfigBrokerQueueSize uint } func (tbc *TestBrokerConfig) SessionQueueSize() uint { return tbc.ConfigSessionQueueSize } func (tbc *TestBrokerConfig) BrokerQueueSize() uint { return tbc.ConfigBrokerQueueSize } ubuntu-push-0.2.1+14.04.20140423.1/server/api/0000755000015301777760000000000012325725172020632 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/api/middleware_test.go0000644000015301777760000000261612325724711024340 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package api import ( "net/http" "net/http/httptest" . "launchpad.net/gocheck" helpers "launchpad.net/ubuntu-push/testing" ) type middlewareSuite struct{} var _ = Suite(&middlewareSuite{}) func (s *middlewareSuite) TestPanicTo500Handler(c *C) { logger := helpers.NewTestLogger(c, "debug") panicking := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { panic("panic in handler") }) h := PanicTo500Handler(panicking, logger) w := httptest.NewRecorder() h.ServeHTTP(w, nil) c.Check(w.Code, Equals, 500) c.Check(logger.Captured(), Matches, "(?s)ERROR\\(PANIC\\) serving http: panic in handler:.*") c.Check(w.Header().Get("Content-Type"), Equals, "application/json") c.Check(w.Body.String(), Equals, `{"error":"internal","message":"INTERNAL SERVER ERROR"}`) } ubuntu-push-0.2.1+14.04.20140423.1/server/api/handlers.go0000644000015301777760000001704712325724711022770 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package api has code that offers a REST API for the applications that // want to push messages. package api import ( "encoding/json" "fmt" "io" "net/http" "time" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/server/broker" "launchpad.net/ubuntu-push/server/store" ) const MaxRequestBodyBytes = 4 * 1024 const JSONMediaType = "application/json" // APIError represents a API error (both internally and as JSON in a response). type APIError struct { // http status code StatusCode int `json:"-"` // machine readable label ErrorLabel string `json:"error"` // human message Message string `json:"message"` } // machine readable error labels const ( ioError = "io-error" invalidRequest = "invalid-request" unknownChannel = "unknown-channel" unavailable = "unavailable" internalError = "internal" ) func (apiErr *APIError) Error() string { return fmt.Sprintf("api %s: %s", apiErr.ErrorLabel, apiErr.Message) } // Well-known prebuilt API errors var ( ErrNoContentLengthProvided = &APIError{ http.StatusLengthRequired, invalidRequest, "A Content-Length must be provided", } ErrRequestBodyEmpty = &APIError{ http.StatusBadRequest, invalidRequest, "Request body empty", } ErrRequestBodyTooLarge = &APIError{ http.StatusRequestEntityTooLarge, invalidRequest, "Request body too large", } ErrWrongContentType = &APIError{ http.StatusUnsupportedMediaType, invalidRequest, "Wrong content type, should be application/json", } ErrWrongRequestMethod = &APIError{ http.StatusMethodNotAllowed, invalidRequest, "Wrong request method, should be POST", } ErrMalformedJSONObject = &APIError{ http.StatusBadRequest, invalidRequest, "Malformed JSON Object", } ErrCouldNotReadBody = &APIError{ http.StatusBadRequest, ioError, "Could not read request body", } ErrMissingData = &APIError{ http.StatusBadRequest, invalidRequest, "Missing data field", } ErrInvalidExpiration = &APIError{ http.StatusBadRequest, invalidRequest, "Invalid expiration date", } ErrPastExpiration = &APIError{ http.StatusBadRequest, invalidRequest, "Past expiration date", } ErrUnknownChannel = &APIError{ http.StatusBadRequest, unknownChannel, "Unknown channel", } ErrUnknown = &APIError{ http.StatusInternalServerError, internalError, "Unknown error", } ErrStoreUnavailable = &APIError{ http.StatusServiceUnavailable, unavailable, "Message store unavailable", } ErrCouldNotStoreNotification = &APIError{ http.StatusServiceUnavailable, unavailable, "Could not store notification", } ) type Message struct { Registration string `json:"registration"` CoalesceTag string `json:"coalesce_tag"` Data json.RawMessage `json:"data"` } // Broadcast request JSON object. type Broadcast struct { Channel string `json:"channel"` ExpireOn string `json:"expire_on"` Data json.RawMessage `json:"data"` } // RespondError writes back a JSON error response for a APIError. func RespondError(writer http.ResponseWriter, apiErr *APIError) { wireError, err := json.Marshal(apiErr) if err != nil { panic(fmt.Errorf("couldn't marshal our own errors: %v", err)) } writer.Header().Set("Content-type", JSONMediaType) writer.WriteHeader(apiErr.StatusCode) writer.Write(wireError) } func checkContentLength(request *http.Request, maxBodySize int64) *APIError { if request.ContentLength == -1 { return ErrNoContentLengthProvided } if request.ContentLength == 0 { return ErrRequestBodyEmpty } if request.ContentLength > maxBodySize { return ErrRequestBodyTooLarge } return nil } func checkRequestAsPost(request *http.Request, maxBodySize int64) *APIError { if err := checkContentLength(request, maxBodySize); err != nil { return err } if request.Header.Get("Content-Type") != JSONMediaType { return ErrWrongContentType } if request.Method != "POST" { return ErrWrongRequestMethod } return nil } // ReadBody checks that a POST request is well-formed and reads its body. func ReadBody(request *http.Request, maxBodySize int64) ([]byte, *APIError) { if err := checkRequestAsPost(request, maxBodySize); err != nil { return nil, err } body := make([]byte, request.ContentLength) _, err := io.ReadFull(request.Body, body) if err != nil { return nil, ErrCouldNotReadBody } return body, nil } var zeroTime = time.Time{} func checkBroadcast(bcast *Broadcast) (time.Time, *APIError) { if len(bcast.Data) == 0 { return zeroTime, ErrMissingData } expire, err := time.Parse(time.RFC3339, bcast.ExpireOn) if err != nil { return zeroTime, ErrInvalidExpiration } if expire.Before(time.Now()) { return zeroTime, ErrPastExpiration } return expire, nil } type StoreForRequest func(w http.ResponseWriter, request *http.Request) (store.PendingStore, error) // context holds the interfaces to delegate to serving requests type context struct { storeForRequest StoreForRequest broker broker.BrokerSending logger logger.Logger } func (ctx *context) getStore(w http.ResponseWriter, request *http.Request) (store.PendingStore, *APIError) { sto, err := ctx.storeForRequest(w, request) if err != nil { apiErr, ok := err.(*APIError) if ok { return nil, apiErr } ctx.logger.Errorf("failed to get store: %v", err) return nil, ErrUnknown } return sto, nil } type BroadcastHandler struct { *context } func (h *BroadcastHandler) doBroadcast(sto store.PendingStore, bcast *Broadcast) *APIError { expire, apiErr := checkBroadcast(bcast) if apiErr != nil { return apiErr } chanId, err := sto.GetInternalChannelId(bcast.Channel) if err != nil { switch err { case store.ErrUnknownChannel: return ErrUnknownChannel default: return ErrUnknown } } err = sto.AppendToChannel(chanId, bcast.Data, expire) if err != nil { h.logger.Errorf("could not store notification: %v", err) return ErrCouldNotStoreNotification } h.broker.Broadcast(chanId) return nil } func (h *BroadcastHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { var apiErr *APIError defer func() { if apiErr != nil { RespondError(writer, apiErr) } }() body, apiErr := ReadBody(request, MaxRequestBodyBytes) if apiErr != nil { return } sto, apiErr := h.getStore(writer, request) if apiErr != nil { return } defer sto.Close() broadcast := &Broadcast{} err := json.Unmarshal(body, broadcast) if err != nil { apiErr = ErrMalformedJSONObject return } apiErr = h.doBroadcast(sto, broadcast) if apiErr != nil { return } writer.Header().Set("Content-Type", "application/json") fmt.Fprintf(writer, `{"ok":true}`) } // MakeHandlersMux makes a handler that dispatches for the various API endpoints. func MakeHandlersMux(storeForRequest StoreForRequest, broker broker.BrokerSending, logger logger.Logger) *http.ServeMux { ctx := &context{ storeForRequest: storeForRequest, broker: broker, logger: logger, } mux := http.NewServeMux() mux.Handle("/broadcast", &BroadcastHandler{context: ctx}) return mux } ubuntu-push-0.2.1+14.04.20140423.1/server/api/middleware.go0000644000015301777760000000236612325724711023303 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package api import ( "fmt" "net/http" "launchpad.net/ubuntu-push/logger" ) // PanicTo500Handler wraps another handler such that panics are recovered // and 500 reported. func PanicTo500Handler(h http.Handler, logger logger.Logger) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { defer func() { if err := recover(); err != nil { logger.PanicStackf("serving http: %v", err) // best effort w.Header().Set("Content-Type", "application/json") w.WriteHeader(500) fmt.Fprintf(w, `{"error":"internal","message":"INTERNAL SERVER ERROR"}`) } }() h.ServeHTTP(w, req) }) } ubuntu-push-0.2.1+14.04.20140423.1/server/api/handlers_test.go0000644000015301777760000003372012325724711024023 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package api import ( "bytes" "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "net/http/httptest" "strings" "testing" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/server/store" helpers "launchpad.net/ubuntu-push/testing" ) func TestHandlers(t *testing.T) { TestingT(t) } type handlersSuite struct { messageEndpoint string json string client *http.Client c *C testlog *helpers.TestLogger } var _ = Suite(&handlersSuite{}) func (s *handlersSuite) SetUpTest(c *C) { s.client = &http.Client{} s.testlog = helpers.NewTestLogger(c, "error") } func (s *handlersSuite) TestAPIError(c *C) { var apiErr error = &APIError{400, invalidRequest, "Message"} c.Check(apiErr.Error(), Equals, "api invalid-request: Message") wire, err := json.Marshal(apiErr) c.Assert(err, IsNil) c.Check(string(wire), Equals, `{"error":"invalid-request","message":"Message"}`) } func (s *handlersSuite) TestReadBodyReadError(c *C) { r := bytes.NewReader([]byte{}) // eof too early req, err := http.NewRequest("POST", "", r) c.Assert(err, IsNil) req.Header.Set("Content-Type", "application/json") req.ContentLength = 1000 _, err = ReadBody(req, 2000) c.Check(err, Equals, ErrCouldNotReadBody) } func (s *handlersSuite) TestReadBodyTooBig(c *C) { r := bytes.NewReader([]byte{}) // not read req, err := http.NewRequest("POST", "", r) c.Assert(err, IsNil) req.Header.Set("Content-Type", "application/json") req.ContentLength = 3000 _, err = ReadBody(req, 2000) c.Check(err, Equals, ErrRequestBodyTooLarge) } func (s *handlersSuite) TestGetStore(c *C) { ctx := &context{storeForRequest: func(w http.ResponseWriter, r *http.Request) (store.PendingStore, error) { return nil, ErrStoreUnavailable }} sto, apiErr := ctx.getStore(nil, nil) c.Check(sto, IsNil) c.Check(apiErr, Equals, ErrStoreUnavailable) ctx = &context{storeForRequest: func(w http.ResponseWriter, r *http.Request) (store.PendingStore, error) { return nil, errors.New("something else") }, logger: s.testlog} sto, apiErr = ctx.getStore(nil, nil) c.Check(sto, IsNil) c.Check(apiErr, Equals, ErrUnknown) c.Check(s.testlog.Captured(), Equals, "ERROR failed to get store: something else\n") } var future = time.Now().Add(4 * time.Hour).Format(time.RFC3339) func (s *handlersSuite) TestCheckBroadcast(c *C) { payload := json.RawMessage(`{"foo":"bar"}`) broadcast := &Broadcast{ Channel: "system", ExpireOn: future, Data: payload, } expire, err := checkBroadcast(broadcast) c.Check(err, IsNil) c.Check(expire.Format(time.RFC3339), Equals, future) broadcast = &Broadcast{ Channel: "system", ExpireOn: future, } _, err = checkBroadcast(broadcast) c.Check(err, Equals, ErrMissingData) broadcast = &Broadcast{ Channel: "system", ExpireOn: "12:00", Data: payload, } _, err = checkBroadcast(broadcast) c.Check(err, Equals, ErrInvalidExpiration) broadcast = &Broadcast{ Channel: "system", ExpireOn: time.Now().Add(-10 * time.Hour).Format(time.RFC3339), Data: payload, } _, err = checkBroadcast(broadcast) c.Check(err, Equals, ErrPastExpiration) } type checkBrokerSending struct { store store.PendingStore chanId store.InternalChannelId err error top int64 payloads []json.RawMessage } func (cbsend *checkBrokerSending) Broadcast(chanId store.InternalChannelId) { top, payloads, err := cbsend.store.GetChannelSnapshot(chanId) cbsend.err = err cbsend.chanId = chanId cbsend.top = top cbsend.payloads = payloads } func (s *handlersSuite) TestDoBroadcast(c *C) { sto := store.NewInMemoryPendingStore() bsend := &checkBrokerSending{store: sto} bh := &BroadcastHandler{&context{nil, bsend, nil}} payload := json.RawMessage(`{"a": 1}`) apiErr := bh.doBroadcast(sto, &Broadcast{ Channel: "system", ExpireOn: future, Data: payload, }) c.Check(apiErr, IsNil) c.Check(bsend.err, IsNil) c.Check(bsend.chanId, Equals, store.SystemInternalChannelId) c.Check(bsend.top, Equals, int64(1)) c.Check(bsend.payloads, DeepEquals, []json.RawMessage{payload}) } func (s *handlersSuite) TestDoBroadcastUnknownChannel(c *C) { sto := store.NewInMemoryPendingStore() bh := &BroadcastHandler{} apiErr := bh.doBroadcast(sto, &Broadcast{ Channel: "unknown", ExpireOn: future, Data: json.RawMessage(`{"a": 1}`), }) c.Check(apiErr, Equals, ErrUnknownChannel) } type interceptInMemoryPendingStore struct { *store.InMemoryPendingStore intercept func(meth string, err error) error } func (isto *interceptInMemoryPendingStore) GetInternalChannelId(channel string) (store.InternalChannelId, error) { chanId, err := isto.InMemoryPendingStore.GetInternalChannelId(channel) return chanId, isto.intercept("GetInternalChannelId", err) } func (isto *interceptInMemoryPendingStore) AppendToChannel(chanId store.InternalChannelId, payload json.RawMessage, expiration time.Time) error { err := isto.InMemoryPendingStore.AppendToChannel(chanId, payload, expiration) return isto.intercept("AppendToChannel", err) } func (s *handlersSuite) TestDoBroadcastUnknownError(c *C) { sto := &interceptInMemoryPendingStore{ store.NewInMemoryPendingStore(), func(meth string, err error) error { return errors.New("other") }, } bh := &BroadcastHandler{} apiErr := bh.doBroadcast(sto, &Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"a": 1}`), }) c.Check(apiErr, Equals, ErrUnknown) } func (s *handlersSuite) TestDoBroadcastCouldNotStoreNotification(c *C) { sto := &interceptInMemoryPendingStore{ store.NewInMemoryPendingStore(), func(meth string, err error) error { if meth == "AppendToChannel" { return errors.New("fail") } return err }, } ctx := &context{logger: s.testlog} bh := &BroadcastHandler{ctx} apiErr := bh.doBroadcast(sto, &Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"a": 1}`), }) c.Check(apiErr, Equals, ErrCouldNotStoreNotification) c.Check(s.testlog.Captured(), Equals, "ERROR could not store notification: fail\n") } func newPostRequest(path string, message interface{}, server *httptest.Server) *http.Request { packedMessage, err := json.Marshal(message) if err != nil { panic(err) } reader := bytes.NewReader(packedMessage) url := server.URL + path request, _ := http.NewRequest("POST", url, reader) request.ContentLength = int64(reader.Len()) request.Header.Set("Content-Type", "application/json") return request } func getResponseBody(response *http.Response) ([]byte, error) { defer response.Body.Close() return ioutil.ReadAll(response.Body) } func checkError(c *C, response *http.Response, apiErr *APIError) { c.Check(response.StatusCode, Equals, apiErr.StatusCode) c.Check(response.Header.Get("Content-Type"), Equals, "application/json") error := &APIError{StatusCode: response.StatusCode} body, err := getResponseBody(response) c.Assert(err, IsNil) err = json.Unmarshal(body, error) c.Assert(err, IsNil) c.Check(error, DeepEquals, apiErr) } type testBrokerSending struct { chanId chan store.InternalChannelId } func (bsend testBrokerSending) Broadcast(chanId store.InternalChannelId) { bsend.chanId <- chanId } func (s *handlersSuite) TestRespondsToBasicSystemBroadcast(c *C) { sto := store.NewInMemoryPendingStore() stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { return sto, nil } bsend := testBrokerSending{make(chan store.InternalChannelId, 1)} testServer := httptest.NewServer(MakeHandlersMux(stoForReq, bsend, nil)) defer testServer.Close() payload := json.RawMessage(`{"foo":"bar"}`) request := newPostRequest("/broadcast", &Broadcast{ Channel: "system", ExpireOn: future, Data: payload, }, testServer) response, err := s.client.Do(request) c.Assert(err, IsNil) c.Check(response.StatusCode, Equals, http.StatusOK) c.Check(response.Header.Get("Content-Type"), Equals, "application/json") body, err := getResponseBody(response) c.Assert(err, IsNil) dest := make(map[string]bool) err = json.Unmarshal(body, &dest) c.Assert(err, IsNil) c.Check(dest, DeepEquals, map[string]bool{"ok": true}) top, _, err := sto.GetChannelSnapshot(store.SystemInternalChannelId) c.Assert(err, IsNil) c.Check(top, Equals, int64(1)) c.Check(<-bsend.chanId, Equals, store.SystemInternalChannelId) } func (s *handlersSuite) TestStoreUnavailable(c *C) { stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { return nil, ErrStoreUnavailable } testServer := httptest.NewServer(MakeHandlersMux(stoForReq, nil, nil)) defer testServer.Close() payload := json.RawMessage(`{"foo":"bar"}`) request := newPostRequest("/broadcast", &Broadcast{ Channel: "system", ExpireOn: future, Data: payload, }, testServer) response, err := s.client.Do(request) c.Assert(err, IsNil) checkError(c, response, ErrStoreUnavailable) } func (s *handlersSuite) TestFromBroadcastError(c *C) { sto := store.NewInMemoryPendingStore() stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { return sto, nil } testServer := httptest.NewServer(MakeHandlersMux(stoForReq, nil, nil)) defer testServer.Close() payload := json.RawMessage(`{"foo":"bar"}`) request := newPostRequest("/broadcast", &Broadcast{ Channel: "unknown", ExpireOn: future, Data: payload, }, testServer) response, err := s.client.Do(request) c.Assert(err, IsNil) checkError(c, response, ErrUnknownChannel) } func (s *handlersSuite) TestMissingData(c *C) { stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { return store.NewInMemoryPendingStore(), nil } ctx := &context{stoForReq, nil, nil} testServer := httptest.NewServer(&BroadcastHandler{ctx}) defer testServer.Close() packedMessage := []byte(`{"channel": "system"}`) reader := bytes.NewReader(packedMessage) request, err := http.NewRequest("POST", testServer.URL, reader) c.Assert(err, IsNil) request.ContentLength = int64(len(packedMessage)) request.Header.Set("Content-Type", "application/json") response, err := s.client.Do(request) c.Assert(err, IsNil) checkError(c, response, ErrMissingData) } func (s *handlersSuite) TestCannotBroadcastMalformedData(c *C) { stoForReq := func(http.ResponseWriter, *http.Request) (store.PendingStore, error) { return store.NewInMemoryPendingStore(), nil } ctx := &context{stoForReq, nil, nil} testServer := httptest.NewServer(&BroadcastHandler{ctx}) defer testServer.Close() packedMessage := []byte("{some bogus-message: ") reader := bytes.NewReader(packedMessage) request, err := http.NewRequest("POST", testServer.URL, reader) c.Assert(err, IsNil) request.ContentLength = int64(len(packedMessage)) request.Header.Set("Content-Type", "application/json") response, err := s.client.Do(request) c.Assert(err, IsNil) checkError(c, response, ErrMalformedJSONObject) } func (s *handlersSuite) TestCannotBroadcastTooBigMessages(c *C) { testServer := httptest.NewServer(&BroadcastHandler{}) defer testServer.Close() bigString := strings.Repeat("a", MaxRequestBodyBytes) dataString := fmt.Sprintf(`"%v"`, bigString) request := newPostRequest("/", &Broadcast{ Channel: "some-channel", ExpireOn: future, Data: json.RawMessage([]byte(dataString)), }, testServer) response, err := s.client.Do(request) c.Assert(err, IsNil) checkError(c, response, ErrRequestBodyTooLarge) } func (s *handlersSuite) TestCannotBroadcastWithoutContentLength(c *C) { testServer := httptest.NewServer(&BroadcastHandler{}) defer testServer.Close() dataString := `{"foo":"bar"}` request := newPostRequest("/", &Broadcast{ Channel: "some-channel", ExpireOn: future, Data: json.RawMessage([]byte(dataString)), }, testServer) request.ContentLength = -1 response, err := s.client.Do(request) c.Assert(err, IsNil) checkError(c, response, ErrNoContentLengthProvided) } func (s *handlersSuite) TestCannotBroadcastEmptyMessages(c *C) { testServer := httptest.NewServer(&BroadcastHandler{}) defer testServer.Close() packedMessage := make([]byte, 0) reader := bytes.NewReader(packedMessage) request, err := http.NewRequest("POST", testServer.URL, reader) c.Assert(err, IsNil) request.ContentLength = int64(len(packedMessage)) request.Header.Set("Content-Type", "application/json") response, err := s.client.Do(request) c.Assert(err, IsNil) checkError(c, response, ErrRequestBodyEmpty) } func (s *handlersSuite) TestCannotBroadcastNonJSONMessages(c *C) { testServer := httptest.NewServer(&BroadcastHandler{}) defer testServer.Close() dataString := `{"foo":"bar"}` request := newPostRequest("/", &Broadcast{ Channel: "some-channel", ExpireOn: future, Data: json.RawMessage([]byte(dataString)), }, testServer) request.Header.Set("Content-Type", "text/plain") response, err := s.client.Do(request) c.Assert(err, IsNil) checkError(c, response, ErrWrongContentType) } func (s *handlersSuite) TestCannotBroadcastNonPostMessages(c *C) { testServer := httptest.NewServer(&BroadcastHandler{}) defer testServer.Close() dataString := `{"foo":"bar"}` packedMessage, err := json.Marshal(&Broadcast{ Channel: "some-channel", ExpireOn: future, Data: json.RawMessage([]byte(dataString)), }) s.c.Assert(err, IsNil) reader := bytes.NewReader(packedMessage) request, err := http.NewRequest("GET", testServer.URL, reader) c.Assert(err, IsNil) request.ContentLength = int64(len(packedMessage)) request.Header.Set("Content-Type", "application/json") response, err := s.client.Do(request) c.Assert(err, IsNil) checkError(c, response, ErrWrongRequestMethod) } ubuntu-push-0.2.1+14.04.20140423.1/server/bootlog_test.go0000644000015301777760000000237112325724711023115 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package server import ( "net" "testing" . "launchpad.net/gocheck" helpers "launchpad.net/ubuntu-push/testing" ) func TestRunners(t *testing.T) { TestingT(t) } type bootlogSuite struct{} var _ = Suite(&bootlogSuite{}) func (s *bootlogSuite) TestBootLogListener(c *C) { prevBootLogger := BootLogger testlog := helpers.NewTestLogger(c, "info") BootLogger = testlog defer func() { BootLogger = prevBootLogger }() lst, err := net.Listen("tcp", "127.0.0.1:0") c.Assert(err, IsNil) defer lst.Close() BootLogListener("client", lst) c.Check(testlog.Captured(), Matches, "INFO listening for client on "+lst.Addr().String()+"\n") } ubuntu-push-0.2.1+14.04.20140423.1/server/config_test.go0000644000015301777760000000425212325724711022715 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package server import ( "bytes" "io/ioutil" "os" "path/filepath" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/config" ) type configSuite struct{} var _ = Suite(&configSuite{}) func (s *configSuite) TestDevicesParsedConfig(c *C) { buf := bytes.NewBufferString(`{ "ping_interval": "5m", "exchange_timeout": "10s", "session_queue_size": 10, "broker_queue_size": 100, "addr": "127.0.0.1:9999", "key_pem_file": "key.key", "cert_pem_file": "cert.cert" }`) cfg := &DevicesParsedConfig{} err := config.ReadConfig(buf, cfg) c.Assert(err, IsNil) c.Check(cfg.PingInterval(), Equals, 5*time.Minute) c.Check(cfg.ExchangeTimeout(), Equals, 10*time.Second) c.Check(cfg.BrokerQueueSize(), Equals, uint(100)) c.Check(cfg.SessionQueueSize(), Equals, uint(10)) c.Check(cfg.Addr(), Equals, "127.0.0.1:9999") } func (s *configSuite) TestDevicesParsedConfigLoadFinish(c *C) { tmpDir := c.MkDir() cfg := &DevicesParsedConfig{ ParsedKeyPEMFile: "key.key", ParsedCertPEMFile: "cert.cert", } err := cfg.FinishLoad(tmpDir) c.Check(err, ErrorMatches, "reading key_pem_file:.*no such file.*") err = ioutil.WriteFile(filepath.Join(tmpDir, "key.key"), []byte("KeY"), os.ModePerm) c.Assert(err, IsNil) err = cfg.FinishLoad(tmpDir) c.Check(err, ErrorMatches, "reading cert_pem_file:.*no such file.*") err = ioutil.WriteFile(filepath.Join(tmpDir, "cert.cert"), []byte("CeRt"), os.ModePerm) c.Assert(err, IsNil) err = cfg.FinishLoad(tmpDir) c.Assert(err, IsNil) c.Check(string(cfg.KeyPEMBlock()), Equals, "KeY") c.Check(string(cfg.CertPEMBlock()), Equals, "CeRt") } ubuntu-push-0.2.1+14.04.20140423.1/server/runner_http.go0000644000015301777760000000342212325724711022757 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package server import ( "net" "net/http" "launchpad.net/ubuntu-push/config" ) // A HTTPServeParsedConfig holds and can be used to parse the HTTP server config. type HTTPServeParsedConfig struct { ParsedHTTPAddr config.ConfigHostPort `json:"http_addr"` ParsedHTTPReadTimeout config.ConfigTimeDuration `json:"http_read_timeout"` ParsedHTTPWriteTimeout config.ConfigTimeDuration `json:"http_write_timeout"` } // HTTPServeRunner returns a function to serve HTTP requests. // If httpLst is not nil it will be used as the underlying listener. func HTTPServeRunner(httpLst net.Listener, h http.Handler, parsedCfg *HTTPServeParsedConfig) func() { if httpLst == nil { var err error httpLst, err = net.Listen("tcp", parsedCfg.ParsedHTTPAddr.HostPort()) if err != nil { BootLogFatalf("start http listening: %v", err) } } BootLogListener("http", httpLst) srv := &http.Server{ Handler: h, ReadTimeout: parsedCfg.ParsedHTTPReadTimeout.TimeDuration(), WriteTimeout: parsedCfg.ParsedHTTPWriteTimeout.TimeDuration(), } return func() { err := srv.Serve(httpLst) if err != nil { BootLogFatalf("accepting http connections: %v", err) } } } ubuntu-push-0.2.1+14.04.20140423.1/server/listener/0000755000015301777760000000000012325725172021706 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/listener/listener_test.go0000644000015301777760000001457012325724711025126 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package listener import ( "crypto/tls" "crypto/x509" "net" "syscall" "testing" "time" . "launchpad.net/gocheck" helpers "launchpad.net/ubuntu-push/testing" ) func TestListener(t *testing.T) { TestingT(t) } type listenerSuite struct { testlog *helpers.TestLogger } var _ = Suite(&listenerSuite{}) const NofileMax = 500 func (s *listenerSuite) SetUpSuite(*C) { // make it easier to get a too many open files error var nofileLimit syscall.Rlimit err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &nofileLimit) if err != nil { panic(err) } nofileLimit.Cur = NofileMax err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &nofileLimit) if err != nil { panic(err) } } func (s *listenerSuite) SetUpTest(c *C) { s.testlog = helpers.NewTestLogger(c, "error") } type testDevListenerCfg struct { addr string } func (cfg *testDevListenerCfg) Addr() string { return cfg.addr } func (cfg *testDevListenerCfg) KeyPEMBlock() []byte { return helpers.TestKeyPEMBlock } func (cfg *testDevListenerCfg) CertPEMBlock() []byte { return helpers.TestCertPEMBlock } func (s *listenerSuite) TestDeviceListen(c *C) { lst, err := DeviceListen(nil, &testDevListenerCfg{"127.0.0.1:0"}) c.Check(err, IsNil) defer lst.Close() c.Check(lst.Addr().String(), Matches, `127.0.0.1:\d{5}`) } func (s *listenerSuite) TestDeviceListenError(c *C) { // assume tests are not running as root _, err := DeviceListen(nil, &testDevListenerCfg{"127.0.0.1:99"}) c.Check(err, ErrorMatches, ".*permission denied.*") } type testNetError struct { temp bool } func (tne *testNetError) Error() string { return "test net error" } func (tne *testNetError) Temporary() bool { return tne.temp } func (tne *testNetError) Timeout() bool { return false } var _ net.Error = &testNetError{} // sanity check func (s *listenerSuite) TestHandleTemporary(c *C) { c.Check(handleTemporary(&testNetError{true}), Equals, true) c.Check(handleTemporary(&testNetError{false}), Equals, false) } func testSession(conn net.Conn) error { defer conn.Close() conn.SetDeadline(time.Now().Add(2 * time.Second)) var buf [1]byte _, err := conn.Read(buf[:]) if err != nil { return err } _, err = conn.Write(buf[:]) return err } func testTlsDial(c *C, addr string) (net.Conn, error) { cp := x509.NewCertPool() ok := cp.AppendCertsFromPEM((&testDevListenerCfg{}).CertPEMBlock()) c.Assert(ok, Equals, true) return tls.Dial("tcp", addr, &tls.Config{RootCAs: cp}) } func testWriteByte(c *C, conn net.Conn, toWrite uint32) { conn.SetDeadline(time.Now().Add(2 * time.Second)) _, err := conn.Write([]byte{byte(toWrite)}) c.Assert(err, IsNil) } func testReadByte(c *C, conn net.Conn, expected uint32) { var buf [1]byte _, err := conn.Read(buf[:]) c.Check(err, IsNil) c.Check(buf[0], Equals, byte(expected)) } func (s *listenerSuite) TestDeviceAcceptLoop(c *C) { lst, err := DeviceListen(nil, &testDevListenerCfg{"127.0.0.1:0"}) c.Check(err, IsNil) defer lst.Close() errCh := make(chan error) go func() { errCh <- lst.AcceptLoop(testSession, s.testlog) }() listenerAddr := lst.Addr().String() conn1, err := testTlsDial(c, listenerAddr) c.Assert(err, IsNil) defer conn1.Close() testWriteByte(c, conn1, '1') conn2, err := testTlsDial(c, listenerAddr) c.Assert(err, IsNil) defer conn2.Close() testWriteByte(c, conn2, '2') testReadByte(c, conn1, '1') testReadByte(c, conn2, '2') lst.Close() c.Check(<-errCh, ErrorMatches, ".*use of closed.*") c.Check(s.testlog.Captured(), Equals, "") } func (s *listenerSuite) TestDeviceAcceptLoopTemporaryError(c *C) { // ENFILE is not the temp network error we want to handle this way // but is relatively easy to generate in a controlled way var err error lst, err := DeviceListen(nil, &testDevListenerCfg{"127.0.0.1:0"}) c.Check(err, IsNil) defer lst.Close() errCh := make(chan error) go func() { errCh <- lst.AcceptLoop(testSession, s.testlog) }() listenerAddr := lst.Addr().String() conns := make([]net.Conn, 0, NofileMax) for i := 0; i < NofileMax; i++ { var conn1 net.Conn conn1, err = net.Dial("tcp", listenerAddr) if err != nil { break } defer conn1.Close() conns = append(conns, conn1) } c.Assert(err, ErrorMatches, "*.too many open.*") for _, conn := range conns { conn.Close() } conn2, err := testTlsDial(c, listenerAddr) c.Assert(err, IsNil) defer conn2.Close() testWriteByte(c, conn2, '2') testReadByte(c, conn2, '2') lst.Close() c.Check(<-errCh, ErrorMatches, ".*use of closed.*") c.Check(s.testlog.Captured(), Matches, ".*device listener:.*accept.*too many open.*-- retrying\n") } func (s *listenerSuite) TestDeviceAcceptLoopPanic(c *C) { lst, err := DeviceListen(nil, &testDevListenerCfg{"127.0.0.1:0"}) c.Check(err, IsNil) defer lst.Close() errCh := make(chan error) go func() { errCh <- lst.AcceptLoop(func(conn net.Conn) error { defer conn.Close() panic("session crash") }, s.testlog) }() listenerAddr := lst.Addr().String() _, err = testTlsDial(c, listenerAddr) c.Assert(err, Not(IsNil)) lst.Close() c.Check(<-errCh, ErrorMatches, ".*use of closed.*") c.Check(s.testlog.Captured(), Matches, "(?s)ERROR\\(PANIC\\) terminating device connection on: session crash:.*AcceptLoop.*") } func (s *listenerSuite) TestForeignListener(c *C) { foreignLst, err := net.Listen("tcp", "127.0.0.1:0") c.Check(err, IsNil) lst, err := DeviceListen(foreignLst, &testDevListenerCfg{"127.0.0.1:0"}) c.Check(err, IsNil) defer lst.Close() errCh := make(chan error) go func() { errCh <- lst.AcceptLoop(testSession, s.testlog) }() listenerAddr := lst.Addr().String() c.Check(listenerAddr, Equals, foreignLst.Addr().String()) conn1, err := testTlsDial(c, listenerAddr) c.Assert(err, IsNil) defer conn1.Close() testWriteByte(c, conn1, '1') testReadByte(c, conn1, '1') lst.Close() c.Check(<-errCh, ErrorMatches, ".*use of closed.*") c.Check(s.testlog.Captured(), Equals, "") } ubuntu-push-0.2.1+14.04.20140423.1/server/listener/listener.go0000644000015301777760000000524312325724711024064 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package listener has code to listen for device connections // and setup sessions for them. package listener import ( "crypto/tls" "net" "time" "launchpad.net/ubuntu-push/logger" ) // A DeviceListenerConfig offers the DeviceListener configuration. type DeviceListenerConfig interface { // Addr to listen on. Addr() string // TLS key KeyPEMBlock() []byte // TLS cert CertPEMBlock() []byte } // DeviceListener listens and setup sessions from device connections. type DeviceListener struct { net.Listener } // DeviceListen creates a DeviceListener for device connections based // on config. If lst is not nil DeviceListen just wraps it with a TLS // layer instead of starting creating a new listener. func DeviceListen(lst net.Listener, cfg DeviceListenerConfig) (*DeviceListener, error) { if lst == nil { var err error lst, err = net.Listen("tcp", cfg.Addr()) if err != nil { return nil, err } } cert, err := tls.X509KeyPair(cfg.CertPEMBlock(), cfg.KeyPEMBlock()) if err != nil { return nil, err } tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, SessionTicketsDisabled: true, } return &DeviceListener{tls.NewListener(lst, tlsCfg)}, err } // handleTemporary checks and handles if the error is just a temporary network // error. func handleTemporary(err error) bool { if netError, isNetError := err.(net.Error); isNetError { if netError.Temporary() { // wait, xxx exponential backoff? time.Sleep(100 * time.Millisecond) return true } } return false } // AcceptLoop accepts connections and starts sessions for them. func (dl *DeviceListener) AcceptLoop(session func(net.Conn) error, logger logger.Logger) error { for { // xxx enforce a connection limit conn, err := dl.Listener.Accept() if err != nil { if handleTemporary(err) { logger.Errorf("device listener: %s -- retrying", err) continue } return err } go func() { defer func() { if err := recover(); err != nil { logger.PanicStackf("terminating device connection on: %v", err) } }() session(conn) }() } } ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/0000755000015301777760000000000012325725172022147 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/acceptanceclient.go0000644000015301777760000000732112325724711025764 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package acceptance contains the acceptance client. package acceptance import ( "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "net" "time" "launchpad.net/ubuntu-push/protocol" ) var wireVersionBytes = []byte{protocol.ProtocolWireVersion} // ClienSession holds a client<->server session and its configuration. type ClientSession struct { // configuration DeviceId string Model string ImageChannel string ServerAddr string ExchangeTimeout time.Duration CertPEMBlock []byte ReportPings bool Levels map[string]int64 Insecure bool // don't verify certs Prefix string // prefix for events // connection Connection net.Conn } // Dial connects to a server using the configuration in the ClientSession // and sets up the connection. func (sess *ClientSession) Dial() error { conn, err := net.DialTimeout("tcp", sess.ServerAddr, sess.ExchangeTimeout) if err != nil { return err } tlsConfig := &tls.Config{} if sess.CertPEMBlock != nil { cp := x509.NewCertPool() ok := cp.AppendCertsFromPEM(sess.CertPEMBlock) if !ok { return errors.New("dial: could not parse certificate") } tlsConfig.RootCAs = cp } tlsConfig.InsecureSkipVerify = sess.Insecure sess.Connection = tls.Client(conn, tlsConfig) return nil } type serverMsg struct { Type string `json:"T"` protocol.BroadcastMsg protocol.NotificationsMsg } // Run the session with the server, emits a stream of events. func (sess *ClientSession) Run(events chan<- string) error { conn := sess.Connection defer conn.Close() conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) _, err := conn.Write(wireVersionBytes) if err != nil { return err } proto := protocol.NewProtocol0(conn) err = proto.WriteMessage(protocol.ConnectMsg{ Type: "connect", DeviceId: sess.DeviceId, Levels: sess.Levels, Info: map[string]interface{}{ "device": sess.Model, "channel": sess.ImageChannel, }, }) if err != nil { return err } var connAck protocol.ConnAckMsg err = proto.ReadMessage(&connAck) if err != nil { return err } pingInterval, err := time.ParseDuration(connAck.Params.PingInterval) if err != nil { return err } events <- fmt.Sprintf("%sconnected %v", sess.Prefix, conn.LocalAddr()) var recv serverMsg for { deadAfter := pingInterval + sess.ExchangeTimeout conn.SetDeadline(time.Now().Add(deadAfter)) err = proto.ReadMessage(&recv) if err != nil { return err } switch recv.Type { case "ping": conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) err := proto.WriteMessage(protocol.PingPongMsg{Type: "pong"}) if err != nil { return err } if sess.ReportPings { events <- sess.Prefix + "ping" } case "broadcast": conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) err := proto.WriteMessage(protocol.PingPongMsg{Type: "ack"}) if err != nil { return err } pack, err := json.Marshal(recv.Payloads) if err != nil { return err } events <- fmt.Sprintf("%sbroadcast chan:%v app:%v topLevel:%d payloads:%s", sess.Prefix, recv.ChanId, recv.AppId, recv.TopLevel, pack) } } return nil } ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/acceptance.sh0000755000015301777760000000055312325724711024575 0ustar pbusernogroup00000000000000# run acceptance tests, expects properly setup GOPATH and deps # can set extra build params like -race with BUILD_FLAGS envvar # can set server pkg name with SERVER_PKG set -ex go test $BUILD_FLAGS -i launchpad.net/ubuntu-push/server/acceptance go build $BUILD_FLAGS -o testserver launchpad.net/ubuntu-push/server/dev go test $BUILD_FLAGS -server ./testserver $* ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/suites/0000755000015301777760000000000012325725172023463 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/suites/helpers.go0000644000015301777760000001055012325724711025453 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package suites import ( "bufio" "encoding/json" "fmt" "io/ioutil" "os" "os/exec" "path/filepath" "regexp" "strings" "time" . "launchpad.net/gocheck" helpers "launchpad.net/ubuntu-push/testing" ) // FillConfig fills cfg from values. func FillConfig(cfg, values map[string]interface{}) { for k, v := range values { cfg[k] = v } } // FillServerConfig fills cfg with default server values and "addr": addr. func FillServerConfig(cfg map[string]interface{}, addr string) { FillConfig(cfg, map[string]interface{}{ "exchange_timeout": "0.1s", "ping_interval": "0.5s", "session_queue_size": 10, "broker_queue_size": 100, "addr": addr, "key_pem_file": helpers.SourceRelative("../ssl/testing.key"), "cert_pem_file": helpers.SourceRelative("../ssl/testing.cert"), }) } // FillHttpServerConfig fills cfg with default http server values and // "http_addr": httpAddr. func FillHTTPServerConfig(cfg map[string]interface{}, httpAddr string) { FillConfig(cfg, map[string]interface{}{ "http_addr": httpAddr, "http_read_timeout": "1s", "http_write_timeout": "1s", }) } // WriteConfig writes out a config and returns the written filepath. func WriteConfig(c *C, dir, filename string, cfg map[string]interface{}) string { cfgFpath := filepath.Join(dir, filename) cfgJson, err := json.Marshal(cfg) if err != nil { c.Fatal(err) } err = ioutil.WriteFile(cfgFpath, cfgJson, os.ModePerm) if err != nil { c.Fatal(err) } return cfgFpath } var rxLineInfo = regexp.MustCompile("^.*? ([[:alpha:]].*)\n") // RunAndObserve runs cmdName and returns a channel that will receive // cmdName stderr logging and a function to kill the process. func RunAndObserve(c *C, cmdName string, arg ...string) (<-chan string, func(os.Signal)) { cmd := exec.Command(cmdName, arg...) stderr, err := cmd.StderrPipe() if err != nil { c.Fatal(err) } err = cmd.Start() if err != nil { c.Fatal(err) } bufErr := bufio.NewReaderSize(stderr, 5000) getLineInfo := func(full bool) (string, error) { for { line, err := bufErr.ReadString('\n') if err != nil { return "", err } if full { return strings.TrimRight(line, "\n"), nil } extracted := rxLineInfo.FindStringSubmatch(line) if extracted == nil { return "", fmt.Errorf("unexpected line: %#v", line) } info := extracted[1] return info, nil } } logs := make(chan string, 10) go func() { panicked := false for { info, err := getLineInfo(panicked) if err != nil { logs <- fmt.Sprintf("%s capture: %v", cmdName, err) close(logs) return } if panicked || strings.HasPrefix(info, "ERROR(PANIC") { panicked = true c.Log(info) continue } logs <- info } }() return logs, func(sig os.Signal) { cmd.Process.Signal(sig) } } const ( DevListeningOnPat = "INFO listening for devices on " HTTPListeningOnPat = "INFO listening for http on " debugPrefix = "DEBUG " ) // ExtractListeningAddr goes over logs ignoring DEBUG lines // until a line starting with pat and returns the rest of that line. func ExtractListeningAddr(c *C, logs <-chan string, pat string) string { for line := range logs { if strings.HasPrefix(line, debugPrefix) { continue } if !strings.HasPrefix(line, pat) { c.Fatalf("matching %v: %v", pat, line) } return line[len(pat):] } panic(fmt.Errorf("logs closed unexpectedly marching %v", pat)) } // NextEvent receives an event from given string channel with a 5s timeout, // or from a channel for errors. func NextEvent(events <-chan string, errCh <-chan error) string { select { case <-time.After(5 * time.Second): panic("too long stuck waiting for next event") case err := <-errCh: return err.Error() // will fail comparison typically case evStr := <-events: return evStr } } ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/suites/broadcast.go0000644000015301777760000002215512325724711025757 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package suites import ( "encoding/json" "fmt" "strings" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/client/gethosts" "launchpad.net/ubuntu-push/protocol" "launchpad.net/ubuntu-push/server/api" ) // BroadCastAcceptanceSuite has tests about broadcast. type BroadcastAcceptanceSuite struct { AcceptanceSuite } // Long after the end of the tests. var future = time.Now().Add(9 * time.Hour).Format(time.RFC3339) func (s *BroadcastAcceptanceSuite) TestBroadcastToConnected(c *C) { events, errCh, stop := s.StartClient(c, "DEVB", nil) got, err := s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 42}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"img1/m1":42}]`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) } func (s *BroadcastAcceptanceSuite) TestBroadcastToConnectedChannelFilter(c *C) { events, errCh, stop := s.StartClient(c, "DEVB", nil) got, err := s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m2": 10}`), }) c.Assert(err, IsNil) got, err = s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 20}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"img1/m1":20}]`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) } func (s *BroadcastAcceptanceSuite) TestBroadcastPending(c *C) { // send broadcast that will be pending got, err := s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 1}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") events, errCh, stop := s.StartClient(c, "DEVB", nil) // gettting pending on connect c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"img1/m1":1}]`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) } func (s *BroadcastAcceptanceSuite) TestBroadcasLargeNeedsSplitting(c *C) { // send bunch of broadcasts that will be pending payloadFmt := fmt.Sprintf(`{"img1/m1":%%d,"bloat":"%s"}`, strings.Repeat("x", 1024*2)) for i := 0; i < 32; i++ { got, err := s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(fmt.Sprintf(payloadFmt, i)), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") } events, errCh, stop := s.StartClient(c, "DEVC", nil) // gettting pending on connect c.Check(NextEvent(events, errCh), Matches, `broadcast chan:0 app: topLevel:30 payloads:\[{"img1/m1":0,.*`) c.Check(NextEvent(events, errCh), Matches, `broadcast chan:0 app: topLevel:32 payloads:\[.*`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) } func (s *BroadcastAcceptanceSuite) TestBroadcastDistribution2(c *C) { // start 1st client events1, errCh1, stop1 := s.StartClient(c, "DEV1", nil) // start 2nd client events2, errCh2, stop2 := s.StartClient(c, "DEV2", nil) // broadcast got, err := s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 42}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") c.Check(NextEvent(events1, errCh1), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"img1/m1":42}]`) c.Check(NextEvent(events2, errCh2), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"img1/m1":42}]`) stop1() stop2() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh1), Equals, 0) c.Check(len(errCh2), Equals, 0) } func (s *BroadcastAcceptanceSuite) TestBroadcastFilterByLevel(c *C) { events, errCh, stop := s.StartClient(c, "DEVD", nil) got, err := s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 1}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:1 payloads:[{"img1/m1":1}]`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) // another broadcast got, err = s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 2}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") // reconnect, provide levels, get only later notification events, errCh, stop = s.StartClient(c, "DEVD", map[string]int64{ protocol.SystemChannelId: 1, }) c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"img1/m1":2}]`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) } func (s *BroadcastAcceptanceSuite) TestBroadcastTooAhead(c *C) { // send broadcasts that will be pending got, err := s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 1}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") got, err = s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 2}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") events, errCh, stop := s.StartClient(c, "DEVB", map[string]int64{ protocol.SystemChannelId: 10, }) // gettting last one pending on connect c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"img1/m1":2}]`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) } func (s *BroadcastAcceptanceSuite) TestBroadcastTooAheadOnEmpty(c *C) { // nothing there events, errCh, stop := s.StartClient(c, "DEVB", map[string]int64{ protocol.SystemChannelId: 10, }) // gettting empty pending on connect c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:0 payloads:null`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) } func (s *BroadcastAcceptanceSuite) TestBroadcastWayBehind(c *C) { // send broadcasts that will be pending got, err := s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 1}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") got, err = s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 2}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") events, errCh, stop := s.StartClient(c, "DEVB", map[string]int64{ protocol.SystemChannelId: -10, }) // gettting pending on connect c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"img1/m1":1},{"img1/m1":2}]`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) } func (s *BroadcastAcceptanceSuite) TestBroadcastExpiration(c *C) { // send broadcast that will be pending, and one that will expire got, err := s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: future, Data: json.RawMessage(`{"img1/m1": 1}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") got, err = s.PostRequest("/broadcast", &api.Broadcast{ Channel: "system", ExpireOn: time.Now().Add(1 * time.Second).Format(time.RFC3339), Data: json.RawMessage(`{"img1/m1": 2}`), }) c.Assert(err, IsNil) c.Assert(got, Matches, ".*ok.*") time.Sleep(2 * time.Second) // second broadcast is expired events, errCh, stop := s.StartClient(c, "DEVB", nil) // gettting pending on connect c.Check(NextEvent(events, errCh), Equals, `broadcast chan:0 app: topLevel:2 payloads:[{"img1/m1":1}]`) stop() c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*EOF`) c.Check(len(errCh), Equals, 0) } // test /delivery-hosts func (s *BroadcastAcceptanceSuite) TestGetHosts(c *C) { gh := gethosts.New("", s.ServerAPIURL+"/delivery-hosts", 2*time.Second) hosts, err := gh.Get() c.Assert(err, IsNil) c.Check(hosts, DeepEquals, []string{s.ServerAddr}) } ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/suites/suite.go0000644000015301777760000001252112325724711025142 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package suites contains reusable acceptance test suites. package suites import ( "bytes" "encoding/json" "flag" "fmt" "io/ioutil" "net" "net/http" "os" "runtime" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/server/acceptance" helpers "launchpad.net/ubuntu-push/testing" ) // ServerHandle holds the information to attach a client to the test server. type ServerHandle struct { ServerAddr string ServerHTTPAddr string ServerEvents <-chan string } // Start a client. func (h *ServerHandle) StartClient(c *C, devId string, levels map[string]int64) (events <-chan string, errorCh <-chan error, stop func()) { errCh := make(chan error, 1) cliEvents := make(chan string, 10) sess := testClientSession(h.ServerAddr, devId, "m1", "img1", false) sess.Levels = levels err := sess.Dial() c.Assert(err, IsNil) clientShutdown := make(chan bool, 1) // abused as an atomic flag intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { // read after ack if op == "read" && len(clientShutdown) > 0 { // exit the sess.Run() goroutine, client will close runtime.Goexit() } return false, 0, nil } sess.Connection = &interceptingConn{sess.Connection, 0, 0, intercept} go func() { errCh <- sess.Run(cliEvents) }() c.Assert(NextEvent(cliEvents, errCh), Matches, "connected .*") c.Assert(NextEvent(h.ServerEvents, nil), Matches, ".*session.* connected .*") c.Assert(NextEvent(h.ServerEvents, nil), Matches, ".*session.* registered "+devId) return cliEvents, errCh, func() { clientShutdown <- true } } // AcceptanceSuite has the basic functionality of the acceptance suites. type AcceptanceSuite struct { // hook to start the server(s) StartServer func(c *C, s *AcceptanceSuite, handle *ServerHandle) // populated by StartServer ServerHandle ServerAPIURL string // KillGroup should be populated by StartServer with functions // to kill the server process KillGroup map[string]func(os.Signal) // hook to adjust requests MassageRequest func(req *http.Request) *http.Request // other state httpClient *http.Client } // Start a new server for each test. func (s *AcceptanceSuite) SetUpTest(c *C) { s.KillGroup = make(map[string]func(os.Signal)) s.StartServer(c, s, &s.ServerHandle) c.Assert(s.ServerHandle.ServerEvents, NotNil) c.Assert(s.ServerHandle.ServerAddr, Not(Equals), "") c.Assert(s.ServerAPIURL, Not(Equals), "") s.httpClient = &http.Client{} } func (s *AcceptanceSuite) TearDownTest(c *C) { for _, f := range s.KillGroup { f(os.Kill) } } // Post a API request. func (s *AcceptanceSuite) PostRequest(path string, message interface{}) (string, error) { packedMessage, err := json.Marshal(message) if err != nil { panic(err) } reader := bytes.NewReader(packedMessage) url := s.ServerAPIURL + path request, _ := http.NewRequest("POST", url, reader) request.ContentLength = int64(reader.Len()) request.Header.Set("Content-Type", "application/json") if s.MassageRequest != nil { request = s.MassageRequest(request) } resp, err := s.httpClient.Do(request) if err != nil { panic(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) return string(body), err } func testClientSession(addr string, deviceId, model, imageChannel string, reportPings bool) *acceptance.ClientSession { certPEMBlock, err := ioutil.ReadFile(helpers.SourceRelative("../ssl/testing.cert")) if err != nil { panic(fmt.Sprintf("could not read ssl/testing.cert: %v", err)) } return &acceptance.ClientSession{ ExchangeTimeout: 100 * time.Millisecond, ServerAddr: addr, CertPEMBlock: certPEMBlock, DeviceId: deviceId, Model: model, ImageChannel: imageChannel, ReportPings: reportPings, } } // typically combined with -gocheck.vv or test selection var logTraffic = flag.Bool("logTraffic", false, "log traffic") type connInterceptor func(ic *interceptingConn, op string, b []byte) (bool, int, error) type interceptingConn struct { net.Conn totalRead int totalWritten int intercept connInterceptor } func (ic *interceptingConn) Write(b []byte) (n int, err error) { done := false before := ic.totalWritten if ic.intercept != nil { done, n, err = ic.intercept(ic, "write", b) } if !done { n, err = ic.Conn.Write(b) } ic.totalWritten += n if *logTraffic { fmt.Printf("W[%v]: %d %#v %v %d\n", ic.Conn.LocalAddr(), before, string(b[:n]), err, ic.totalWritten) } return } func (ic *interceptingConn) Read(b []byte) (n int, err error) { done := false before := ic.totalRead if ic.intercept != nil { done, n, err = ic.intercept(ic, "read", b) } if !done { n, err = ic.Conn.Read(b) } ic.totalRead += n if *logTraffic { fmt.Printf("R[%v]: %d %#v %v %d\n", ic.Conn.LocalAddr(), before, string(b[:n]), err, ic.totalRead) } return } ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/suites/pingpong.go0000644000015301777760000000644012325724711025635 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package suites import ( "runtime" "strings" "time" . "launchpad.net/gocheck" ) // PingPongAcceptanceSuite has tests about connectivity and ping-pong requests. type PingPongAcceptanceSuite struct { AcceptanceSuite } // Tests about connection, ping-pong, disconnection scenarios func (s *PingPongAcceptanceSuite) TestConnectPingPing(c *C) { errCh := make(chan error, 1) events := make(chan string, 10) sess := testClientSession(s.ServerAddr, "DEVA", "m1", "img1", true) err := sess.Dial() c.Assert(err, IsNil) intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { // would be 3rd ping read, based on logged traffic if op == "read" && ic.totalRead >= 79 { // exit the sess.Run() goroutine, client will close runtime.Goexit() } return false, 0, nil } sess.Connection = &interceptingConn{sess.Connection, 0, 0, intercept} go func() { errCh <- sess.Run(events) }() connectCli := NextEvent(events, errCh) connectSrv := NextEvent(s.ServerEvents, nil) registeredSrv := NextEvent(s.ServerEvents, nil) tconnect := time.Now() c.Assert(connectSrv, Matches, ".*session.* connected .*") c.Assert(registeredSrv, Matches, ".*session.* registered DEVA") c.Assert(strings.HasSuffix(connectSrv, connectCli), Equals, true) c.Assert(NextEvent(events, errCh), Equals, "ping") elapsedOfPing := float64(time.Since(tconnect)) / float64(500*time.Millisecond) c.Check(elapsedOfPing >= 1.0, Equals, true) c.Check(elapsedOfPing < 1.05, Equals, true) c.Assert(NextEvent(events, errCh), Equals, "ping") c.Assert(NextEvent(s.ServerEvents, nil), Matches, ".*session.* ended with: EOF") c.Check(len(errCh), Equals, 0) } func (s *PingPongAcceptanceSuite) TestConnectPingNeverPong(c *C) { errCh := make(chan error, 1) events := make(chan string, 10) sess := testClientSession(s.ServerAddr, "DEVB", "m1", "img1", true) err := sess.Dial() c.Assert(err, IsNil) intercept := func(ic *interceptingConn, op string, b []byte) (bool, int, error) { // would be pong to 2nd ping, based on logged traffic if op == "write" && ic.totalRead >= 67 { time.Sleep(200 * time.Millisecond) // exit the sess.Run() goroutine, client will close runtime.Goexit() } return false, 0, nil } sess.Connection = &interceptingConn{sess.Connection, 0, 0, intercept} go func() { errCh <- sess.Run(events) }() c.Assert(NextEvent(events, errCh), Matches, "connected .*") c.Assert(NextEvent(s.ServerEvents, nil), Matches, ".*session.* connected .*") c.Assert(NextEvent(s.ServerEvents, nil), Matches, ".*session.* registered .*") c.Assert(NextEvent(events, errCh), Equals, "ping") c.Assert(NextEvent(s.ServerEvents, nil), Matches, `.* ended with:.*timeout`) c.Check(len(errCh), Equals, 0) } ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/cmd/0000755000015301777760000000000012325725172022712 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/cmd/acceptanceclient.go0000644000015301777760000000531712325724720026532 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // acceptanceclient command for playing. package main import ( "flag" "fmt" "log" "os" "path/filepath" "launchpad.net/ubuntu-push/config" "launchpad.net/ubuntu-push/server/acceptance" ) var ( insecureFlag = flag.Bool("insecure", false, "disable checking of server certificate and hostname") reportPingsFlag = flag.Bool("reportPings", true, "report each Ping from the server") deviceModel = flag.String("model", "?", "device image model") imageChannel = flag.String("imageChannel", "?", "image channel") ) type configuration struct { // session configuration ExchangeTimeout config.ConfigTimeDuration `json:"exchange_timeout"` // server connection config Addr config.ConfigHostPort CertPEMFile string `json:"cert_pem_file"` } func main() { flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: acceptancclient [options] \n") flag.PrintDefaults() } missingArg := func(what string) { fmt.Fprintf(os.Stderr, "missing %s\n", what) flag.Usage() os.Exit(2) } flag.Parse() narg := flag.NArg() switch { case narg < 1: missingArg("config file") case narg < 2: missingArg("device-id") } configFName := flag.Arg(0) f, err := os.Open(configFName) if err != nil { log.Fatalf("reading config: %v", err) } cfg := &configuration{} err = config.ReadConfig(f, cfg) if err != nil { log.Fatalf("reading config: %v", err) } session := &acceptance.ClientSession{ ExchangeTimeout: cfg.ExchangeTimeout.TimeDuration(), ServerAddr: cfg.Addr.HostPort(), DeviceId: flag.Arg(1), // flags Model: *deviceModel, ImageChannel: *imageChannel, ReportPings: *reportPingsFlag, Insecure: *insecureFlag, } log.Printf("with: %#v", session) if !*insecureFlag { session.CertPEMBlock, err = config.LoadFile(cfg.CertPEMFile, filepath.Dir(configFName)) if err != nil { log.Fatalf("reading CertPEMFile: %v", err) } } err = session.Dial() if err != nil { log.Fatalln(err) } events := make(chan string, 5) go func() { for { log.Println(<-events) } }() err = session.Run(events) if err != nil { log.Fatalln(err) } } ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/acceptance_test.go0000644000015301777760000000400212325724711025615 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package acceptance_test import ( "flag" "fmt" "testing" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/server/acceptance/suites" ) func TestAcceptance(t *testing.T) { TestingT(t) } var serverCmd = flag.String("server", "", "server to test") func testServerConfig(addr, httpAddr string) map[string]interface{} { cfg := make(map[string]interface{}) suites.FillServerConfig(cfg, addr) suites.FillHTTPServerConfig(cfg, httpAddr) cfg["delivery_domain"] = "localhost" return cfg } // Start a server. func StartServer(c *C, s *suites.AcceptanceSuite, handle *suites.ServerHandle) { if *serverCmd == "" { c.Skip("executable server not specified") } tmpDir := c.MkDir() cfg := testServerConfig("127.0.0.1:0", "127.0.0.1:0") cfgFilename := suites.WriteConfig(c, tmpDir, "config.json", cfg) logs, killServer := suites.RunAndObserve(c, *serverCmd, cfgFilename) s.KillGroup["server"] = killServer handle.ServerHTTPAddr = suites.ExtractListeningAddr(c, logs, suites.HTTPListeningOnPat) s.ServerAPIURL = fmt.Sprintf("http://%s", handle.ServerHTTPAddr) handle.ServerAddr = suites.ExtractListeningAddr(c, logs, suites.DevListeningOnPat) handle.ServerEvents = logs } // ping pong/connectivity var _ = Suite(&suites.PingPongAcceptanceSuite{suites.AcceptanceSuite{StartServer: StartServer}}) // broadcast var _ = Suite(&suites.BroadcastAcceptanceSuite{suites.AcceptanceSuite{StartServer: StartServer}}) ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/ssl/0000755000015301777760000000000012325725172022750 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/ssl/testing.key0000644000015301777760000000076112325724711025141 0ustar pbusernogroup00000000000000-----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBAPw+niki17X2qALE2A2AzE1q5dvK9CI4OduRtT9IgbFLC6psqAT2 1NA+QbY17nWSSpyP65zkMkwKXrbDzstwLPkCAwEAAQJAKwXbIBULScP6QA6m8xam wgWbkvN41GVWqPafPV32kPBvKwSc+M1e+JR7g3/xPZE7TCELcfYi4yXEHZZI3Pbh oQIhAP/UsgJbsfH1GFv8Y8qGl5l/kmwwkwHhuKvEC87Yur9FAiEA/GlQv3ZfaXnT lcCFT0aL02O0RDiRYyMUG/JAZQJs6CUCIQCHO5SZYIUwxIGK5mCNxxXOAzyQSiD7 hqkKywf+4FvfDQIhALa0TLyqJFom0t7c4iIGAIRc8UlIYQSPiajI64+x9775AiEA 0v4fgSK/Rq059zW1753JjuB6aR0Uh+3RqJII4dUR1Wg= -----END RSA PRIVATE KEY----- ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/ssl/testing.cert0000644000015301777760000000103612325724711025302 0ustar pbusernogroup00000000000000-----BEGIN CERTIFICATE----- MIIBYzCCAQ+gAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD bzAeFw0xMzEyMTkyMDU1NDNaFw0yMzEyMTcyMDU1NDNaMBIxEDAOBgNVBAoTB0Fj bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAPw+niki17X2qALE2A2AzE1q5dvK 9CI4OduRtT9IgbFLC6psqAT21NA+QbY17nWSSpyP65zkMkwKXrbDzstwLPkCAwEA AaNUMFIwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud EwEB/wQFMAMBAf8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMAsGCSqGSIb3 DQEBBQNBAFqiVI+Km2XPSO+pxITaPvhmuzg+XG3l1+2di3gL+HlDobocjBqRctRU YySO32W07acjGJmCHUKpCJuq9X8hpmk= -----END CERTIFICATE----- ubuntu-push-0.2.1+14.04.20140423.1/server/acceptance/ssl/README0000644000015301777760000000030312325724711023622 0ustar pbusernogroup00000000000000testing.key/testing.cert ------------------------ Generated with: go run /usr/lib/go/src/pkg/crypto/tls/generate_cert.go -ca -host localhost -rsa-bits 512 -duration 87600h and then renamed. ubuntu-push-0.2.1+14.04.20140423.1/server/session/0000755000015301777760000000000012325725172021544 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/session/session.go0000644000015301777760000001034612325724711023560 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package session has code handling long-lived connections from devices. package session import ( "net" "time" "launchpad.net/ubuntu-push/protocol" "launchpad.net/ubuntu-push/server/broker" ) // SessionConfig is for carrying the session configuration. type SessionConfig interface { // pings are emitted each ping interval PingInterval() time.Duration // send and waiting for response shouldn't take more than exchange // timeout ExchangeTimeout() time.Duration } // sessionStart manages the start of the protocol session. func sessionStart(proto protocol.Protocol, brkr broker.Broker, cfg SessionConfig) (broker.BrokerSession, error) { var connMsg protocol.ConnectMsg proto.SetDeadline(time.Now().Add(cfg.ExchangeTimeout())) err := proto.ReadMessage(&connMsg) if err != nil { return nil, err } if connMsg.Type != "connect" { return nil, &broker.ErrAbort{"expected CONNECT message"} } err = proto.WriteMessage(&protocol.ConnAckMsg{ Type: "connack", Params: protocol.ConnAckParams{PingInterval: cfg.PingInterval().String()}, }) if err != nil { return nil, err } return brkr.Register(&connMsg) } // exchange writes outMsg message, reads answer in inMsg func exchange(proto protocol.Protocol, outMsg, inMsg interface{}, exchangeTimeout time.Duration) error { proto.SetDeadline(time.Now().Add(exchangeTimeout)) err := proto.WriteMessage(outMsg) if err != nil { return err } if inMsg == nil { // no answer expected, breaking connection return &broker.ErrAbort{"session broken for reason"} } err = proto.ReadMessage(inMsg) if err != nil { return err } return nil } // sessionLoop manages the exchanges of the protocol session. func sessionLoop(proto protocol.Protocol, sess broker.BrokerSession, cfg SessionConfig, track SessionTracker) error { pingInterval := cfg.PingInterval() exchangeTimeout := cfg.ExchangeTimeout() pingTimer := time.NewTimer(pingInterval) intervalStart := time.Now() ch := sess.SessionChannel() Loop: for { select { case <-pingTimer.C: track.EffectivePingInterval(time.Since(intervalStart)) pingMsg := &protocol.PingPongMsg{"ping"} var pongMsg protocol.PingPongMsg err := exchange(proto, pingMsg, &pongMsg, exchangeTimeout) if err != nil { return err } if pongMsg.Type != "pong" { return &broker.ErrAbort{"expected PONG message"} } pingTimer.Reset(pingInterval) case exchg, ok := <-ch: pingTimer.Stop() if !ok { return &broker.ErrAbort{"terminated"} } outMsg, inMsg, err := exchg.Prepare(sess) if err == broker.ErrNop { // nothing to do pingTimer.Reset(pingInterval) intervalStart = time.Now() continue Loop } if err != nil { return err } for { done := outMsg.Split() err = exchange(proto, outMsg, inMsg, exchangeTimeout) if err != nil { return err } if done { pingTimer.Reset(pingInterval) intervalStart = time.Now() } err = exchg.Acked(sess, done) if err != nil { return err } if done { break } } } } } // Session manages the session with a client. func Session(conn net.Conn, brkr broker.Broker, cfg SessionConfig, track SessionTracker) error { defer conn.Close() track.Start(conn) v, err := protocol.ReadWireFormatVersion(conn, cfg.ExchangeTimeout()) if err != nil { return track.End(err) } if v != protocol.ProtocolWireVersion { return track.End(&broker.ErrAbort{"unexpected wire format version"}) } proto := protocol.NewProtocol0(conn) sess, err := sessionStart(proto, brkr, cfg) if err != nil { return track.End(err) } track.Registered(sess) defer brkr.Unregister(sess) return track.End(sessionLoop(proto, sess, cfg, track)) } ubuntu-push-0.2.1+14.04.20140423.1/server/session/tracker_test.go0000644000015301777760000000423312325724711024565 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package session import ( "fmt" "net" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/server/broker" "launchpad.net/ubuntu-push/server/broker/testing" helpers "launchpad.net/ubuntu-push/testing" ) type trackerSuite struct { testlog *helpers.TestLogger } func (s *trackerSuite) SetUpTest(c *C) { s.testlog = helpers.NewTestLogger(c, "debug") } var _ = Suite(&trackerSuite{}) type testRemoteAddrable struct{} func (tra *testRemoteAddrable) RemoteAddr() net.Addr { return &net.TCPAddr{net.IPv4(127, 0, 0, 1), 9999, ""} } func (s *trackerSuite) TestSessionTrackStart(c *C) { track := NewTracker(s.testlog) track.Start(&testRemoteAddrable{}) c.Check(track.(*tracker).sessionId, Not(Equals), 0) regExpected := fmt.Sprintf(`DEBUG session\(%x\) connected 127\.0\.0\.1:9999\n`, track.(*tracker).sessionId) c.Check(s.testlog.Captured(), Matches, regExpected) } func (s *trackerSuite) TestSessionTrackRegistered(c *C) { track := NewTracker(s.testlog) track.Start(&testRemoteAddrable{}) track.Registered(&testing.TestBrokerSession{DeviceId: "DEV-ID"}) regExpected := fmt.Sprintf(`.*connected.*\nINFO session\(%x\) registered DEV-ID\n`, track.(*tracker).sessionId) c.Check(s.testlog.Captured(), Matches, regExpected) } func (s *trackerSuite) TestSessionTrackEnd(c *C) { track := NewTracker(s.testlog) track.Start(&testRemoteAddrable{}) track.End(&broker.ErrAbort{}) regExpected := fmt.Sprintf(`.*connected.*\nDEBUG session\(%x\) ended with: session aborted \(\)\n`, track.(*tracker).sessionId) c.Check(s.testlog.Captured(), Matches, regExpected) } ubuntu-push-0.2.1+14.04.20140423.1/server/session/tracker.go0000644000015301777760000000377712325724711023542 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package session import ( "net" "time" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/server/broker" ) // SessionTracker logs session events. type SessionTracker interface { logger.Logger // Session got started. Start(WithRemoteAddr) // Session got registered with broker as sess BrokerSession. Registered(sess broker.BrokerSession) // Report effective elapsed ping interval. EffectivePingInterval(time.Duration) // Session got ended with error err (can be nil). End(error) error } // WithRemoteAddr can report a remote address. type WithRemoteAddr interface { RemoteAddr() net.Addr } var sessionsEpoch = time.Date(2013, 1, 1, 0, 0, 0, 0, time.UTC).UnixNano() // Tracker implements SessionTracker simply. type tracker struct { logger.Logger sessionId int64 // xxx use timeuuid later } func NewTracker(logger logger.Logger) SessionTracker { return &tracker{Logger: logger} } func (trk *tracker) Start(conn WithRemoteAddr) { trk.sessionId = time.Now().UnixNano() - sessionsEpoch trk.Debugf("session(%x) connected %v", trk.sessionId, conn.RemoteAddr()) } func (trk *tracker) Registered(sess broker.BrokerSession) { trk.Infof("session(%x) registered %v", trk.sessionId, sess.DeviceIdentifier()) } func (trk *tracker) EffectivePingInterval(time.Duration) { } func (trk *tracker) End(err error) error { trk.Debugf("session(%x) ended with: %v", trk.sessionId, err) return err } ubuntu-push-0.2.1+14.04.20140423.1/server/session/session_test.go0000644000015301777760000005107112325724711024617 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package session import ( "bufio" "encoding/json" "errors" "fmt" "io" "net" "reflect" stdtesting "testing" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/protocol" "launchpad.net/ubuntu-push/server/broker" "launchpad.net/ubuntu-push/server/broker/testing" helpers "launchpad.net/ubuntu-push/testing" ) func TestSession(t *stdtesting.T) { TestingT(t) } type sessionSuite struct { testlog *helpers.TestLogger } func (s *sessionSuite) SetUpTest(c *C) { s.testlog = helpers.NewTestLogger(c, "debug") } var _ = Suite(&sessionSuite{}) type testProtocol struct { up chan interface{} down chan interface{} } // takeNext takes a value from given channel with a 5s timeout func takeNext(ch <-chan interface{}) interface{} { select { case <-time.After(5 * time.Second): panic("test protocol exchange stuck: too long waiting") case v := <-ch: return v } return nil } func (c *testProtocol) SetDeadline(t time.Time) { deadAfter := t.Sub(time.Now()) deadAfter = (deadAfter + time.Millisecond/2) / time.Millisecond * time.Millisecond c.down <- fmt.Sprintf("deadline %v", deadAfter) } func (c *testProtocol) ReadMessage(dest interface{}) error { switch v := takeNext(c.up).(type) { case error: return v default: // make sure JSON.Unmarshal works with dest var marshalledMsg []byte marshalledMsg, err := json.Marshal(v) if err != nil { return fmt.Errorf("can't jsonify test value: %v", v) } return json.Unmarshal(marshalledMsg, dest) } return nil } func (c *testProtocol) WriteMessage(src interface{}) error { // make sure JSON.Marshal works with src _, err := json.Marshal(src) if err != nil { return err } val := reflect.ValueOf(src) if val.Kind() == reflect.Ptr { src = val.Elem().Interface() } c.down <- src switch v := takeNext(c.up).(type) { case error: return v } return nil } type testSessionConfig struct { exchangeTimeout time.Duration pingInterval time.Duration } func (tsc *testSessionConfig) PingInterval() time.Duration { return tsc.pingInterval } func (tsc *testSessionConfig) ExchangeTimeout() time.Duration { return tsc.exchangeTimeout } var cfg10msPingInterval5msExchangeTout = &testSessionConfig{ pingInterval: 10 * time.Millisecond, exchangeTimeout: 5 * time.Millisecond, } type testBroker struct { registration chan interface{} err error } func newTestBroker() *testBroker { return &testBroker{registration: make(chan interface{}, 2)} } func (tb *testBroker) Register(connect *protocol.ConnectMsg) (broker.BrokerSession, error) { tb.registration <- "register " + connect.DeviceId return &testing.TestBrokerSession{DeviceId: connect.DeviceId}, tb.err } func (tb *testBroker) Unregister(sess broker.BrokerSession) { tb.registration <- "unregister " + sess.DeviceIdentifier() } func (s *sessionSuite) TestSessionStart(c *C) { var sess broker.BrokerSession errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} brkr := newTestBroker() go func() { var err error sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout) errCh <- err }() c.Check(takeNext(down), Equals, "deadline 5ms") up <- protocol.ConnectMsg{Type: "connect", ClientVer: "1", DeviceId: "dev-1"} c.Check(takeNext(down), Equals, protocol.ConnAckMsg{ Type: "connack", Params: protocol.ConnAckParams{(10 * time.Millisecond).String()}, }) up <- nil // no write error err := <-errCh c.Check(err, IsNil) c.Check(takeNext(brkr.registration), Equals, "register dev-1") c.Check(sess.DeviceIdentifier(), Equals, "dev-1") } func (s *sessionSuite) TestSessionRegisterError(c *C) { var sess broker.BrokerSession errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} brkr := newTestBroker() errRegister := errors.New("register failure") brkr.err = errRegister go func() { var err error sess, err = sessionStart(tp, brkr, cfg10msPingInterval5msExchangeTout) errCh <- err }() up <- protocol.ConnectMsg{Type: "connect", ClientVer: "1", DeviceId: "dev-1"} takeNext(down) // CONNACK up <- nil // no write error err := <-errCh c.Check(err, Equals, errRegister) } func (s *sessionSuite) TestSessionStartReadError(c *C) { up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} up <- io.ErrUnexpectedEOF _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout) c.Check(err, Equals, io.ErrUnexpectedEOF) } func (s *sessionSuite) TestSessionStartWriteError(c *C) { up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} up <- protocol.ConnectMsg{Type: "connect"} up <- io.ErrUnexpectedEOF _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout) c.Check(err, Equals, io.ErrUnexpectedEOF) // sanity c.Check(takeNext(down), Matches, "deadline.*") c.Check(takeNext(down), FitsTypeOf, protocol.ConnAckMsg{}) } func (s *sessionSuite) TestSessionStartMismatch(c *C) { up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} up <- protocol.ConnectMsg{Type: "what"} _, err := sessionStart(tp, nil, cfg10msPingInterval5msExchangeTout) c.Check(err, DeepEquals, &broker.ErrAbort{"expected CONNECT message"}) } var cfg5msPingInterval2msExchangeTout = &testSessionConfig{ pingInterval: 5 * time.Millisecond, exchangeTimeout: 2 * time.Millisecond, } func (s *sessionSuite) TestSessionLoop(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} sess := &testing.TestBrokerSession{} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) up <- nil // no write error up <- protocol.PingPongMsg{Type: "pong"} c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) up <- nil // no write error up <- io.ErrUnexpectedEOF err := <-errCh c.Check(err, Equals, io.ErrUnexpectedEOF) } func (s *sessionSuite) TestSessionLoopWriteError(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} sess := &testing.TestBrokerSession{} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), FitsTypeOf, protocol.PingPongMsg{}) up <- io.ErrUnexpectedEOF // write error err := <-errCh c.Check(err, Equals, io.ErrUnexpectedEOF) } func (s *sessionSuite) TestSessionLoopMismatch(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} sess := &testing.TestBrokerSession{} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) up <- nil // no write error up <- protocol.PingPongMsg{Type: "what"} err := <-errCh c.Check(err, DeepEquals, &broker.ErrAbort{"expected PONG message"}) } type testMsg struct { Type string `json:"T"` Part int `json:"P"` nParts int } func (m *testMsg) Split() bool { if m.nParts == 0 { return true } m.Part++ if m.Part == m.nParts { return true } return false } type testExchange struct { inMsg testMsg prepErr error finErr error finSleep time.Duration nParts int done chan interface{} } func (exchg *testExchange) Prepare(sess broker.BrokerSession) (outMsg protocol.SplittableMsg, inMsg interface{}, err error) { return &testMsg{Type: "msg", nParts: exchg.nParts}, &exchg.inMsg, exchg.prepErr } func (exchg *testExchange) Acked(sess broker.BrokerSession, done bool) error { time.Sleep(exchg.finSleep) if exchg.done != nil { var doneStr string if done { doneStr = "y" } else { doneStr = "n" } exchg.done <- doneStr } return exchg.finErr } func (s *sessionSuite) TestSessionLoopExchange(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} exchanges := make(chan broker.Exchange, 1) exchanges <- &testExchange{} sess := &testing.TestBrokerSession{Exchanges: exchanges} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, testMsg{Type: "msg"}) up <- nil // no write error up <- testMsg{Type: "ack"} c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) up <- nil // no write error up <- io.EOF err := <-errCh c.Check(err, Equals, io.EOF) } func (s *sessionSuite) TestSessionLoopKick(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} exchanges := make(chan broker.Exchange, 1) sess := &testing.TestBrokerSession{Exchanges: exchanges} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() close(exchanges) err := <-errCh c.Check(err, DeepEquals, &broker.ErrAbort{"terminated"}) } func (s *sessionSuite) TestSessionLoopExchangeErrNop(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} exchanges := make(chan broker.Exchange, 1) exchanges <- &testExchange{prepErr: broker.ErrNop} sess := &testing.TestBrokerSession{Exchanges: exchanges} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) up <- nil // no write error up <- io.EOF err := <-errCh c.Check(err, Equals, io.EOF) } func (s *sessionSuite) TestSessionLoopExchangeSplit(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} exchanges := make(chan broker.Exchange, 1) exchange := &testExchange{nParts: 2, done: make(chan interface{}, 2)} exchanges <- exchange sess := &testing.TestBrokerSession{Exchanges: exchanges} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, testMsg{Type: "msg", Part: 1, nParts: 2}) up <- nil // no write error up <- testMsg{Type: "ack"} c.Check(takeNext(exchange.done), Equals, "n") c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, testMsg{Type: "msg", Part: 2, nParts: 2}) up <- nil // no write error up <- testMsg{Type: "ack"} c.Check(takeNext(exchange.done), Equals, "y") c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) up <- nil // no write error up <- io.EOF err := <-errCh c.Check(err, Equals, io.EOF) } func (s *sessionSuite) TestSessionLoopExchangePrepareError(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} exchanges := make(chan broker.Exchange, 1) prepErr := errors.New("prepare failure") exchanges <- &testExchange{prepErr: prepErr} sess := &testing.TestBrokerSession{Exchanges: exchanges} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() err := <-errCh c.Check(err, Equals, prepErr) } func (s *sessionSuite) TestSessionLoopExchangeAckedError(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} exchanges := make(chan broker.Exchange, 1) finErr := errors.New("finish error") exchanges <- &testExchange{finErr: finErr} sess := &testing.TestBrokerSession{Exchanges: exchanges} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, testMsg{Type: "msg"}) up <- nil // no write error up <- testMsg{Type: "ack"} err := <-errCh c.Check(err, Equals, finErr) } func (s *sessionSuite) TestSessionLoopExchangeWriteError(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} exchanges := make(chan broker.Exchange, 1) exchanges <- &testExchange{} sess := &testing.TestBrokerSession{Exchanges: exchanges} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), FitsTypeOf, testMsg{}) up <- io.ErrUnexpectedEOF err := <-errCh c.Check(err, Equals, io.ErrUnexpectedEOF) } func (s *sessionSuite) TestSessionLoopConnBrokenExchange(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} exchanges := make(chan broker.Exchange, 1) exchanges <- &broker.ConnBrokenExchange{"REASON"} sess := &testing.TestBrokerSession{Exchanges: exchanges} go func() { errCh <- sessionLoop(tp, sess, cfg5msPingInterval2msExchangeTout, nopTrack) }() c.Check(takeNext(down), Equals, "deadline 2ms") c.Check(takeNext(down), DeepEquals, protocol.ConnBrokenMsg{"connbroken", "REASON"}) up <- nil // no write error err := <-errCh c.Check(err, DeepEquals, &broker.ErrAbort{"session broken for reason"}) } type testTracker struct { SessionTracker interval chan interface{} } func (trk *testTracker) EffectivePingInterval(interval time.Duration) { trk.interval <- interval } var cfg50msPingInterval = &testSessionConfig{ pingInterval: 50 * time.Millisecond, exchangeTimeout: 10 * time.Millisecond, } func (s *sessionSuite) TestSessionLoopExchangeNextPing(c *C) { track := &testTracker{NewTracker(s.testlog), make(chan interface{}, 1)} errCh := make(chan error, 1) up := make(chan interface{}, 5) down := make(chan interface{}, 5) tp := &testProtocol{up, down} exchanges := make(chan broker.Exchange, 1) exchanges <- &testExchange{finSleep: 15 * time.Millisecond} sess := &testing.TestBrokerSession{Exchanges: exchanges} go func() { errCh <- sessionLoop(tp, sess, cfg50msPingInterval, track) }() c.Check(takeNext(down), Equals, "deadline 10ms") c.Check(takeNext(down), DeepEquals, testMsg{Type: "msg"}) up <- nil // no write error up <- testMsg{Type: "ack"} // next ping interval starts around here interval := takeNext(track.interval).(time.Duration) c.Check(takeNext(down), Equals, "deadline 10ms") c.Check(takeNext(down), DeepEquals, protocol.PingPongMsg{Type: "ping"}) effectiveOfPing := float64(interval) / float64(50*time.Millisecond) comment := Commentf("effectiveOfPing=%f", effectiveOfPing) c.Check(effectiveOfPing > 0.95, Equals, true, comment) c.Check(effectiveOfPing < 1.15, Equals, true, comment) up <- nil // no write error up <- io.EOF err := <-errCh c.Check(err, Equals, io.EOF) } func serverClientWire() (srv net.Conn, cli net.Conn, lst net.Listener) { lst, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { panic(err) } cli, err = net.DialTCP("tcp", nil, lst.Addr().(*net.TCPAddr)) if err != nil { panic(err) } srv, err = lst.Accept() if err != nil { panic(err) } return } type rememberDeadlineConn struct { net.Conn deadlineKind []string } func (c *rememberDeadlineConn) SetDeadline(t time.Time) error { c.deadlineKind = append(c.deadlineKind, "both") return c.Conn.SetDeadline(t) } func (c *rememberDeadlineConn) SetReadDeadline(t time.Time) error { c.deadlineKind = append(c.deadlineKind, "read") return c.Conn.SetDeadline(t) } func (c *rememberDeadlineConn) SetWriteDeadline(t time.Time) error { c.deadlineKind = append(c.deadlineKind, "write") return c.Conn.SetDeadline(t) } func (s *sessionSuite) TestSessionWire(c *C) { track := NewTracker(s.testlog) errCh := make(chan error, 1) srv, cli, lst := serverClientWire() defer lst.Close() remSrv := &rememberDeadlineConn{srv, make([]string, 0, 2)} brkr := newTestBroker() go func() { errCh <- Session(remSrv, brkr, cfg50msPingInterval, track) }() io.WriteString(cli, "\x00") io.WriteString(cli, "\x00\x20{\"T\":\"connect\",\"DeviceId\":\"DEV\"}") // connack downStream := bufio.NewReader(cli) msg, err := downStream.ReadBytes(byte('}')) c.Check(err, IsNil) c.Check(msg, DeepEquals, []byte("\x00\x30{\"T\":\"connack\",\"Params\":{\"PingInterval\":\"50ms\"}")) // eat the last } rbr, err := downStream.ReadByte() c.Check(err, IsNil) c.Check(rbr, Equals, byte('}')) // first ping msg, err = downStream.ReadBytes(byte('}')) c.Check(err, IsNil) c.Check(msg, DeepEquals, []byte("\x00\x0c{\"T\":\"ping\"}")) c.Check(takeNext(brkr.registration), Equals, "register DEV") c.Check(len(brkr.registration), Equals, 0) // not yet unregistered cli.Close() err = <-errCh c.Check(remSrv.deadlineKind, DeepEquals, []string{"read", "both", "both"}) c.Check(err, Equals, io.EOF) c.Check(takeNext(brkr.registration), Equals, "unregister DEV") // tracking c.Check(s.testlog.Captured(), Matches, `.*connected.*\n.*registered DEV.*\n.*ended with: EOF\n`) } func (s *sessionSuite) TestSessionWireTimeout(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) srv, cli, lst := serverClientWire() defer lst.Close() remSrv := &rememberDeadlineConn{srv, make([]string, 0, 2)} brkr := newTestBroker() go func() { errCh <- Session(remSrv, brkr, cfg5msPingInterval2msExchangeTout, nopTrack) }() err := <-errCh c.Check(err, FitsTypeOf, &net.OpError{}) c.Check(remSrv.deadlineKind, DeepEquals, []string{"read"}) cli.Close() } func (s *sessionSuite) TestSessionWireWrongVersion(c *C) { track := NewTracker(s.testlog) errCh := make(chan error, 1) srv, cli, lst := serverClientWire() defer lst.Close() brkr := newTestBroker() go func() { errCh <- Session(srv, brkr, cfg50msPingInterval, track) }() io.WriteString(cli, "\x10") err := <-errCh c.Check(err, DeepEquals, &broker.ErrAbort{"unexpected wire format version"}) cli.Close() // tracking c.Check(s.testlog.Captured(), Matches, `.*connected.*\n.*ended with: session aborted \(unexpected.*version\)\n`) } func (s *sessionSuite) TestSessionWireEarlyClose(c *C) { track := NewTracker(s.testlog) errCh := make(chan error, 1) srv, cli, lst := serverClientWire() defer lst.Close() brkr := newTestBroker() go func() { errCh <- Session(srv, brkr, cfg50msPingInterval, track) }() cli.Close() err := <-errCh c.Check(err, Equals, io.EOF) // tracking c.Check(s.testlog.Captured(), Matches, `.*connected.*\n.*ended with: EOF\n`) } func (s *sessionSuite) TestSessionWireEarlyClose2(c *C) { track := NewTracker(s.testlog) errCh := make(chan error, 1) srv, cli, lst := serverClientWire() defer lst.Close() brkr := newTestBroker() go func() { errCh <- Session(srv, brkr, cfg50msPingInterval, track) }() io.WriteString(cli, "\x00") io.WriteString(cli, "\x00") cli.Close() err := <-errCh c.Check(err, Equals, io.EOF) // tracking c.Check(s.testlog.Captured(), Matches, `.*connected.*\n.*ended with: EOF\n`) } func (s *sessionSuite) TestSessionWireTimeout2(c *C) { nopTrack := NewTracker(s.testlog) errCh := make(chan error, 1) srv, cli, lst := serverClientWire() defer lst.Close() remSrv := &rememberDeadlineConn{srv, make([]string, 0, 2)} brkr := newTestBroker() go func() { errCh <- Session(remSrv, brkr, cfg5msPingInterval2msExchangeTout, nopTrack) }() io.WriteString(cli, "\x00") io.WriteString(cli, "\x00") err := <-errCh c.Check(err, FitsTypeOf, &net.OpError{}) c.Check(remSrv.deadlineKind, DeepEquals, []string{"read", "both"}) cli.Close() } ubuntu-push-0.2.1+14.04.20140423.1/server/runner_test.go0000644000015301777760000001006212325724711022755 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package server import ( "fmt" "io/ioutil" "net" "net/http" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/config" helpers "launchpad.net/ubuntu-push/testing" ) type runnerSuite struct { prevBootLogListener func(string, net.Listener) prevBootLogFatalf func(string, ...interface{}) lst net.Listener kind string } var _ = Suite(&runnerSuite{}) func (s *runnerSuite) SetUpSuite(c *C) { s.prevBootLogFatalf = BootLogFatalf s.prevBootLogListener = BootLogListener BootLogFatalf = func(format string, v ...interface{}) { panic(fmt.Sprintf(format, v...)) } BootLogListener = func(kind string, lst net.Listener) { s.kind = kind s.lst = lst } } func (s *runnerSuite) TearDownSuite(c *C) { BootLogListener = s.prevBootLogListener BootLogFatalf = s.prevBootLogFatalf } var testHTTPServeParsedConfig = HTTPServeParsedConfig{ "127.0.0.1:0", config.ConfigTimeDuration{5 * time.Second}, config.ConfigTimeDuration{5 * time.Second}, } func testHandle(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "yay!\n") } func (s *runnerSuite) TestHTTPServeRunner(c *C) { errCh := make(chan interface{}, 1) h := http.HandlerFunc(testHandle) runner := HTTPServeRunner(nil, h, &testHTTPServeParsedConfig) c.Assert(s.lst, Not(IsNil)) defer s.lst.Close() c.Check(s.kind, Equals, "http") go func() { defer func() { errCh <- recover() }() runner() }() resp, err := http.Get(fmt.Sprintf("http://%s/", s.lst.Addr())) c.Assert(err, IsNil) defer resp.Body.Close() c.Assert(resp.StatusCode, Equals, 200) body, err := ioutil.ReadAll(resp.Body) c.Assert(err, IsNil) c.Check(string(body), Equals, "yay!\n") s.lst.Close() c.Check(<-errCh, Matches, "accepting http connections:.*closed.*") } var testDevicesParsedConfig = DevicesParsedConfig{ ParsedPingInterval: config.ConfigTimeDuration{60 * time.Second}, ParsedExchangeTimeout: config.ConfigTimeDuration{10 * time.Second}, ParsedBrokerQueueSize: config.ConfigQueueSize(1000), ParsedSessionQueueSize: config.ConfigQueueSize(10), ParsedAddr: "127.0.0.1:0", ParsedKeyPEMFile: "", ParsedCertPEMFile: "", keyPEMBlock: helpers.TestKeyPEMBlock, certPEMBlock: helpers.TestCertPEMBlock, } func (s *runnerSuite) TestDevicesRunner(c *C) { prevBootLogger := BootLogger testlog := helpers.NewTestLogger(c, "debug") BootLogger = testlog defer func() { BootLogger = prevBootLogger }() runner := DevicesRunner(nil, func(conn net.Conn) error { return nil }, BootLogger, &testDevicesParsedConfig) c.Assert(s.lst, Not(IsNil)) s.lst.Close() c.Check(s.kind, Equals, "devices") c.Check(runner, PanicMatches, "accepting device connections:.*closed.*") } func (s *runnerSuite) TestDevicesRunnerAdoptListener(c *C) { prevBootLogger := BootLogger testlog := helpers.NewTestLogger(c, "debug") BootLogger = testlog defer func() { BootLogger = prevBootLogger }() lst0, err := net.Listen("tcp", "127.0.0.1:0") c.Assert(err, IsNil) defer lst0.Close() DevicesRunner(lst0, func(conn net.Conn) error { return nil }, BootLogger, &testDevicesParsedConfig) c.Assert(s.lst, Not(IsNil)) c.Check(s.lst.Addr().String(), Equals, lst0.Addr().String()) s.lst.Close() } func (s *runnerSuite) TestHTTPServeRunnerAdoptListener(c *C) { lst0, err := net.Listen("tcp", "127.0.0.1:0") c.Assert(err, IsNil) defer lst0.Close() HTTPServeRunner(lst0, nil, &testHTTPServeParsedConfig) c.Assert(s.lst, Equals, lst0) c.Check(s.kind, Equals, "http") } ubuntu-push-0.2.1+14.04.20140423.1/server/store/0000755000015301777760000000000012325725172021215 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/server/store/inmemory_test.go0000644000015301777760000000513112325724711024440 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package store import ( "encoding/json" "time" . "launchpad.net/gocheck" ) type inMemorySuite struct{} var _ = Suite(&inMemorySuite{}) func (s *inMemorySuite) TestGetInternalChannelId(c *C) { sto := NewInMemoryPendingStore() chanId, err := sto.GetInternalChannelId("system") c.Check(err, IsNil) c.Check(chanId, Equals, SystemInternalChannelId) chanId, err = sto.GetInternalChannelId("blah") c.Check(err, Equals, ErrUnknownChannel) c.Check(chanId, Equals, InternalChannelId("")) } func (s *inMemorySuite) TestGetChannelSnapshotEmpty(c *C) { sto := NewInMemoryPendingStore() top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId) c.Assert(err, IsNil) c.Check(top, Equals, int64(0)) c.Check(res, DeepEquals, []json.RawMessage(nil)) } func (s *inMemorySuite) TestAppendToChannelAndGetChannelSnapshot(c *C) { sto := NewInMemoryPendingStore() notification1 := json.RawMessage(`{"a":1}`) notification2 := json.RawMessage(`{"a":2}`) muchLater := time.Now().Add(time.Minute) sto.AppendToChannel(SystemInternalChannelId, notification1, muchLater) sto.AppendToChannel(SystemInternalChannelId, notification2, muchLater) top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId) c.Assert(err, IsNil) c.Check(top, Equals, int64(2)) c.Check(res, DeepEquals, []json.RawMessage{notification1, notification2}) } func (s *inMemorySuite) TestAppendToChannelAndGetChannelSnapshotWithExpiration(c *C) { sto := NewInMemoryPendingStore() notification1 := json.RawMessage(`{"a":1}`) notification2 := json.RawMessage(`{"a":2}`) verySoon := time.Now().Add(100 * time.Millisecond) muchLater := time.Now().Add(time.Minute) sto.AppendToChannel(SystemInternalChannelId, notification1, muchLater) sto.AppendToChannel(SystemInternalChannelId, notification2, verySoon) time.Sleep(200 * time.Millisecond) top, res, err := sto.GetChannelSnapshot(SystemInternalChannelId) c.Assert(err, IsNil) c.Check(top, Equals, int64(2)) c.Check(res, DeepEquals, []json.RawMessage{notification1}) } ubuntu-push-0.2.1+14.04.20140423.1/server/store/store.go0000644000015301777760000000452012325724711022677 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package store takes care of storing pending notifications. package store import ( "encoding/hex" "encoding/json" "errors" "time" ) type InternalChannelId string var ErrUnknownChannel = errors.New("unknown channel name") var ErrFull = errors.New("channel is full") var ErrExpected128BitsHexRepr = errors.New("expected 128 bits hex repr") const SystemInternalChannelId = InternalChannelId("0") func InternalChannelIdToHex(chanId InternalChannelId) string { if chanId == SystemInternalChannelId { return "0" } panic("general InternalChannelIdToHex not implemeted yet") } var zero128 [16]byte const noId = InternalChannelId("") func HexToInternalChannelId(hexRepr string) (InternalChannelId, error) { if hexRepr == "0" { return SystemInternalChannelId, nil } if len(hexRepr) != 32 { return noId, ErrExpected128BitsHexRepr } var idbytes [16]byte _, err := hex.Decode(idbytes[:], []byte(hexRepr)) if err != nil { return noId, ErrExpected128BitsHexRepr } if idbytes == zero128 { return SystemInternalChannelId, nil } return InternalChannelId(idbytes[:]), nil } // PendingStore let store notifications into channels. type PendingStore interface { // GetInternalChannelId returns the internal store id for a channel // given the name. GetInternalChannelId(name string) (InternalChannelId, error) // AppendToChannel appends a notification to the channel. AppendToChannel(chanId InternalChannelId, notification json.RawMessage, expiration time.Time) error // GetChannelSnapshot gets all the current notifications and // current top level in the channel. GetChannelSnapshot(chanId InternalChannelId) (topLevel int64, payloads []json.RawMessage, err error) // Close is to be called when done with the store. Close() } ubuntu-push-0.2.1+14.04.20140423.1/server/store/inmemory.go0000644000015301777760000000503512325724711023404 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package store import ( "encoding/json" "sync" "time" ) // one stored notification type notification struct { payload json.RawMessage expiration time.Time } // one stored channel type channel struct { topLevel int64 notifications []notification } // InMemoryPendingStore is a basic in-memory pending notification store. type InMemoryPendingStore struct { lock sync.Mutex store map[InternalChannelId]*channel } // NewInMemoryPendingStore returns a new InMemoryStore. func NewInMemoryPendingStore() *InMemoryPendingStore { return &InMemoryPendingStore{ store: make(map[InternalChannelId]*channel), } } func (sto *InMemoryPendingStore) GetInternalChannelId(name string) (InternalChannelId, error) { if name == "system" { return SystemInternalChannelId, nil } return InternalChannelId(""), ErrUnknownChannel } func (sto *InMemoryPendingStore) AppendToChannel(chanId InternalChannelId, notificationPayload json.RawMessage, expiration time.Time) error { sto.lock.Lock() defer sto.lock.Unlock() prev := sto.store[chanId] if prev == nil { prev = &channel{} } prev.topLevel++ prev.notifications = append(prev.notifications, notification{ payload: notificationPayload, expiration: expiration, }) sto.store[chanId] = prev return nil } func (sto *InMemoryPendingStore) GetChannelSnapshot(chanId InternalChannelId) (int64, []json.RawMessage, error) { sto.lock.Lock() defer sto.lock.Unlock() channel, ok := sto.store[chanId] if !ok { return 0, nil, nil } topLevel := channel.topLevel n := len(channel.notifications) res := make([]json.RawMessage, 0, n) now := time.Now() for _, notification := range channel.notifications { if notification.expiration.Before(now) { continue } res = append(res, notification.payload) } return topLevel, res, nil } func (sto *InMemoryPendingStore) Close() { // ignored } // sanity check we implement the interface var _ PendingStore = (*InMemoryPendingStore)(nil) ubuntu-push-0.2.1+14.04.20140423.1/server/store/store_test.go0000644000015301777760000000344212325724711023740 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package store import ( // "fmt" "testing" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/protocol" ) func TestStore(t *testing.T) { TestingT(t) } type storeSuite struct{} var _ = Suite(&storeSuite{}) func (s *storeSuite) TestInternalChannelIdToHex(c *C) { c.Check(InternalChannelIdToHex(SystemInternalChannelId), Equals, protocol.SystemChannelId) } func (s *storeSuite) TestHexToInternalChannelId(c *C) { i0, err := HexToInternalChannelId("0") c.Check(err, IsNil) c.Check(i0, Equals, SystemInternalChannelId) i1, err := HexToInternalChannelId("00000000000000000000000000000000") c.Check(err, IsNil) c.Check(i1, Equals, SystemInternalChannelId) i2, err := HexToInternalChannelId("f1c9bf7096084cb2a154979ce00c7f50") c.Check(err, IsNil) c.Check(i2, Equals, InternalChannelId("\xf1\xc9\xbf\x70\x96\x08\x4c\xb2\xa1\x54\x97\x9c\xe0\x0c\x7f\x50")) _, err = HexToInternalChannelId("01") c.Check(err, Equals, ErrExpected128BitsHexRepr) _, err = HexToInternalChannelId("abceddddddddddddddddzeeeeeeeeeee") c.Check(err, Equals, ErrExpected128BitsHexRepr) _, err = HexToInternalChannelId("f1c9bf7096084cb2a154979ce00c7f50ff") c.Check(err, Equals, ErrExpected128BitsHexRepr) } ubuntu-push-0.2.1+14.04.20140423.1/server/bootlog.go0000644000015301777760000000212312325724711022051 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package server import ( "net" "os" "launchpad.net/ubuntu-push/logger" ) // boot logging and hooks func bootLogListener(kind string, lst net.Listener) { BootLogger.Infof("listening for %s on %v", kind, lst.Addr()) } var ( BootLogger = logger.NewSimpleLogger(os.Stderr, "debug") // Boot logging helpers through BootLogger. BootLogListener func(kind string, lst net.Listener) = bootLogListener BootLogFatalf = BootLogger.Fatalf ) ubuntu-push-0.2.1+14.04.20140423.1/logger/0000755000015301777760000000000012325725172020032 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/logger/logger_test.go0000644000015301777760000001073612325724720022704 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package logger import ( "bytes" "fmt" "log" "os" "runtime" "testing" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/config" ) func TestLogger(t *testing.T) { TestingT(t) } type loggerSuite struct{} var _ = Suite(&loggerSuite{}) func (s *loggerSuite) TestErrorf(c *C) { buf := &bytes.Buffer{} logger := NewSimpleLogger(buf, "error") logger.Errorf("%v %d", "error", 1) c.Check(buf.String(), Matches, ".* ERROR error 1\n") } func (s *loggerSuite) TestFatalf(c *C) { defer func() { osExit = os.Exit }() var exitCode int osExit = func(code int) { exitCode = code } buf := &bytes.Buffer{} logger := NewSimpleLogger(buf, "error") logger.Fatalf("%v %v", "error", "fatal") c.Check(buf.String(), Matches, ".* ERROR error fatal\n") c.Check(exitCode, Equals, 1) } func (s *loggerSuite) TestInfof(c *C) { buf := &bytes.Buffer{} logger := NewSimpleLogger(buf, "info") logger.Infof("%v %d", "info", 1) c.Check(buf.String(), Matches, ".* INFO info 1\n") } func (s *loggerSuite) TestDebugf(c *C) { buf := &bytes.Buffer{} logger := NewSimpleLogger(buf, "debug") logger.Debugf("%v %d", "debug", 1) c.Check(buf.String(), Matches, `.* DEBUG debug 1\n`) } func (s *loggerSuite) TestFormat(c *C) { buf := &bytes.Buffer{} logger := NewSimpleLogger(buf, "error") logger.Errorf("%v %d", "error", 2) c.Check(buf.String(), Matches, `.* .*\.\d+ ERROR error 2\n`) } func (s *loggerSuite) TestLevel(c *C) { buf := &bytes.Buffer{} logger := NewSimpleLogger(buf, "error") logger.Errorf("%s%d", "e", 3) logger.Infof("%s%d", "i", 3) logger.Debugf("%s%d", "d", 3) c.Check(buf.String(), Matches, `.* ERROR e3\n`) buf.Reset() logger = NewSimpleLogger(buf, "info") logger.Errorf("%s%d", "e", 4) logger.Debugf("%s%d", "d", 4) logger.Infof("%s%d", "i", 4) c.Check(buf.String(), Matches, `.* ERROR e4\n.* INFO i4\n`) buf.Reset() logger = NewSimpleLogger(buf, "debug") logger.Errorf("%s%d", "e", 5) logger.Debugf("%s%d", "d", 5) logger.Infof("%s%d", "i", 5) c.Check(buf.String(), Matches, `.* ERROR e5\n.* DEBUG d5\n.* INFO i5\n`) } func panicAndRecover(logger Logger, n int, doPanic bool, line *int, ok *bool) { defer func() { if err := recover(); err != nil { logger.PanicStackf("%v %d", err, n) } }() _, _, *line, *ok = runtime.Caller(0) if doPanic { panic("Troubles") // @ line + 2 } } func (s *loggerSuite) TestPanicStackfPanicScenario(c *C) { buf := &bytes.Buffer{} logger := NewSimpleLogger(buf, "error") var line int var ok bool panicAndRecover(logger, 6, true, &line, &ok) c.Assert(ok, Equals, true) c.Check(buf.String(), Matches, fmt.Sprintf("(?s).* ERROR\\(PANIC\\) Troubles 6:.*panicAndRecover.*logger_test.go:%d.*", line+2)) } func (s *loggerSuite) TestPanicStackfNoPanicScenario(c *C) { buf := &bytes.Buffer{} logger := NewSimpleLogger(buf, "error") var line int var ok bool panicAndRecover(logger, 6, false, &line, &ok) c.Check(buf.String(), Equals, "") } func (s *loggerSuite) TestReexposeOutput(c *C) { buf := &bytes.Buffer{} baselog := log.New(buf, "", log.Lshortfile) logger := NewSimpleLoggerFromMinimalLogger(baselog, "error") baselog.Output(1, "foobar") logger.Output(1, "foobaz") c.Check(buf.String(), Matches, "logger_test.go:[0-9]+: foobar\nlogger_test.go:[0-9]+: foobaz\n") } type testLogLevelConfig struct { Lvl ConfigLogLevel } func (s *loggerSuite) TestReadConfigLogLevel(c *C) { buf := bytes.NewBufferString(`{"lvl": "debug"}`) var cfg testLogLevelConfig err := config.ReadConfig(buf, &cfg) c.Assert(err, IsNil) c.Check(cfg.Lvl.Level(), Equals, "debug") } func (s *loggerSuite) TestReadConfigLogLevelErrors(c *C) { var cfg testLogLevelConfig checkError := func(jsonCfg string, expectedError string) { buf := bytes.NewBufferString(jsonCfg) err := config.ReadConfig(buf, &cfg) c.Check(err, ErrorMatches, expectedError) } checkError(`{"lvl": 1}`, "lvl:.*type string") checkError(`{"lvl": "foo"}`, "lvl: not a log level: foo") } ubuntu-push-0.2.1+14.04.20140423.1/logger/logger.go0000644000015301777760000001002612325724720021635 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package logger defines a simple logger API with level of logging control. package logger import ( "fmt" "io" "log" "os" "runtime" "launchpad.net/ubuntu-push/config" ) // Logger is a simple logger interface with logging at levels. type Logger interface { // Re-expose base Output for logging events. Output(calldept int, s string) error // Errorf logs an error. Errorf(format string, v ...interface{}) // Fatalf logs an error and exits the program with os.Exit(1). Fatalf(format string, v ...interface{}) // PanicStackf logs an error message and a stacktrace, for use // in panic recovery. PanicStackf(format string, v ...interface{}) // Infof logs an info message. Infof(format string, v ...interface{}) // Debugf logs a debug message. Debugf(format string, v ...interface{}) } type simpleLogger struct { outputFunc func(calldepth int, s string) error nlevel int } const ( lError = iota lInfo lDebug ) var levelToNLevel = map[string]int{ "error": lError, "info": lInfo, "debug": lDebug, } // MinimalLogger is the minimal interface required to build a simple logger. type MinimalLogger interface { Output(calldepth int, s string) error } // NewSimpleLoggerFromMinimalLogger creates a logger logging only up // to the given level. The level can be, in order: "error", "info", // "debug". It takes a value just implementing stlib Logger.Output(). func NewSimpleLoggerFromMinimalLogger(minLog MinimalLogger, level string) Logger { nlevel := levelToNLevel[level] return &simpleLogger{ minLog.Output, nlevel, } } // NewSimpleLogger creates a logger logging only up to the given // level. The level can be, in order: "error", "info", "debug". It takes an // io.Writer. func NewSimpleLogger(w io.Writer, level string) Logger { return NewSimpleLoggerFromMinimalLogger( log.New(w, "", log.Ldate|log.Ltime|log.Lmicroseconds), level, ) } func (lg *simpleLogger) Output(calldepth int, s string) error { return lg.outputFunc(calldepth+2, s) } func (lg *simpleLogger) Errorf(format string, v ...interface{}) { lg.outputFunc(2, fmt.Sprintf("ERROR "+format, v...)) } var osExit = os.Exit // for testing func (lg *simpleLogger) Fatalf(format string, v ...interface{}) { lg.outputFunc(2, fmt.Sprintf("ERROR "+format, v...)) osExit(1) } func (lg *simpleLogger) PanicStackf(format string, v ...interface{}) { msg := fmt.Sprintf(format, v...) stack := make([]byte, 8*1024) // Stack writes less but doesn't fail stackWritten := runtime.Stack(stack, false) stack = stack[:stackWritten] lg.outputFunc(2, fmt.Sprintf("ERROR(PANIC) %s:\n%s", msg, stack)) } func (lg *simpleLogger) Infof(format string, v ...interface{}) { if lg.nlevel >= lInfo { lg.outputFunc(2, fmt.Sprintf("INFO "+format, v...)) } } func (lg *simpleLogger) Debugf(format string, v ...interface{}) { if lg.nlevel >= lDebug { lg.outputFunc(2, fmt.Sprintf("DEBUG "+format, v...)) } } // config bits // ConfigLogLevel can hold a log level in a configuration struct. type ConfigLogLevel string func (cll *ConfigLogLevel) ConfigFromJSONString() {} func (cll *ConfigLogLevel) UnmarshalJSON(b []byte) error { return config.UnmarshalJSONViaString(cll, b) } func (cll *ConfigLogLevel) SetFromString(enc string) error { _, ok := levelToNLevel[enc] if !ok { return fmt.Errorf("not a log level: %s", enc) } *cll = ConfigLogLevel(enc) return nil } // Level returns the log level string held in cll. func (cll ConfigLogLevel) Level() string { return string(cll) } ubuntu-push-0.2.1+14.04.20140423.1/protocol/0000755000015301777760000000000012325725172020414 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/protocol/state-diag-session.gv0000644000015301777760000000172712325724711024462 0ustar pbusernogroup00000000000000digraph state_diagram_session { label = "State diagram for session"; size="12,6"; rankdir=LR; node [shape = circle]; start1 -> start2 [ label = "Read wire version" ]; start2 -> start3 [ label = "Read CONNECT" ]; start3 -> loop [ label = "Write CONNACK" ]; loop -> ping [ label = "Elapsed ping interval" ]; loop -> broadcast [label = "Receive broadcast request"]; ping -> pong_wait [label = "Write PING"]; broadcast -> ack_wait [label = "Write BROADCAST [fits one wire msg]"]; broadcast -> split_broadcast [label = "BROADCAST does not fit one wire msg"]; pong_wait -> loop [label = "Read PONG"]; ack_wait -> loop [label = "Read ACK"]; // split messages split_broadcast -> split_ack_wait [label = "Write split BROADCAST"]; split_ack_wait -> split_broadcast [label = "Read ACK"]; split_broadcast -> loop [label = "All split msgs written"]; } ubuntu-push-0.2.1+14.04.20140423.1/protocol/protocol.go0000644000015301777760000000572212325724711022610 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package protocol implements the client-daemon <-> push-server protocol. package protocol import ( "bytes" "encoding/binary" "encoding/json" "fmt" "io" "net" "time" ) // Protocol is a connection capable of writing and reading the wire format // of protocol messages. type Protocol interface { SetDeadline(t time.Time) ReadMessage(msg interface{}) error WriteMessage(msg interface{}) error } func ReadWireFormatVersion(conn net.Conn, exchangeTimeout time.Duration) (ver int, err error) { var buf1 [1]byte err = conn.SetReadDeadline(time.Now().Add(exchangeTimeout)) if err != nil { panic(fmt.Errorf("can't set deadline: %v", err)) } _, err = conn.Read(buf1[:]) ver = int(buf1[0]) return } const ProtocolWireVersion = 0 // protocol0 handles version 0 of the wire format type protocol0 struct { buffer *bytes.Buffer enc *json.Encoder conn net.Conn } // NewProtocol0 creates and initialises a protocol with wire format version 0. func NewProtocol0(conn net.Conn) Protocol { buf := bytes.NewBuffer(make([]byte, 5000)) return &protocol0{ buffer: buf, enc: json.NewEncoder(buf), conn: conn} } // SetDeadline sets the deadline for the subsequent WriteMessage/ReadMessage exchange. func (c *protocol0) SetDeadline(t time.Time) { err := c.conn.SetDeadline(t) if err != nil { panic(fmt.Errorf("can't set deadline: %v", err)) } } // ReadMessage reads from the connection one message with a JSON body // preceded by its big-endian uint16 length. func (c *protocol0) ReadMessage(msg interface{}) error { c.buffer.Reset() _, err := io.CopyN(c.buffer, c.conn, 2) if err != nil { return err } length := binary.BigEndian.Uint16(c.buffer.Bytes()) c.buffer.Reset() _, err = io.CopyN(c.buffer, c.conn, int64(length)) if err != nil { return err } return json.Unmarshal(c.buffer.Bytes(), msg) } // WriteMessage writes one message to the connection with a JSON body // preceding it with its big-endian uint16 length. func (c *protocol0) WriteMessage(msg interface{}) error { c.buffer.Reset() c.buffer.WriteString("\x00\x00") // placeholder for length err := c.enc.Encode(msg) if err != nil { panic(fmt.Errorf("WriteMessage got: %v", err)) } msgLen := c.buffer.Len() - 3 // length space, extra newline toWrite := c.buffer.Bytes() binary.BigEndian.PutUint16(toWrite[:2], uint16(msgLen)) _, err = c.conn.Write(toWrite[:msgLen+2]) return err } ubuntu-push-0.2.1+14.04.20140423.1/protocol/state-diag-client.gv0000644000015301777760000000121612325724711024246 0ustar pbusernogroup00000000000000digraph state_diagram_client { label = "State diagram for client"; size="12,6"; rankdir=LR; node [shape = doublecircle]; pingTimeout; node [shape = circle]; start1 -> start2 [ label = "Write wire version" ]; start2 -> start3 [ label = "Write CONNECT" ]; start3 -> loop [ label = "Read CONNACK" ]; loop -> pong [ label = "Read PING" ]; loop -> broadcast [label = "Read BROADCAST"]; pong -> loop [label = "Write PONG"]; broadcast -> loop [label = "Write ACK"]; loop -> pingTimeout [ label = "Elapsed ping interval + exchange interval"]; } ubuntu-push-0.2.1+14.04.20140423.1/protocol/messages.go0000644000015301777760000000616512325724711022560 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package protocol // Structs representing messages. import ( "encoding/json" ) // System channel id using a shortened hex-encoded form for the NIL UUID. const SystemChannelId = "0" // CONNECT message type ConnectMsg struct { Type string `json:"T"` ClientVer string DeviceId string Authorization string Info map[string]interface{} `json:",omitempty"` // platform etc... // maps channel ids (hex encoded UUIDs) to known client channel levels Levels map[string]int64 } // CONNACK message type ConnAckMsg struct { Type string `json:"T"` Params ConnAckParams } // ConnAckParams carries the connection parameters from the server on // connection acknowledgement. type ConnAckParams struct { // ping interval formatted time.Duration PingInterval string } // SplittableMsg are messages that may require and are capable of splitting. type SplittableMsg interface { Split() (done bool) } // CONNBROKEN message, server side is breaking the connection for reason. type ConnBrokenMsg struct { Type string `json:"T"` // reason Reason string } func (m *ConnBrokenMsg) Split() bool { return true } // CONNBROKEN reasons const ( BrokenHostMismatch = "host-mismatch" ) // PING/PONG messages type PingPongMsg struct { Type string `json:"T"` } const maxPayloadSize = 62 * 1024 // BROADCAST messages type BroadcastMsg struct { Type string `json:"T"` AppId string `json:",omitempty"` ChanId string TopLevel int64 Payloads []json.RawMessage splitting int } func (m *BroadcastMsg) Split() bool { var prevTop int64 if m.splitting == 0 { prevTop = m.TopLevel - int64(len(m.Payloads)) } else { prevTop = m.TopLevel m.Payloads = m.Payloads[len(m.Payloads):m.splitting] m.TopLevel = prevTop + int64(len(m.Payloads)) } payloads := m.Payloads var size int for i := range payloads { size += len(payloads[i]) if size > maxPayloadSize { m.TopLevel = prevTop + int64(i) m.splitting = len(payloads) m.Payloads = payloads[:i] return false } } return true } // Reset resets the splitting state if the message storage is to be // reused. func (b *BroadcastMsg) Reset() { b.splitting = 0 } // NOTIFICATIONS message type NotificationsMsg struct { Type string `json:"T"` Notifications []Notification } // A single unicast notification type Notification struct { AppId string `json:"A"` MsgId string `json:"M"` // payload Payload json.RawMessage `json:"P"` } // ACKnowledgement message type AckMsg struct { Type string `json:"T"` } // xxx ... query levels messages ubuntu-push-0.2.1+14.04.20140423.1/protocol/messages_test.go0000644000015301777760000000541412325724711023613 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package protocol import ( "encoding/json" "fmt" "strings" . "launchpad.net/gocheck" ) type messagesSuite struct{} var _ = Suite(&messagesSuite{}) func (s *messagesSuite) TestSplitBroadcastMsgNop(c *C) { b := &BroadcastMsg{ Type: "broadcast", AppId: "APP", ChanId: "0", TopLevel: 2, Payloads: []json.RawMessage{json.RawMessage(`{b:1}`), json.RawMessage(`{b:2}`)}, } done := b.Split() c.Check(done, Equals, true) c.Check(b.TopLevel, Equals, int64(2)) c.Check(cap(b.Payloads), Equals, 2) c.Check(len(b.Payloads), Equals, 2) } var payloadFmt = fmt.Sprintf(`{"b":%%d,"bloat":"%s"}`, strings.Repeat("x", 1024*2)) func manyParts(c int) []json.RawMessage { payloads := make([]json.RawMessage, 0, 1) for i := 0; i < c; i++ { payloads = append(payloads, json.RawMessage(fmt.Sprintf(payloadFmt, i))) } return payloads } func (s *messagesSuite) TestSplitBroadcastMsgManyParts(c *C) { payloads := manyParts(33) n := len(payloads) // more interesting this way c.Assert(cap(payloads), Not(Equals), n) b := &BroadcastMsg{ Type: "broadcast", AppId: "APP", ChanId: "0", TopLevel: 500, Payloads: payloads, } done := b.Split() c.Assert(done, Equals, false) n1 := len(b.Payloads) c.Check(b.TopLevel, Equals, int64(500-n+n1)) buf, err := json.Marshal(b) c.Assert(err, IsNil) c.Assert(len(buf) <= 65535, Equals, true) c.Check(len(buf)+len(payloads[n1]) > maxPayloadSize, Equals, true) done = b.Split() c.Assert(done, Equals, true) n2 := len(b.Payloads) c.Check(b.TopLevel, Equals, int64(500)) c.Check(n1+n2, Equals, n) payloads = manyParts(61) n = len(payloads) b = &BroadcastMsg{ Type: "broadcast", AppId: "APP", ChanId: "0", TopLevel: int64(n), Payloads: payloads, } done = b.Split() c.Assert(done, Equals, false) n1 = len(b.Payloads) done = b.Split() c.Assert(done, Equals, false) n2 = len(b.Payloads) done = b.Split() c.Assert(done, Equals, true) n3 := len(b.Payloads) c.Check(b.TopLevel, Equals, int64(n)) c.Check(n1+n2+n3, Equals, n) // reset b.Reset() c.Check(b.splitting, Equals, 0) } func (s *messagesSuite) TestSplitConnBrokenMsg(c *C) { c.Check((&ConnBrokenMsg{}).Split(), Equals, true) } ubuntu-push-0.2.1+14.04.20140423.1/protocol/protocol_test.go0000644000015301777760000001410712325724711023644 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package protocol import ( "encoding/binary" "encoding/json" "io" "net" "testing" "time" . "launchpad.net/gocheck" ) func TestProtocol(t *testing.T) { TestingT(t) } type protocolSuite struct{} var _ = Suite(&protocolSuite{}) type deadline struct { kind string deadAfter time.Duration } func (d *deadline) setDeadAfter(t time.Time) { deadAfter := t.Sub(time.Now()) d.deadAfter = (deadAfter + time.Millisecond/2) / time.Millisecond * time.Millisecond } type rw struct { buf []byte n int err error } type testConn struct { deadlines []*deadline reads []rw writes []*rw } func (tc *testConn) LocalAddr() net.Addr { return nil } func (tc *testConn) RemoteAddr() net.Addr { return nil } func (tc *testConn) Close() error { return nil } func (tc *testConn) SetDeadline(t time.Time) error { deadline := tc.deadlines[0] deadline.kind = "both" deadline.setDeadAfter(t) tc.deadlines = tc.deadlines[1:] return nil } func (tc *testConn) SetReadDeadline(t time.Time) error { deadline := tc.deadlines[0] deadline.kind = "read" deadline.setDeadAfter(t) tc.deadlines = tc.deadlines[1:] return nil } func (tc *testConn) SetWriteDeadline(t time.Time) error { deadline := tc.deadlines[0] deadline.kind = "write" deadline.setDeadAfter(t) tc.deadlines = tc.deadlines[1:] return nil } func (tc *testConn) Read(buf []byte) (n int, err error) { read := tc.reads[0] copy(buf, read.buf) tc.reads = tc.reads[1:] return read.n, read.err } func (tc *testConn) Write(buf []byte) (n int, err error) { write := tc.writes[0] n = copy(write.buf, buf) write.buf = write.buf[:n] write.n = n err = write.err tc.writes = tc.writes[1:] return } func (s *protocolSuite) TestReadWireFormatVersion(c *C) { deadl := deadline{} read1 := rw{buf: []byte{42}, n: 1} tc := &testConn{reads: []rw{read1}, deadlines: []*deadline{&deadl}} ver, err := ReadWireFormatVersion(tc, time.Minute) c.Check(err, IsNil) c.Check(ver, Equals, 42) c.Check(deadl.kind, Equals, "read") c.Check(deadl.deadAfter, Equals, time.Minute) } func (s *protocolSuite) TestReadWireFormatVersionError(c *C) { deadl := deadline{} read1 := rw{err: io.EOF} tc := &testConn{reads: []rw{read1}, deadlines: []*deadline{&deadl}} _, err := ReadWireFormatVersion(tc, time.Minute) c.Check(err, Equals, io.EOF) } func (s *protocolSuite) TestSetDeadline(c *C) { deadl := deadline{} tc := &testConn{deadlines: []*deadline{&deadl}} pc := NewProtocol0(tc) pc.SetDeadline(time.Now().Add(time.Minute)) c.Check(deadl.kind, Equals, "both") c.Check(deadl.deadAfter, Equals, time.Minute) } type testMsg struct { Type string `json:"T"` A uint64 } func lengthAsBytes(length uint16) []byte { var buf [2]byte var res = buf[:] binary.BigEndian.PutUint16(res, length) return res } func (s *protocolSuite) TestReadMessage(c *C) { msgBuf, _ := json.Marshal(testMsg{Type: "msg", A: 2000}) readMsgLen := rw{buf: lengthAsBytes(uint16(len(msgBuf))), n: 2} readMsgBody := rw{buf: msgBuf, n: len(msgBuf)} tc := &testConn{reads: []rw{readMsgLen, readMsgBody}} pc := NewProtocol0(tc) var recvMsg testMsg err := pc.ReadMessage(&recvMsg) c.Check(err, IsNil) c.Check(recvMsg, DeepEquals, testMsg{Type: "msg", A: 2000}) } func (s *protocolSuite) TestReadMessageBits(c *C) { msgBuf, _ := json.Marshal(testMsg{Type: "msg", A: 2000}) readMsgLen := rw{buf: lengthAsBytes(uint16(len(msgBuf))), n: 2} readMsgBody1 := rw{buf: msgBuf[:5], n: 5} readMsgBody2 := rw{buf: msgBuf[5:], n: len(msgBuf) - 5} tc := &testConn{reads: []rw{readMsgLen, readMsgBody1, readMsgBody2}} pc := NewProtocol0(tc) var recvMsg testMsg err := pc.ReadMessage(&recvMsg) c.Check(err, IsNil) c.Check(recvMsg, DeepEquals, testMsg{Type: "msg", A: 2000}) } func (s *protocolSuite) TestReadMessageIOErrors(c *C) { msgBuf, _ := json.Marshal(testMsg{Type: "msg", A: 2000}) readMsgLenErr := rw{n: 1, err: io.ErrClosedPipe} tc1 := &testConn{reads: []rw{readMsgLenErr}} pc1 := NewProtocol0(tc1) var recvMsg testMsg err := pc1.ReadMessage(&recvMsg) c.Check(err, Equals, io.ErrClosedPipe) readMsgLen := rw{buf: lengthAsBytes(uint16(len(msgBuf))), n: 2} readMsgBody1 := rw{buf: msgBuf[:5], n: 5} readMsgBody2Err := rw{n: 2, err: io.EOF} tc2 := &testConn{reads: []rw{readMsgLen, readMsgBody1, readMsgBody2Err}} pc2 := NewProtocol0(tc2) err = pc2.ReadMessage(&recvMsg) c.Check(err, Equals, io.EOF) } func (s *protocolSuite) TestReadMessageBrokenJSON(c *C) { msgBuf := []byte("{\"T\"}") readMsgLen := rw{buf: lengthAsBytes(uint16(len(msgBuf))), n: 2} readMsgBody := rw{buf: msgBuf, n: len(msgBuf)} tc := &testConn{reads: []rw{readMsgLen, readMsgBody}} pc := NewProtocol0(tc) var recvMsg testMsg err := pc.ReadMessage(&recvMsg) c.Check(err, FitsTypeOf, &json.SyntaxError{}) } func (s *protocolSuite) TestWriteMessage(c *C) { writeMsg := rw{buf: make([]byte, 64)} tc := &testConn{writes: []*rw{&writeMsg}} pc := NewProtocol0(tc) msg := testMsg{Type: "m", A: 9999} err := pc.WriteMessage(&msg) c.Check(err, IsNil) var msgLen int = int(binary.BigEndian.Uint16(writeMsg.buf[:2])) c.Check(msgLen, Equals, len(writeMsg.buf)-2) var wroteMsg testMsg formatErr := json.Unmarshal(writeMsg.buf[2:], &wroteMsg) c.Check(formatErr, IsNil) c.Check(wroteMsg, DeepEquals, testMsg{Type: "m", A: 9999}) } func (s *protocolSuite) TestWriteMessageIOErrors(c *C) { writeMsgErr := rw{buf: make([]byte, 0), err: io.ErrClosedPipe} tc1 := &testConn{writes: []*rw{&writeMsgErr}} pc1 := NewProtocol0(tc1) msg := testMsg{Type: "m", A: 9999} err := pc1.WriteMessage(&msg) c.Check(err, Equals, io.ErrClosedPipe) } ubuntu-push-0.2.1+14.04.20140423.1/protocol/state-diag-session.svg0000644000015301777760000002261512325724711024644 0ustar pbusernogroup00000000000000 state_diagram_session State diagram for session start1 start1 start2 start2 start1->start2 Read wire version start3 start3 start2->start3 Read CONNECT loop loop start3->loop Write CONNACK ping ping loop->ping Elapsed ping interval broadcast broadcast loop->broadcast Receive broadcast request pong_wait pong_wait ping->pong_wait Write PING ack_wait ack_wait broadcast->ack_wait Write BROADCAST [fits one wire msg] split_broadcast split_broadcast broadcast->split_broadcast BROADCAST does not fit one wire msg pong_wait->loop Read PONG ack_wait->loop Read ACK split_broadcast->loop All split msgs written split_ack_wait split_ack_wait split_broadcast->split_ack_wait Write split BROADCAST split_ack_wait->split_broadcast Read ACK ubuntu-push-0.2.1+14.04.20140423.1/protocol/state-diag-client.svg0000644000015301777760000001441612325724711024437 0ustar pbusernogroup00000000000000 state_diagram_client State diagram for client pingTimeout pingTimeout start1 start1 start2 start2 start1->start2 Write wire version start3 start3 start2->start3 Write CONNECT loop loop start3->loop Read CONNACK loop->pingTimeout Elapsed ping interval + exchange interval pong pong loop->pong Read PING broadcast broadcast loop->broadcast Read BROADCAST pong->loop Write PONG broadcast->loop Write ACK ubuntu-push-0.2.1+14.04.20140423.1/client/0000755000015301777760000000000012325725172020031 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/client/client.dot0000644000015301777760000000505112325724711022016 0ustar pbusernogroup00000000000000digraph g { // both "neato" and "dot" produce reasonable & interesting outputs graph [rankdir=LR size="20,15" overlap="scale" splines="true"] node [fontname="Ubuntu Mono" fontsize=12 margin=0] edge [fontname="Ubuntu Mono" fontsize=10 decorate=true] // states node [] "INIT" [shape=doublecircle] "Error" [shape=doublecircle color="#990000"] "Configured" "Identified" "SysBusOK" [label="System\nBus\nGO"] "Waiting4Conn" [label="Waiting\nfor\nConnectivity"] "Connected" [label="Network\nConnected"] "SesBusOK" [label="Session\nBus\nGO"] "Running" "Notified" "Shown" [label="Notification\nShown"] "Clicked" // auto-transitions node [shape=triangle] "read config" [label="read\nconfig"] "get system id" [label="get\nsystem\nid"] "conn sys bus" [label="connect\nsystem\nbus"] "watch conn" [label="watch\nconnectivity"] "conn ses bus" [label="connect\nsession\nbus"] "start session" [label="start\npush\nsession"] "show notification" [label="show\nnotification"] "dispatch URL" [label="dispatch\nURL"] // "INIT" -> "read config" "Configured" -> "get system id" "Identified" -> { "conn sys bus" "conn ses bus" } "SysBusOK" -> "watch conn" "Connected" -> "start session" "Notified" -> "show notification" "Clicked" -> "dispatch URL" -> "SesBusOK" "Shown" -> "SesBusOK" // XXX state:state auto-transition? // events edge [color="#000099"] "Waiting4Conn" -> "Connected" [label="connected"] "Waiting4Conn" -> "Waiting4Conn" [label="disconnected"] "SesBusOK" -> "Notified" [label="notification\narrived"] "SesBusOK" -> "Clicked" [label="user\nclicked\nnotification"] { "Connected" "Running" } -> "Waiting4Conn" [constraint=false label="disconnected"] "Running" -> "SesBusOK" [constraint=false style=dotted label=notification] "Shown" -> "Running" [constraint=false style=dotted label=shown] // OKs edge [color="#009900" label="OK"] "read config" -> "Configured" "get system id" -> "Identified" "conn sys bus" -> "SysBusOK" "conn ses bus" -> "SesBusOK" "watch conn" -> "Waiting4Conn" "start session" -> "Running" "show notification" -> "Shown" //err edge [color="#990000" label="err" constraint=false] { "read config" "get system id" } -> Error "conn ses bus" -> "conn ses bus" "conn sys bus" -> "conn sys bus" "watch conn" -> "conn sys bus" "start session" -> "start session" } ubuntu-push-0.2.1+14.04.20140423.1/client/client.go0000644000015301777760000002624412325724720021644 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package client implements the Ubuntu Push Notifications client-side // daemon. package client import ( "encoding/pem" "errors" "fmt" "io/ioutil" "os" "strings" "launchpad.net/go-dbus/v1" "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/bus/connectivity" "launchpad.net/ubuntu-push/bus/networkmanager" "launchpad.net/ubuntu-push/bus/notifications" "launchpad.net/ubuntu-push/bus/systemimage" "launchpad.net/ubuntu-push/bus/urldispatcher" "launchpad.net/ubuntu-push/client/session" "launchpad.net/ubuntu-push/client/session/levelmap" "launchpad.net/ubuntu-push/config" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/util" "launchpad.net/ubuntu-push/whoopsie/identifier" ) // ClientConfig holds the client configuration type ClientConfig struct { connectivity.ConnectivityConfig // q.v. // A reasonably large timeout for receive/answer pairs ExchangeTimeout config.ConfigTimeDuration `json:"exchange_timeout"` // A timeout to use when trying to connect to the server ConnectTimeout config.ConfigTimeDuration `json:"connect_timeout"` // The server to connect to or url to query for hosts to connect to Addr string // Host list management HostsCachingExpiryTime config.ConfigTimeDuration `json:"hosts_cache_expiry"` // potentially refresh host list after ExpectAllRepairedTime config.ConfigTimeDuration `json:"expect_all_repaired"` // worth retrying all servers after // The PEM-encoded server certificate CertPEMFile string `json:"cert_pem_file"` // The logging level (one of "debug", "info", "error") LogLevel logger.ConfigLogLevel `json:"log_level"` } // PushClient is the Ubuntu Push Notifications client-side daemon. type PushClient struct { leveldbPath string configPath string config ClientConfig log logger.Logger pem []byte idder identifier.Id deviceId string notificationsEndp bus.Endpoint urlDispatcherEndp bus.Endpoint connectivityEndp bus.Endpoint systemImageEndp bus.Endpoint systemImageInfo *systemimage.InfoResult connCh chan bool hasConnectivity bool actionsCh <-chan notifications.RawActionReply session *session.ClientSession sessionConnectedCh chan uint32 } var ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::" // Creates a new Ubuntu Push Notifications client-side daemon that will use // the given configuration file. func NewPushClient(configPath string, leveldbPath string) *PushClient { client := new(PushClient) client.configPath = configPath client.leveldbPath = leveldbPath return client } // configure loads its configuration, and sets it up. func (client *PushClient) configure() error { _, err := os.Stat(client.configPath) if err != nil { return fmt.Errorf("config: %v", err) } err = config.ReadFiles(&client.config, client.configPath, "") if err != nil { return fmt.Errorf("config: %v", err) } // ignore spaces client.config.Addr = strings.Replace(client.config.Addr, " ", "", -1) if client.config.Addr == "" { return errors.New("no hosts specified") } // later, we'll be specifying more logging options in the config file client.log = logger.NewSimpleLogger(os.Stderr, client.config.LogLevel.Level()) // overridden for testing client.idder = identifier.New() client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log) client.urlDispatcherEndp = bus.SessionBus.Endpoint(urldispatcher.BusAddress, client.log) client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log) client.systemImageEndp = bus.SystemBus.Endpoint(systemimage.BusAddress, client.log) client.connCh = make(chan bool, 1) client.sessionConnectedCh = make(chan uint32, 1) if client.config.CertPEMFile != "" { client.pem, err = ioutil.ReadFile(client.config.CertPEMFile) if err != nil { return fmt.Errorf("reading PEM file: %v", err) } // sanity check p, _ := pem.Decode(client.pem) if p == nil { return fmt.Errorf("no PEM found in PEM file") } } return nil } // deriveSessionConfig dervies the session configuration from the client configuration bits. func (client *PushClient) deriveSessionConfig(info map[string]interface{}) session.ClientSessionConfig { return session.ClientSessionConfig{ ConnectTimeout: client.config.ConnectTimeout.TimeDuration(), ExchangeTimeout: client.config.ExchangeTimeout.TimeDuration(), HostsCachingExpiryTime: client.config.HostsCachingExpiryTime.TimeDuration(), ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(), PEM: client.pem, Info: info, } } // getDeviceId gets the whoopsie identifier for the device func (client *PushClient) getDeviceId() error { err := client.idder.Generate() if err != nil { return err } client.deviceId = client.idder.String() return nil } // takeTheBus starts the connection(s) to D-Bus and sets up associated event channels func (client *PushClient) takeTheBus() error { go connectivity.ConnectedState(client.connectivityEndp, client.config.ConnectivityConfig, client.log, client.connCh) iniCh := make(chan uint32) go func() { iniCh <- util.NewAutoRedialer(client.notificationsEndp).Redial() }() go func() { iniCh <- util.NewAutoRedialer(client.urlDispatcherEndp).Redial() }() go func() { iniCh <- util.NewAutoRedialer(client.systemImageEndp).Redial() }() <-iniCh <-iniCh <-iniCh sysimg := systemimage.New(client.systemImageEndp, client.log) info, err := sysimg.Info() if err != nil { return err } client.systemImageInfo = info actionsCh, err := notifications.Raw(client.notificationsEndp, client.log).WatchActions() client.actionsCh = actionsCh return err } // initSession creates the session object func (client *PushClient) initSession() error { info := map[string]interface{}{ "device": client.systemImageInfo.Device, "channel": client.systemImageInfo.Channel, "build_number": client.systemImageInfo.BuildNumber, } sess, err := session.NewSession(client.config.Addr, client.deriveSessionConfig(info), client.deviceId, client.levelMapFactory, client.log) if err != nil { return err } client.session = sess return nil } // levelmapFactory returns a levelMap for the session func (client *PushClient) levelMapFactory() (levelmap.LevelMap, error) { if client.leveldbPath == "" { return levelmap.NewLevelMap() } else { return levelmap.NewSqliteLevelMap(client.leveldbPath) } } // handleConnState deals with connectivity events func (client *PushClient) handleConnState(hasConnectivity bool) { if client.hasConnectivity == hasConnectivity { // nothing to do! return } client.hasConnectivity = hasConnectivity if hasConnectivity { client.session.AutoRedial(client.sessionConnectedCh) } else { client.session.Close() } } // handleErr deals with the session erroring out of its loop func (client *PushClient) handleErr(err error) { // if we're not connected, we don't really care client.log.Errorf("session exited: %s", err) if client.hasConnectivity { client.session.AutoRedial(client.sessionConnectedCh) } } // filterNotification finds out if the notification is about an actual // upgrade for the device. It expects msg.Decoded entries to look // like: // // { // "IMAGE-CHANNEL/DEVICE-MODEL": [BUILD-NUMBER, CHANNEL-ALIAS] // ... // } func (client *PushClient) filterNotification(msg *session.Notification) bool { n := len(msg.Decoded) if n == 0 { return false } // they are all for us, consider last last := msg.Decoded[n-1] tag := fmt.Sprintf("%s/%s", client.systemImageInfo.Channel, client.systemImageInfo.Device) entry, ok := last[tag] if !ok { return false } pair, ok := entry.([]interface{}) if !ok { return false } if len(pair) < 1 { return false } buildNumber, ok := pair[0].(float64) if !ok { return false } curBuildNumber := float64(client.systemImageInfo.BuildNumber) if buildNumber > curBuildNumber { return true } // xxx we should really compare channel_target and alias here // going backward by a margin, assume switch of target if buildNumber < curBuildNumber && (curBuildNumber-buildNumber) > 10 { return true } return false } // handleNotification deals with receiving a notification func (client *PushClient) handleNotification(msg *session.Notification) error { if !client.filterNotification(msg) { return nil } action_id := ACTION_ID_SNOWFLAKE a := []string{action_id, "Go get it!"} // action value not visible on the phone h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}} nots := notifications.Raw(client.notificationsEndp, client.log) body := "Tap to open the system updater." not_id, err := nots.Notify( "ubuntu-push-client", // app name uint32(0), // id "update_manager_icon", // icon "There's an updated system image.", // summary body, // body a, // actions h, // hints int32(10*1000), // timeout (ms) ) if err != nil { client.log.Errorf("showing notification: %s", err) return err } client.log.Debugf("got notification id %d", not_id) return nil } // handleClick deals with the user clicking a notification func (client *PushClient) handleClick(action_id string) error { if action_id != ACTION_ID_SNOWFLAKE { return nil } // it doesn't get much simpler... urld := urldispatcher.New(client.urlDispatcherEndp, client.log) return urld.DispatchURL("settings:///system/system-update") } // doLoop connects events with their handlers func (client *PushClient) doLoop(connhandler func(bool), clickhandler func(string) error, notifhandler func(*session.Notification) error, errhandler func(error)) { for { select { case state := <-client.connCh: connhandler(state) case action := <-client.actionsCh: clickhandler(action.ActionId) case msg := <-client.session.MsgCh: notifhandler(msg) case err := <-client.session.ErrCh: errhandler(err) case count := <-client.sessionConnectedCh: client.log.Debugf("Session connected after %d attempts", count) } } } // doStart calls each of its arguments in order, returning the first non-nil // error (or nil at the end) func (client *PushClient) doStart(fs ...func() error) error { for _, f := range fs { if err := f(); err != nil { return err } } return nil } // Loop calls doLoop with the "real" handlers func (client *PushClient) Loop() { client.doLoop(client.handleConnState, client.handleClick, client.handleNotification, client.handleErr) } // Start calls doStart with the "real" starters func (client *PushClient) Start() error { return client.doStart( client.configure, client.getDeviceId, client.takeTheBus, client.initSession, ) } ubuntu-push-0.2.1+14.04.20140423.1/client/client_test.go0000644000015301777760000006265312325724720022707 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package client import ( "encoding/json" "errors" "flag" "fmt" "io/ioutil" "net/http" "net/http/httptest" "os" "path/filepath" "reflect" "testing" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/bus/networkmanager" "launchpad.net/ubuntu-push/bus/notifications" "launchpad.net/ubuntu-push/bus/systemimage" testibus "launchpad.net/ubuntu-push/bus/testing" "launchpad.net/ubuntu-push/client/session" "launchpad.net/ubuntu-push/client/session/levelmap" "launchpad.net/ubuntu-push/config" helpers "launchpad.net/ubuntu-push/testing" "launchpad.net/ubuntu-push/testing/condition" "launchpad.net/ubuntu-push/util" "launchpad.net/ubuntu-push/whoopsie/identifier" idtesting "launchpad.net/ubuntu-push/whoopsie/identifier/testing" ) func TestClient(t *testing.T) { TestingT(t) } // takeNext takes a value from given channel with a 5s timeout func takeNextBool(ch <-chan bool) bool { select { case <-time.After(5 * time.Second): panic("channel stuck: too long waiting") case v := <-ch: return v } } type clientSuite struct { timeouts []time.Duration configPath string leveldbPath string log *helpers.TestLogger } var _ = Suite(&clientSuite{}) const ( staticText = "something ipsum dolor something" staticHash = "6155f83b471583f47c99998a472a178f" ) func mkHandler(text string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.(http.Flusher).Flush() w.Write([]byte(text)) w.(http.Flusher).Flush() } } func (cs *clientSuite) SetUpSuite(c *C) { config.IgnoreParsedFlags = true // because configure() uses cs.timeouts = util.SwapTimeouts([]time.Duration{0}) cs.leveldbPath = "" } func (cs *clientSuite) TearDownSuite(c *C) { util.SwapTimeouts(cs.timeouts) cs.timeouts = nil } func (cs *clientSuite) writeTestConfig(overrides map[string]interface{}) { pem_file := helpers.SourceRelative("../server/acceptance/ssl/testing.cert") cfgMap := map[string]interface{}{ "connect_timeout": "7ms", "exchange_timeout": "10ms", "hosts_cache_expiry": "1h", "expect_all_repaired": "30m", "stabilizing_timeout": "0ms", "connectivity_check_url": "", "connectivity_check_md5": "", "addr": ":0", "cert_pem_file": pem_file, "recheck_timeout": "3h", "log_level": "debug", } for k, v := range overrides { cfgMap[k] = v } cfgBlob, err := json.Marshal(cfgMap) if err != nil { panic(err) } ioutil.WriteFile(cs.configPath, cfgBlob, 0600) } func (cs *clientSuite) SetUpTest(c *C) { cs.log = helpers.NewTestLogger(c, "debug") dir := c.MkDir() cs.configPath = filepath.Join(dir, "config") cs.writeTestConfig(nil) } type sqlientSuite struct{ clientSuite } func (s *sqlientSuite) SetUpSuite(c *C) { s.clientSuite.SetUpSuite(c) s.leveldbPath = ":memory:" } var _ = Suite(&sqlientSuite{}) /***************************************************************** configure tests ******************************************************************/ func (cs *clientSuite) TestConfigureWorks(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) err := cli.configure() c.Assert(err, IsNil) c.Assert(cli.config, NotNil) c.Check(cli.config.ExchangeTimeout.TimeDuration(), Equals, time.Duration(10*time.Millisecond)) } func (cs *clientSuite) TestConfigureWorksWithFlags(c *C) { flag.CommandLine = flag.NewFlagSet("client", flag.ContinueOnError) os.Args = []string{"client", "-addr", "foo:7777"} cli := NewPushClient(cs.configPath, cs.leveldbPath) err := cli.configure() c.Assert(err, IsNil) c.Assert(cli.config, NotNil) c.Check(cli.config.Addr, Equals, "foo:7777") } func (cs *clientSuite) TestConfigureSetsUpLog(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) c.Check(cli.log, IsNil) err := cli.configure() c.Assert(err, IsNil) c.Assert(cli.log, NotNil) } func (cs *clientSuite) TestConfigureSetsUpPEM(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) c.Check(cli.pem, IsNil) err := cli.configure() c.Assert(err, IsNil) c.Assert(cli.pem, NotNil) } func (cs *clientSuite) TestConfigureSetsUpIdder(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) c.Check(cli.idder, IsNil) err := cli.configure() c.Assert(err, IsNil) c.Assert(cli.idder, FitsTypeOf, identifier.New()) } func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) c.Check(cli.notificationsEndp, IsNil) c.Check(cli.urlDispatcherEndp, IsNil) c.Check(cli.connectivityEndp, IsNil) err := cli.configure() c.Assert(err, IsNil) c.Assert(cli.notificationsEndp, NotNil) c.Assert(cli.urlDispatcherEndp, NotNil) c.Assert(cli.connectivityEndp, NotNil) } func (cs *clientSuite) TestConfigureSetsUpConnCh(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) c.Check(cli.connCh, IsNil) err := cli.configure() c.Assert(err, IsNil) c.Assert(cli.connCh, NotNil) } func (cs *clientSuite) TestConfigureBailsOnBadFilename(c *C) { cli := NewPushClient("/does/not/exist", cs.leveldbPath) err := cli.configure() c.Assert(err, NotNil) } func (cs *clientSuite) TestConfigureBailsOnBadConfig(c *C) { cli := NewPushClient("/etc/passwd", cs.leveldbPath) err := cli.configure() c.Assert(err, NotNil) } func (cs *clientSuite) TestConfigureBailsOnBadPEMFilename(c *C) { cs.writeTestConfig(map[string]interface{}{ "cert_pem_file": "/a/b/c", }) cli := NewPushClient(cs.configPath, cs.leveldbPath) err := cli.configure() c.Assert(err, ErrorMatches, "reading PEM file: .*") } func (cs *clientSuite) TestConfigureBailsOnBadPEM(c *C) { cs.writeTestConfig(map[string]interface{}{ "cert_pem_file": "/etc/passwd", }) cli := NewPushClient(cs.configPath, cs.leveldbPath) err := cli.configure() c.Assert(err, ErrorMatches, "no PEM found.*") } func (cs *clientSuite) TestConfigureBailsOnNoHosts(c *C) { cs.writeTestConfig(map[string]interface{}{ "addr": " ", }) cli := NewPushClient(cs.configPath, cs.leveldbPath) err := cli.configure() c.Assert(err, ErrorMatches, "no hosts specified") } func (cs *clientSuite) TestConfigureRemovesBlanksInAddr(c *C) { cs.writeTestConfig(map[string]interface{}{ "addr": " foo: 443", }) cli := NewPushClient(cs.configPath, cs.leveldbPath) err := cli.configure() c.Assert(err, IsNil) c.Check(cli.config.Addr, Equals, "foo:443") } /***************************************************************** deriveSessionConfig tests ******************************************************************/ func (cs *clientSuite) TestDeriveSessionConfig(c *C) { info := map[string]interface{}{ "foo": 1, } cli := NewPushClient(cs.configPath, cs.leveldbPath) err := cli.configure() c.Assert(err, IsNil) expected := session.ClientSessionConfig{ ConnectTimeout: 7 * time.Millisecond, ExchangeTimeout: 10 * time.Millisecond, HostsCachingExpiryTime: 1 * time.Hour, ExpectAllRepairedTime: 30 * time.Minute, PEM: cli.pem, Info: info, } // sanity check that we are looking at all fields vExpected := reflect.ValueOf(expected) nf := vExpected.NumField() for i := 0; i < nf; i++ { fv := vExpected.Field(i) // field isn't empty/zero c.Assert(fv.Interface(), Not(DeepEquals), reflect.Zero(fv.Type()).Interface(), Commentf("forgot about: %s", vExpected.Type().Field(i).Name)) } // finally compare conf := cli.deriveSessionConfig(info) c.Check(conf, DeepEquals, expected) } /***************************************************************** getDeviceId tests ******************************************************************/ func (cs *clientSuite) TestGetDeviceIdWorks(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.idder = identifier.New() c.Check(cli.deviceId, Equals, "") c.Check(cli.getDeviceId(), IsNil) c.Check(cli.deviceId, HasLen, 128) } func (cs *clientSuite) TestGetDeviceIdCanFail(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.idder = idtesting.Failing() c.Check(cli.deviceId, Equals, "") c.Check(cli.getDeviceId(), NotNil) } /***************************************************************** takeTheBus tests ******************************************************************/ func (cs *clientSuite) TestTakeTheBusWorks(c *C) { // http server used for connectivity test ts := httptest.NewServer(mkHandler(staticText)) defer ts.Close() // testing endpoints nCond := condition.Fail2Work(3) nEndp := testibus.NewMultiValuedTestingEndpoint(nCond, condition.Work(true), []interface{}{uint32(1), "hello"}) uCond := condition.Fail2Work(5) uEndp := testibus.NewTestingEndpoint(uCond, condition.Work(false)) cCond := condition.Fail2Work(7) cEndp := testibus.NewTestingEndpoint(cCond, condition.Work(true), uint32(networkmanager.ConnectedGlobal), ) siCond := condition.Fail2Work(2) siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}}) testibus.SetWatchTicker(cEndp, make(chan bool)) // ok, create the thing cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log err := cli.configure() c.Assert(err, IsNil) // the user actions channel has not been set up c.Check(cli.actionsCh, IsNil) // and stomp on things for testing cli.config.ConnectivityConfig.ConnectivityCheckURL = ts.URL cli.config.ConnectivityConfig.ConnectivityCheckMD5 = staticHash cli.notificationsEndp = nEndp cli.urlDispatcherEndp = uEndp cli.connectivityEndp = cEndp cli.systemImageEndp = siEndp c.Assert(cli.takeTheBus(), IsNil) // the notifications and urldispatcher endpoints retried until connected c.Check(nCond.OK(), Equals, true) c.Check(uCond.OK(), Equals, true) // the user actions channel has now been set up c.Check(cli.actionsCh, NotNil) c.Check(takeNextBool(cli.connCh), Equals, false) c.Check(takeNextBool(cli.connCh), Equals, true) // the connectivity endpoint retried until connected c.Check(cCond.OK(), Equals, true) // the systemimage endpoint retried until connected c.Check(siCond.OK(), Equals, true) } // takeTheBus can, in fact, fail func (cs *clientSuite) TestTakeTheBusCanFail(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) err := cli.configure() cli.log = cs.log c.Assert(err, IsNil) // the user actions channel has not been set up c.Check(cli.actionsCh, IsNil) // and stomp on things for testing cli.notificationsEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) cli.urlDispatcherEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) cli.connectivityEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) cli.systemImageEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) c.Check(cli.takeTheBus(), NotNil) c.Check(cli.actionsCh, IsNil) } /***************************************************************** handleErr tests ******************************************************************/ func (cs *clientSuite) TestHandleErr(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.systemImageInfo = siInfoRes c.Assert(cli.initSession(), IsNil) cs.log.ResetCapture() cli.hasConnectivity = true cli.handleErr(errors.New("bananas")) c.Check(cs.log.Captured(), Matches, ".*session exited.*bananas\n") } /***************************************************************** levelmapFactory tests ******************************************************************/ func (cs *clientSuite) TestLevelMapFactoryNoDbPath(c *C) { cli := NewPushClient(cs.configPath, "") ln, err := cli.levelMapFactory() c.Assert(err, IsNil) c.Check(fmt.Sprintf("%T", ln), Equals, "*levelmap.mapLevelMap") } func (cs *clientSuite) TestLevelMapFactoryWithDbPath(c *C) { cli := NewPushClient(cs.configPath, ":memory:") ln, err := cli.levelMapFactory() c.Assert(err, IsNil) c.Check(fmt.Sprintf("%T", ln), Equals, "*levelmap.sqliteLevelMap") } /***************************************************************** handleConnState tests ******************************************************************/ func (cs *clientSuite) TestHandleConnStateD2C(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.systemImageInfo = siInfoRes c.Assert(cli.initSession(), IsNil) c.Assert(cli.hasConnectivity, Equals, false) cli.handleConnState(true) c.Check(cli.hasConnectivity, Equals, true) c.Assert(cli.session, NotNil) } func (cs *clientSuite) TestHandleConnStateSame(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log // here we want to check that we don't do anything c.Assert(cli.session, IsNil) c.Assert(cli.hasConnectivity, Equals, false) cli.handleConnState(false) c.Check(cli.session, IsNil) cli.hasConnectivity = true cli.handleConnState(true) c.Check(cli.session, IsNil) } func (cs *clientSuite) TestHandleConnStateC2D(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, levelmap.NewLevelMap, cs.log) cli.session.Dial() cli.hasConnectivity = true // cli.session.State() will be "Error" here, for now at least c.Check(cli.session.State(), Not(Equals), session.Disconnected) cli.handleConnState(false) c.Check(cli.session.State(), Equals, session.Disconnected) } func (cs *clientSuite) TestHandleConnStateC2DPending(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, levelmap.NewLevelMap, cs.log) cli.hasConnectivity = true cli.handleConnState(false) c.Check(cli.session.State(), Equals, session.Disconnected) } /***************************************************************** filterNotification tests ******************************************************************/ var siInfoRes = &systemimage.InfoResult{ Device: "mako", Channel: "daily", BuildNumber: 102, LastUpdate: "Unknown", } func (cs *clientSuite) TestFilterNotification(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.systemImageInfo = siInfoRes // empty msg := &session.Notification{} c.Check(cli.filterNotification(msg), Equals, false) // same build number msg = &session.Notification{ Decoded: []map[string]interface{}{ map[string]interface{}{ "daily/mako": []interface{}{float64(102), "tubular"}, }, }, } c.Check(cli.filterNotification(msg), Equals, false) // higher build number and pick last msg = &session.Notification{ Decoded: []map[string]interface{}{ map[string]interface{}{ "daily/mako": []interface{}{float64(102), "tubular"}, }, map[string]interface{}{ "daily/mako": []interface{}{float64(103), "tubular"}, }, }, } c.Check(cli.filterNotification(msg), Equals, true) // going backward by a margin, assume switch of alias msg = &session.Notification{ Decoded: []map[string]interface{}{ map[string]interface{}{ "daily/mako": []interface{}{float64(102), "tubular"}, }, map[string]interface{}{ "daily/mako": []interface{}{float64(2), "urban"}, }, }, } c.Check(cli.filterNotification(msg), Equals, true) } func (cs *clientSuite) TestFilterNotificationRobust(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.systemImageInfo = siInfoRes msg := &session.Notification{ Decoded: []map[string]interface{}{ map[string]interface{}{}, }, } c.Check(cli.filterNotification(msg), Equals, false) for _, broken := range []interface{}{ 5, []interface{}{}, []interface{}{55}, } { msg := &session.Notification{ Decoded: []map[string]interface{}{ map[string]interface{}{ "daily/mako": broken, }, }, } c.Check(cli.filterNotification(msg), Equals, false) } } /***************************************************************** handleNotification tests ******************************************************************/ var ( positiveNotification = &session.Notification{ Decoded: []map[string]interface{}{ map[string]interface{}{ "daily/mako": []interface{}{float64(103), "tubular"}, }, }, } negativeNotification = &session.Notification{ Decoded: []map[string]interface{}{ map[string]interface{}{ "daily/mako": []interface{}{float64(102), "tubular"}, }, }, } ) func (cs *clientSuite) TestHandleNotification(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.systemImageInfo = siInfoRes endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) cli.notificationsEndp = endp cli.log = cs.log c.Check(cli.handleNotification(positiveNotification), IsNil) // check we sent the notification args := testibus.GetCallArgs(endp) c.Assert(args, HasLen, 1) c.Check(args[0].Member, Equals, "Notify") c.Check(cs.log.Captured(), Matches, `.* got notification id \d+\s*`) } func (cs *clientSuite) TestHandleNotificationNothingToDo(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.systemImageInfo = siInfoRes endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) cli.notificationsEndp = endp cli.log = cs.log c.Check(cli.handleNotification(negativeNotification), IsNil) // check we sent the notification args := testibus.GetCallArgs(endp) c.Assert(args, HasLen, 0) c.Check(cs.log.Captured(), Matches, "") } func (cs *clientSuite) TestHandleNotificationFail(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.systemImageInfo = siInfoRes cli.log = cs.log endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) cli.notificationsEndp = endp c.Check(cli.handleNotification(positiveNotification), NotNil) } /***************************************************************** handleClick tests ******************************************************************/ func (cs *clientSuite) TestHandleClick(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) cli.urlDispatcherEndp = endp // check we don't fail on something random c.Check(cli.handleClick("something random"), IsNil) // ... but we don't send anything either args := testibus.GetCallArgs(endp) c.Assert(args, HasLen, 0) // check we worked with the right action id c.Check(cli.handleClick(ACTION_ID_SNOWFLAKE), IsNil) // check we sent the notification args = testibus.GetCallArgs(endp) c.Assert(args, HasLen, 1) c.Check(args[0].Member, Equals, "DispatchURL") c.Check(args[0].Args, DeepEquals, []interface{}{"settings:///system/system-update"}) } /***************************************************************** doLoop tests ******************************************************************/ func (cs *clientSuite) TestDoLoopConn(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.systemImageInfo = siInfoRes cli.connCh = make(chan bool, 1) cli.connCh <- true c.Assert(cli.initSession(), IsNil) ch := make(chan bool, 1) go cli.doLoop(func(bool) { ch <- true }, func(_ string) error { return nil }, func(_ *session.Notification) error { return nil }, func(error) {}) c.Check(takeNextBool(ch), Equals, true) } func (cs *clientSuite) TestDoLoopClick(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.systemImageInfo = siInfoRes c.Assert(cli.initSession(), IsNil) aCh := make(chan notifications.RawActionReply, 1) aCh <- notifications.RawActionReply{} cli.actionsCh = aCh ch := make(chan bool, 1) go cli.doLoop(func(bool) {}, func(_ string) error { ch <- true; return nil }, func(_ *session.Notification) error { return nil }, func(error) {}) c.Check(takeNextBool(ch), Equals, true) } func (cs *clientSuite) TestDoLoopNotif(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.systemImageInfo = siInfoRes c.Assert(cli.initSession(), IsNil) cli.session.MsgCh = make(chan *session.Notification, 1) cli.session.MsgCh <- &session.Notification{} ch := make(chan bool, 1) go cli.doLoop(func(bool) {}, func(_ string) error { return nil }, func(_ *session.Notification) error { ch <- true; return nil }, func(error) {}) c.Check(takeNextBool(ch), Equals, true) } func (cs *clientSuite) TestDoLoopErr(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.log = cs.log cli.systemImageInfo = siInfoRes c.Assert(cli.initSession(), IsNil) cli.session.ErrCh = make(chan error, 1) cli.session.ErrCh <- nil ch := make(chan bool, 1) go cli.doLoop(func(bool) {}, func(_ string) error { return nil }, func(_ *session.Notification) error { return nil }, func(error) { ch <- true }) c.Check(takeNextBool(ch), Equals, true) } /***************************************************************** doStart tests ******************************************************************/ func (cs *clientSuite) TestDoStartWorks(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) one_called := false two_called := false one := func() error { one_called = true; return nil } two := func() error { two_called = true; return nil } c.Check(cli.doStart(one, two), IsNil) c.Check(one_called, Equals, true) c.Check(two_called, Equals, true) } func (cs *clientSuite) TestDoStartFailsAsExpected(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) one_called := false two_called := false failure := errors.New("Failure") one := func() error { one_called = true; return failure } two := func() error { two_called = true; return nil } c.Check(cli.doStart(one, two), Equals, failure) c.Check(one_called, Equals, true) c.Check(two_called, Equals, false) } /***************************************************************** Loop() tests ******************************************************************/ func (cs *clientSuite) TestLoop(c *C) { cli := NewPushClient(cs.configPath, cs.leveldbPath) cli.connCh = make(chan bool) cli.sessionConnectedCh = make(chan uint32) aCh := make(chan notifications.RawActionReply, 1) cli.actionsCh = aCh cli.log = cs.log cli.notificationsEndp = testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(true), []interface{}{uint32(1), "hello"}) cli.urlDispatcherEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false)) cli.connectivityEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.ConnectedGlobal)) cli.systemImageInfo = siInfoRes c.Assert(cli.initSession(), IsNil) cli.session.MsgCh = make(chan *session.Notification) cli.session.ErrCh = make(chan error) // we use tick() to make sure things have been through the // event loop at least once before looking at things; // otherwise there's a race between what we're trying to look // at and the loop itself. tick := func() { cli.sessionConnectedCh <- 42 } go cli.Loop() // sessionConnectedCh to nothing in particular, but it'll help sync this test cli.sessionConnectedCh <- 42 tick() c.Check(cs.log.Captured(), Matches, "(?ms).*Session connected after 42 attempts$") // * actionsCh to the click handler/url dispatcher aCh <- notifications.RawActionReply{ActionId: ACTION_ID_SNOWFLAKE} tick() uargs := testibus.GetCallArgs(cli.urlDispatcherEndp) c.Assert(uargs, HasLen, 1) c.Check(uargs[0].Member, Equals, "DispatchURL") // loop() should have connected: // * connCh to the connectivity checker c.Check(cli.hasConnectivity, Equals, false) cli.connCh <- true tick() c.Check(cli.hasConnectivity, Equals, true) cli.connCh <- false tick() c.Check(cli.hasConnectivity, Equals, false) // * session.MsgCh to the notifications handler cli.session.MsgCh <- positiveNotification tick() nargs := testibus.GetCallArgs(cli.notificationsEndp) c.Check(nargs, HasLen, 1) // * session.ErrCh to the error handler cli.session.ErrCh <- nil tick() c.Check(cs.log.Captured(), Matches, "(?ms).*session exited.*") } /***************************************************************** Start() tests ******************************************************************/ // XXX this is a hack. func (cs *clientSuite) hasDbus() bool { for _, b := range []bus.Bus{bus.SystemBus, bus.SessionBus} { if b.Endpoint(bus.BusDaemonAddress, cs.log).Dial() != nil { return false } } return true } func (cs *clientSuite) TestStart(c *C) { if !cs.hasDbus() { c.Skip("no dbus") } cli := NewPushClient(cs.configPath, cs.leveldbPath) // before start, everything sucks: // no config, c.Check(string(cli.config.Addr), Equals, "") // no device id, c.Check(cli.deviceId, HasLen, 0) // no session, c.Check(cli.session, IsNil) // no bus, c.Check(cli.notificationsEndp, IsNil) // no nuthin'. // so we start, err := cli.Start() // and it works c.Check(err, IsNil) // and now everthing is better! We have a config, c.Check(string(cli.config.Addr), Equals, ":0") // and a device id, c.Check(cli.deviceId, HasLen, 128) // and a session, c.Check(cli.session, NotNil) // and a bus, c.Check(cli.notificationsEndp, NotNil) // and everthying us just peachy! } func (cs *clientSuite) TestStartCanFail(c *C) { cli := NewPushClient("/does/not/exist", cs.leveldbPath) // easiest way for it to fail is to feed it a bad config err := cli.Start() // and it works. Err. Doesn't. c.Check(err, NotNil) } ubuntu-push-0.2.1+14.04.20140423.1/client/gethosts/0000755000015301777760000000000012325725172021671 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/client/gethosts/gethost.go0000644000015301777760000000472712325724720023705 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package gethosts implements finding hosts to connect to for delivery of notifications. package gethosts import ( "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "time" "launchpad.net/ubuntu-push/external/murmur3" http13 "launchpad.net/ubuntu-push/http13client" ) // GetHost implements finding hosts to connect to consulting a remote endpoint providing a hash of the device identifier. type GetHost struct { hash string endpointUrl string cli *http13.Client } // New makes a GetHost. func New(deviceId, endpointUrl string, timeout time.Duration) *GetHost { hash := murmur3.Sum64([]byte(deviceId)) return &GetHost{ hash: fmt.Sprintf("%x", hash), endpointUrl: endpointUrl, cli: &http13.Client{ Transport: &http13.Transport{TLSHandshakeTimeout: timeout}, Timeout: timeout, }, } } // Host contains the domain and hosts returned by the remote endpoint type Host struct { Domain string Hosts []string } var ( ErrRequest = errors.New("request was not accepted") ErrInternal = errors.New("remote had an internal error") ErrTemporary = errors.New("remote had a temporary error") ) // Get gets a list of hosts consulting the endpoint. func (gh *GetHost) Get() (*Host, error) { resp, err := gh.cli.Get(gh.endpointUrl + "?h=" + gh.hash) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { switch { case resp.StatusCode == http.StatusInternalServerError: return nil, ErrInternal case resp.StatusCode > http.StatusInternalServerError: return nil, ErrTemporary default: return nil, ErrRequest } } body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } var parsed Host err = json.Unmarshal(body, &parsed) if err != nil { return nil, ErrTemporary } if len(parsed.Hosts) == 0 { return nil, ErrTemporary } return &parsed, nil } ubuntu-push-0.2.1+14.04.20140423.1/client/gethosts/gethost_test.go0000644000015301777760000000565512325724720024745 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package gethosts import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/external/murmur3" ) func TestGetHosts(t *testing.T) { TestingT(t) } type getHostsSuite struct{} var _ = Suite(&getHostsSuite{}) func (s *getHostsSuite) TestNew(c *C) { gh := New("foobar", "http://where/hosts", 10*time.Second) c.Check(gh.hash, Equals, fmt.Sprintf("%x", murmur3.Sum64([]byte("foobar")))) c.Check(gh.endpointUrl, Equals, "http://where/hosts") } func (s *getHostsSuite) TestGet(c *C) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { x := r.FormValue("h") b, err := json.Marshal(map[string]interface{}{ "domain": "example.com", "hosts": []string{"http://" + x}, }) if err != nil { panic(err) } w.Header().Set("Content-Type", "application/json") w.Write(b) })) defer ts.Close() gh := New("foobar", ts.URL, 1*time.Second) res, err := gh.Get() c.Assert(err, IsNil) c.Check(*res, DeepEquals, Host{Domain: "example.com", Hosts: []string{"http://c1130408a700afe0"}}) } func (s *getHostsSuite) TestGetTimeout(c *C) { started := make(chan bool, 1) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { started <- true time.Sleep(700 * time.Millisecond) })) defer func() { <-started ts.Close() }() gh := New("foobar", ts.URL, 500*time.Millisecond) _, err := gh.Get() c.Check(err, ErrorMatches, ".*closed.*") } func (s *getHostsSuite) TestGetErrorScenarios(c *C) { status := make(chan int, 1) body := make(chan string, 1) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(<-status) fmt.Fprintf(w, "%s", <-body) })) defer ts.Close() gh := New("foobar", ts.URL, 1*time.Second) scenario := func(st int, bdy string, expectedErr error) { status <- st body <- bdy _, err := gh.Get() c.Check(err, Equals, expectedErr) } scenario(http.StatusBadRequest, "", ErrRequest) scenario(http.StatusInternalServerError, "", ErrInternal) scenario(http.StatusGatewayTimeout, "", ErrTemporary) scenario(http.StatusOK, "{", ErrTemporary) scenario(http.StatusOK, "{}", ErrTemporary) scenario(http.StatusOK, `{"domain": "example.com"}`, ErrTemporary) scenario(http.StatusOK, `{"hosts": ["one"]}`, nil) } ubuntu-push-0.2.1+14.04.20140423.1/client/session/0000755000015301777760000000000012325725172021514 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/client/session/session.go0000644000015301777760000003460412325724720023533 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package session handles the minutiae of interacting with // the Ubuntu Push Notifications server. package session import ( "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "math/rand" "net" "strings" "sync" "sync/atomic" "time" "launchpad.net/ubuntu-push/client/gethosts" "launchpad.net/ubuntu-push/client/session/levelmap" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/protocol" "launchpad.net/ubuntu-push/util" ) var wireVersionBytes = []byte{protocol.ProtocolWireVersion} type Notification struct { TopLevel int64 Decoded []map[string]interface{} } type serverMsg struct { Type string `json:"T"` protocol.BroadcastMsg protocol.NotificationsMsg protocol.ConnBrokenMsg } // parseServerAddrSpec recognizes whether spec is a HTTP URL to get // hosts from or a |-separated list of host:port pairs. func parseServerAddrSpec(spec string) (hostsEndpoint string, fallbackHosts []string) { if strings.HasPrefix(spec, "http") { return spec, nil } return "", strings.Split(spec, "|") } // ClientSessionState is a way to broadly track the progress of the session type ClientSessionState uint32 const ( Error ClientSessionState = iota Disconnected Connected Started Running ) type hostGetter interface { Get() (*gethosts.Host, error) } // ClientSessionConfig groups the client session configuration. type ClientSessionConfig struct { ConnectTimeout time.Duration ExchangeTimeout time.Duration HostsCachingExpiryTime time.Duration ExpectAllRepairedTime time.Duration PEM []byte Info map[string]interface{} } // ClientSession holds a client<->server session and its configuration. type ClientSession struct { // configuration DeviceId string ClientSessionConfig Levels levelmap.LevelMap Protocolator func(net.Conn) protocol.Protocol // hosts getHost hostGetter fallbackHosts []string deliveryHostsTimestamp time.Time deliveryHosts []string lastAttemptTimestamp time.Time leftToTry int tryHost int // hook for testing timeSince func(time.Time) time.Duration // connection connLock sync.RWMutex Connection net.Conn Log logger.Logger TLS *tls.Config proto protocol.Protocol pingInterval time.Duration retrier util.AutoRedialer // status stateP *uint32 ErrCh chan error MsgCh chan *Notification // autoredial knobs shouldDelayP *uint32 lastAutoRedial time.Time redialDelay func(*ClientSession) time.Duration redialJitter func(time.Duration) time.Duration redialDelays []time.Duration redialDelaysIdx int } func redialDelay(sess *ClientSession) time.Duration { if sess.ShouldDelay() { t := sess.redialDelays[sess.redialDelaysIdx] if len(sess.redialDelays) > sess.redialDelaysIdx+1 { sess.redialDelaysIdx++ } return t + sess.redialJitter(t) } else { sess.redialDelaysIdx = 0 return 0 } } func NewSession(serverAddrSpec string, conf ClientSessionConfig, deviceId string, levelmapFactory func() (levelmap.LevelMap, error), log logger.Logger) (*ClientSession, error) { state := uint32(Disconnected) levels, err := levelmapFactory() if err != nil { return nil, err } var getHost hostGetter log.Infof("using addr: %v", serverAddrSpec) hostsEndpoint, fallbackHosts := parseServerAddrSpec(serverAddrSpec) if hostsEndpoint != "" { getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout) } var shouldDelay uint32 = 0 sess := &ClientSession{ ClientSessionConfig: conf, getHost: getHost, fallbackHosts: fallbackHosts, DeviceId: deviceId, Log: log, Protocolator: protocol.NewProtocol0, Levels: levels, TLS: &tls.Config{}, stateP: &state, timeSince: time.Since, shouldDelayP: &shouldDelay, redialDelay: redialDelay, redialDelays: util.Timeouts(), } sess.redialJitter = sess.Jitter if sess.PEM != nil { cp := x509.NewCertPool() ok := cp.AppendCertsFromPEM(sess.PEM) if !ok { return nil, errors.New("could not parse certificate") } sess.TLS.RootCAs = cp } return sess, nil } func (sess *ClientSession) ShouldDelay() bool { return atomic.LoadUint32(sess.shouldDelayP) != 0 } func (sess *ClientSession) setShouldDelay() { atomic.StoreUint32(sess.shouldDelayP, uint32(1)) } func (sess *ClientSession) clearShouldDelay() { atomic.StoreUint32(sess.shouldDelayP, uint32(0)) } func (sess *ClientSession) State() ClientSessionState { return ClientSessionState(atomic.LoadUint32(sess.stateP)) } func (sess *ClientSession) setState(state ClientSessionState) { atomic.StoreUint32(sess.stateP, uint32(state)) } func (sess *ClientSession) setConnection(conn net.Conn) { sess.connLock.Lock() defer sess.connLock.Unlock() sess.Connection = conn } func (sess *ClientSession) getConnection() net.Conn { sess.connLock.RLock() defer sess.connLock.RUnlock() return sess.Connection } // getHosts sets deliveryHosts possibly querying a remote endpoint func (sess *ClientSession) getHosts() error { if sess.getHost != nil { if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime { return nil } host, err := sess.getHost.Get() if err != nil { sess.Log.Errorf("getHosts: %v", err) sess.setState(Error) return err } sess.deliveryHostsTimestamp = time.Now() sess.deliveryHosts = host.Hosts if sess.TLS != nil { sess.TLS.ServerName = host.Domain } } else { sess.deliveryHosts = sess.fallbackHosts } return nil } func (sess *ClientSession) resetHosts() { sess.deliveryHosts = nil } // startConnectionAttempt/nextHostToTry help connect iterating over candidate hosts func (sess *ClientSession) startConnectionAttempt() { if sess.timeSince(sess.lastAttemptTimestamp) > sess.ExpectAllRepairedTime { sess.tryHost = 0 } sess.leftToTry = len(sess.deliveryHosts) if sess.leftToTry == 0 { panic("should have got hosts from config or remote at this point") } sess.lastAttemptTimestamp = time.Now() } func (sess *ClientSession) nextHostToTry() string { if sess.leftToTry == 0 { return "" } res := sess.deliveryHosts[sess.tryHost] sess.tryHost = (sess.tryHost + 1) % len(sess.deliveryHosts) sess.leftToTry-- return res } // we reached the Started state, we can retry with the same host if we // have to retry again func (sess *ClientSession) started() { sess.tryHost-- if sess.tryHost == -1 { sess.tryHost = len(sess.deliveryHosts) - 1 } sess.setState(Started) } // connect to a server using the configuration in the ClientSession // and set up the connection. func (sess *ClientSession) connect() error { sess.setShouldDelay() sess.startConnectionAttempt() var err error var conn net.Conn for { host := sess.nextHostToTry() if host == "" { sess.setState(Error) return fmt.Errorf("connect: %s", err) } sess.Log.Debugf("trying to connect to: %v", host) conn, err = net.DialTimeout("tcp", host, sess.ConnectTimeout) if err == nil { break } } sess.setConnection(tls.Client(conn, sess.TLS)) sess.setState(Connected) return nil } func (sess *ClientSession) stopRedial() { if sess.retrier != nil { sess.retrier.Stop() sess.retrier = nil } } func (sess *ClientSession) AutoRedial(doneCh chan uint32) { sess.stopRedial() if time.Since(sess.lastAutoRedial) < 2*time.Second { sess.setShouldDelay() } time.Sleep(sess.redialDelay(sess)) sess.retrier = util.NewAutoRedialer(sess) sess.lastAutoRedial = time.Now() go func() { doneCh <- sess.retrier.Redial() }() } func (sess *ClientSession) Close() { sess.stopRedial() sess.doClose() } func (sess *ClientSession) doClose() { sess.connLock.Lock() defer sess.connLock.Unlock() if sess.Connection != nil { sess.Connection.Close() // we ignore Close errors, on purpose (the thinking being that // the connection isn't really usable, and you've got nothing // you could do to recover at this stage). sess.Connection = nil } sess.setState(Disconnected) } // handle "ping" messages func (sess *ClientSession) handlePing() error { err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"}) if err == nil { sess.Log.Debugf("ping.") sess.clearShouldDelay() } else { sess.setState(Error) sess.Log.Errorf("unable to pong: %s", err) } return err } func (sess *ClientSession) decodeBroadcast(bcast *serverMsg) *Notification { decoded := make([]map[string]interface{}, 0) for _, p := range bcast.Payloads { var v map[string]interface{} err := json.Unmarshal(p, &v) if err != nil { sess.Log.Debugf("expected map in broadcast: %v", err) continue } decoded = append(decoded, v) } return &Notification{ TopLevel: bcast.TopLevel, Decoded: decoded, } } // handle "broadcast" messages func (sess *ClientSession) handleBroadcast(bcast *serverMsg) error { err := sess.Levels.Set(bcast.ChanId, bcast.TopLevel) if err != nil { sess.setState(Error) sess.Log.Errorf("unable to set level: %v", err) sess.proto.WriteMessage(protocol.AckMsg{"nak"}) return err } // the server assumes if we ack the broadcast, we've updated // our levels. Hence the order. err = sess.proto.WriteMessage(protocol.AckMsg{"ack"}) if err != nil { sess.setState(Error) sess.Log.Errorf("unable to ack broadcast: %s", err) return err } sess.clearShouldDelay() sess.Log.Debugf("broadcast chan:%v app:%v topLevel:%d payloads:%s", bcast.ChanId, bcast.AppId, bcast.TopLevel, bcast.Payloads) if bcast.ChanId == protocol.SystemChannelId { // the system channel id, the only one we care about for now sess.Log.Debugf("sending it over") sess.MsgCh <- sess.decodeBroadcast(bcast) sess.Log.Debugf("sent it over") } else { sess.Log.Debugf("what is this weird channel, %#v?", bcast.ChanId) } return nil } // handle "connbroken" messages func (sess *ClientSession) handleConnBroken(connBroken *serverMsg) error { sess.setState(Error) reason := connBroken.Reason err := fmt.Errorf("server broke connection: %s", reason) sess.Log.Errorf("%s", err) switch reason { case protocol.BrokenHostMismatch: sess.resetHosts() } return err } // loop runs the session with the server, emits a stream of events. func (sess *ClientSession) loop() error { var err error var recv serverMsg sess.setState(Running) for { deadAfter := sess.pingInterval + sess.ExchangeTimeout sess.proto.SetDeadline(time.Now().Add(deadAfter)) err = sess.proto.ReadMessage(&recv) if err != nil { sess.setState(Error) return err } switch recv.Type { case "ping": err = sess.handlePing() case "broadcast": err = sess.handleBroadcast(&recv) case "connbroken": err = sess.handleConnBroken(&recv) } if err != nil { return err } } } // Call this when you've connected and want to start looping. func (sess *ClientSession) start() error { conn := sess.getConnection() err := conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) if err != nil { sess.setState(Error) sess.Log.Errorf("unable to start: set deadline: %s", err) return err } _, err = conn.Write(wireVersionBytes) // The Writer docs: Write must return a non-nil error if it returns // n < len(p). So, no need to check number of bytes written, hooray. if err != nil { sess.setState(Error) sess.Log.Errorf("unable to start: write version: %s", err) return err } proto := sess.Protocolator(conn) proto.SetDeadline(time.Now().Add(sess.ExchangeTimeout)) levels, err := sess.Levels.GetAll() if err != nil { sess.setState(Error) sess.Log.Errorf("unable to start: get levels: %v", err) return err } err = proto.WriteMessage(protocol.ConnectMsg{ Type: "connect", DeviceId: sess.DeviceId, // xxx get the SSO Authorization string from the phone Authorization: "", Levels: levels, Info: sess.Info, }) if err != nil { sess.setState(Error) sess.Log.Errorf("unable to start: connect: %s", err) return err } var connAck protocol.ConnAckMsg err = proto.ReadMessage(&connAck) if err != nil { sess.setState(Error) sess.Log.Errorf("unable to start: connack: %s", err) return err } if connAck.Type != "connack" { sess.setState(Error) return fmt.Errorf("expecting CONNACK, got %#v", connAck.Type) } pingInterval, err := time.ParseDuration(connAck.Params.PingInterval) if err != nil { sess.setState(Error) sess.Log.Errorf("unable to start: parse ping interval: %s", err) return err } sess.proto = proto sess.pingInterval = pingInterval sess.Log.Debugf("Connected %v.", conn.RemoteAddr()) sess.started() // deals with choosing which host to retry with as well return nil } // run calls connect, and if it works it calls start, and if it works // it runs loop in a goroutine, and ships its return value over ErrCh. func (sess *ClientSession) run(closer func(), hostGetter, connecter, starter, looper func() error) error { closer() err := hostGetter() if err != nil { return err } err = connecter() if err == nil { err = starter() if err == nil { sess.ErrCh = make(chan error, 1) sess.MsgCh = make(chan *Notification) go func() { sess.ErrCh <- looper() }() } } return err } // This Jitter returns a random time.Duration somewhere in [-spread, spread]. func (sess *ClientSession) Jitter(spread time.Duration) time.Duration { if spread < 0 { panic("spread must be non-negative") } n := int64(spread) return time.Duration(rand.Int63n(2*n+1) - n) } // Dial takes the session from newly created (or newly disconnected) // to running the main loop. func (sess *ClientSession) Dial() error { if sess.Protocolator == nil { // a missing protocolator means you've willfully overridden // it; returning an error here would prompt AutoRedial to just // keep on trying. panic("can't Dial() without a protocol constructor.") } return sess.run(sess.doClose, sess.getHosts, sess.connect, sess.start, sess.loop) } func init() { rand.Seed(time.Now().Unix()) // good enough for us (we're not using it for crypto) } ubuntu-push-0.2.1+14.04.20140423.1/client/session/levelmap/0000755000015301777760000000000012325725172023321 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/client/session/levelmap/sqlevelmap_test.go0000644000015301777760000000510612325724711027060 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package levelmap import ( _ "code.google.com/p/gosqlite/sqlite3" "database/sql" . "launchpad.net/gocheck" ) type sqlmSuite struct{ lmSuite } var _ = Suite(&sqlmSuite{}) func (s *sqlmSuite) SetUpSuite(c *C) { s.constructor = func() (LevelMap, error) { return NewSqliteLevelMap(":memory:") } } func (s *sqlmSuite) TestNewCanFail(c *C) { m, err := NewSqliteLevelMap("/does/not/exist") c.Assert(m, IsNil) c.Check(err, NotNil) } func (s *sqlmSuite) TestSetCanFail(c *C) { dir := c.MkDir() filename := dir + "test.db" db, err := sql.Open("sqlite3", filename) c.Assert(err, IsNil) // create the wrong kind of table _, err = db.Exec("CREATE TABLE level_map (foo)") c.Assert(err, IsNil) // m, err := NewSqliteLevelMap(filename) c.Check(err, IsNil) c.Assert(m, NotNil) err = m.Set("foo", 42) c.Check(err, ErrorMatches, "cannot set .*") } func (s *sqlmSuite) TestGetAllCanFail(c *C) { dir := c.MkDir() filename := dir + "test.db" db, err := sql.Open("sqlite3", filename) c.Assert(err, IsNil) // create the wrong kind of table _, err = db.Exec("CREATE TABLE level_map AS SELECT 'what'") c.Assert(err, IsNil) // m, err := NewSqliteLevelMap(filename) c.Check(err, IsNil) c.Assert(m, NotNil) all, err := m.GetAll() c.Check(all, IsNil) c.Check(err, ErrorMatches, "cannot read level .*") } func (s *sqlmSuite) TestGetAllCanFailDifferently(c *C) { dir := c.MkDir() filename := dir + "test.db" db, err := sql.Open("sqlite3", filename) c.Assert(err, IsNil) // create a view with the name the table will have _, err = db.Exec("CREATE TABLE foo (foo)") c.Assert(err, IsNil) _, err = db.Exec("CREATE VIEW level_map AS SELECT * FROM foo") c.Assert(err, IsNil) // break the view _, err = db.Exec("DROP TABLE foo") c.Assert(err, IsNil) // m, err := NewSqliteLevelMap(filename) c.Check(err, IsNil) c.Assert(m, NotNil) all, err := m.GetAll() c.Check(all, IsNil) c.Check(err, ErrorMatches, "cannot retrieve levels .*") } ubuntu-push-0.2.1+14.04.20140423.1/client/session/levelmap/levelmap.go0000644000015301777760000000261712325724711025461 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package levelmap holds implementations of the LevelMap that the client // session uses to keep track of what messages it has seen. package levelmap type LevelMap interface { // Set() (re)sets the given level to the given value. Set(level string, top int64) error // GetAll() returns a "simple" map of the current levels. GetAll() (map[string]int64, error) } type mapLevelMap map[string]int64 func (m *mapLevelMap) Set(level string, top int64) error { (*m)[level] = top return nil } func (m *mapLevelMap) GetAll() (map[string]int64, error) { return map[string]int64(*m), nil } var _ LevelMap = &mapLevelMap{} // NewLevelMap returns an implementation of LevelMap that is memory-based and // does not save state. func NewLevelMap() (LevelMap, error) { return &mapLevelMap{}, nil } ubuntu-push-0.2.1+14.04.20140423.1/client/session/levelmap/sqlevelmap.go0000644000015301777760000000376212325724711026027 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package levelmap import ( _ "code.google.com/p/gosqlite/sqlite3" "database/sql" "fmt" ) type sqliteLevelMap struct { db *sql.DB } // NewSqliteLevelMap returns an implementation of LevelMap that // persists the map in an sqlite database. func NewSqliteLevelMap(filename string) (LevelMap, error) { db, err := sql.Open("sqlite3", filename) if err != nil { return nil, fmt.Errorf("cannot open sqlite level map %#v: %v", filename, err) } _, err = db.Exec("CREATE TABLE IF NOT EXISTS level_map (level text primary key, top integer)") if err != nil { return nil, fmt.Errorf("cannot (re)create sqlite level map table: %v", err) } return &sqliteLevelMap{db}, nil } func (pm *sqliteLevelMap) Set(level string, top int64) error { _, err := pm.db.Exec("REPLACE INTO level_map (level, top) VALUES (?, ?)", level, top) if err != nil { return fmt.Errorf("cannot set %#v to %#v in level map: %v", level, top, err) } return nil } func (pm *sqliteLevelMap) GetAll() (map[string]int64, error) { rows, err := pm.db.Query("SELECT * FROM level_map") if err != nil { return nil, fmt.Errorf("cannot retrieve levels from sqlite level map: %v", err) } m := map[string]int64{} for rows.Next() { var level string var top int64 err = rows.Scan(&level, &top) if err != nil { return nil, fmt.Errorf("cannot read level from sqlite level map: %v", err) } m[level] = top } return m, nil } ubuntu-push-0.2.1+14.04.20140423.1/client/session/levelmap/levelmap_test.go0000644000015301777760000000276312325724711026522 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package levelmap import ( . "launchpad.net/gocheck" "testing" ) func TestLevelMap(t *testing.T) { TestingT(t) } type lmSuite struct { constructor func() (LevelMap, error) } var _ = Suite(&lmSuite{}) func (s *lmSuite) SetUpSuite(c *C) { s.constructor = NewLevelMap } func (s *lmSuite) TestAllTheThings(c *C) { var err error var lm LevelMap // checks NewLevelMap returns a LevelMap lm, err = s.constructor() // and that it works c.Assert(err, IsNil) // setting a couple of things, sets them c.Check(lm.Set("this", 12), IsNil) c.Check(lm.Set("that", 42), IsNil) all, err := lm.GetAll() c.Check(err, IsNil) c.Check(all, DeepEquals, map[string]int64{"this": 12, "that": 42}) // re-setting one of them, resets it c.Check(lm.Set("this", 999), IsNil) all, err = lm.GetAll() c.Check(err, IsNil) c.Check(all, DeepEquals, map[string]int64{"this": 999, "that": 42}) // huzzah } ubuntu-push-0.2.1+14.04.20140423.1/client/session/session_test.go0000644000015301777760000012364512325724720024576 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package session import ( "crypto/tls" "encoding/json" "errors" "fmt" "io" "io/ioutil" "net" "net/http" "net/http/httptest" "reflect" "testing" "time" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/client/gethosts" "launchpad.net/ubuntu-push/client/session/levelmap" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/protocol" helpers "launchpad.net/ubuntu-push/testing" "launchpad.net/ubuntu-push/testing/condition" "launchpad.net/ubuntu-push/util" ) func TestSession(t *testing.T) { TestingT(t) } // // helpers! candidates to live in their own ../testing/ package. // type xAddr string func (x xAddr) Network() string { return "<:>" } func (x xAddr) String() string { return string(x) } // testConn (roughly based on the one in protocol_test) type testConn struct { Name string Deadlines []time.Duration Writes [][]byte WriteCondition condition.Interface DeadlineCondition condition.Interface CloseCondition condition.Interface } func (tc *testConn) LocalAddr() net.Addr { return xAddr(tc.Name) } func (tc *testConn) RemoteAddr() net.Addr { return xAddr(tc.Name) } func (tc *testConn) Close() error { if tc.CloseCondition == nil || tc.CloseCondition.OK() { return nil } else { return errors.New("closer on fire") } } func (tc *testConn) SetDeadline(t time.Time) error { tc.Deadlines = append(tc.Deadlines, t.Sub(time.Now())) if tc.DeadlineCondition == nil || tc.DeadlineCondition.OK() { return nil } else { return errors.New("deadliner on fire") } } func (tc *testConn) SetReadDeadline(t time.Time) error { panic("SetReadDeadline not implemented.") } func (tc *testConn) SetWriteDeadline(t time.Time) error { panic("SetWriteDeadline not implemented.") } func (tc *testConn) Read(buf []byte) (n int, err error) { panic("Read not implemented.") } func (tc *testConn) Write(buf []byte) (int, error) { store := make([]byte, len(buf)) copy(store, buf) tc.Writes = append(tc.Writes, store) if tc.WriteCondition == nil || tc.WriteCondition.OK() { return len(store), nil } else { return -1, errors.New("writer on fire") } } // test protocol (from session_test) type testProtocol struct { up chan interface{} down chan interface{} } // takeNext takes a value from given channel with a 5s timeout func takeNext(ch <-chan interface{}) interface{} { select { case <-time.After(5 * time.Second): panic("test protocol exchange stuck: too long waiting") case v := <-ch: return v } return nil } func (c *testProtocol) SetDeadline(t time.Time) { deadAfter := t.Sub(time.Now()) deadAfter = (deadAfter + time.Millisecond/2) / time.Millisecond * time.Millisecond c.down <- fmt.Sprintf("deadline %v", deadAfter) } func (c *testProtocol) ReadMessage(dest interface{}) error { switch v := takeNext(c.up).(type) { case error: return v default: // make sure JSON.Unmarshal works with dest var marshalledMsg []byte marshalledMsg, err := json.Marshal(v) if err != nil { return fmt.Errorf("can't jsonify test value %v: %s", v, err) } return json.Unmarshal(marshalledMsg, dest) } return nil } func (c *testProtocol) WriteMessage(src interface{}) error { // make sure JSON.Marshal works with src _, err := json.Marshal(src) if err != nil { return err } val := reflect.ValueOf(src) if val.Kind() == reflect.Ptr { src = val.Elem().Interface() } c.down <- src switch v := takeNext(c.up).(type) { case error: return v } return nil } // brokenLevelMap is a LevelMap that always breaks type brokenLevelMap struct{} func (*brokenLevelMap) Set(string, int64) error { return errors.New("broken.") } func (*brokenLevelMap) GetAll() (map[string]int64, error) { return nil, errors.New("broken.") } ///// type clientSessionSuite struct { log logger.Logger lvls func() (levelmap.LevelMap, error) } func (cs *clientSessionSuite) SetUpTest(c *C) { cs.log = helpers.NewTestLogger(c, "debug") } // in-memory level map testing var _ = Suite(&clientSessionSuite{lvls: levelmap.NewLevelMap}) // sqlite level map testing type clientSqlevelsSessionSuite struct{ clientSessionSuite } var _ = Suite(&clientSqlevelsSessionSuite{}) func (cs *clientSqlevelsSessionSuite) SetUpSuite(c *C) { cs.lvls = func() (levelmap.LevelMap, error) { return levelmap.NewSqliteLevelMap(":memory:") } } /**************************************************************** parseServerAddrSpec() tests ****************************************************************/ func (cs *clientSessionSuite) TestParseServerAddrSpec(c *C) { hEp, fallbackHosts := parseServerAddrSpec("http://foo/hosts") c.Check(hEp, Equals, "http://foo/hosts") c.Check(fallbackHosts, IsNil) hEp, fallbackHosts = parseServerAddrSpec("foo:443") c.Check(hEp, Equals, "") c.Check(fallbackHosts, DeepEquals, []string{"foo:443"}) hEp, fallbackHosts = parseServerAddrSpec("foo:443|bar:443") c.Check(hEp, Equals, "") c.Check(fallbackHosts, DeepEquals, []string{"foo:443", "bar:443"}) } /**************************************************************** NewSession() tests ****************************************************************/ var dummyConf = ClientSessionConfig{} func (cs *clientSessionSuite) TestNewSessionPlainWorks(c *C) { sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log) c.Check(sess, NotNil) c.Check(err, IsNil) c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"}) // the session is happy and redial delayer is default c.Check(sess.ShouldDelay(), Equals, false) c.Check(fmt.Sprintf("%#v", sess.redialDelay), Equals, fmt.Sprintf("%#v", redialDelay)) c.Check(sess.redialDelays, DeepEquals, util.Timeouts()) // but no root CAs set c.Check(sess.TLS.RootCAs, IsNil) c.Check(sess.State(), Equals, Disconnected) } func (cs *clientSessionSuite) TestNewSessionHostEndpointWorks(c *C) { sess, err := NewSession("http://foo/hosts", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) c.Check(sess.getHost, NotNil) } var certfile string = helpers.SourceRelative("../../server/acceptance/ssl/testing.cert") var pem, _ = ioutil.ReadFile(certfile) func (cs *clientSessionSuite) TestNewSessionPEMWorks(c *C) { conf := ClientSessionConfig{PEM: pem} sess, err := NewSession("", conf, "wah", cs.lvls, cs.log) c.Check(sess, NotNil) c.Assert(err, IsNil) c.Check(sess.TLS.RootCAs, NotNil) } func (cs *clientSessionSuite) TestNewSessionBadPEMFileContentFails(c *C) { badpem := []byte("This is not the PEM you're looking for.") conf := ClientSessionConfig{PEM: badpem} sess, err := NewSession("", conf, "wah", cs.lvls, cs.log) c.Check(sess, IsNil) c.Check(err, NotNil) } func (cs *clientSessionSuite) TestNewSessionBadLevelMapFails(c *C) { ferr := func() (levelmap.LevelMap, error) { return nil, errors.New("Busted.") } sess, err := NewSession("", dummyConf, "wah", ferr, cs.log) c.Check(sess, IsNil) c.Assert(err, NotNil) } /**************************************************************** getHosts() tests ****************************************************************/ func (cs *clientSessionSuite) TestGetHostsFallback(c *C) { fallback := []string{"foo:443", "bar:443"} sess := &ClientSession{fallbackHosts: fallback} err := sess.getHosts() c.Assert(err, IsNil) c.Check(sess.deliveryHosts, DeepEquals, fallback) } type testHostGetter struct { domain string hosts []string err error } func (thg *testHostGetter) Get() (*gethosts.Host, error) { return &gethosts.Host{thg.domain, thg.hosts}, thg.err } func (cs *clientSessionSuite) TestGetHostsRemote(c *C) { hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil} sess := &ClientSession{getHost: hostGetter, timeSince: time.Since} err := sess.getHosts() c.Assert(err, IsNil) c.Check(sess.deliveryHosts, DeepEquals, []string{"foo:443", "bar:443"}) } func (cs *clientSessionSuite) TestGetHostsRemoteError(c *C) { sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log) c.Assert(err, IsNil) hostsErr := errors.New("failed") hostGetter := &testHostGetter{"", nil, hostsErr} sess.getHost = hostGetter err = sess.getHosts() c.Assert(err, Equals, hostsErr) c.Check(sess.deliveryHosts, IsNil) c.Check(sess.State(), Equals, Error) } func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) { hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil} sess := &ClientSession{ getHost: hostGetter, ClientSessionConfig: ClientSessionConfig{ HostsCachingExpiryTime: 2 * time.Hour, }, timeSince: time.Since, } err := sess.getHosts() c.Assert(err, IsNil) hostGetter.hosts = []string{"baz:443"} // cached err = sess.getHosts() c.Assert(err, IsNil) c.Check(sess.deliveryHosts, DeepEquals, []string{"foo:443", "bar:443"}) // expired sess.timeSince = func(ts time.Time) time.Duration { return 3 * time.Hour } err = sess.getHosts() c.Assert(err, IsNil) c.Check(sess.deliveryHosts, DeepEquals, []string{"baz:443"}) } func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) { hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil} sess := &ClientSession{ getHost: hostGetter, ClientSessionConfig: ClientSessionConfig{ HostsCachingExpiryTime: 2 * time.Hour, }, timeSince: time.Since, } err := sess.getHosts() c.Assert(err, IsNil) hostGetter.hosts = []string{"baz:443"} // cached err = sess.getHosts() c.Assert(err, IsNil) c.Check(sess.deliveryHosts, DeepEquals, []string{"foo:443", "bar:443"}) // reset sess.resetHosts() err = sess.getHosts() c.Assert(err, IsNil) c.Check(sess.deliveryHosts, DeepEquals, []string{"baz:443"}) } /**************************************************************** startConnectionAttempt()/nextHostToTry()/started tests ****************************************************************/ func (cs *clientSessionSuite) TestStartConnectionAttempt(c *C) { since := time.Since(time.Time{}) sess := &ClientSession{ ClientSessionConfig: ClientSessionConfig{ ExpectAllRepairedTime: 10 * time.Second, }, timeSince: func(ts time.Time) time.Duration { return since }, deliveryHosts: []string{"foo:443", "bar:443"}, } // start from first host sess.startConnectionAttempt() c.Check(sess.lastAttemptTimestamp, Not(Equals), 0) c.Check(sess.tryHost, Equals, 0) c.Check(sess.leftToTry, Equals, 2) since = 1 * time.Second sess.tryHost = 1 // just continue sess.startConnectionAttempt() c.Check(sess.tryHost, Equals, 1) sess.tryHost = 2 } func (cs *clientSessionSuite) TestStartConnectionAttemptNoHostsPanic(c *C) { since := time.Since(time.Time{}) sess := &ClientSession{ ClientSessionConfig: ClientSessionConfig{ ExpectAllRepairedTime: 10 * time.Second, }, timeSince: func(ts time.Time) time.Duration { return since }, } c.Check(sess.startConnectionAttempt, PanicMatches, "should have got hosts from config or remote at this point") } func (cs *clientSessionSuite) TestNextHostToTry(c *C) { sess := &ClientSession{ deliveryHosts: []string{"foo:443", "bar:443", "baz:443"}, tryHost: 0, leftToTry: 3, } c.Check(sess.nextHostToTry(), Equals, "foo:443") c.Check(sess.nextHostToTry(), Equals, "bar:443") c.Check(sess.nextHostToTry(), Equals, "baz:443") c.Check(sess.nextHostToTry(), Equals, "") c.Check(sess.nextHostToTry(), Equals, "") c.Check(sess.tryHost, Equals, 0) sess.leftToTry = 3 sess.tryHost = 1 c.Check(sess.nextHostToTry(), Equals, "bar:443") c.Check(sess.nextHostToTry(), Equals, "baz:443") c.Check(sess.nextHostToTry(), Equals, "foo:443") c.Check(sess.nextHostToTry(), Equals, "") c.Check(sess.nextHostToTry(), Equals, "") c.Check(sess.tryHost, Equals, 1) } func (cs *clientSessionSuite) TestStarted(c *C) { sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log) c.Assert(err, IsNil) sess.deliveryHosts = []string{"foo:443", "bar:443", "baz:443"} sess.tryHost = 1 sess.started() c.Check(sess.tryHost, Equals, 0) c.Check(sess.State(), Equals, Started) sess.started() c.Check(sess.tryHost, Equals, 2) } /**************************************************************** connect() tests ****************************************************************/ func (cs *clientSessionSuite) TestConnectFailsWithNoAddress(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.deliveryHosts = []string{"nowhere"} sess.clearShouldDelay() err = sess.connect() c.Check(sess.ShouldDelay(), Equals, true) c.Check(err, ErrorMatches, ".*connect.*address.*") c.Check(sess.State(), Equals, Error) } func (cs *clientSessionSuite) TestConnectConnects(c *C) { srv, err := net.Listen("tcp", "localhost:0") c.Assert(err, IsNil) defer srv.Close() sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.deliveryHosts = []string{srv.Addr().String()} sess.clearShouldDelay() err = sess.connect() c.Check(sess.ShouldDelay(), Equals, true) c.Check(err, IsNil) c.Check(sess.Connection, NotNil) c.Check(sess.State(), Equals, Connected) } func (cs *clientSessionSuite) TestConnectSecondConnects(c *C) { srv, err := net.Listen("tcp", "localhost:0") c.Assert(err, IsNil) defer srv.Close() sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.deliveryHosts = []string{"nowhere", srv.Addr().String()} sess.clearShouldDelay() err = sess.connect() c.Check(sess.ShouldDelay(), Equals, true) c.Check(err, IsNil) c.Check(sess.Connection, NotNil) c.Check(sess.State(), Equals, Connected) c.Check(sess.tryHost, Equals, 0) } func (cs *clientSessionSuite) TestConnectConnectFail(c *C) { srv, err := net.Listen("tcp", "localhost:0") c.Assert(err, IsNil) sess, err := NewSession(srv.Addr().String(), dummyConf, "wah", cs.lvls, cs.log) srv.Close() c.Assert(err, IsNil) sess.deliveryHosts = []string{srv.Addr().String()} sess.clearShouldDelay() err = sess.connect() c.Check(sess.ShouldDelay(), Equals, true) c.Check(err, ErrorMatches, ".*connection refused") c.Check(sess.State(), Equals, Error) } /**************************************************************** Close() tests ****************************************************************/ func (cs *clientSessionSuite) TestClose(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestClose"} sess.Close() c.Check(sess.Connection, IsNil) c.Check(sess.State(), Equals, Disconnected) } func (cs *clientSessionSuite) TestCloseTwice(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestCloseTwice"} sess.Close() c.Check(sess.Connection, IsNil) sess.Close() c.Check(sess.Connection, IsNil) c.Check(sess.State(), Equals, Disconnected) } func (cs *clientSessionSuite) TestCloseFails(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestCloseFails", CloseCondition: condition.Work(false)} sess.Close() c.Check(sess.Connection, IsNil) // nothing you can do to clean up anyway c.Check(sess.State(), Equals, Disconnected) } type derp struct{ stopped bool } func (*derp) Redial() uint32 { return 0 } func (d *derp) Stop() { d.stopped = true } func (cs *clientSessionSuite) TestCloseStopsRetrier(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) ar := new(derp) sess.retrier = ar c.Check(ar.stopped, Equals, false) sess.Close() c.Check(ar.stopped, Equals, true) sess.Close() // double close check c.Check(ar.stopped, Equals, true) } /**************************************************************** AutoRedial() tests ****************************************************************/ func (cs *clientSessionSuite) TestAutoRedialWorks(c *C) { // checks that AutoRedial sets up a retrier and tries redialing it sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) ar := new(derp) sess.retrier = ar c.Check(ar.stopped, Equals, false) sess.AutoRedial(nil) c.Check(ar.stopped, Equals, true) } func (cs *clientSessionSuite) TestAutoRedialStopsRetrier(c *C) { // checks that AutoRedial stops the previous retrier sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) ch := make(chan uint32) c.Check(sess.retrier, IsNil) sess.AutoRedial(ch) c.Assert(sess.retrier, NotNil) sess.retrier.Stop() c.Check(<-ch, Not(Equals), 0) } func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) flag := false sess.redialDelay = func(sess *ClientSession) time.Duration { flag = true; return 0 } sess.AutoRedial(nil) c.Check(flag, Equals, true) } func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.redialDelay = func(sess *ClientSession) time.Duration { return 0 } sess.AutoRedial(nil) c.Check(sess.ShouldDelay(), Equals, false) sess.stopRedial() sess.clearShouldDelay() sess.AutoRedial(nil) c.Check(sess.ShouldDelay(), Equals, true) } /**************************************************************** handlePing() tests ****************************************************************/ type msgSuite struct { sess *ClientSession upCh chan interface{} downCh chan interface{} errCh chan error } var _ = Suite(&msgSuite{}) func (s *msgSuite) SetUpTest(c *C) { var err error conf := ClientSessionConfig{ ExchangeTimeout: time.Millisecond, } s.sess, err = NewSession("", conf, "wah", levelmap.NewLevelMap, helpers.NewTestLogger(c, "debug")) c.Assert(err, IsNil) s.sess.Connection = &testConn{Name: "TestHandle*"} s.errCh = make(chan error, 1) s.upCh = make(chan interface{}, 5) s.downCh = make(chan interface{}, 5) s.sess.proto = &testProtocol{up: s.upCh, down: s.downCh} // make the message channel buffered s.sess.MsgCh = make(chan *Notification, 5) } func (s *msgSuite) TestHandlePingWorks(c *C) { s.upCh <- nil // no error c.Check(s.sess.handlePing(), IsNil) c.Assert(len(s.downCh), Equals, 1) c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"}) } func (s *msgSuite) TestHandlePingHandlesPongWriteError(c *C) { failure := errors.New("Pong") s.upCh <- failure c.Check(s.sess.handlePing(), Equals, failure) c.Assert(len(s.downCh), Equals, 1) c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"}) c.Check(s.sess.State(), Equals, Error) } func (s *msgSuite) TestHandlePingClearsDelay(c *C) { s.sess.setShouldDelay() s.upCh <- nil // no error c.Check(s.sess.handlePing(), IsNil) c.Assert(len(s.downCh), Equals, 1) c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"}) c.Check(s.sess.ShouldDelay(), Equals, false) } func (s *msgSuite) TestHandlePingDoesNotClearsDelayOnError(c *C) { s.sess.setShouldDelay() s.upCh <- errors.New("Pong") c.Check(s.sess.handlePing(), NotNil) c.Assert(len(s.downCh), Equals, 1) c.Check(<-s.downCh, Equals, protocol.PingPongMsg{Type: "pong"}) c.Check(s.sess.ShouldDelay(), Equals, true) } /**************************************************************** handleBroadcast() tests ****************************************************************/ func (s *msgSuite) TestHandleBroadcastWorks(c *C) { msg := serverMsg{"broadcast", protocol.BroadcastMsg{ Type: "broadcast", AppId: "--ignored--", ChanId: "0", TopLevel: 2, Payloads: []json.RawMessage{ json.RawMessage(`{"img1/m1":[101,"tubular"]}`), json.RawMessage("false"), // shouldn't happen but robust json.RawMessage(`{"img1/m1":[102,"tubular"]}`), }, }, protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}} go func() { s.errCh <- s.sess.handleBroadcast(&msg) }() c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) s.upCh <- nil // ack ok c.Check(<-s.errCh, Equals, nil) c.Assert(len(s.sess.MsgCh), Equals, 1) c.Check(<-s.sess.MsgCh, DeepEquals, &Notification{ TopLevel: 2, Decoded: []map[string]interface{}{ map[string]interface{}{ "img1/m1": []interface{}{float64(101), "tubular"}, }, map[string]interface{}{ "img1/m1": []interface{}{float64(102), "tubular"}, }, }, }) // and finally, the session keeps track of the levels levels, err := s.sess.Levels.GetAll() c.Check(err, IsNil) c.Check(levels, DeepEquals, map[string]int64{"0": 2}) } func (s *msgSuite) TestHandleBroadcastBadAckWrite(c *C) { msg := serverMsg{"broadcast", protocol.BroadcastMsg{ Type: "broadcast", AppId: "APP", ChanId: "0", TopLevel: 2, Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)}, }, protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}} go func() { s.errCh <- s.sess.handleBroadcast(&msg) }() c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) failure := errors.New("ACK ACK ACK") s.upCh <- failure c.Assert(<-s.errCh, Equals, failure) c.Check(s.sess.State(), Equals, Error) } func (s *msgSuite) TestHandleBroadcastWrongChannel(c *C) { msg := serverMsg{"broadcast", protocol.BroadcastMsg{ Type: "broadcast", AppId: "APP", ChanId: "something awful", TopLevel: 2, Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)}, }, protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}} go func() { s.errCh <- s.sess.handleBroadcast(&msg) }() c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) s.upCh <- nil // ack ok c.Check(<-s.errCh, IsNil) c.Check(len(s.sess.MsgCh), Equals, 0) } func (s *msgSuite) TestHandleBroadcastWrongBrokenLevelmap(c *C) { s.sess.Levels = &brokenLevelMap{} msg := serverMsg{"broadcast", protocol.BroadcastMsg{ Type: "broadcast", AppId: "--ignored--", ChanId: "0", TopLevel: 2, Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)}, }, protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}} go func() { s.errCh <- s.sess.handleBroadcast(&msg) }() s.upCh <- nil // ack ok // start returns with error c.Check(<-s.errCh, Not(Equals), nil) // no message sent out c.Check(len(s.sess.MsgCh), Equals, 0) // and nak'ed it c.Check(len(s.downCh), Equals, 1) c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"nak"}) } func (s *msgSuite) TestHandleBroadcastClearsDelay(c *C) { s.sess.setShouldDelay() msg := serverMsg{"broadcast", protocol.BroadcastMsg{}, protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}} go func() { s.errCh <- s.sess.handleBroadcast(&msg) }() c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) s.upCh <- nil // ack ok c.Check(<-s.errCh, IsNil) c.Check(s.sess.ShouldDelay(), Equals, false) } func (s *msgSuite) TestHandleBroadcastDoesNotClearDelayOnError(c *C) { s.sess.setShouldDelay() msg := serverMsg{"broadcast", protocol.BroadcastMsg{}, protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{}} go func() { s.errCh <- s.sess.handleBroadcast(&msg) }() c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) s.upCh <- errors.New("bcast") c.Check(<-s.errCh, NotNil) c.Check(s.sess.ShouldDelay(), Equals, true) } /**************************************************************** handleConnBroken() tests ****************************************************************/ func (s *msgSuite) TestHandleConnBrokenUnkwown(c *C) { msg := serverMsg{"connbroken", protocol.BroadcastMsg{}, protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{ Reason: "REASON", }, } go func() { s.errCh <- s.sess.handleConnBroken(&msg) }() c.Check(<-s.errCh, ErrorMatches, "server broke connection: REASON") c.Check(s.sess.State(), Equals, Error) } func (s *msgSuite) TestHandleConnBrokenHostMismatch(c *C) { msg := serverMsg{"connbroken", protocol.BroadcastMsg{}, protocol.NotificationsMsg{}, protocol.ConnBrokenMsg{ Reason: protocol.BrokenHostMismatch, }, } s.sess.deliveryHosts = []string{"foo:443", "bar:443"} go func() { s.errCh <- s.sess.handleConnBroken(&msg) }() c.Check(<-s.errCh, ErrorMatches, "server broke connection: host-mismatch") c.Check(s.sess.State(), Equals, Error) // hosts were reset c.Check(s.sess.deliveryHosts, IsNil) } /**************************************************************** loop() tests ****************************************************************/ type loopSuite msgSuite var _ = Suite(&loopSuite{}) func (s *loopSuite) SetUpTest(c *C) { (*msgSuite)(s).SetUpTest(c) s.sess.Connection.(*testConn).Name = "TestLoop*" go func() { s.errCh <- s.sess.loop() }() } func (s *loopSuite) TestLoopReadError(c *C) { c.Check(s.sess.State(), Equals, Running) s.upCh <- errors.New("Read") err := <-s.errCh c.Check(err, ErrorMatches, "Read") c.Check(s.sess.State(), Equals, Error) } func (s *loopSuite) TestLoopPing(c *C) { c.Check(s.sess.State(), Equals, Running) c.Check(takeNext(s.downCh), Equals, "deadline 1ms") s.upCh <- protocol.PingPongMsg{Type: "ping"} c.Check(takeNext(s.downCh), Equals, protocol.PingPongMsg{Type: "pong"}) failure := errors.New("pong") s.upCh <- failure c.Check(<-s.errCh, Equals, failure) } func (s *loopSuite) TestLoopLoopsDaLoop(c *C) { c.Check(s.sess.State(), Equals, Running) for i := 1; i < 10; i++ { c.Check(takeNext(s.downCh), Equals, "deadline 1ms") s.upCh <- protocol.PingPongMsg{Type: "ping"} c.Check(takeNext(s.downCh), Equals, protocol.PingPongMsg{Type: "pong"}) s.upCh <- nil } failure := errors.New("pong") s.upCh <- failure c.Check(<-s.errCh, Equals, failure) } func (s *loopSuite) TestLoopBroadcast(c *C) { c.Check(s.sess.State(), Equals, Running) b := &protocol.BroadcastMsg{ Type: "broadcast", AppId: "--ignored--", ChanId: "0", TopLevel: 2, Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)}, } c.Check(takeNext(s.downCh), Equals, "deadline 1ms") s.upCh <- b c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"}) failure := errors.New("ack") s.upCh <- failure c.Check(<-s.errCh, Equals, failure) } func (s *loopSuite) TestLoopConnBroken(c *C) { c.Check(s.sess.State(), Equals, Running) broken := protocol.ConnBrokenMsg{ Type: "connbroken", Reason: "REASON", } c.Check(takeNext(s.downCh), Equals, "deadline 1ms") s.upCh <- broken c.Check(<-s.errCh, NotNil) } /**************************************************************** start() tests ****************************************************************/ func (cs *clientSessionSuite) TestStartFailsIfSetDeadlineFails(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestStartFailsIfSetDeadlineFails", DeadlineCondition: condition.Work(false)} // setdeadline will fail err = sess.start() c.Check(err, ErrorMatches, ".*deadline.*") c.Check(sess.State(), Equals, Error) } func (cs *clientSessionSuite) TestStartFailsIfWriteFails(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestStartFailsIfWriteFails", WriteCondition: condition.Work(false)} // write will fail err = sess.start() c.Check(err, ErrorMatches, ".*write.*") c.Check(sess.State(), Equals, Error) } func (cs *clientSessionSuite) TestStartFailsIfGetLevelsFails(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Levels = &brokenLevelMap{} sess.Connection = &testConn{Name: "TestStartConnectMessageFails"} errCh := make(chan error, 1) upCh := make(chan interface{}, 5) downCh := make(chan interface{}, 5) proto := &testProtocol{up: upCh, down: downCh} sess.Protocolator = func(_ net.Conn) protocol.Protocol { return proto } go func() { errCh <- sess.start() }() c.Check(takeNext(downCh), Equals, "deadline 0") err = <-errCh c.Check(err, ErrorMatches, "broken.") } func (cs *clientSessionSuite) TestStartConnectMessageFails(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestStartConnectMessageFails"} errCh := make(chan error, 1) upCh := make(chan interface{}, 5) downCh := make(chan interface{}, 5) proto := &testProtocol{up: upCh, down: downCh} sess.Protocolator = func(_ net.Conn) protocol.Protocol { return proto } go func() { errCh <- sess.start() }() c.Check(takeNext(downCh), Equals, "deadline 0") c.Check(takeNext(downCh), DeepEquals, protocol.ConnectMsg{ Type: "connect", DeviceId: sess.DeviceId, Levels: map[string]int64{}, }) upCh <- errors.New("Overflow error in /dev/null") err = <-errCh c.Check(err, ErrorMatches, "Overflow.*null") c.Check(sess.State(), Equals, Error) } func (cs *clientSessionSuite) TestStartConnackReadError(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestStartConnackReadError"} errCh := make(chan error, 1) upCh := make(chan interface{}, 5) downCh := make(chan interface{}, 5) proto := &testProtocol{up: upCh, down: downCh} sess.Protocolator = func(_ net.Conn) protocol.Protocol { return proto } go func() { errCh <- sess.start() }() c.Check(takeNext(downCh), Equals, "deadline 0") _, ok := takeNext(downCh).(protocol.ConnectMsg) c.Check(ok, Equals, true) upCh <- nil // no error upCh <- io.EOF err = <-errCh c.Check(err, ErrorMatches, ".*EOF.*") c.Check(sess.State(), Equals, Error) } func (cs *clientSessionSuite) TestStartBadConnack(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestStartBadConnack"} errCh := make(chan error, 1) upCh := make(chan interface{}, 5) downCh := make(chan interface{}, 5) proto := &testProtocol{up: upCh, down: downCh} sess.Protocolator = func(_ net.Conn) protocol.Protocol { return proto } go func() { errCh <- sess.start() }() c.Check(takeNext(downCh), Equals, "deadline 0") _, ok := takeNext(downCh).(protocol.ConnectMsg) c.Check(ok, Equals, true) upCh <- nil // no error upCh <- protocol.ConnAckMsg{Type: "connack"} err = <-errCh c.Check(err, ErrorMatches, ".*invalid.*") c.Check(sess.State(), Equals, Error) } func (cs *clientSessionSuite) TestStartNotConnack(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestStartBadConnack"} errCh := make(chan error, 1) upCh := make(chan interface{}, 5) downCh := make(chan interface{}, 5) proto := &testProtocol{up: upCh, down: downCh} sess.Protocolator = func(_ net.Conn) protocol.Protocol { return proto } go func() { errCh <- sess.start() }() c.Check(takeNext(downCh), Equals, "deadline 0") _, ok := takeNext(downCh).(protocol.ConnectMsg) c.Check(ok, Equals, true) upCh <- nil // no error upCh <- protocol.ConnAckMsg{Type: "connnak"} err = <-errCh c.Check(err, ErrorMatches, ".*CONNACK.*") c.Check(sess.State(), Equals, Error) } func (cs *clientSessionSuite) TestStartWorks(c *C) { info := map[string]interface{}{ "foo": 1, "bar": "baz", } conf := ClientSessionConfig{ Info: info, } sess, err := NewSession("", conf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Connection = &testConn{Name: "TestStartWorks"} errCh := make(chan error, 1) upCh := make(chan interface{}, 5) downCh := make(chan interface{}, 5) proto := &testProtocol{up: upCh, down: downCh} sess.Protocolator = func(_ net.Conn) protocol.Protocol { return proto } go func() { errCh <- sess.start() }() c.Check(takeNext(downCh), Equals, "deadline 0") msg, ok := takeNext(downCh).(protocol.ConnectMsg) c.Check(ok, Equals, true) c.Check(msg.DeviceId, Equals, "wah") c.Check(msg.Info, DeepEquals, info) upCh <- nil // no error upCh <- protocol.ConnAckMsg{ Type: "connack", Params: protocol.ConnAckParams{(10 * time.Millisecond).String()}, } // start is now done. err = <-errCh c.Check(err, IsNil) c.Check(sess.State(), Equals, Started) } /**************************************************************** run() tests ****************************************************************/ func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) failure := errors.New("TestRunBailsIfHostGetterFails") has_closed := false err = sess.run( func() { has_closed = true }, func() error { return failure }, nil, nil, nil) c.Check(err, Equals, failure) c.Check(has_closed, Equals, true) } func (cs *clientSessionSuite) TestRunBailsIfConnectFails(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) failure := errors.New("TestRunBailsIfConnectFails") err = sess.run( func() {}, func() error { return nil }, func() error { return failure }, nil, nil) c.Check(err, Equals, failure) } func (cs *clientSessionSuite) TestRunBailsIfStartFails(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) failure := errors.New("TestRunBailsIfStartFails") err = sess.run( func() {}, func() error { return nil }, func() error { return nil }, func() error { return failure }, nil) c.Check(err, Equals, failure) } func (cs *clientSessionSuite) TestRunRunsEvenIfLoopFails(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) // just to make a point: until here we haven't set ErrCh & MsgCh (no // biggie if this stops being true) c.Check(sess.ErrCh, IsNil) c.Check(sess.MsgCh, IsNil) failureCh := make(chan error) // must be unbuffered notf := &Notification{} err = sess.run( func() {}, func() error { return nil }, func() error { return nil }, func() error { return nil }, func() error { sess.MsgCh <- notf; return <-failureCh }) c.Check(err, Equals, nil) // if run doesn't error it sets up the channels c.Assert(sess.ErrCh, NotNil) c.Assert(sess.MsgCh, NotNil) c.Check(<-sess.MsgCh, Equals, notf) failure := errors.New("TestRunRunsEvenIfLoopFails") failureCh <- failure c.Check(<-sess.ErrCh, Equals, failure) // so now you know it was running in a goroutine :) } /**************************************************************** Jitter() tests ****************************************************************/ func (cs *clientSessionSuite) TestJitter(c *C) { sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) num_tries := 20 // should do the math spread := time.Second // has_neg := false has_pos := false has_zero := true for i := 0; i < num_tries; i++ { n := sess.Jitter(spread) if n > 0 { has_pos = true } else if n < 0 { has_neg = true } else { has_zero = true } } c.Check(has_neg, Equals, true) c.Check(has_pos, Equals, true) c.Check(has_zero, Equals, true) // a negative spread is caught in the reasonable place c.Check(func() { sess.Jitter(time.Duration(-1)) }, PanicMatches, "spread must be non-negative") } /**************************************************************** Dial() tests ****************************************************************/ func (cs *clientSessionSuite) TestDialPanics(c *C) { // one last unhappy test sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) sess.Protocolator = nil c.Check(sess.Dial, PanicMatches, ".*protocol constructor.") } var ( dialTestTimeout = 100 * time.Millisecond dialTestConf = ClientSessionConfig{ ExchangeTimeout: dialTestTimeout, PEM: helpers.TestCertPEMBlock, } ) func (cs *clientSessionSuite) TestDialBadServerName(c *C) { // a borked server name cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock) c.Assert(err, IsNil) tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, SessionTicketsDisabled: true, } lst, err := tls.Listen("tcp", "localhost:0", tlsCfg) c.Assert(err, IsNil) // advertise ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, err := json.Marshal(map[string]interface{}{ "domain": "xyzzy", // <-- *** THIS *** is the bit that'll break it "hosts": []string{"nowhere", lst.Addr().String()}, }) if err != nil { panic(err) } w.Header().Set("Content-Type", "application/json") w.Write(b) })) defer ts.Close() sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) tconn := &testConn{} sess.Connection = tconn upCh := make(chan interface{}, 5) downCh := make(chan interface{}, 5) errCh := make(chan error, 1) proto := &testProtocol{up: upCh, down: downCh} sess.Protocolator = func(net.Conn) protocol.Protocol { return proto } go func() { errCh <- sess.Dial() }() srv, err := lst.Accept() c.Assert(err, IsNil) // connect done _, err = protocol.ReadWireFormatVersion(srv, dialTestTimeout) c.Check(err, NotNil) c.Check(<-errCh, NotNil) c.Check(sess.State(), Equals, Error) } func (cs *clientSessionSuite) TestDialWorks(c *C) { // happy path thoughts cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock) c.Assert(err, IsNil) tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, SessionTicketsDisabled: true, } lst, err := tls.Listen("tcp", "localhost:0", tlsCfg) c.Assert(err, IsNil) // advertise ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { b, err := json.Marshal(map[string]interface{}{ "domain": "localhost", "hosts": []string{"nowhere", lst.Addr().String()}, }) if err != nil { panic(err) } w.Header().Set("Content-Type", "application/json") w.Write(b) })) defer ts.Close() sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) tconn := &testConn{CloseCondition: condition.Fail2Work(10)} sess.Connection = tconn // just to be sure: c.Check(tconn.CloseCondition.String(), Matches, ".* 10 to go.") upCh := make(chan interface{}, 5) downCh := make(chan interface{}, 5) proto := &testProtocol{up: upCh, down: downCh} sess.Protocolator = func(net.Conn) protocol.Protocol { return proto } go sess.Dial() srv, err := lst.Accept() c.Assert(err, IsNil) // connect done // Dial should have had the session's old connection (tconn) closed // before connecting a new one; if that was done, tconn's condition // ticked forward: c.Check(tconn.CloseCondition.String(), Matches, ".* 9 to go.") // now, start: 1. protocol version v, err := protocol.ReadWireFormatVersion(srv, dialTestTimeout) c.Assert(err, IsNil) c.Assert(v, Equals, protocol.ProtocolWireVersion) // if something goes wrong session would try the first/other host c.Check(sess.tryHost, Equals, 0) // 2. "connect" (but on the fake protcol above! woo) c.Check(takeNext(downCh), Equals, "deadline 100ms") _, ok := takeNext(downCh).(protocol.ConnectMsg) c.Check(ok, Equals, true) upCh <- nil // no error upCh <- protocol.ConnAckMsg{ Type: "connack", Params: protocol.ConnAckParams{(10 * time.Millisecond).String()}, } // start is now done. // 3. "loop" // ping works, c.Check(takeNext(downCh), Equals, "deadline 110ms") upCh <- protocol.PingPongMsg{Type: "ping"} c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"}) upCh <- nil // session would retry the same host c.Check(sess.tryHost, Equals, 1) // and broadcasts... b := &protocol.BroadcastMsg{ Type: "broadcast", AppId: "--ignored--", ChanId: "0", TopLevel: 2, Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)}, } c.Check(takeNext(downCh), Equals, "deadline 110ms") upCh <- b c.Check(takeNext(downCh), Equals, protocol.AckMsg{"ack"}) upCh <- nil // ...get bubbled up, c.Check(<-sess.MsgCh, NotNil) // and their TopLevel remembered levels, err := sess.Levels.GetAll() c.Check(err, IsNil) c.Check(levels, DeepEquals, map[string]int64{"0": 2}) // and ping still work even after that. c.Check(takeNext(downCh), Equals, "deadline 110ms") upCh <- protocol.PingPongMsg{Type: "ping"} c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"}) failure := errors.New("pongs") upCh <- failure c.Check(<-sess.ErrCh, Equals, failure) } func (cs *clientSessionSuite) TestDialWorksDirect(c *C) { // happy path thoughts cert, err := tls.X509KeyPair(helpers.TestCertPEMBlock, helpers.TestKeyPEMBlock) c.Assert(err, IsNil) tlsCfg := &tls.Config{ Certificates: []tls.Certificate{cert}, SessionTicketsDisabled: true, } lst, err := tls.Listen("tcp", "localhost:0", tlsCfg) c.Assert(err, IsNil) sess, err := NewSession(lst.Addr().String(), dialTestConf, "wah", cs.lvls, cs.log) c.Assert(err, IsNil) defer sess.Close() upCh := make(chan interface{}, 5) downCh := make(chan interface{}, 5) proto := &testProtocol{up: upCh, down: downCh} sess.Protocolator = func(net.Conn) protocol.Protocol { return proto } go sess.Dial() _, err = lst.Accept() c.Assert(err, IsNil) // connect done } /**************************************************************** redialDelay() tests ****************************************************************/ func (cs *clientSessionSuite) TestShouldDelay(c *C) { sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log) c.Assert(err, IsNil) c.Check(sess.ShouldDelay(), Equals, false) sess.setShouldDelay() c.Check(sess.ShouldDelay(), Equals, true) sess.clearShouldDelay() c.Check(sess.ShouldDelay(), Equals, false) } func (cs *clientSessionSuite) TestRedialDelay(c *C) { sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log) c.Assert(err, IsNil) sess.redialDelays = []time.Duration{17, 42} n := 0 sess.redialJitter = func(time.Duration) time.Duration { n++; return 0 } // we get increasing delays while we're unhappy sess.setShouldDelay() c.Check(redialDelay(sess), Equals, time.Duration(17)) c.Check(redialDelay(sess), Equals, time.Duration(42)) c.Check(redialDelay(sess), Equals, time.Duration(42)) // once we're happy, delays drop to 0 sess.clearShouldDelay() c.Check(redialDelay(sess), Equals, time.Duration(0)) // and start again from the top if we become unhappy again sess.setShouldDelay() c.Check(redialDelay(sess), Equals, time.Duration(17)) // and redialJitter got called every time shouldDelay was true c.Check(n, Equals, 4) } ubuntu-push-0.2.1+14.04.20140423.1/.precommit0000755000015301777760000000170112325724711020553 0ustar pbusernogroup00000000000000#!/bin/sh set -e echo "$@" # put me in the project root, call me ".precommit". # And put this here-document in ~/.bazaar/plugins/precommit_script.py: <. */ package testing // key&cert generated with go run /usr/lib/go/src/pkg/crypto/tls/generate_cert.go -ca -host localhost -rsa-bits 512 -duration 87600h var ( TestKeyPEMBlock = []byte(`-----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBAPw+niki17X2qALE2A2AzE1q5dvK9CI4OduRtT9IgbFLC6psqAT2 1NA+QbY17nWSSpyP65zkMkwKXrbDzstwLPkCAwEAAQJAKwXbIBULScP6QA6m8xam wgWbkvN41GVWqPafPV32kPBvKwSc+M1e+JR7g3/xPZE7TCELcfYi4yXEHZZI3Pbh oQIhAP/UsgJbsfH1GFv8Y8qGl5l/kmwwkwHhuKvEC87Yur9FAiEA/GlQv3ZfaXnT lcCFT0aL02O0RDiRYyMUG/JAZQJs6CUCIQCHO5SZYIUwxIGK5mCNxxXOAzyQSiD7 hqkKywf+4FvfDQIhALa0TLyqJFom0t7c4iIGAIRc8UlIYQSPiajI64+x9775AiEA 0v4fgSK/Rq059zW1753JjuB6aR0Uh+3RqJII4dUR1Wg= -----END RSA PRIVATE KEY-----`) TestCertPEMBlock = []byte(`-----BEGIN CERTIFICATE----- MIIBYzCCAQ+gAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD bzAeFw0xMzEyMTkyMDU1NDNaFw0yMzEyMTcyMDU1NDNaMBIxEDAOBgNVBAoTB0Fj bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAPw+niki17X2qALE2A2AzE1q5dvK 9CI4OduRtT9IgbFLC6psqAT21NA+QbY17nWSSpyP65zkMkwKXrbDzstwLPkCAwEA AaNUMFIwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud EwEB/wQFMAMBAf8wGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMAsGCSqGSIb3 DQEBBQNBAFqiVI+Km2XPSO+pxITaPvhmuzg+XG3l1+2di3gL+HlDobocjBqRctRU YySO32W07acjGJmCHUKpCJuq9X8hpmk= -----END CERTIFICATE-----`) ) ubuntu-push-0.2.1+14.04.20140423.1/testing/helpers.go0000644000015301777760000000575712325724711022235 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package testing contains helpers for testing. package testing import ( "fmt" "os" "path/filepath" "runtime" "strings" "sync" "launchpad.net/ubuntu-push/logger" ) type captureHelper struct { outputFunc func(int, string) error lock sync.Mutex logEvents []string logEventCb func(string) } func (h *captureHelper) Output(calldepth int, s string) error { err := h.outputFunc(calldepth+2, s) if err == nil { h.lock.Lock() defer h.lock.Unlock() if h.logEventCb != nil { h.logEventCb(s) } h.logEvents = append(h.logEvents, s+"\n") } return err } func (h *captureHelper) captured() string { h.lock.Lock() defer h.lock.Unlock() return strings.Join(h.logEvents, "") } func (h *captureHelper) reset() { h.lock.Lock() defer h.lock.Unlock() h.logEvents = nil } func (h *captureHelper) setLogEventCb(cb func(string)) { h.lock.Lock() defer h.lock.Unlock() h.logEventCb = cb } // TestLogger implements logger.Logger using gocheck.C and supporting // capturing log strings. type TestLogger struct { logger.Logger helper *captureHelper } // NewTestLogger can be used in tests instead of // NewSimpleLogger(FromMinimalLogger). func NewTestLogger(minLog logger.MinimalLogger, level string) *TestLogger { h := &captureHelper{outputFunc: minLog.Output} log := &TestLogger{ Logger: logger.NewSimpleLoggerFromMinimalLogger(h, level), helper: h, } return log } // Captured returns accumulated log events. func (tlog *TestLogger) Captured() string { return tlog.helper.captured() } // Reset resets accumulated log events. func (tlog *TestLogger) ResetCapture() { tlog.helper.reset() } // SetLogEventCb sets a callback invoked for log events. func (tlog *TestLogger) SetLogEventCb(cb func(string)) { tlog.helper.setLogEventCb(cb) } // SourceRelative produces a path relative to the source code, makes // sense only for tests when the code is available on disk. func SourceRelative(relativePath string) string { _, file, _, ok := runtime.Caller(1) if !ok { panic("failed to get source filename using Caller()") } dir := filepath.Dir(file) root := os.Getenv("UBUNTU_PUSH_TEST_RESOURCES_ROOT") if root != "" { const sep = "launchpad.net/ubuntu-push/" idx := strings.LastIndex(dir, sep) if idx == -1 { panic(fmt.Errorf("Unable to find %s in %#v", sep, dir)) } idx += len(sep) dir = filepath.Join(root, dir[idx:]) } return filepath.Join(dir, relativePath) } ubuntu-push-0.2.1+14.04.20140423.1/testing/condition/0000755000015301777760000000000012325725172022216 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/testing/condition/condition.go0000644000015301777760000000613112325724711024532 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package condition implements a strategy family for use in testing. package condition import ( "fmt" "strings" "sync" ) type Interface interface { OK() bool String() string } // Work is a simple boolean condition; either it works all the time // (when true), or it fails all the time (when false). func Work(wk bool) work { return work(wk) } type work bool func (c work) OK() bool { if c { return true } else { return false } } func (c work) String() string { if c { return "Always Working." } else { return "Never Working." } } var _ Interface = work(false) // Fail2Work fails for the first n times its OK() method is checked, // and then mysteriously starts working. func Fail2Work(left int32) *fail2Work { c := new(fail2Work) c.Left = left return c } type fail2Work struct { Left int32 lock sync.RWMutex } func (c *fail2Work) OK() bool { c.lock.Lock() defer c.lock.Unlock() if c.Left > 0 { c.Left-- return false } else { return true } } func (c *fail2Work) String() string { c.lock.RLock() defer c.lock.RUnlock() if c.Left > 0 { return fmt.Sprintf("Still Broken, %d to go.", c.Left) } else { return "Working." } } var _ Interface = &fail2Work{} // Not builds a condition that negates the one passed in. func Not(sub Interface) *not { return ¬{sub} } type not struct{ sub Interface } func (c *not) OK() bool { return !c.sub.OK() } func (c *not) String() string { return fmt.Sprintf("Not %s", c.sub) } var _ Interface = ¬{} type _iter struct { cond Interface remaining int } func (i _iter) String() string { return fmt.Sprintf("%d of %s", i.remaining, i.cond) } type chain struct { subs []*_iter lock sync.RWMutex } func (c *chain) OK() bool { var sub *_iter c.lock.Lock() defer c.lock.Unlock() for _, sub = range c.subs { if sub.remaining > 0 { sub.remaining-- return sub.cond.OK() } } return sub.cond.OK() } func (c *chain) String() string { ss := make([]string, len(c.subs)) c.lock.RLock() defer c.lock.RUnlock() for i, sub := range c.subs { ss[i] = sub.String() } return strings.Join(ss, " Then: ") } var _ Interface = new(chain) // Chain(n1, cond1, n2, cond2, ...) returns cond1.OK() the first n1 // times OK() is called, cond2.OK() the following n2 times, etc. func Chain(args ...interface{}) *chain { iters := make([]*_iter, 0, len(args)/2) for len(args) > 1 { rem := args[0].(int) sub := args[1].(Interface) iters = append(iters, &_iter{sub, rem}) args = args[2:] } return &chain{subs: iters} } ubuntu-push-0.2.1+14.04.20140423.1/testing/condition/condition_test.go0000644000015301777760000000521012325724711025566 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package condition import ( "testing" "launchpad.net/gocheck" // not into . because we have our own Not ) // hook up gocheck func Test(t *testing.T) { gocheck.TestingT(t) } type CondSuite struct{} var _ = gocheck.Suite(&CondSuite{}) func (s *CondSuite) TestConditionWorkTrue(c *gocheck.C) { cond := Work(true) c.Check(cond.OK(), gocheck.Equals, true) c.Check(cond.String(), gocheck.Equals, "Always Working.") } func (s *CondSuite) TestConditionWorkFalse(c *gocheck.C) { cond := Work(false) c.Check(cond.OK(), gocheck.Equals, false) c.Check(cond.String(), gocheck.Equals, "Never Working.") } func (s *CondSuite) TestConditionFail2Work(c *gocheck.C) { cond := Fail2Work(2) c.Check(cond.String(), gocheck.Equals, "Still Broken, 2 to go.") c.Check(cond.OK(), gocheck.Equals, false) c.Check(cond.String(), gocheck.Equals, "Still Broken, 1 to go.") c.Check(cond.OK(), gocheck.Equals, false) c.Check(cond.String(), gocheck.Equals, "Working.") c.Check(cond.OK(), gocheck.Equals, true) c.Check(cond.String(), gocheck.Equals, "Working.") c.Check(cond.OK(), gocheck.Equals, true) c.Check(cond.String(), gocheck.Equals, "Working.") } func (s *CondSuite) TestConditionNot(c *gocheck.C) { cond := Not(Fail2Work(1)) c.Check(cond.String(), gocheck.Equals, "Not Still Broken, 1 to go.") c.Check(cond.OK(), gocheck.Equals, true) c.Check(cond.String(), gocheck.Equals, "Not Working.") c.Check(cond.OK(), gocheck.Equals, false) } func (s *CondSuite) TestConditionChain(c *gocheck.C) { cond := Chain(2, Work(true), 3, Work(false), 0, Work(true)) c.Check(cond.String(), gocheck.Equals, "2 of Always Working. Then: 3 of Never Working. Then: 0 of Always Working.") c.Check(cond.OK(), gocheck.Equals, true) c.Check(cond.OK(), gocheck.Equals, true) c.Check(cond.OK(), gocheck.Equals, false) c.Check(cond.OK(), gocheck.Equals, false) c.Check(cond.OK(), gocheck.Equals, false) c.Check(cond.OK(), gocheck.Equals, true) // c.Check(cond.OK(), gocheck.Equals, true) // c.Check(cond.OK(), gocheck.Equals, true) // c.Check(cond.OK(), gocheck.Equals, true) } ubuntu-push-0.2.1+14.04.20140423.1/LICENSE0000644000015301777760000000116512325724711017561 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ ubuntu-push-0.2.1+14.04.20140423.1/COPYING0000644000015301777760000010451312325724711017610 0ustar pbusernogroup00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ubuntu-push-0.2.1+14.04.20140423.1/scripts/0000755000015301777760000000000012325725172020242 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/scripts/check_fmt0000755000015301777760000000076312325724711022117 0ustar pbusernogroup00000000000000#!/bin/bash # Checks that all Go files in the specified project respect gofmt formatting. # Requires GOPATH to be set to a single path, not a list of them. PROJECT=${1:?missing project} PROBLEMS= for pkg in $(go list ${PROJECT}/...) ; do NONCOMPLIANT=$(gofmt -l ${GOPATH}/src/${pkg}/*.go) if [ -n "${NONCOMPLIANT}" ]; then echo pkg $pkg has some gofmt non-compliant files: echo ${NONCOMPLIANT}|xargs -d ' ' -n1 basename PROBLEMS="y" fi done test -z "${PROBLEMS}" ubuntu-push-0.2.1+14.04.20140423.1/http13client/0000755000015301777760000000000012325725172021075 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/http13client/response.go0000644000015301777760000001503212325724711023261 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // HTTP Response reading and parsing. package http import ( "bufio" "crypto/tls" "errors" "io" "net/http" "net/textproto" "net/url" "strconv" "strings" ) var respExcludeHeader = map[string]bool{ "Content-Length": true, "Transfer-Encoding": true, "Trailer": true, } // Response represents the response from an HTTP request. // type Response struct { Status string // e.g. "200 OK" StatusCode int // e.g. 200 Proto string // e.g. "HTTP/1.0" ProtoMajor int // e.g. 1 ProtoMinor int // e.g. 0 // Header maps header keys to values. If the response had multiple // headers with the same key, they may be concatenated, with comma // delimiters. (Section 4.2 of RFC 2616 requires that multiple headers // be semantically equivalent to a comma-delimited sequence.) Values // duplicated by other fields in this struct (e.g., ContentLength) are // omitted from Header. // // Keys in the map are canonicalized (see CanonicalHeaderKey). Header http.Header // Body represents the response body. // // The http Client and Transport guarantee that Body is always // non-nil, even on responses without a body or responses with // a zero-lengthed body. // // The Body is automatically dechunked if the server replied // with a "chunked" Transfer-Encoding. Body io.ReadCloser // ContentLength records the length of the associated content. The // value -1 indicates that the length is unknown. Unless Request.Method // is "HEAD", values >= 0 indicate that the given number of bytes may // be read from Body. ContentLength int64 // Contains transfer encodings from outer-most to inner-most. Value is // nil, means that "identity" encoding is used. TransferEncoding []string // Close records whether the header directed that the connection be // closed after reading Body. The value is advice for clients: neither // ReadResponse nor Response.Write ever closes a connection. Close bool // Trailer maps trailer keys to values, in the same // format as the header. Trailer http.Header // The Request that was sent to obtain this Response. // Request's Body is nil (having already been consumed). // This is only populated for Client requests. Request *Request // TLS contains information about the TLS connection on which the // response was received. It is nil for unencrypted responses. // The pointer is shared between responses and should not be // modified. TLS *tls.ConnectionState } // Cookies parses and returns the cookies set in the Set-Cookie headers. func (r *Response) Cookies() []*http.Cookie { return readSetCookies(r.Header) } var ErrNoLocation = errors.New("http: no Location header in response") // Location returns the URL of the response's "Location" header, // if present. Relative redirects are resolved relative to // the Response's Request. ErrNoLocation is returned if no // Location header is present. func (r *Response) Location() (*url.URL, error) { lv := r.Header.Get("Location") if lv == "" { return nil, ErrNoLocation } if r.Request != nil && r.Request.URL != nil { return r.Request.URL.Parse(lv) } return url.Parse(lv) } // ReadResponse reads and returns an HTTP response from r. // The req parameter optionally specifies the Request that corresponds // to this Response. If nil, a GET request is assumed. // Clients must call resp.Body.Close when finished reading resp.Body. // After that call, clients can inspect resp.Trailer to find key/value // pairs included in the response trailer. func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) { tp := textproto.NewReader(r) resp := &Response{ Request: req, } // Parse the first line of the response. line, err := tp.ReadLine() if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return nil, err } f := strings.SplitN(line, " ", 3) if len(f) < 2 { return nil, &badStringError{"malformed HTTP response", line} } reasonPhrase := "" if len(f) > 2 { reasonPhrase = f[2] } resp.Status = f[1] + " " + reasonPhrase resp.StatusCode, err = strconv.Atoi(f[1]) if err != nil { return nil, &badStringError{"malformed HTTP status code", f[1]} } resp.Proto = f[0] var ok bool if resp.ProtoMajor, resp.ProtoMinor, ok = ParseHTTPVersion(resp.Proto); !ok { return nil, &badStringError{"malformed HTTP version", resp.Proto} } // Parse the response headers. mimeHeader, err := tp.ReadMIMEHeader() if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return nil, err } resp.Header = http.Header(mimeHeader) fixPragmaCacheControl(resp.Header) err = readTransfer(resp, r) if err != nil { return nil, err } return resp, nil } // RFC2616: Should treat // Pragma: no-cache // like // Cache-Control: no-cache func fixPragmaCacheControl(header http.Header) { if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" { if _, presentcc := header["Cache-Control"]; !presentcc { header["Cache-Control"] = []string{"no-cache"} } } } // ProtoAtLeast reports whether the HTTP protocol used // in the response is at least major.minor. func (r *Response) ProtoAtLeast(major, minor int) bool { return r.ProtoMajor > major || r.ProtoMajor == major && r.ProtoMinor >= minor } // Writes the response (header, body and trailer) in wire format. This method // consults the following fields of the response: // // StatusCode // ProtoMajor // ProtoMinor // Request.Method // TransferEncoding // Trailer // Body // ContentLength // Header, values for non-canonical keys will have unpredictable behavior // // Body is closed after it is sent. func (r *Response) Write(w io.Writer) error { // Status line text := r.Status if text == "" { text = http.StatusText(r.StatusCode) if text == "" { text = "status code " + strconv.Itoa(r.StatusCode) } } protoMajor, protoMinor := strconv.Itoa(r.ProtoMajor), strconv.Itoa(r.ProtoMinor) statusCode := strconv.Itoa(r.StatusCode) + " " text = strings.TrimPrefix(text, statusCode) io.WriteString(w, "HTTP/"+protoMajor+"."+protoMinor+" "+statusCode+text+"\r\n") // Process Body,ContentLength,Close,Trailer tw, err := newTransferWriter(r) if err != nil { return err } err = tw.WriteHeader(w) if err != nil { return err } // Rest of header err = r.Header.WriteSubset(w, respExcludeHeader) if err != nil { return err } // End-of-header io.WriteString(w, "\r\n") // Write body and trailer err = tw.WriteBody(w) if err != nil { return err } // Success return nil } ubuntu-push-0.2.1+14.04.20140423.1/http13client/Makefile0000644000015301777760000000242412325724711022535 0ustar pbusernogroup00000000000000# help massage and extract from go development 1.3 net/http the client bits grab: cp $(GOCHECKOUT)/src/pkg/net/http/*.go . cp $(GOCHECKOUT)/LICENSE . mkdir -p httptest mkdir -p httputil mkdir -p testdata cp $(GOCHECKOUT)/src/pkg/net/http/httptest/*.go httptest cp $(GOCHECKOUT)/src/pkg/net//http/httputil/*.go httputil cp $(GOCHECKOUT)/src/pkg/net/http/testdata/* testdata hg -R $(GOCHECKOUT) summary > _using.txt full-prepare: patch -R -p5 < _patches/sync_pool.Rpatch patch -p1 < _patches/no_keepalive.patch sed -i -e 's+"net/http"+"launchpad.net/ubuntu-push/http13client"+' *.go httptest/*.go httputil/*.go sed -i -e 's+"net/http/+"launchpad.net/ubuntu-push/http13client/+' *.go httptest/*.go httputil/*.go patch -p1 < _patches/no_serve_test_unsupported_bench.patch prune: rm -rf example_test.go filetransport*.go fs*.go race.go range_test.go \ sniff*.go httptest httputil testdata triv.go jar.go status.go sed -i -e 's+"launchpad.net/ubuntu-push/http13client/+"net/http/+' *.go fix: patch -p1 < _patches/empty_server.patch patch -p1 < _patches/fix_tests.patch patch -p1 < _patches/fix_code.patch patch -p1 < _patches/fix_status.patch patch -p1 < _patches/tweak_doc_go.patch go fmt wipe: rm -rf *.go httptest httputil testdata .PHONY: grab full-prepare prune fix wipe ubuntu-push-0.2.1+14.04.20140423.1/http13client/header_test.go0000644000015301777760000001153012325724711023711 0ustar pbusernogroup00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http import ( "bytes" "net/http" "runtime" "testing" "time" ) var headerWriteTests = []struct { h http.Header exclude map[string]bool expected string }{ {http.Header{}, nil, ""}, { http.Header{ "Content-Type": {"text/html; charset=UTF-8"}, "Content-Length": {"0"}, }, nil, "Content-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\n", }, { http.Header{ "Content-Length": {"0", "1", "2"}, }, nil, "Content-Length: 0\r\nContent-Length: 1\r\nContent-Length: 2\r\n", }, { http.Header{ "Expires": {"-1"}, "Content-Length": {"0"}, "Content-Encoding": {"gzip"}, }, map[string]bool{"Content-Length": true}, "Content-Encoding: gzip\r\nExpires: -1\r\n", }, { http.Header{ "Expires": {"-1"}, "Content-Length": {"0", "1", "2"}, "Content-Encoding": {"gzip"}, }, map[string]bool{"Content-Length": true}, "Content-Encoding: gzip\r\nExpires: -1\r\n", }, { http.Header{ "Expires": {"-1"}, "Content-Length": {"0"}, "Content-Encoding": {"gzip"}, }, map[string]bool{"Content-Length": true, "Expires": true, "Content-Encoding": true}, "", }, { http.Header{ "Nil": nil, "Empty": {}, "Blank": {""}, "Double-Blank": {"", ""}, }, nil, "Blank: \r\nDouble-Blank: \r\nDouble-Blank: \r\n", }, // Tests header sorting when over the insertion sort threshold side: { http.Header{ "k1": {"1a", "1b"}, "k2": {"2a", "2b"}, "k3": {"3a", "3b"}, "k4": {"4a", "4b"}, "k5": {"5a", "5b"}, "k6": {"6a", "6b"}, "k7": {"7a", "7b"}, "k8": {"8a", "8b"}, "k9": {"9a", "9b"}, }, map[string]bool{"k5": true}, "k1: 1a\r\nk1: 1b\r\nk2: 2a\r\nk2: 2b\r\nk3: 3a\r\nk3: 3b\r\n" + "k4: 4a\r\nk4: 4b\r\nk6: 6a\r\nk6: 6b\r\n" + "k7: 7a\r\nk7: 7b\r\nk8: 8a\r\nk8: 8b\r\nk9: 9a\r\nk9: 9b\r\n", }, } func TestHeaderWrite(t *testing.T) { var buf bytes.Buffer for i, test := range headerWriteTests { test.h.WriteSubset(&buf, test.exclude) if buf.String() != test.expected { t.Errorf("#%d:\n got: %q\nwant: %q", i, buf.String(), test.expected) } buf.Reset() } } var parseTimeTests = []struct { h http.Header err bool }{ {http.Header{"Date": {""}}, true}, {http.Header{"Date": {"invalid"}}, true}, {http.Header{"Date": {"1994-11-06T08:49:37Z00:00"}}, true}, {http.Header{"Date": {"Sun, 06 Nov 1994 08:49:37 GMT"}}, false}, {http.Header{"Date": {"Sunday, 06-Nov-94 08:49:37 GMT"}}, false}, {http.Header{"Date": {"Sun Nov 6 08:49:37 1994"}}, false}, } func TestParseTime(t *testing.T) { expect := time.Date(1994, 11, 6, 8, 49, 37, 0, time.UTC) for i, test := range parseTimeTests { d, err := http.ParseTime(test.h.Get("Date")) if err != nil { if !test.err { t.Errorf("#%d:\n got err: %v", i, err) } continue } if test.err { t.Errorf("#%d:\n should err", i) continue } if !expect.Equal(d) { t.Errorf("#%d:\n got: %v\nwant: %v", i, d, expect) } } } type hasTokenTest struct { header string token string want bool } var hasTokenTests = []hasTokenTest{ {"", "", false}, {"", "foo", false}, {"foo", "foo", true}, {"foo ", "foo", true}, {" foo", "foo", true}, {" foo ", "foo", true}, {"foo,bar", "foo", true}, {"bar,foo", "foo", true}, {"bar, foo", "foo", true}, {"bar,foo, baz", "foo", true}, {"bar, foo,baz", "foo", true}, {"bar,foo, baz", "foo", true}, {"bar, foo, baz", "foo", true}, {"FOO", "foo", true}, {"FOO ", "foo", true}, {" FOO", "foo", true}, {" FOO ", "foo", true}, {"FOO,BAR", "foo", true}, {"BAR,FOO", "foo", true}, {"BAR, FOO", "foo", true}, {"BAR,FOO, baz", "foo", true}, {"BAR, FOO,BAZ", "foo", true}, {"BAR,FOO, BAZ", "foo", true}, {"BAR, FOO, BAZ", "foo", true}, {"foobar", "foo", false}, {"barfoo ", "foo", false}, } func TestHasToken(t *testing.T) { for _, tt := range hasTokenTests { if hasToken(tt.header, tt.token) != tt.want { t.Errorf("hasToken(%q, %q) = %v; want %v", tt.header, tt.token, !tt.want, tt.want) } } } var testHeader = http.Header{ "Content-Length": {"123"}, "Content-Type": {"text/plain"}, "Date": {"some date at some time Z"}, "Server": {DefaultUserAgent}, } var buf bytes.Buffer func BenchmarkHeaderWriteSubset(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { buf.Reset() testHeader.WriteSubset(&buf, nil) } } func TestHeaderWriteSubsetAllocs(t *testing.T) { if testing.Short() { t.Skip("skipping alloc test in short mode") } /*if raceEnabled { t.Skip("skipping test under race detector") }*/ if runtime.GOMAXPROCS(0) > 1 { t.Skip("skipping; GOMAXPROCS>1") } n := testing.AllocsPerRun(100, func() { buf.Reset() testHeader.WriteSubset(&buf, nil) }) if n > 0 { t.Errorf("allocs = %g; want 0", n) } } ubuntu-push-0.2.1+14.04.20140423.1/http13client/request.go0000644000015301777760000006044612325724711023124 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // HTTP Request reading and parsing. package http import ( "bufio" "bytes" "crypto/tls" "errors" "fmt" "io" "io/ioutil" "mime" "mime/multipart" "net/http" "net/textproto" "net/url" "strconv" "strings" ) const ( maxValueLength = 4096 maxHeaderLines = 1024 chunkSize = 4 << 10 // 4 KB chunks defaultMaxMemory = 32 << 20 // 32 MB ) // ErrMissingFile is returned by FormFile when the provided file field name // is either not present in the request or not a file field. var ErrMissingFile = errors.New("http: no such file") // HTTP request parsing errors. type ProtocolError struct { ErrorString string } func (err *ProtocolError) Error() string { return err.ErrorString } var ( ErrHeaderTooLong = &ProtocolError{"header too long"} ErrShortBody = &ProtocolError{"entity body too short"} ErrNotSupported = &ProtocolError{"feature not supported"} ErrUnexpectedTrailer = &ProtocolError{"trailer header without chunked transfer encoding"} ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"} ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"} ErrMissingBoundary = &ProtocolError{"no multipart boundary param in Content-Type"} ) type badStringError struct { what string str string } func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } // Headers that Request.Write handles itself and should be skipped. var reqWriteExcludeHeader = map[string]bool{ "Host": true, // not in Header map anyway "User-Agent": true, "Content-Length": true, "Transfer-Encoding": true, "Trailer": true, } // A Request represents an HTTP request received by a server // or to be sent by a client. type Request struct { Method string // GET, POST, PUT, etc. // URL is created from the URI supplied on the Request-Line // as stored in RequestURI. // // For most requests, fields other than Path and RawQuery // will be empty. (See RFC 2616, Section 5.1.2) URL *url.URL // The protocol version for incoming requests. // Outgoing requests always use HTTP/1.1. Proto string // "HTTP/1.0" ProtoMajor int // 1 ProtoMinor int // 0 // A header maps request lines to their values. // If the header says // // accept-encoding: gzip, deflate // Accept-Language: en-us // Connection: keep-alive // // then // // Header = map[string][]string{ // "Accept-Encoding": {"gzip, deflate"}, // "Accept-Language": {"en-us"}, // "Connection": {"keep-alive"}, // } // // HTTP defines that header names are case-insensitive. // The request parser implements this by canonicalizing the // name, making the first character and any characters // following a hyphen uppercase and the rest lowercase. Header http.Header // Body is the request's body. // // For client requests, a nil body means the request has no // body, such as a GET request. The HTTP Client's Transport // is responsible for calling the Close method. // // For server requests, the Request Body is always non-nil // but will return EOF immediately when no body is present. // The Server will close the request body. The ServeHTTP // Handler does not need to. Body io.ReadCloser // ContentLength records the length of the associated content. // The value -1 indicates that the length is unknown. // Values >= 0 indicate that the given number of bytes may // be read from Body. // For outgoing requests, a value of 0 means unknown if Body is not nil. ContentLength int64 // TransferEncoding lists the transfer encodings from outermost to // innermost. An empty list denotes the "identity" encoding. // TransferEncoding can usually be ignored; chunked encoding is // automatically added and removed as necessary when sending and // receiving requests. TransferEncoding []string // Close indicates whether to close the connection after // replying to this request. Close bool // The host on which the URL is sought. // Per RFC 2616, this is either the value of the Host: header // or the host name given in the URL itself. // It may be of the form "host:port". Host string // Form contains the parsed form data, including both the URL // field's query parameters and the POST or PUT form data. // This field is only available after ParseForm is called. // The HTTP client ignores Form and uses Body instead. Form url.Values // PostForm contains the parsed form data from POST or PUT // body parameters. // This field is only available after ParseForm is called. // The HTTP client ignores PostForm and uses Body instead. PostForm url.Values // MultipartForm is the parsed multipart form, including file uploads. // This field is only available after ParseMultipartForm is called. // The HTTP client ignores MultipartForm and uses Body instead. MultipartForm *multipart.Form // Trailer maps trailer keys to values. Like for Header, if the // response has multiple trailer lines with the same key, they will be // concatenated, delimited by commas. // For server requests, Trailer is only populated after Body has been // closed or fully consumed. // Trailer support is only partially complete. Trailer http.Header // RemoteAddr allows HTTP servers and other software to record // the network address that sent the request, usually for // logging. This field is not filled in by ReadRequest and // has no defined format. The HTTP server in this package // sets RemoteAddr to an "IP:port" address before invoking a // handler. // This field is ignored by the HTTP client. RemoteAddr string // RequestURI is the unmodified Request-URI of the // Request-Line (RFC 2616, Section 5.1) as sent by the client // to a server. Usually the URL field should be used instead. // It is an error to set this field in an HTTP client request. RequestURI string // TLS allows HTTP servers and other software to record // information about the TLS connection on which the request // was received. This field is not filled in by ReadRequest. // The HTTP server in this package sets the field for // TLS-enabled connections before invoking a handler; // otherwise it leaves the field nil. // This field is ignored by the HTTP client. TLS *tls.ConnectionState } // ProtoAtLeast reports whether the HTTP protocol used // in the request is at least major.minor. func (r *Request) ProtoAtLeast(major, minor int) bool { return r.ProtoMajor > major || r.ProtoMajor == major && r.ProtoMinor >= minor } // UserAgent returns the client's User-Agent, if sent in the request. func (r *Request) UserAgent() string { return r.Header.Get("User-Agent") } // Cookies parses and returns the HTTP cookies sent with the request. func (r *Request) Cookies() []*http.Cookie { return readCookies(r.Header, "") } var ErrNoCookie = errors.New("http: named cookie not present") // Cookie returns the named cookie provided in the request or // ErrNoCookie if not found. func (r *Request) Cookie(name string) (*http.Cookie, error) { for _, c := range readCookies(r.Header, name) { return c, nil } return nil, ErrNoCookie } // AddCookie adds a cookie to the request. Per RFC 6265 section 5.4, // AddCookie does not attach more than one Cookie header field. That // means all cookies, if any, are written into the same line, // separated by semicolon. func (r *Request) AddCookie(c *http.Cookie) { s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) if c := r.Header.Get("Cookie"); c != "" { r.Header.Set("Cookie", c+"; "+s) } else { r.Header.Set("Cookie", s) } } // Referer returns the referring URL, if sent in the request. // // Referer is misspelled as in the request itself, a mistake from the // earliest days of HTTP. This value can also be fetched from the // Header map as Header["Referer"]; the benefit of making it available // as a method is that the compiler can diagnose programs that use the // alternate (correct English) spelling req.Referrer() but cannot // diagnose programs that use Header["Referrer"]. func (r *Request) Referer() string { return r.Header.Get("Referer") } // multipartByReader is a sentinel value. // Its presence in Request.MultipartForm indicates that parsing of the request // body has been handed off to a MultipartReader instead of ParseMultipartFrom. var multipartByReader = &multipart.Form{ Value: make(map[string][]string), File: make(map[string][]*multipart.FileHeader), } // MultipartReader returns a MIME multipart reader if this is a // multipart/form-data POST request, else returns nil and an error. // Use this function instead of ParseMultipartForm to // process the request body as a stream. func (r *Request) MultipartReader() (*multipart.Reader, error) { if r.MultipartForm == multipartByReader { return nil, errors.New("http: MultipartReader called twice") } if r.MultipartForm != nil { return nil, errors.New("http: multipart handled by ParseMultipartForm") } r.MultipartForm = multipartByReader return r.multipartReader() } func (r *Request) multipartReader() (*multipart.Reader, error) { v := r.Header.Get("Content-Type") if v == "" { return nil, ErrNotMultipart } d, params, err := mime.ParseMediaType(v) if err != nil || d != "multipart/form-data" { return nil, ErrNotMultipart } boundary, ok := params["boundary"] if !ok { return nil, ErrMissingBoundary } return multipart.NewReader(r.Body, boundary), nil } // Return value if nonempty, def otherwise. func valueOrDefault(value, def string) string { if value != "" { return value } return def } // NOTE: This is not intended to reflect the actual Go version being used. // It was changed from "Go http package" to "Go 1.1 package http" at the // time of the Go 1.1 release because the former User-Agent had ended up // on a blacklist for some intrusion detection systems. // See https://codereview.appspot.com/7532043. const defaultUserAgent = "Go 1.1 package http" // Write writes an HTTP/1.1 request -- header and body -- in wire format. // This method consults the following fields of the request: // Host // URL // Method (defaults to "GET") // Header // ContentLength // TransferEncoding // Body // // If Body is present, Content-Length is <= 0 and TransferEncoding // hasn't been set to "identity", Write adds "Transfer-Encoding: // chunked" to the header. Body is closed after it is sent. func (r *Request) Write(w io.Writer) error { return r.write(w, false, nil) } // WriteProxy is like Write but writes the request in the form // expected by an HTTP proxy. In particular, WriteProxy writes the // initial Request-URI line of the request with an absolute URI, per // section 5.1.2 of RFC 2616, including the scheme and host. // In either case, WriteProxy also writes a Host header, using // either r.Host or r.URL.Host. func (r *Request) WriteProxy(w io.Writer) error { return r.write(w, true, nil) } // extraHeaders may be nil func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders http.Header) error { host := req.Host if host == "" { if req.URL == nil { return errors.New("http: Request.Write on Request with no Host or URL set") } host = req.URL.Host } ruri := req.URL.RequestURI() if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" { ruri = req.URL.Scheme + "://" + host + ruri } else if req.Method == "CONNECT" && req.URL.Path == "" { // CONNECT requests normally give just the host and port, not a full URL. ruri = host } // TODO(bradfitz): escape at least newlines in ruri? // Wrap the writer in a bufio Writer if it's not already buffered. // Don't always call NewWriter, as that forces a bytes.Buffer // and other small bufio Writers to have a minimum 4k buffer // size. var bw *bufio.Writer if _, ok := w.(io.ByteWriter); !ok { bw = bufio.NewWriter(w) w = bw } fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri) // Header lines fmt.Fprintf(w, "Host: %s\r\n", host) // Use the defaultUserAgent unless the Header contains one, which // may be blank to not send the header. userAgent := defaultUserAgent if req.Header != nil { if ua := req.Header["User-Agent"]; len(ua) > 0 { userAgent = ua[0] } } if userAgent != "" { fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent) } // Process Body,ContentLength,Close,Trailer tw, err := newTransferWriter(req) if err != nil { return err } err = tw.WriteHeader(w) if err != nil { return err } // TODO: split long values? (If so, should share code with Conn.Write) err = req.Header.WriteSubset(w, reqWriteExcludeHeader) if err != nil { return err } if extraHeaders != nil { err = extraHeaders.Write(w) if err != nil { return err } } io.WriteString(w, "\r\n") // Write body and trailer err = tw.WriteBody(w) if err != nil { return err } if bw != nil { return bw.Flush() } return nil } // ParseHTTPVersion parses a HTTP version string. // "HTTP/1.0" returns (1, 0, true). func ParseHTTPVersion(vers string) (major, minor int, ok bool) { const Big = 1000000 // arbitrary upper bound switch vers { case "HTTP/1.1": return 1, 1, true case "HTTP/1.0": return 1, 0, true } if !strings.HasPrefix(vers, "HTTP/") { return 0, 0, false } dot := strings.Index(vers, ".") if dot < 0 { return 0, 0, false } major, err := strconv.Atoi(vers[5:dot]) if err != nil || major < 0 || major > Big { return 0, 0, false } minor, err = strconv.Atoi(vers[dot+1:]) if err != nil || minor < 0 || minor > Big { return 0, 0, false } return major, minor, true } // NewRequest returns a new Request given a method, URL, and optional body. // // If the provided body is also an io.Closer, the returned // Request.Body is set to body and will be closed by the Client // methods Do, Post, and PostForm, and Transport.RoundTrip. func NewRequest(method, urlStr string, body io.Reader) (*Request, error) { u, err := url.Parse(urlStr) if err != nil { return nil, err } rc, ok := body.(io.ReadCloser) if !ok && body != nil { rc = ioutil.NopCloser(body) } req := &Request{ Method: method, URL: u, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: make(http.Header), Body: rc, Host: u.Host, } if body != nil { switch v := body.(type) { case *bytes.Buffer: req.ContentLength = int64(v.Len()) case *bytes.Reader: req.ContentLength = int64(v.Len()) case *strings.Reader: req.ContentLength = int64(v.Len()) } } return req, nil } // SetBasicAuth sets the request's Authorization header to use HTTP // Basic Authentication with the provided username and password. // // With HTTP Basic Authentication the provided username and password // are not encrypted. func (r *Request) SetBasicAuth(username, password string) { r.Header.Set("Authorization", "Basic "+basicAuth(username, password)) } // parseRequestLine parses "GET /foo HTTP/1.1" into its three parts. func parseRequestLine(line string) (method, requestURI, proto string, ok bool) { s1 := strings.Index(line, " ") s2 := strings.Index(line[s1+1:], " ") if s1 < 0 || s2 < 0 { return } s2 += s1 + 1 return line[:s1], line[s1+1 : s2], line[s2+1:], true } // TODO(bradfitz): use a sync.Cache when available var textprotoReaderCache = make(chan *textproto.Reader, 4) func newTextprotoReader(br *bufio.Reader) *textproto.Reader { select { case r := <-textprotoReaderCache: r.R = br return r default: return textproto.NewReader(br) } } func putTextprotoReader(r *textproto.Reader) { r.R = nil select { case textprotoReaderCache <- r: default: } } // ReadRequest reads and parses a request from b. func ReadRequest(b *bufio.Reader) (req *Request, err error) { tp := newTextprotoReader(b) req = new(Request) // First line: GET /index.html HTTP/1.0 var s string if s, err = tp.ReadLine(); err != nil { return nil, err } defer func() { putTextprotoReader(tp) if err == io.EOF { err = io.ErrUnexpectedEOF } }() var ok bool req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s) if !ok { return nil, &badStringError{"malformed HTTP request", s} } rawurl := req.RequestURI if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok { return nil, &badStringError{"malformed HTTP version", req.Proto} } // CONNECT requests are used two different ways, and neither uses a full URL: // The standard use is to tunnel HTTPS through an HTTP proxy. // It looks like "CONNECT www.google.com:443 HTTP/1.1", and the parameter is // just the authority section of a URL. This information should go in req.URL.Host. // // The net/rpc package also uses CONNECT, but there the parameter is a path // that starts with a slash. It can be parsed with the regular URL parser, // and the path will end up in req.URL.Path, where it needs to be in order for // RPC to work. justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/") if justAuthority { rawurl = "http://" + rawurl } if req.URL, err = url.ParseRequestURI(rawurl); err != nil { return nil, err } if justAuthority { // Strip the bogus "http://" back off. req.URL.Scheme = "" } // Subsequent lines: Key: value. mimeHeader, err := tp.ReadMIMEHeader() if err != nil { return nil, err } req.Header = http.Header(mimeHeader) // RFC2616: Must treat // GET /index.html HTTP/1.1 // Host: www.google.com // and // GET http://www.google.com/index.html HTTP/1.1 // Host: doesntmatter // the same. In the second case, any Host line is ignored. req.Host = req.URL.Host if req.Host == "" { req.Host = req.Header.Get("Host") } delete(req.Header, "Host") fixPragmaCacheControl(req.Header) // TODO: Parse specific header values: // Accept // Accept-Encoding // Accept-Language // Authorization // Cache-Control // Connection // Date // Expect // From // If-Match // If-Modified-Since // If-None-Match // If-Range // If-Unmodified-Since // Max-Forwards // Proxy-Authorization // Referer [sic] // TE (transfer-codings) // Trailer // Transfer-Encoding // Upgrade // User-Agent // Via // Warning err = readTransfer(req, b) if err != nil { return nil, err } return req, nil } // MaxBytesReader is similar to io.LimitReader but is intended for // limiting the size of incoming request bodies. In contrast to // io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a // non-EOF error for a Read beyond the limit, and Closes the // underlying reader when its Close method is called. // // MaxBytesReader prevents clients from accidentally or maliciously // sending a large request and wasting server resources. func MaxBytesReader(w http.ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser { return &maxBytesReader{w: w, r: r, n: n} } type maxBytesReader struct { w http.ResponseWriter r io.ReadCloser // underlying reader n int64 // max bytes remaining stopped bool } func (l *maxBytesReader) Read(p []byte) (n int, err error) { if l.n <= 0 { if !l.stopped { l.stopped = true } return 0, errors.New("http: request body too large") } if int64(len(p)) > l.n { p = p[:l.n] } n, err = l.r.Read(p) l.n -= int64(n) return } func (l *maxBytesReader) Close() error { return l.r.Close() } func copyValues(dst, src url.Values) { for k, vs := range src { for _, value := range vs { dst.Add(k, value) } } } func parsePostForm(r *Request) (vs url.Values, err error) { if r.Body == nil { err = errors.New("missing form body") return } ct := r.Header.Get("Content-Type") // RFC 2616, section 7.2.1 - empty type // SHOULD be treated as application/octet-stream if ct == "" { ct = "application/octet-stream" } ct, _, err = mime.ParseMediaType(ct) switch { case ct == "application/x-www-form-urlencoded": var reader io.Reader = r.Body maxFormSize := int64(1<<63 - 1) if _, ok := r.Body.(*maxBytesReader); !ok { maxFormSize = int64(10 << 20) // 10 MB is a lot of text. reader = io.LimitReader(r.Body, maxFormSize+1) } b, e := ioutil.ReadAll(reader) if e != nil { if err == nil { err = e } break } if int64(len(b)) > maxFormSize { err = errors.New("http: POST too large") return } vs, e = url.ParseQuery(string(b)) if err == nil { err = e } case ct == "multipart/form-data": // handled by ParseMultipartForm (which is calling us, or should be) // TODO(bradfitz): there are too many possible // orders to call too many functions here. // Clean this up and write more tests. // request_test.go contains the start of this, // in TestParseMultipartFormOrder and others. } return } // ParseForm parses the raw query from the URL and updates r.Form. // // For POST or PUT requests, it also parses the request body as a form and // put the results into both r.PostForm and r.Form. // POST and PUT body parameters take precedence over URL query string values // in r.Form. // // If the request Body's size has not already been limited by MaxBytesReader, // the size is capped at 10MB. // // ParseMultipartForm calls ParseForm automatically. // It is idempotent. func (r *Request) ParseForm() error { var err error if r.PostForm == nil { if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" { r.PostForm, err = parsePostForm(r) } if r.PostForm == nil { r.PostForm = make(url.Values) } } if r.Form == nil { if len(r.PostForm) > 0 { r.Form = make(url.Values) copyValues(r.Form, r.PostForm) } var newValues url.Values if r.URL != nil { var e error newValues, e = url.ParseQuery(r.URL.RawQuery) if err == nil { err = e } } if newValues == nil { newValues = make(url.Values) } if r.Form == nil { r.Form = newValues } else { copyValues(r.Form, newValues) } } return err } // ParseMultipartForm parses a request body as multipart/form-data. // The whole request body is parsed and up to a total of maxMemory bytes of // its file parts are stored in memory, with the remainder stored on // disk in temporary files. // ParseMultipartForm calls ParseForm if necessary. // After one call to ParseMultipartForm, subsequent calls have no effect. func (r *Request) ParseMultipartForm(maxMemory int64) error { if r.MultipartForm == multipartByReader { return errors.New("http: multipart handled by MultipartReader") } if r.Form == nil { err := r.ParseForm() if err != nil { return err } } if r.MultipartForm != nil { return nil } mr, err := r.multipartReader() if err == ErrNotMultipart { return nil } else if err != nil { return err } f, err := mr.ReadForm(maxMemory) if err != nil { return err } for k, v := range f.Value { r.Form[k] = append(r.Form[k], v...) } r.MultipartForm = f return nil } // FormValue returns the first value for the named component of the query. // POST and PUT body parameters take precedence over URL query string values. // FormValue calls ParseMultipartForm and ParseForm if necessary. // To access multiple values of the same key use ParseForm. func (r *Request) FormValue(key string) string { if r.Form == nil { r.ParseMultipartForm(defaultMaxMemory) } if vs := r.Form[key]; len(vs) > 0 { return vs[0] } return "" } // PostFormValue returns the first value for the named component of the POST // or PUT request body. URL query parameters are ignored. // PostFormValue calls ParseMultipartForm and ParseForm if necessary. func (r *Request) PostFormValue(key string) string { if r.PostForm == nil { r.ParseMultipartForm(defaultMaxMemory) } if vs := r.PostForm[key]; len(vs) > 0 { return vs[0] } return "" } // FormFile returns the first file for the provided form key. // FormFile calls ParseMultipartForm and ParseForm if necessary. func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) { if r.MultipartForm == multipartByReader { return nil, nil, errors.New("http: multipart handled by MultipartReader") } if r.MultipartForm == nil { err := r.ParseMultipartForm(defaultMaxMemory) if err != nil { return nil, nil, err } } if r.MultipartForm != nil && r.MultipartForm.File != nil { if fhs := r.MultipartForm.File[key]; len(fhs) > 0 { f, err := fhs[0].Open() return f, fhs[0], err } } return nil, nil, ErrMissingFile } func (r *Request) expectsContinue() bool { return hasToken(r.Header.Get("Expect"), "100-continue") } func (r *Request) wantsHttp10KeepAlive() bool { if r.ProtoMajor != 1 || r.ProtoMinor != 0 { return false } return hasToken(r.Header.Get("Connection"), "keep-alive") } func (r *Request) wantsClose() bool { return hasToken(r.Header.Get("Connection"), "close") } ubuntu-push-0.2.1+14.04.20140423.1/http13client/z_last_test.go0000644000015301777760000000524112325724711023757 0ustar pbusernogroup00000000000000// Copyright 2013 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 http_test import ( "launchpad.net/ubuntu-push/http13client" "runtime" "sort" "strings" "testing" "time" ) func interestingGoroutines() (gs []string) { buf := make([]byte, 2<<20) buf = buf[:runtime.Stack(buf, true)] for _, g := range strings.Split(string(buf), "\n\n") { sl := strings.SplitN(g, "\n", 2) if len(sl) != 2 { continue } stack := strings.TrimSpace(sl[1]) if stack == "" || strings.Contains(stack, "created by net.startServer") || strings.Contains(stack, "created by testing.RunTests") || strings.Contains(stack, "closeWriteAndWait") || strings.Contains(stack, "testing.Main(") || // These only show up with GOTRACEBACK=2; Issue 5005 (comment 28) strings.Contains(stack, "runtime.goexit") || strings.Contains(stack, "created by runtime.gc") || strings.Contains(stack, "runtime.MHeap_Scavenger") { continue } gs = append(gs, stack) } sort.Strings(gs) return } // Verify the other tests didn't leave any goroutines running. // This is in a file named z_last_test.go so it sorts at the end. func TestGoroutinesRunning(t *testing.T) { if testing.Short() { t.Skip("not counting goroutines for leakage in -short mode") } gs := interestingGoroutines() n := 0 stackCount := make(map[string]int) for _, g := range gs { stackCount[g]++ n++ } t.Logf("num goroutines = %d", n) if n > 0 { t.Error("Too many goroutines.") for stack, count := range stackCount { t.Logf("%d instances of:\n%s", count, stack) } } } func afterTest(t *testing.T) { http.DefaultTransport.(*http.Transport).CloseIdleConnections() if testing.Short() { return } var bad string badSubstring := map[string]string{ ").readLoop(": "a Transport", ").writeLoop(": "a Transport", "created by net/http/httptest.(*Server).Start": "an httptest.Server", "timeoutHandler": "a TimeoutHandler", "net.(*netFD).connect(": "a timing out dial", ").noteClientGone(": "a closenotifier sender", } var stacks string for i := 0; i < 4; i++ { bad = "" stacks = strings.Join(interestingGoroutines(), "\n\n") for substr, what := range badSubstring { if strings.Contains(stacks, substr) { bad = what } } if bad == "" { return } // Bad stuff found, but goroutines might just still be // shutting down, so give it some time. time.Sleep(250 * time.Millisecond) } t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks) } ubuntu-push-0.2.1+14.04.20140423.1/http13client/responsewrite_test.go0000644000015301777760000000473212325724711025400 0ustar pbusernogroup00000000000000// 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 http import ( "bytes" "io/ioutil" "net/http" "strings" "testing" ) type respWriteTest struct { Resp Response Raw string } func TestResponseWrite(t *testing.T) { respWriteTests := []respWriteTest{ // HTTP/1.0, identity coding; no trailer { Response{ StatusCode: 503, ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), Header: http.Header{}, Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")), ContentLength: 6, }, "HTTP/1.0 503 Service Unavailable\r\n" + "Content-Length: 6\r\n\r\n" + "abcdef", }, // Unchunked response without Content-Length. { Response{ StatusCode: 200, ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), Header: http.Header{}, Body: ioutil.NopCloser(strings.NewReader("abcdef")), ContentLength: -1, }, "HTTP/1.0 200 OK\r\n" + "\r\n" + "abcdef", }, // HTTP/1.1, chunked coding; empty trailer; close { Response{ StatusCode: 200, ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), Header: http.Header{}, Body: ioutil.NopCloser(strings.NewReader("abcdef")), ContentLength: 6, TransferEncoding: []string{"chunked"}, Close: true, }, "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + "6\r\nabcdef\r\n0\r\n\r\n", }, // Header value with a newline character (Issue 914). // Also tests removal of leading and trailing whitespace. { Response{ StatusCode: 204, ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), Header: http.Header{ "Foo": []string{" Bar\nBaz "}, }, Body: nil, ContentLength: 0, TransferEncoding: []string{"chunked"}, Close: true, }, "HTTP/1.1 204 No Content\r\n" + "Connection: close\r\n" + "Foo: Bar Baz\r\n" + "\r\n", }, } for i := range respWriteTests { tt := &respWriteTests[i] var braw bytes.Buffer err := tt.Resp.Write(&braw) if err != nil { t.Errorf("error writing #%d: %s", i, err) continue } sraw := braw.String() if sraw != tt.Raw { t.Errorf("Test %d, expecting:\n%q\nGot:\n%q\n", i, tt.Raw, sraw) continue } } } ubuntu-push-0.2.1+14.04.20140423.1/http13client/client.go0000644000015301777760000003441312325724711022705 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // HTTP client. See RFC 2616. // // This is the high-level Client interface. // The low-level implementation is in transport.go. package http import ( "encoding/base64" "errors" "fmt" "io" "io/ioutil" "log" "net/http" "net/url" "strings" "sync" "time" ) // A Client is an HTTP client. Its zero value (DefaultClient) is a // usable client that uses DefaultTransport. // // The Client's Transport typically has internal state (cached TCP // connections), so Clients should be reused instead of created as // needed. Clients are safe for concurrent use by multiple goroutines. // // A Client is higher-level than a RoundTripper (such as Transport) // and additionally handles HTTP details such as cookies and // redirects. type Client struct { // Transport specifies the mechanism by which individual // HTTP requests are made. // If nil, DefaultTransport is used. Transport RoundTripper // CheckRedirect specifies the policy for handling redirects. // If CheckRedirect is not nil, the client calls it before // following an HTTP redirect. The arguments req and via are // the upcoming request and the requests made already, oldest // first. If CheckRedirect returns an error, the Client's Get // method returns both the previous Response and // CheckRedirect's error (wrapped in a url.Error) instead of // issuing the Request req. // // If CheckRedirect is nil, the Client uses its default policy, // which is to stop after 10 consecutive requests. CheckRedirect func(req *Request, via []*Request) error // Jar specifies the cookie jar. // If Jar is nil, cookies are not sent in requests and ignored // in responses. Jar http.CookieJar // Timeout specifies a time limit for requests made by this // Client. The timeout includes connection time, any // redirects, and reading the response body. The timer remains // running after Get, Head, Post, or Do return and will // interrupt reading of the Response.Body. // // A Timeout of zero means no timeout. // // The Client's Transport must support the CancelRequest // method or Client will return errors when attempting to make // a request with Get, Head, Post, or Do. Client's default // Transport (DefaultTransport) supports CancelRequest. Timeout time.Duration } // DefaultClient is the default Client and is used by Get, Head, and Post. var DefaultClient = &Client{} // RoundTripper is an interface representing the ability to execute a // single HTTP transaction, obtaining the Response for a given Request. // // A RoundTripper must be safe for concurrent use by multiple // goroutines. type RoundTripper interface { // RoundTrip executes a single HTTP transaction, returning // the Response for the request req. RoundTrip should not // attempt to interpret the response. In particular, // RoundTrip must return err == nil if it obtained a response, // regardless of the response's HTTP status code. A non-nil // err should be reserved for failure to obtain a response. // Similarly, RoundTrip should not attempt to handle // higher-level protocol details such as redirects, // authentication, or cookies. // // RoundTrip should not modify the request, except for // consuming and closing the Body. The request's URL and // Header fields are guaranteed to be initialized. RoundTrip(*Request) (*Response, error) } // Given a string of the form "host", "host:port", or "[ipv6::address]:port", // return true if the string includes a port. func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } // Used in Send to implement io.ReadCloser by bundling together the // bufio.Reader through which we read the response, and the underlying // network connection. type readClose struct { io.Reader io.Closer } func (c *Client) send(req *Request) (*Response, error) { if c.Jar != nil { for _, cookie := range c.Jar.Cookies(req.URL) { req.AddCookie(cookie) } } resp, err := send(req, c.transport()) if err != nil { return nil, err } if c.Jar != nil { if rc := resp.Cookies(); len(rc) > 0 { c.Jar.SetCookies(req.URL, rc) } } return resp, err } // Do sends an HTTP request and returns an HTTP response, following // policy (e.g. redirects, cookies, auth) as configured on the client. // // An error is returned if caused by client policy (such as // CheckRedirect), or if there was an HTTP protocol error. // A non-2xx response doesn't cause an error. // // When err is nil, resp always contains a non-nil resp.Body. // // Callers should close resp.Body when done reading from it. If // resp.Body is not closed, the Client's underlying RoundTripper // (typically Transport) may not be able to re-use a persistent TCP // connection to the server for a subsequent "keep-alive" request. // // Generally Get, Post, or PostForm will be used instead of Do. func (c *Client) Do(req *Request) (resp *Response, err error) { if req.Method == "GET" || req.Method == "HEAD" { return c.doFollowingRedirects(req, shouldRedirectGet) } if req.Method == "POST" || req.Method == "PUT" { return c.doFollowingRedirects(req, shouldRedirectPost) } return c.send(req) } func (c *Client) transport() RoundTripper { if c.Transport != nil { return c.Transport } return DefaultTransport } // send issues an HTTP request. // Caller should close resp.Body when done reading from it. func send(req *Request, t RoundTripper) (resp *Response, err error) { if t == nil { return nil, errors.New("http: no Client.Transport or DefaultTransport") } if req.URL == nil { return nil, errors.New("http: nil Request.URL") } if req.RequestURI != "" { return nil, errors.New("http: Request.RequestURI can't be set in client requests.") } // Most the callers of send (Get, Post, et al) don't need // Headers, leaving it uninitialized. We guarantee to the // Transport that this has been initialized, though. if req.Header == nil { req.Header = make(http.Header) } if u := req.URL.User; u != nil { username := u.Username() password, _ := u.Password() req.Header.Set("Authorization", "Basic "+basicAuth(username, password)) } resp, err = t.RoundTrip(req) if err != nil { if resp != nil { log.Printf("RoundTripper returned a response & error; ignoring response") } return nil, err } return resp, nil } // See 2 (end of page 4) http://www.ietf.org/rfc/rfc2617.txt // "To receive authorization, the client sends the userid and password, // separated by a single colon (":") character, within a base64 // encoded string in the credentials." // It is not meant to be urlencoded. func basicAuth(username, password string) string { auth := username + ":" + password return base64.StdEncoding.EncodeToString([]byte(auth)) } // True if the specified HTTP status code is one for which the Get utility should // automatically redirect. func shouldRedirectGet(statusCode int) bool { switch statusCode { case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect: return true } return false } // True if the specified HTTP status code is one for which the Post utility should // automatically redirect. func shouldRedirectPost(statusCode int) bool { switch statusCode { case http.StatusFound, http.StatusSeeOther: return true } return false } // Get issues a GET to the specified URL. If the response is one of the following // redirect codes, Get follows the redirect, up to a maximum of 10 redirects: // // 301 (Moved Permanently) // 302 (Found) // 303 (See Other) // 307 (Temporary Redirect) // // An error is returned if there were too many redirects or if there // was an HTTP protocol error. A non-2xx response doesn't cause an // error. // // When err is nil, resp always contains a non-nil resp.Body. // Caller should close resp.Body when done reading from it. // // Get is a wrapper around DefaultClient.Get. func Get(url string) (resp *Response, err error) { return DefaultClient.Get(url) } // Get issues a GET to the specified URL. If the response is one of the // following redirect codes, Get follows the redirect after calling the // Client's CheckRedirect function. // // 301 (Moved Permanently) // 302 (Found) // 303 (See Other) // 307 (Temporary Redirect) // // An error is returned if the Client's CheckRedirect function fails // or if there was an HTTP protocol error. A non-2xx response doesn't // cause an error. // // When err is nil, resp always contains a non-nil resp.Body. // Caller should close resp.Body when done reading from it. func (c *Client) Get(url string) (resp *Response, err error) { req, err := NewRequest("GET", url, nil) if err != nil { return nil, err } return c.doFollowingRedirects(req, shouldRedirectGet) } func (c *Client) doFollowingRedirects(ireq *Request, shouldRedirect func(int) bool) (resp *Response, err error) { var base *url.URL redirectChecker := c.CheckRedirect if redirectChecker == nil { redirectChecker = defaultCheckRedirect } var via []*Request if ireq.URL == nil { return nil, errors.New("http: nil Request.URL") } var reqmu sync.Mutex // guards req req := ireq var timer *time.Timer if c.Timeout > 0 { type canceler interface { CancelRequest(*Request) } tr, ok := c.transport().(canceler) if !ok { return nil, fmt.Errorf("net/http: Client Transport of type %T doesn't support CancelRequest; Timeout not supported", c.transport()) } timer = time.AfterFunc(c.Timeout, func() { reqmu.Lock() defer reqmu.Unlock() tr.CancelRequest(req) }) } urlStr := "" // next relative or absolute URL to fetch (after first request) redirectFailed := false for redirect := 0; ; redirect++ { if redirect != 0 { nreq := new(Request) nreq.Method = ireq.Method if ireq.Method == "POST" || ireq.Method == "PUT" { nreq.Method = "GET" } nreq.Header = make(http.Header) nreq.URL, err = base.Parse(urlStr) if err != nil { break } if len(via) > 0 { // Add the Referer header. lastReq := via[len(via)-1] if lastReq.URL.Scheme != "https" { nreq.Header.Set("Referer", lastReq.URL.String()) } err = redirectChecker(nreq, via) if err != nil { redirectFailed = true break } } reqmu.Lock() req = nreq reqmu.Unlock() } urlStr = req.URL.String() if resp, err = c.send(req); err != nil { break } if shouldRedirect(resp.StatusCode) { // Read the body if small so underlying TCP connection will be re-used. // No need to check for errors: if it fails, Transport won't reuse it anyway. const maxBodySlurpSize = 2 << 10 if resp.ContentLength == -1 || resp.ContentLength <= maxBodySlurpSize { io.CopyN(ioutil.Discard, resp.Body, maxBodySlurpSize) } resp.Body.Close() if urlStr = resp.Header.Get("Location"); urlStr == "" { err = errors.New(fmt.Sprintf("%d response missing Location header", resp.StatusCode)) break } base = req.URL via = append(via, req) continue } if timer != nil { resp.Body = &cancelTimerBody{timer, resp.Body} } return resp, nil } method := ireq.Method urlErr := &url.Error{ Op: method[0:1] + strings.ToLower(method[1:]), URL: urlStr, Err: err, } if redirectFailed { // Special case for Go 1 compatibility: return both the response // and an error if the CheckRedirect function failed. // See http://golang.org/issue/3795 return resp, urlErr } if resp != nil { resp.Body.Close() } return nil, urlErr } func defaultCheckRedirect(req *Request, via []*Request) error { if len(via) >= 10 { return errors.New("stopped after 10 redirects") } return nil } // Post issues a POST to the specified URL. // // Caller should close resp.Body when done reading from it. // // Post is a wrapper around DefaultClient.Post func Post(url string, bodyType string, body io.Reader) (resp *Response, err error) { return DefaultClient.Post(url, bodyType, body) } // Post issues a POST to the specified URL. // // Caller should close resp.Body when done reading from it. // // If the provided body is also an io.Closer, it is closed after the // body is successfully written to the server. func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) { req, err := NewRequest("POST", url, body) if err != nil { return nil, err } req.Header.Set("Content-Type", bodyType) return c.doFollowingRedirects(req, shouldRedirectPost) } // PostForm issues a POST to the specified URL, with data's keys and // values URL-encoded as the request body. // // When err is nil, resp always contains a non-nil resp.Body. // Caller should close resp.Body when done reading from it. // // PostForm is a wrapper around DefaultClient.PostForm func PostForm(url string, data url.Values) (resp *Response, err error) { return DefaultClient.PostForm(url, data) } // PostForm issues a POST to the specified URL, // with data's keys and values urlencoded as the request body. // // When err is nil, resp always contains a non-nil resp.Body. // Caller should close resp.Body when done reading from it. func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) { return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) } // Head issues a HEAD to the specified URL. If the response is one of the // following redirect codes, Head follows the redirect after calling the // Client's CheckRedirect function. // // 301 (Moved Permanently) // 302 (Found) // 303 (See Other) // 307 (Temporary Redirect) // // Head is a wrapper around DefaultClient.Head func Head(url string) (resp *Response, err error) { return DefaultClient.Head(url) } // Head issues a HEAD to the specified URL. If the response is one of the // following redirect codes, Head follows the redirect after calling the // Client's CheckRedirect function. // // 301 (Moved Permanently) // 302 (Found) // 303 (See Other) // 307 (Temporary Redirect) func (c *Client) Head(url string) (resp *Response, err error) { req, err := NewRequest("HEAD", url, nil) if err != nil { return nil, err } return c.doFollowingRedirects(req, shouldRedirectGet) } type cancelTimerBody struct { t *time.Timer rc io.ReadCloser } func (b *cancelTimerBody) Read(p []byte) (n int, err error) { n, err = b.rc.Read(p) if err == io.EOF { b.t.Stop() } return } func (b *cancelTimerBody) Close() error { err := b.rc.Close() b.t.Stop() return err } ubuntu-push-0.2.1+14.04.20140423.1/http13client/client_test.go0000644000015301777760000005445012325724711023747 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Tests for client.go package http_test import ( "bytes" "crypto/tls" "crypto/x509" "encoding/base64" "errors" "fmt" "io" "io/ioutil" . "launchpad.net/ubuntu-push/http13client" "net" "net/http" "net/http/httptest" "net/url" "strconv" "strings" "sync" "testing" "time" ) var robotsTxtHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Last-Modified", "sometime") fmt.Fprintf(w, "User-agent: go\nDisallow: /something/") }) // pedanticReadAll works like ioutil.ReadAll but additionally // verifies that r obeys the documented io.Reader contract. func pedanticReadAll(r io.Reader) (b []byte, err error) { var bufa [64]byte buf := bufa[:] for { n, err := r.Read(buf) if n == 0 && err == nil { return nil, fmt.Errorf("Read: n=0 with err=nil") } b = append(b, buf[:n]...) if err == io.EOF { n, err := r.Read(buf) if n != 0 || err != io.EOF { return nil, fmt.Errorf("Read: n=%d err=%#v after EOF", n, err) } return b, nil } if err != nil { return b, err } } } type chanWriter chan string func (w chanWriter) Write(p []byte) (n int, err error) { w <- string(p) return len(p), nil } func TestClient(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(robotsTxtHandler) defer ts.Close() r, err := Get(ts.URL) var b []byte if err == nil { b, err = pedanticReadAll(r.Body) r.Body.Close() } if err != nil { t.Error(err) } else if s := string(b); !strings.HasPrefix(s, "User-agent:") { t.Errorf("Incorrect page body (did not begin with User-agent): %q", s) } } func TestClientHead(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(robotsTxtHandler) defer ts.Close() r, err := Head(ts.URL) if err != nil { t.Fatal(err) } if _, ok := r.Header["Last-Modified"]; !ok { t.Error("Last-Modified header not found.") } } type recordingTransport struct { req *Request } func (t *recordingTransport) RoundTrip(req *Request) (resp *Response, err error) { t.req = req return nil, errors.New("dummy impl") } func TestGetRequestFormat(t *testing.T) { defer afterTest(t) tr := &recordingTransport{} client := &Client{Transport: tr} url := "http://dummy.faketld/" client.Get(url) // Note: doesn't hit network if tr.req.Method != "GET" { t.Errorf("expected method %q; got %q", "GET", tr.req.Method) } if tr.req.URL.String() != url { t.Errorf("expected URL %q; got %q", url, tr.req.URL.String()) } if tr.req.Header == nil { t.Errorf("expected non-nil request Header") } } func TestPostRequestFormat(t *testing.T) { defer afterTest(t) tr := &recordingTransport{} client := &Client{Transport: tr} url := "http://dummy.faketld/" json := `{"key":"value"}` b := strings.NewReader(json) client.Post(url, "application/json", b) // Note: doesn't hit network if tr.req.Method != "POST" { t.Errorf("got method %q, want %q", tr.req.Method, "POST") } if tr.req.URL.String() != url { t.Errorf("got URL %q, want %q", tr.req.URL.String(), url) } if tr.req.Header == nil { t.Fatalf("expected non-nil request Header") } if tr.req.Close { t.Error("got Close true, want false") } if g, e := tr.req.ContentLength, int64(len(json)); g != e { t.Errorf("got ContentLength %d, want %d", g, e) } } func TestPostFormRequestFormat(t *testing.T) { defer afterTest(t) tr := &recordingTransport{} client := &Client{Transport: tr} urlStr := "http://dummy.faketld/" form := make(url.Values) form.Set("foo", "bar") form.Add("foo", "bar2") form.Set("bar", "baz") client.PostForm(urlStr, form) // Note: doesn't hit network if tr.req.Method != "POST" { t.Errorf("got method %q, want %q", tr.req.Method, "POST") } if tr.req.URL.String() != urlStr { t.Errorf("got URL %q, want %q", tr.req.URL.String(), urlStr) } if tr.req.Header == nil { t.Fatalf("expected non-nil request Header") } if g, e := tr.req.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; g != e { t.Errorf("got Content-Type %q, want %q", g, e) } if tr.req.Close { t.Error("got Close true, want false") } // Depending on map iteration, body can be either of these. expectedBody := "foo=bar&foo=bar2&bar=baz" expectedBody1 := "bar=baz&foo=bar&foo=bar2" if g, e := tr.req.ContentLength, int64(len(expectedBody)); g != e { t.Errorf("got ContentLength %d, want %d", g, e) } bodyb, err := ioutil.ReadAll(tr.req.Body) if err != nil { t.Fatalf("ReadAll on req.Body: %v", err) } if g := string(bodyb); g != expectedBody && g != expectedBody1 { t.Errorf("got body %q, want %q or %q", g, expectedBody, expectedBody1) } } func TestClientRedirects(t *testing.T) { defer afterTest(t) var ts *httptest.Server ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { n, _ := strconv.Atoi(r.FormValue("n")) // Test Referer header. (7 is arbitrary position to test at) if n == 7 { if g, e := r.Referer(), ts.URL+"/?n=6"; e != g { t.Errorf("on request ?n=7, expected referer of %q; got %q", e, g) } } if n < 15 { http.Redirect(w, r, fmt.Sprintf("/?n=%d", n+1), http.StatusFound) return } fmt.Fprintf(w, "n=%d", n) })) defer ts.Close() c := &Client{} _, err := c.Get(ts.URL) if e, g := "Get /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g { t.Errorf("with default client Get, expected error %q, got %q", e, g) } // HEAD request should also have the ability to follow redirects. _, err = c.Head(ts.URL) if e, g := "Head /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g { t.Errorf("with default client Head, expected error %q, got %q", e, g) } // Do should also follow redirects. greq, _ := NewRequest("GET", ts.URL, nil) _, err = c.Do(greq) if e, g := "Get /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g { t.Errorf("with default client Do, expected error %q, got %q", e, g) } var checkErr error var lastVia []*Request c = &Client{CheckRedirect: func(_ *Request, via []*Request) error { lastVia = via return checkErr }} res, err := c.Get(ts.URL) if err != nil { t.Fatalf("Get error: %v", err) } res.Body.Close() finalUrl := res.Request.URL.String() if e, g := "", fmt.Sprintf("%v", err); e != g { t.Errorf("with custom client, expected error %q, got %q", e, g) } if !strings.HasSuffix(finalUrl, "/?n=15") { t.Errorf("expected final url to end in /?n=15; got url %q", finalUrl) } if e, g := 15, len(lastVia); e != g { t.Errorf("expected lastVia to have contained %d elements; got %d", e, g) } checkErr = errors.New("no redirects allowed") res, err = c.Get(ts.URL) if urlError, ok := err.(*url.Error); !ok || urlError.Err != checkErr { t.Errorf("with redirects forbidden, expected a *url.Error with our 'no redirects allowed' error inside; got %#v (%q)", err, err) } if res == nil { t.Fatalf("Expected a non-nil Response on CheckRedirect failure (http://golang.org/issue/3795)") } res.Body.Close() if res.Header.Get("Location") == "" { t.Errorf("no Location header in Response") } } func TestPostRedirects(t *testing.T) { defer afterTest(t) var log struct { sync.Mutex bytes.Buffer } var ts *httptest.Server ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Lock() fmt.Fprintf(&log.Buffer, "%s %s ", r.Method, r.RequestURI) log.Unlock() if v := r.URL.Query().Get("code"); v != "" { code, _ := strconv.Atoi(v) if code/100 == 3 { w.Header().Set("Location", ts.URL) } w.WriteHeader(code) } })) defer ts.Close() tests := []struct { suffix string want int // response code }{ {"/", 200}, {"/?code=301", 301}, {"/?code=302", 200}, {"/?code=303", 200}, {"/?code=404", 404}, } for _, tt := range tests { res, err := Post(ts.URL+tt.suffix, "text/plain", strings.NewReader("Some content")) if err != nil { t.Fatal(err) } if res.StatusCode != tt.want { t.Errorf("POST %s: status code = %d; want %d", tt.suffix, res.StatusCode, tt.want) } } log.Lock() got := log.String() log.Unlock() want := "POST / POST /?code=301 POST /?code=302 GET / POST /?code=303 GET / POST /?code=404 " if got != want { t.Errorf("Log differs.\n Got: %q\nWant: %q", got, want) } } var expectedCookies = []*http.Cookie{ {Name: "ChocolateChip", Value: "tasty"}, {Name: "First", Value: "Hit"}, {Name: "Second", Value: "Hit"}, } var echoCookiesRedirectHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for _, cookie := range r.Cookies() { http.SetCookie(w, cookie) } if r.URL.Path == "/" { http.SetCookie(w, expectedCookies[1]) http.Redirect(w, r, "/second", http.StatusMovedPermanently) } else { http.SetCookie(w, expectedCookies[2]) w.Write([]byte("hello")) } }) func TestClientSendsCookieFromJar(t *testing.T) { tr := &recordingTransport{} client := &Client{Transport: tr} client.Jar = &TestJar{perURL: make(map[string][]*http.Cookie)} us := "http://dummy.faketld/" u, _ := url.Parse(us) client.Jar.SetCookies(u, expectedCookies) client.Get(us) // Note: doesn't hit network matchReturnedCookies(t, expectedCookies, tr.req.Cookies()) client.Head(us) // Note: doesn't hit network matchReturnedCookies(t, expectedCookies, tr.req.Cookies()) client.Post(us, "text/plain", strings.NewReader("body")) // Note: doesn't hit network matchReturnedCookies(t, expectedCookies, tr.req.Cookies()) client.PostForm(us, url.Values{}) // Note: doesn't hit network matchReturnedCookies(t, expectedCookies, tr.req.Cookies()) req, _ := NewRequest("GET", us, nil) client.Do(req) // Note: doesn't hit network matchReturnedCookies(t, expectedCookies, tr.req.Cookies()) req, _ = NewRequest("POST", us, nil) client.Do(req) // Note: doesn't hit network matchReturnedCookies(t, expectedCookies, tr.req.Cookies()) } // Just enough correctness for our redirect tests. Uses the URL.Host as the // scope of all cookies. type TestJar struct { m sync.Mutex perURL map[string][]*http.Cookie } func (j *TestJar) SetCookies(u *url.URL, cookies []*http.Cookie) { j.m.Lock() defer j.m.Unlock() if j.perURL == nil { j.perURL = make(map[string][]*http.Cookie) } j.perURL[u.Host] = cookies } func (j *TestJar) Cookies(u *url.URL) []*http.Cookie { j.m.Lock() defer j.m.Unlock() return j.perURL[u.Host] } func TestRedirectCookiesJar(t *testing.T) { defer afterTest(t) var ts *httptest.Server ts = httptest.NewServer(echoCookiesRedirectHandler) defer ts.Close() c := &Client{ Jar: new(TestJar), } u, _ := url.Parse(ts.URL) c.Jar.SetCookies(u, []*http.Cookie{expectedCookies[0]}) resp, err := c.Get(ts.URL) if err != nil { t.Fatalf("Get: %v", err) } resp.Body.Close() matchReturnedCookies(t, expectedCookies, resp.Cookies()) } func matchReturnedCookies(t *testing.T, expected, given []*http.Cookie) { if len(given) != len(expected) { t.Logf("Received cookies: %v", given) t.Errorf("Expected %d cookies, got %d", len(expected), len(given)) } for _, ec := range expected { foundC := false for _, c := range given { if ec.Name == c.Name && ec.Value == c.Value { foundC = true break } } if !foundC { t.Errorf("Missing cookie %v", ec) } } } func TestJarCalls(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pathSuffix := r.RequestURI[1:] if r.RequestURI == "/nosetcookie" { return // dont set cookies for this path } http.SetCookie(w, &http.Cookie{Name: "name" + pathSuffix, Value: "val" + pathSuffix}) if r.RequestURI == "/" { http.Redirect(w, r, "http://secondhost.fake/secondpath", 302) } })) defer ts.Close() jar := new(RecordingJar) c := &Client{ Jar: jar, Transport: &Transport{ Dial: func(_ string, _ string) (net.Conn, error) { return net.Dial("tcp", ts.Listener.Addr().String()) }, }, } _, err := c.Get("http://firsthost.fake/") if err != nil { t.Fatal(err) } _, err = c.Get("http://firsthost.fake/nosetcookie") if err != nil { t.Fatal(err) } got := jar.log.String() want := `Cookies("http://firsthost.fake/") SetCookie("http://firsthost.fake/", [name=val]) Cookies("http://secondhost.fake/secondpath") SetCookie("http://secondhost.fake/secondpath", [namesecondpath=valsecondpath]) Cookies("http://firsthost.fake/nosetcookie") ` if got != want { t.Errorf("Got Jar calls:\n%s\nWant:\n%s", got, want) } } // RecordingJar keeps a log of calls made to it, without // tracking any cookies. type RecordingJar struct { mu sync.Mutex log bytes.Buffer } func (j *RecordingJar) SetCookies(u *url.URL, cookies []*http.Cookie) { j.logf("SetCookie(%q, %v)\n", u, cookies) } func (j *RecordingJar) Cookies(u *url.URL) []*http.Cookie { j.logf("Cookies(%q)\n", u) return nil } func (j *RecordingJar) logf(format string, args ...interface{}) { j.mu.Lock() defer j.mu.Unlock() fmt.Fprintf(&j.log, format, args...) } func TestStreamingGet(t *testing.T) { defer afterTest(t) say := make(chan string) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.(http.Flusher).Flush() for str := range say { w.Write([]byte(str)) w.(http.Flusher).Flush() } })) defer ts.Close() c := &Client{} res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } var buf [10]byte for _, str := range []string{"i", "am", "also", "known", "as", "comet"} { say <- str n, err := io.ReadFull(res.Body, buf[0:len(str)]) if err != nil { t.Fatalf("ReadFull on %q: %v", str, err) } if n != len(str) { t.Fatalf("Receiving %q, only read %d bytes", str, n) } got := string(buf[0:n]) if got != str { t.Fatalf("Expected %q, got %q", str, got) } } close(say) _, err = io.ReadFull(res.Body, buf[0:1]) if err != io.EOF { t.Fatalf("at end expected EOF, got %v", err) } } type writeCountingConn struct { net.Conn count *int } func (c *writeCountingConn) Write(p []byte) (int, error) { *c.count++ return c.Conn.Write(p) } // TestClientWrites verifies that client requests are buffered and we // don't send a TCP packet per line of the http request + body. func TestClientWrites(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { })) defer ts.Close() writes := 0 dialer := func(netz string, addr string) (net.Conn, error) { c, err := net.Dial(netz, addr) if err == nil { c = &writeCountingConn{c, &writes} } return c, err } c := &Client{Transport: &Transport{Dial: dialer}} _, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } if writes != 1 { t.Errorf("Get request did %d Write calls, want 1", writes) } writes = 0 _, err = c.PostForm(ts.URL, url.Values{"foo": {"bar"}}) if err != nil { t.Fatal(err) } if writes != 1 { t.Errorf("Post request did %d Write calls, want 1", writes) } } func TestClientErrorWithRequestURI(t *testing.T) { defer afterTest(t) req, _ := NewRequest("GET", "http://localhost:1234/", nil) req.RequestURI = "/this/field/is/illegal/and/should/error/" _, err := DefaultClient.Do(req) if err == nil { t.Fatalf("expected an error") } if !strings.Contains(err.Error(), "RequestURI") { t.Errorf("wanted error mentioning RequestURI; got error: %v", err) } } func newTLSTransport(t *testing.T, ts *httptest.Server) *Transport { certs := x509.NewCertPool() for _, c := range ts.TLS.Certificates { roots, err := x509.ParseCertificates(c.Certificate[len(c.Certificate)-1]) if err != nil { t.Fatalf("error parsing server's root cert: %v", err) } for _, root := range roots { certs.AddCert(root) } } return &Transport{ TLSClientConfig: &tls.Config{RootCAs: certs}, } } func TestClientWithCorrectTLSServerName(t *testing.T) { defer afterTest(t) ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.TLS.ServerName != "127.0.0.1" { t.Errorf("expected client to set ServerName 127.0.0.1, got: %q", r.TLS.ServerName) } })) defer ts.Close() c := &Client{Transport: newTLSTransport(t, ts)} if _, err := c.Get(ts.URL); err != nil { t.Fatalf("expected successful TLS connection, got error: %v", err) } } // Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName // when not empty. // // tls.Config.ServerName (non-empty, set to "example.com") takes // precedence over "some-other-host.tld" which previously incorrectly // took precedence. We don't actually connect to (or even resolve) // "some-other-host.tld", though, because of the Transport.Dial hook. // // The httptest.Server has a cert with "example.com" as its name. func TestTransportUsesTLSConfigServerName(t *testing.T) { defer afterTest(t) ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello")) })) defer ts.Close() tr := newTLSTransport(t, ts) tr.TLSClientConfig.ServerName = "example.com" // one of httptest's Server cert names tr.Dial = func(netw, addr string) (net.Conn, error) { return net.Dial(netw, ts.Listener.Addr().String()) } defer tr.CloseIdleConnections() c := &Client{Transport: tr} res, err := c.Get("https://some-other-host.tld/") if err != nil { t.Fatal(err) } res.Body.Close() } func TestResponseSetsTLSConnectionState(t *testing.T) { defer afterTest(t) ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello")) })) defer ts.Close() tr := newTLSTransport(t, ts) tr.TLSClientConfig.CipherSuites = []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA} tr.Dial = func(netw, addr string) (net.Conn, error) { return net.Dial(netw, ts.Listener.Addr().String()) } defer tr.CloseIdleConnections() c := &Client{Transport: tr} res, err := c.Get("https://example.com/") if err != nil { t.Fatal(err) } defer res.Body.Close() if res.TLS == nil { t.Fatal("Response didn't set TLS Connection State.") } if got, want := res.TLS.CipherSuite, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA; got != want { t.Errorf("TLS Cipher Suite = %d; want %d", got, want) } } // Verify Response.ContentLength is populated. http://golang.org/issue/4126 func TestClientHeadContentLength(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if v := r.FormValue("cl"); v != "" { w.Header().Set("Content-Length", v) } })) defer ts.Close() tests := []struct { suffix string want int64 }{ {"/?cl=1234", 1234}, {"/?cl=0", 0}, {"", -1}, } for _, tt := range tests { req, _ := NewRequest("HEAD", ts.URL+tt.suffix, nil) res, err := DefaultClient.Do(req) if err != nil { t.Fatal(err) } if res.ContentLength != tt.want { t.Errorf("Content-Length = %d; want %d", res.ContentLength, tt.want) } bs, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatal(err) } if len(bs) != 0 { t.Errorf("Unexpected content: %q", bs) } } } func TestEmptyPasswordAuth(t *testing.T) { defer afterTest(t) gopher := "gopher" ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if strings.HasPrefix(auth, "Basic ") { encoded := auth[6:] decoded, err := base64.StdEncoding.DecodeString(encoded) if err != nil { t.Fatal(err) } expected := gopher + ":" s := string(decoded) if expected != s { t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected) } } else { t.Errorf("Invalid auth %q", auth) } })) defer ts.Close() c := &Client{} req, err := NewRequest("GET", ts.URL, nil) if err != nil { t.Fatal(err) } req.URL.User = url.User(gopher) resp, err := c.Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() } func TestBasicAuth(t *testing.T) { defer afterTest(t) tr := &recordingTransport{} client := &Client{Transport: tr} url := "http://My%20User:My%20Pass@dummy.faketld/" expected := "My User:My Pass" client.Get(url) if tr.req.Method != "GET" { t.Errorf("got method %q, want %q", tr.req.Method, "GET") } if tr.req.URL.String() != url { t.Errorf("got URL %q, want %q", tr.req.URL.String(), url) } if tr.req.Header == nil { t.Fatalf("expected non-nil request Header") } auth := tr.req.Header.Get("Authorization") if strings.HasPrefix(auth, "Basic ") { encoded := auth[6:] decoded, err := base64.StdEncoding.DecodeString(encoded) if err != nil { t.Fatal(err) } s := string(decoded) if expected != s { t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected) } } else { t.Errorf("Invalid auth %q", auth) } } func TestClientTimeout(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } defer afterTest(t) sawRoot := make(chan bool, 1) sawSlow := make(chan bool, 1) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { sawRoot <- true http.Redirect(w, r, "/slow", http.StatusFound) return } if r.URL.Path == "/slow" { w.Write([]byte("Hello")) w.(http.Flusher).Flush() sawSlow <- true time.Sleep(2 * time.Second) return } })) defer ts.Close() const timeout = 500 * time.Millisecond c := &Client{ Timeout: timeout, } res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } select { case <-sawRoot: // good. default: t.Fatal("handler never got / request") } select { case <-sawSlow: // good. default: t.Fatal("handler never got /slow request") } errc := make(chan error, 1) go func() { _, err := ioutil.ReadAll(res.Body) errc <- err res.Body.Close() }() const failTime = timeout * 2 select { case err := <-errc: if err == nil { t.Error("expected error from ReadAll") } // Expected error. case <-time.After(failTime): t.Errorf("timeout after %v waiting for timeout of %v", failTime, timeout) } } func TestClientRedirectEatsBody(t *testing.T) { defer afterTest(t) saw := make(chan string, 2) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { saw <- r.RemoteAddr if r.URL.Path == "/" { http.Redirect(w, r, "/foo", http.StatusFound) // which includes a body } })) defer ts.Close() res, err := Get(ts.URL) if err != nil { t.Fatal(err) } _, err = ioutil.ReadAll(res.Body) if err != nil { t.Fatal(err) } res.Body.Close() var first string select { case first = <-saw: default: t.Fatal("server didn't see a request") } var second string select { case second = <-saw: default: t.Fatal("server didn't see a second request") } if first != second { t.Fatal("server saw different client ports before & after the redirect") } } ubuntu-push-0.2.1+14.04.20140423.1/http13client/doc.go0000644000015301777760000000305112325724711022166 0ustar pbusernogroup00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. /* Package http contains the client subset of go 1.3 development net/http. Get, Head, Post, and PostForm make HTTP (or HTTPS) requests: resp, err := http.Get("http://example.com/") ... resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf) ... resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}}) The client must close the response body when finished with it: resp, err := http.Get("http://example.com/") if err != nil { // handle error } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) // ... For control over HTTP client headers, redirect policy, and other settings, create a Client: client := &http.Client{ CheckRedirect: redirectPolicyFunc, } resp, err := client.Get("http://example.com") // ... req, err := http.NewRequest("GET", "http://example.com", nil) // ... req.Header.Add("If-None-Match", `W/"wyzzy"`) resp, err := client.Do(req) // ... For control over proxies, TLS configuration, keep-alives, compression, and other settings, create a Transport: tr := &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: pool}, DisableCompression: true, } client := &http.Client{Transport: tr} resp, err := client.Get("https://example.com") Clients and Transports are safe for concurrent use by multiple goroutines and for efficiency should only be created once and re-used. */ package http ubuntu-push-0.2.1+14.04.20140423.1/http13client/_using.txt0000644000015301777760000000017312325724711023121 0ustar pbusernogroup00000000000000parent: 19512:32c32aef2a41 tip test: enable bug385_32 test on amd64p32. branch: default commit: (clean) update: (current) ubuntu-push-0.2.1+14.04.20140423.1/http13client/npn_test.go0000644000015301777760000000554312325724711023263 0ustar pbusernogroup00000000000000// Copyright 2013 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 http_test import ( "bufio" "crypto/tls" "fmt" "io" "io/ioutil" . "launchpad.net/ubuntu-push/http13client" "net/http" "net/http/httptest" "strings" "testing" ) func TestNextProtoUpgrade(t *testing.T) { ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "path=%s,proto=", r.URL.Path) if r.TLS != nil { w.Write([]byte(r.TLS.NegotiatedProtocol)) } if r.RemoteAddr == "" { t.Error("request with no RemoteAddr") } if r.Body == nil { t.Errorf("request with nil Body") } })) ts.TLS = &tls.Config{ NextProtos: []string{"unhandled-proto", "tls-0.9"}, } ts.Config.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){ "tls-0.9": handleTLSProtocol09, } ts.StartTLS() defer ts.Close() tr := newTLSTransport(t, ts) defer tr.CloseIdleConnections() c := &Client{Transport: tr} // Normal request, without NPN. { res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } body, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatal(err) } if want := "path=/,proto="; string(body) != want { t.Errorf("plain request = %q; want %q", body, want) } } // Request to an advertised but unhandled NPN protocol. // Server will hang up. { tr.CloseIdleConnections() tr.TLSClientConfig.NextProtos = []string{"unhandled-proto"} _, err := c.Get(ts.URL) if err == nil { t.Errorf("expected error on unhandled-proto request") } } // Request using the "tls-0.9" protocol, which we register here. // It is HTTP/0.9 over TLS. { tlsConfig := newTLSTransport(t, ts).TLSClientConfig tlsConfig.NextProtos = []string{"tls-0.9"} conn, err := tls.Dial("tcp", ts.Listener.Addr().String(), tlsConfig) if err != nil { t.Fatal(err) } conn.Write([]byte("GET /foo\n")) body, err := ioutil.ReadAll(conn) if err != nil { t.Fatal(err) } if want := "path=/foo,proto=tls-0.9"; string(body) != want { t.Errorf("plain request = %q; want %q", body, want) } } } // handleTLSProtocol09 implements the HTTP/0.9 protocol over TLS, for the // TestNextProtoUpgrade test. func handleTLSProtocol09(srv *http.Server, conn *tls.Conn, h http.Handler) { br := bufio.NewReader(conn) line, err := br.ReadString('\n') if err != nil { return } line = strings.TrimSpace(line) path := strings.TrimPrefix(line, "GET ") if path == line { return } req, _ := http.NewRequest("GET", path, nil) req.Proto = "HTTP/0.9" req.ProtoMajor = 0 req.ProtoMinor = 9 rw := &http09Writer{conn, make(http.Header)} h.ServeHTTP(rw, req) } type http09Writer struct { io.Writer h http.Header } func (w http09Writer) Header() http.Header { return w.h } func (w http09Writer) WriteHeader(int) {} // no headers ubuntu-push-0.2.1+14.04.20140423.1/http13client/transport_test.go0000644000015301777760000014055312325724711024525 0ustar pbusernogroup00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Tests for transport.go package http_test import ( "bufio" "bytes" "compress/gzip" "crypto/rand" "errors" "fmt" "io" "io/ioutil" . "launchpad.net/ubuntu-push/http13client" "log" "net" "net/http" "net/http/httptest" "net/url" "os" "runtime" "strconv" "strings" "sync" "testing" "time" ) // TODO: test 5 pipelined requests with responses: 1) OK, 2) OK, Connection: Close // and then verify that the final 2 responses get errors back. // hostPortHandler writes back the client's "host:port". var hostPortHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.FormValue("close") == "true" { w.Header().Set("Connection", "close") } w.Write([]byte(r.RemoteAddr)) }) // testCloseConn is a net.Conn tracked by a testConnSet. type testCloseConn struct { net.Conn set *testConnSet } func (c *testCloseConn) Close() error { c.set.remove(c) return c.Conn.Close() } // testConnSet tracks a set of TCP connections and whether they've // been closed. type testConnSet struct { t *testing.T closed map[net.Conn]bool list []net.Conn // in order created mutex sync.Mutex } func (tcs *testConnSet) insert(c net.Conn) { tcs.mutex.Lock() defer tcs.mutex.Unlock() tcs.closed[c] = false tcs.list = append(tcs.list, c) } func (tcs *testConnSet) remove(c net.Conn) { tcs.mutex.Lock() defer tcs.mutex.Unlock() tcs.closed[c] = true } // some tests use this to manage raw tcp connections for later inspection func makeTestDial(t *testing.T) (*testConnSet, func(n, addr string) (net.Conn, error)) { connSet := &testConnSet{ t: t, closed: make(map[net.Conn]bool), } dial := func(n, addr string) (net.Conn, error) { c, err := net.Dial(n, addr) if err != nil { return nil, err } tc := &testCloseConn{c, connSet} connSet.insert(tc) return tc, nil } return connSet, dial } func (tcs *testConnSet) check(t *testing.T) { tcs.mutex.Lock() defer tcs.mutex.Unlock() for i, c := range tcs.list { if !tcs.closed[c] { t.Errorf("TCP connection #%d, %p (of %d total) was not closed", i+1, c, len(tcs.list)) } } } // Two subsequent requests and verify their response is the same. // The response from the server is our own IP:port func TestTransportKeepAlives(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(hostPortHandler) defer ts.Close() for _, disableKeepAlive := range []bool{false, true} { tr := &Transport{DisableKeepAlives: disableKeepAlive} defer tr.CloseIdleConnections() c := &Client{Transport: tr} fetch := func(n int) string { res, err := c.Get(ts.URL) if err != nil { t.Fatalf("error in disableKeepAlive=%v, req #%d, GET: %v", disableKeepAlive, n, err) } body, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatalf("error in disableKeepAlive=%v, req #%d, ReadAll: %v", disableKeepAlive, n, err) } return string(body) } body1 := fetch(1) body2 := fetch(2) bodiesDiffer := body1 != body2 if bodiesDiffer != disableKeepAlive { t.Errorf("error in disableKeepAlive=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q", disableKeepAlive, bodiesDiffer, body1, body2) } } } func TestTransportConnectionCloseOnResponse(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(hostPortHandler) defer ts.Close() connSet, testDial := makeTestDial(t) for _, connectionClose := range []bool{false, true} { tr := &Transport{ Dial: testDial, } c := &Client{Transport: tr} fetch := func(n int) string { req := new(Request) var err error req.URL, err = url.Parse(ts.URL + fmt.Sprintf("/?close=%v", connectionClose)) if err != nil { t.Fatalf("URL parse error: %v", err) } req.Method = "GET" req.Proto = "HTTP/1.1" req.ProtoMajor = 1 req.ProtoMinor = 1 res, err := c.Do(req) if err != nil { t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err) } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err) } return string(body) } body1 := fetch(1) body2 := fetch(2) bodiesDiffer := body1 != body2 if bodiesDiffer != connectionClose { t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q", connectionClose, bodiesDiffer, body1, body2) } tr.CloseIdleConnections() } connSet.check(t) } func TestTransportConnectionCloseOnRequest(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(hostPortHandler) defer ts.Close() connSet, testDial := makeTestDial(t) for _, connectionClose := range []bool{false, true} { tr := &Transport{ Dial: testDial, } c := &Client{Transport: tr} fetch := func(n int) string { req := new(Request) var err error req.URL, err = url.Parse(ts.URL) if err != nil { t.Fatalf("URL parse error: %v", err) } req.Method = "GET" req.Proto = "HTTP/1.1" req.ProtoMajor = 1 req.ProtoMinor = 1 req.Close = connectionClose res, err := c.Do(req) if err != nil { t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err) } body, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err) } return string(body) } body1 := fetch(1) body2 := fetch(2) bodiesDiffer := body1 != body2 if bodiesDiffer != connectionClose { t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q", connectionClose, bodiesDiffer, body1, body2) } tr.CloseIdleConnections() } connSet.check(t) } func TestTransportIdleCacheKeys(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(hostPortHandler) defer ts.Close() tr := &Transport{DisableKeepAlives: false} c := &Client{Transport: tr} if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g { t.Errorf("After CloseIdleConnections expected %d idle conn cache keys; got %d", e, g) } resp, err := c.Get(ts.URL) if err != nil { t.Error(err) } ioutil.ReadAll(resp.Body) keys := tr.IdleConnKeysForTesting() if e, g := 1, len(keys); e != g { t.Fatalf("After Get expected %d idle conn cache keys; got %d", e, g) } if e := "|http|" + ts.Listener.Addr().String(); keys[0] != e { t.Errorf("Expected idle cache key %q; got %q", e, keys[0]) } tr.CloseIdleConnections() if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g { t.Errorf("After CloseIdleConnections expected %d idle conn cache keys; got %d", e, g) } } // Tests that the HTTP transport re-uses connections when a client // reads to the end of a response Body without closing it. func TestTransportReadToEndReusesConn(t *testing.T) { defer afterTest(t) const msg = "foobar" var addrSeen map[string]int ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { addrSeen[r.RemoteAddr]++ if r.URL.Path == "/chunked/" { w.WriteHeader(200) w.(http.Flusher).Flush() } else { w.Header().Set("Content-Type", strconv.Itoa(len(msg))) w.WriteHeader(200) } w.Write([]byte(msg)) })) defer ts.Close() buf := make([]byte, len(msg)) for pi, path := range []string{"/content-length/", "/chunked/"} { wantLen := []int{len(msg), -1}[pi] addrSeen = make(map[string]int) for i := 0; i < 3; i++ { res, err := Get(ts.URL + path) if err != nil { t.Errorf("Get %s: %v", path, err) continue } // We want to close this body eventually (before the // defer afterTest at top runs), but not before the // len(addrSeen) check at the bottom of this test, // since Closing this early in the loop would risk // making connections be re-used for the wrong reason. defer res.Body.Close() if res.ContentLength != int64(wantLen) { t.Errorf("%s res.ContentLength = %d; want %d", path, res.ContentLength, wantLen) } n, err := res.Body.Read(buf) if n != len(msg) || err != io.EOF { t.Errorf("%s Read = %v, %v; want %d, EOF", path, n, err, len(msg)) } } if len(addrSeen) != 1 { t.Errorf("for %s, server saw %d distinct client addresses; want 1", path, len(addrSeen)) } } } func TestTransportMaxPerHostIdleConns(t *testing.T) { defer afterTest(t) resch := make(chan string) gotReq := make(chan bool) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotReq <- true msg := <-resch _, err := w.Write([]byte(msg)) if err != nil { t.Fatalf("Write: %v", err) } })) defer ts.Close() maxIdleConns := 2 tr := &Transport{DisableKeepAlives: false, MaxIdleConnsPerHost: maxIdleConns} c := &Client{Transport: tr} // Start 3 outstanding requests and wait for the server to get them. // Their responses will hang until we write to resch, though. donech := make(chan bool) doReq := func() { resp, err := c.Get(ts.URL) if err != nil { t.Error(err) return } if _, err := ioutil.ReadAll(resp.Body); err != nil { t.Errorf("ReadAll: %v", err) return } donech <- true } go doReq() <-gotReq go doReq() <-gotReq go doReq() <-gotReq if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g { t.Fatalf("Before writes, expected %d idle conn cache keys; got %d", e, g) } resch <- "res1" <-donech keys := tr.IdleConnKeysForTesting() if e, g := 1, len(keys); e != g { t.Fatalf("after first response, expected %d idle conn cache keys; got %d", e, g) } cacheKey := "|http|" + ts.Listener.Addr().String() if keys[0] != cacheKey { t.Fatalf("Expected idle cache key %q; got %q", cacheKey, keys[0]) } if e, g := 1, tr.IdleConnCountForTesting(cacheKey); e != g { t.Errorf("after first response, expected %d idle conns; got %d", e, g) } resch <- "res2" <-donech if e, g := 2, tr.IdleConnCountForTesting(cacheKey); e != g { t.Errorf("after second response, expected %d idle conns; got %d", e, g) } resch <- "res3" <-donech if e, g := maxIdleConns, tr.IdleConnCountForTesting(cacheKey); e != g { t.Errorf("after third response, still expected %d idle conns; got %d", e, g) } } func TestTransportServerClosingUnexpectedly(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(hostPortHandler) defer ts.Close() tr := &Transport{} c := &Client{Transport: tr} fetch := func(n, retries int) string { condFatalf := func(format string, arg ...interface{}) { if retries <= 0 { t.Fatalf(format, arg...) } t.Logf("retrying shortly after expected error: "+format, arg...) time.Sleep(time.Second / time.Duration(retries)) } for retries >= 0 { retries-- res, err := c.Get(ts.URL) if err != nil { condFatalf("error in req #%d, GET: %v", n, err) continue } body, err := ioutil.ReadAll(res.Body) if err != nil { condFatalf("error in req #%d, ReadAll: %v", n, err) continue } res.Body.Close() return string(body) } panic("unreachable") } body1 := fetch(1, 0) body2 := fetch(2, 0) ts.CloseClientConnections() // surprise! // This test has an expected race. Sleeping for 25 ms prevents // it on most fast machines, causing the next fetch() call to // succeed quickly. But if we do get errors, fetch() will retry 5 // times with some delays between. time.Sleep(25 * time.Millisecond) body3 := fetch(3, 5) if body1 != body2 { t.Errorf("expected body1 and body2 to be equal") } if body2 == body3 { t.Errorf("expected body2 and body3 to be different") } } // Test for http://golang.org/issue/2616 (appropriate issue number) // This fails pretty reliably with GOMAXPROCS=100 or something high. func TestStressSurpriseServerCloses(t *testing.T) { defer afterTest(t) if testing.Short() { t.Skip("skipping test in short mode") } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Length", "5") w.Header().Set("Content-Type", "text/plain") w.Write([]byte("Hello")) w.(http.Flusher).Flush() conn, buf, _ := w.(http.Hijacker).Hijack() buf.Flush() conn.Close() })) defer ts.Close() tr := &Transport{DisableKeepAlives: false} c := &Client{Transport: tr} // Do a bunch of traffic from different goroutines. Send to activityc // after each request completes, regardless of whether it failed. const ( numClients = 50 reqsPerClient = 250 ) activityc := make(chan bool) for i := 0; i < numClients; i++ { go func() { for i := 0; i < reqsPerClient; i++ { res, err := c.Get(ts.URL) if err == nil { // We expect errors since the server is // hanging up on us after telling us to // send more requests, so we don't // actually care what the error is. // But we want to close the body in cases // where we won the race. res.Body.Close() } activityc <- true } }() } // Make sure all the request come back, one way or another. for i := 0; i < numClients*reqsPerClient; i++ { select { case <-activityc: case <-time.After(5 * time.Second): t.Fatalf("presumed deadlock; no HTTP client activity seen in awhile") } } } // TestTransportHeadResponses verifies that we deal with Content-Lengths // with no bodies properly func TestTransportHeadResponses(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "HEAD" { panic("expected HEAD; got " + r.Method) } w.Header().Set("Content-Length", "123") w.WriteHeader(200) })) defer ts.Close() tr := &Transport{DisableKeepAlives: false} c := &Client{Transport: tr} for i := 0; i < 2; i++ { res, err := c.Head(ts.URL) if err != nil { t.Errorf("error on loop %d: %v", i, err) continue } if e, g := "123", res.Header.Get("Content-Length"); e != g { t.Errorf("loop %d: expected Content-Length header of %q, got %q", i, e, g) } if e, g := int64(123), res.ContentLength; e != g { t.Errorf("loop %d: expected res.ContentLength of %v, got %v", i, e, g) } if all, err := ioutil.ReadAll(res.Body); err != nil { t.Errorf("loop %d: Body ReadAll: %v", i, err) } else if len(all) != 0 { t.Errorf("Bogus body %q", all) } } } // TestTransportHeadChunkedResponse verifies that we ignore chunked transfer-encoding // on responses to HEAD requests. func TestTransportHeadChunkedResponse(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "HEAD" { panic("expected HEAD; got " + r.Method) } w.Header().Set("Transfer-Encoding", "chunked") // client should ignore w.Header().Set("x-client-ipport", r.RemoteAddr) w.WriteHeader(200) })) defer ts.Close() tr := &Transport{DisableKeepAlives: false} c := &Client{Transport: tr} res1, err := c.Head(ts.URL) if err != nil { t.Fatalf("request 1 error: %v", err) } res2, err := c.Head(ts.URL) if err != nil { t.Fatalf("request 2 error: %v", err) } if v1, v2 := res1.Header.Get("x-client-ipport"), res2.Header.Get("x-client-ipport"); v1 != v2 { t.Errorf("ip/ports differed between head requests: %q vs %q", v1, v2) } } var roundTripTests = []struct { accept string expectAccept string compressed bool }{ // Requests with no accept-encoding header use transparent compression {"", "gzip", false}, // Requests with other accept-encoding should pass through unmodified {"foo", "foo", false}, // Requests with accept-encoding == gzip should be passed through {"gzip", "gzip", true}, } // Test that the modification made to the Request by the RoundTripper is cleaned up func TestRoundTripGzip(t *testing.T) { defer afterTest(t) const responseBody = "test response body" ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { accept := req.Header.Get("Accept-Encoding") if expect := req.FormValue("expect_accept"); accept != expect { t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q", req.FormValue("testnum"), accept, expect) } if accept == "gzip" { rw.Header().Set("Content-Encoding", "gzip") gz := gzip.NewWriter(rw) gz.Write([]byte(responseBody)) gz.Close() } else { rw.Header().Set("Content-Encoding", accept) rw.Write([]byte(responseBody)) } })) defer ts.Close() for i, test := range roundTripTests { // Test basic request (no accept-encoding) req, _ := NewRequest("GET", fmt.Sprintf("%s/?testnum=%d&expect_accept=%s", ts.URL, i, test.expectAccept), nil) if test.accept != "" { req.Header.Set("Accept-Encoding", test.accept) } res, err := DefaultTransport.RoundTrip(req) var body []byte if test.compressed { var r *gzip.Reader r, err = gzip.NewReader(res.Body) if err != nil { t.Errorf("%d. gzip NewReader: %v", i, err) continue } body, err = ioutil.ReadAll(r) res.Body.Close() } else { body, err = ioutil.ReadAll(res.Body) } if err != nil { t.Errorf("%d. Error: %q", i, err) continue } if g, e := string(body), responseBody; g != e { t.Errorf("%d. body = %q; want %q", i, g, e) } if g, e := req.Header.Get("Accept-Encoding"), test.accept; g != e { t.Errorf("%d. Accept-Encoding = %q; want %q (it was mutated, in violation of RoundTrip contract)", i, g, e) } if g, e := res.Header.Get("Content-Encoding"), test.accept; g != e { t.Errorf("%d. Content-Encoding = %q; want %q", i, g, e) } } } func TestTransportGzip(t *testing.T) { defer afterTest(t) const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" const nRandBytes = 1024 * 1024 ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if req.Method == "HEAD" { if g := req.Header.Get("Accept-Encoding"); g != "" { t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g) } return } if g, e := req.Header.Get("Accept-Encoding"), "gzip"; g != e { t.Errorf("Accept-Encoding = %q, want %q", g, e) } rw.Header().Set("Content-Encoding", "gzip") var w io.Writer = rw var buf bytes.Buffer if req.FormValue("chunked") == "0" { w = &buf defer io.Copy(rw, &buf) defer func() { rw.Header().Set("Content-Length", strconv.Itoa(buf.Len())) }() } gz := gzip.NewWriter(w) gz.Write([]byte(testString)) if req.FormValue("body") == "large" { io.CopyN(gz, rand.Reader, nRandBytes) } gz.Close() })) defer ts.Close() for _, chunked := range []string{"1", "0"} { c := &Client{Transport: &Transport{}} // First fetch something large, but only read some of it. res, err := c.Get(ts.URL + "/?body=large&chunked=" + chunked) if err != nil { t.Fatalf("large get: %v", err) } buf := make([]byte, len(testString)) n, err := io.ReadFull(res.Body, buf) if err != nil { t.Fatalf("partial read of large response: size=%d, %v", n, err) } if e, g := testString, string(buf); e != g { t.Errorf("partial read got %q, expected %q", g, e) } res.Body.Close() // Read on the body, even though it's closed n, err = res.Body.Read(buf) if n != 0 || err == nil { t.Errorf("expected error post-closed large Read; got = %d, %v", n, err) } // Then something small. res, err = c.Get(ts.URL + "/?chunked=" + chunked) if err != nil { t.Fatal(err) } body, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatal(err) } if g, e := string(body), testString; g != e { t.Fatalf("body = %q; want %q", g, e) } if g, e := res.Header.Get("Content-Encoding"), ""; g != e { t.Fatalf("Content-Encoding = %q; want %q", g, e) } // Read on the body after it's been fully read: n, err = res.Body.Read(buf) if n != 0 || err == nil { t.Errorf("expected Read error after exhausted reads; got %d, %v", n, err) } res.Body.Close() n, err = res.Body.Read(buf) if n != 0 || err == nil { t.Errorf("expected Read error after Close; got %d, %v", n, err) } } // And a HEAD request too, because they're always weird. c := &Client{Transport: &Transport{}} res, err := c.Head(ts.URL) if err != nil { t.Fatalf("Head: %v", err) } if res.StatusCode != 200 { t.Errorf("Head status=%d; want=200", res.StatusCode) } } func TestTransportProxy(t *testing.T) { defer afterTest(t) ch := make(chan string, 1) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ch <- "real server" })) defer ts.Close() proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ch <- "proxy for " + r.URL.String() })) defer proxy.Close() pu, err := url.Parse(proxy.URL) if err != nil { t.Fatal(err) } c := &Client{Transport: &Transport{Proxy: ProxyURL(pu)}} c.Head(ts.URL) got := <-ch want := "proxy for " + ts.URL + "/" if got != want { t.Errorf("want %q, got %q", want, got) } } // TestTransportGzipRecursive sends a gzip quine and checks that the // client gets the same value back. This is more cute than anything, // but checks that we don't recurse forever, and checks that // Content-Encoding is removed. func TestTransportGzipRecursive(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Encoding", "gzip") w.Write(rgz) })) defer ts.Close() c := &Client{Transport: &Transport{}} res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } body, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatal(err) } if !bytes.Equal(body, rgz) { t.Fatalf("Incorrect result from recursive gz:\nhave=%x\nwant=%x", body, rgz) } if g, e := res.Header.Get("Content-Encoding"), ""; g != e { t.Fatalf("Content-Encoding = %q; want %q", g, e) } } // tests that persistent goroutine connections shut down when no longer desired. func TestTransportPersistConnLeak(t *testing.T) { if runtime.GOOS == "plan9" { t.Skip("skipping test; see http://golang.org/issue/7237") } defer afterTest(t) gotReqCh := make(chan bool) unblockCh := make(chan bool) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotReqCh <- true <-unblockCh w.Header().Set("Content-Length", "0") w.WriteHeader(204) })) defer ts.Close() tr := &Transport{} c := &Client{Transport: tr} n0 := runtime.NumGoroutine() const numReq = 25 didReqCh := make(chan bool) for i := 0; i < numReq; i++ { go func() { res, err := c.Get(ts.URL) didReqCh <- true if err != nil { t.Errorf("client fetch error: %v", err) return } res.Body.Close() }() } // Wait for all goroutines to be stuck in the Handler. for i := 0; i < numReq; i++ { <-gotReqCh } nhigh := runtime.NumGoroutine() // Tell all handlers to unblock and reply. for i := 0; i < numReq; i++ { unblockCh <- true } // Wait for all HTTP clients to be done. for i := 0; i < numReq; i++ { <-didReqCh } tr.CloseIdleConnections() time.Sleep(100 * time.Millisecond) runtime.GC() runtime.GC() // even more. nfinal := runtime.NumGoroutine() growth := nfinal - n0 // We expect 0 or 1 extra goroutine, empirically. Allow up to 5. // Previously we were leaking one per numReq. if int(growth) > 5 { t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth) t.Error("too many new goroutines") } } // golang.org/issue/4531: Transport leaks goroutines when // request.ContentLength is explicitly short func TestTransportPersistConnLeakShortBody(t *testing.T) { if runtime.GOOS == "plan9" { t.Skip("skipping test; see http://golang.org/issue/7237") } defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { })) defer ts.Close() tr := &Transport{} c := &Client{Transport: tr} n0 := runtime.NumGoroutine() body := []byte("Hello") for i := 0; i < 20; i++ { req, err := NewRequest("POST", ts.URL, bytes.NewReader(body)) if err != nil { t.Fatal(err) } req.ContentLength = int64(len(body) - 2) // explicitly short _, err = c.Do(req) if err == nil { t.Fatal("Expect an error from writing too long of a body.") } } nhigh := runtime.NumGoroutine() tr.CloseIdleConnections() time.Sleep(400 * time.Millisecond) runtime.GC() nfinal := runtime.NumGoroutine() growth := nfinal - n0 // We expect 0 or 1 extra goroutine, empirically. Allow up to 5. // Previously we were leaking one per numReq. t.Logf("goroutine growth: %d -> %d -> %d (delta: %d)", n0, nhigh, nfinal, growth) if int(growth) > 5 { t.Error("too many new goroutines") } } // This used to crash; http://golang.org/issue/3266 func TestTransportIdleConnCrash(t *testing.T) { defer afterTest(t) tr := &Transport{} c := &Client{Transport: tr} unblockCh := make(chan bool, 1) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-unblockCh tr.CloseIdleConnections() })) defer ts.Close() didreq := make(chan bool) go func() { res, err := c.Get(ts.URL) if err != nil { t.Error(err) } else { res.Body.Close() // returns idle conn } didreq <- true }() unblockCh <- true <-didreq } // Test that the transport doesn't close the TCP connection early, // before the response body has been read. This was a regression // which sadly lacked a triggering test. The large response body made // the old race easier to trigger. func TestIssue3644(t *testing.T) { defer afterTest(t) const numFoos = 5000 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Connection", "close") for i := 0; i < numFoos; i++ { w.Write([]byte("foo ")) } })) defer ts.Close() tr := &Transport{} c := &Client{Transport: tr} res, err := c.Get(ts.URL) if err != nil { t.Fatal(err) } defer res.Body.Close() bs, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatal(err) } if len(bs) != numFoos*len("foo ") { t.Errorf("unexpected response length") } } // Test that a client receives a server's reply, even if the server doesn't read // the entire request body. func TestIssue3595(t *testing.T) { defer afterTest(t) const deniedMsg = "sorry, denied." ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, deniedMsg, http.StatusUnauthorized) })) defer ts.Close() tr := &Transport{} c := &Client{Transport: tr} res, err := c.Post(ts.URL, "application/octet-stream", neverEnding('a')) if err != nil { t.Errorf("Post: %v", err) return } got, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatalf("Body ReadAll: %v", err) } if !strings.Contains(string(got), deniedMsg) { t.Errorf("Known bug: response %q does not contain %q", got, deniedMsg) } } // From http://golang.org/issue/4454 , // "client fails to handle requests with no body and chunked encoding" func TestChunkedNoContent(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNoContent) })) defer ts.Close() for _, closeBody := range []bool{true, false} { c := &Client{Transport: &Transport{}} const n = 4 for i := 1; i <= n; i++ { res, err := c.Get(ts.URL) if err != nil { t.Errorf("closingBody=%v, req %d/%d: %v", closeBody, i, n, err) } else { if closeBody { res.Body.Close() } } } } } func TestTransportConcurrency(t *testing.T) { defer afterTest(t) maxProcs, numReqs := 16, 500 if testing.Short() { maxProcs, numReqs = 4, 50 } defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs)) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%v", r.FormValue("echo")) })) defer ts.Close() var wg sync.WaitGroup wg.Add(numReqs) tr := &Transport{ Dial: func(netw, addr string) (c net.Conn, err error) { // Due to the Transport's "socket late // binding" (see idleConnCh in transport.go), // the numReqs HTTP requests below can finish // with a dial still outstanding. So count // our dials as work too so the leak checker // doesn't complain at us. wg.Add(1) defer wg.Done() return net.Dial(netw, addr) }, } defer tr.CloseIdleConnections() c := &Client{Transport: tr} reqs := make(chan string) defer close(reqs) for i := 0; i < maxProcs*2; i++ { go func() { for req := range reqs { res, err := c.Get(ts.URL + "/?echo=" + req) if err != nil { t.Errorf("error on req %s: %v", req, err) wg.Done() continue } all, err := ioutil.ReadAll(res.Body) if err != nil { t.Errorf("read error on req %s: %v", req, err) wg.Done() continue } if string(all) != req { t.Errorf("body of req %s = %q; want %q", req, all, req) } res.Body.Close() wg.Done() } }() } for i := 0; i < numReqs; i++ { reqs <- fmt.Sprintf("request-%d", i) } wg.Wait() } func TestIssue4191_InfiniteGetTimeout(t *testing.T) { if runtime.GOOS == "plan9" { t.Skip("skipping test; see http://golang.org/issue/7237") } defer afterTest(t) const debug = false mux := http.NewServeMux() mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) { io.Copy(w, neverEnding('a')) }) ts := httptest.NewServer(mux) timeout := 100 * time.Millisecond client := &Client{ Transport: &Transport{ Dial: func(n, addr string) (net.Conn, error) { conn, err := net.Dial(n, addr) if err != nil { return nil, err } conn.SetDeadline(time.Now().Add(timeout)) if debug { conn = NewLoggingConn("client", conn) } return conn, nil }, DisableKeepAlives: true, }, } getFailed := false nRuns := 5 if testing.Short() { nRuns = 1 } for i := 0; i < nRuns; i++ { if debug { println("run", i+1, "of", nRuns) } sres, err := client.Get(ts.URL + "/get") if err != nil { if !getFailed { // Make the timeout longer, once. getFailed = true t.Logf("increasing timeout") i-- timeout *= 10 continue } t.Errorf("Error issuing GET: %v", err) break } _, err = io.Copy(ioutil.Discard, sres.Body) if err == nil { t.Errorf("Unexpected successful copy") break } } if debug { println("tests complete; waiting for handlers to finish") } ts.Close() } func TestIssue4191_InfiniteGetToPutTimeout(t *testing.T) { if runtime.GOOS == "plan9" { t.Skip("skipping test; see http://golang.org/issue/7237") } defer afterTest(t) const debug = false mux := http.NewServeMux() mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) { io.Copy(w, neverEnding('a')) }) mux.HandleFunc("/put", func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() io.Copy(ioutil.Discard, r.Body) }) ts := httptest.NewServer(mux) timeout := 100 * time.Millisecond client := &Client{ Transport: &Transport{ Dial: func(n, addr string) (net.Conn, error) { conn, err := net.Dial(n, addr) if err != nil { return nil, err } conn.SetDeadline(time.Now().Add(timeout)) if debug { conn = NewLoggingConn("client", conn) } return conn, nil }, DisableKeepAlives: true, }, } getFailed := false nRuns := 5 if testing.Short() { nRuns = 1 } for i := 0; i < nRuns; i++ { if debug { println("run", i+1, "of", nRuns) } sres, err := client.Get(ts.URL + "/get") if err != nil { if !getFailed { // Make the timeout longer, once. getFailed = true t.Logf("increasing timeout") i-- timeout *= 10 continue } t.Errorf("Error issuing GET: %v", err) break } req, _ := NewRequest("PUT", ts.URL+"/put", sres.Body) _, err = client.Do(req) if err == nil { sres.Body.Close() t.Errorf("Unexpected successful PUT") break } sres.Body.Close() } if debug { println("tests complete; waiting for handlers to finish") } ts.Close() } func TestTransportResponseHeaderTimeout(t *testing.T) { defer afterTest(t) if testing.Short() { t.Skip("skipping timeout test in -short mode") } mux := http.NewServeMux() mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {}) mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) { time.Sleep(2 * time.Second) }) ts := httptest.NewServer(mux) defer ts.Close() tr := &Transport{ ResponseHeaderTimeout: 500 * time.Millisecond, } defer tr.CloseIdleConnections() c := &Client{Transport: tr} tests := []struct { path string want int wantErr string }{ {path: "/fast", want: 200}, {path: "/slow", wantErr: "timeout awaiting response headers"}, {path: "/fast", want: 200}, } for i, tt := range tests { res, err := c.Get(ts.URL + tt.path) if err != nil { uerr, ok := err.(*url.Error) if !ok { t.Errorf("error is not an url.Error; got: %#v", err) continue } nerr, ok := uerr.Err.(net.Error) if !ok { t.Errorf("error does not satisfy net.Error interface; got: %#v", err) continue } if !nerr.Timeout() { t.Errorf("want timeout error; got: %q", nerr) continue } if strings.Contains(err.Error(), tt.wantErr) { continue } t.Errorf("%d. unexpected error: %v", i, err) continue } if tt.wantErr != "" { t.Errorf("%d. no error. expected error: %v", i, tt.wantErr) continue } if res.StatusCode != tt.want { t.Errorf("%d for path %q status = %d; want %d", i, tt.path, res.StatusCode, tt.want) } } } func TestTransportCancelRequest(t *testing.T) { defer afterTest(t) if testing.Short() { t.Skip("skipping test in -short mode") } unblockc := make(chan bool) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello") w.(http.Flusher).Flush() // send headers and some body <-unblockc })) defer ts.Close() defer close(unblockc) tr := &Transport{} defer tr.CloseIdleConnections() c := &Client{Transport: tr} req, _ := NewRequest("GET", ts.URL, nil) res, err := c.Do(req) if err != nil { t.Fatal(err) } go func() { time.Sleep(1 * time.Second) tr.CancelRequest(req) }() t0 := time.Now() body, err := ioutil.ReadAll(res.Body) d := time.Since(t0) if err == nil { t.Error("expected an error reading the body") } if string(body) != "Hello" { t.Errorf("Body = %q; want Hello", body) } if d < 500*time.Millisecond { t.Errorf("expected ~1 second delay; got %v", d) } // Verify no outstanding requests after readLoop/writeLoop // goroutines shut down. for tries := 3; tries > 0; tries-- { n := tr.NumPendingRequestsForTesting() if n == 0 { break } time.Sleep(100 * time.Millisecond) if tries == 1 { t.Errorf("pending requests = %d; want 0", n) } } } func TestTransportCancelRequestInDial(t *testing.T) { defer afterTest(t) if testing.Short() { t.Skip("skipping test in -short mode") } var logbuf bytes.Buffer eventLog := log.New(&logbuf, "", 0) unblockDial := make(chan bool) defer close(unblockDial) inDial := make(chan bool) tr := &Transport{ Dial: func(network, addr string) (net.Conn, error) { eventLog.Println("dial: blocking") inDial <- true <-unblockDial return nil, errors.New("nope") }, } cl := &Client{Transport: tr} gotres := make(chan bool) req, _ := NewRequest("GET", "http://something.no-network.tld/", nil) go func() { _, err := cl.Do(req) eventLog.Printf("Get = %v", err) gotres <- true }() select { case <-inDial: case <-time.After(5 * time.Second): t.Fatal("timeout; never saw blocking dial") } eventLog.Printf("canceling") tr.CancelRequest(req) select { case <-gotres: case <-time.After(5 * time.Second): panic("hang. events are: " + logbuf.String()) t.Fatal("timeout; cancel didn't work?") } got := logbuf.String() want := `dial: blocking canceling Get = Get http://something.no-network.tld/: net/http: request canceled while waiting for connection ` if got != want { t.Errorf("Got events:\n%s\nWant:\n%s", got, want) } } // golang.org/issue/3672 -- Client can't close HTTP stream // Calling Close on a Response.Body used to just read until EOF. // Now it actually closes the TCP connection. func TestTransportCloseResponseBody(t *testing.T) { defer afterTest(t) writeErr := make(chan error, 1) msg := []byte("young\n") ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for { _, err := w.Write(msg) if err != nil { writeErr <- err return } w.(http.Flusher).Flush() } })) defer ts.Close() tr := &Transport{} defer tr.CloseIdleConnections() c := &Client{Transport: tr} req, _ := NewRequest("GET", ts.URL, nil) defer tr.CancelRequest(req) res, err := c.Do(req) if err != nil { t.Fatal(err) } const repeats = 3 buf := make([]byte, len(msg)*repeats) want := bytes.Repeat(msg, repeats) _, err = io.ReadFull(res.Body, buf) if err != nil { t.Fatal(err) } if !bytes.Equal(buf, want) { t.Fatalf("read %q; want %q", buf, want) } didClose := make(chan error, 1) go func() { didClose <- res.Body.Close() }() select { case err := <-didClose: if err != nil { t.Errorf("Close = %v", err) } case <-time.After(10 * time.Second): t.Fatal("too long waiting for close") } select { case err := <-writeErr: if err == nil { t.Errorf("expected non-nil write error") } case <-time.After(10 * time.Second): t.Fatal("too long waiting for write error") } } type fooProto struct{} func (fooProto) RoundTrip(req *Request) (*Response, error) { res := &Response{ Status: "200 OK", StatusCode: 200, Header: make(http.Header), Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())), } return res, nil } func TestTransportAltProto(t *testing.T) { defer afterTest(t) tr := &Transport{} c := &Client{Transport: tr} tr.RegisterProtocol("foo", fooProto{}) res, err := c.Get("foo://bar.com/path") if err != nil { t.Fatal(err) } bodyb, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatal(err) } body := string(bodyb) if e := "You wanted foo://bar.com/path"; body != e { t.Errorf("got response %q, want %q", body, e) } } func TestTransportNoHost(t *testing.T) { defer afterTest(t) tr := &Transport{} _, err := tr.RoundTrip(&Request{ Header: make(http.Header), URL: &url.URL{ Scheme: "http", }, }) want := "http: no Host in request URL" if got := fmt.Sprint(err); got != want { t.Errorf("error = %v; want %q", err, want) } } func TestTransportSocketLateBinding(t *testing.T) { defer afterTest(t) mux := http.NewServeMux() fooGate := make(chan bool, 1) mux.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("foo-ipport", r.RemoteAddr) w.(http.Flusher).Flush() <-fooGate }) mux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("bar-ipport", r.RemoteAddr) }) ts := httptest.NewServer(mux) defer ts.Close() dialGate := make(chan bool, 1) tr := &Transport{ Dial: func(n, addr string) (net.Conn, error) { <-dialGate return net.Dial(n, addr) }, DisableKeepAlives: false, } defer tr.CloseIdleConnections() c := &Client{ Transport: tr, } dialGate <- true // only allow one dial fooRes, err := c.Get(ts.URL + "/foo") if err != nil { t.Fatal(err) } fooAddr := fooRes.Header.Get("foo-ipport") if fooAddr == "" { t.Fatal("No addr on /foo request") } time.AfterFunc(200*time.Millisecond, func() { // let the foo response finish so we can use its // connection for /bar fooGate <- true io.Copy(ioutil.Discard, fooRes.Body) fooRes.Body.Close() }) barRes, err := c.Get(ts.URL + "/bar") if err != nil { t.Fatal(err) } barAddr := barRes.Header.Get("bar-ipport") if barAddr != fooAddr { t.Fatalf("/foo came from conn %q; /bar came from %q instead", fooAddr, barAddr) } barRes.Body.Close() dialGate <- true } // Issue 2184 func TestTransportReading100Continue(t *testing.T) { defer afterTest(t) const numReqs = 5 reqBody := func(n int) string { return fmt.Sprintf("request body %d", n) } reqID := func(n int) string { return fmt.Sprintf("REQ-ID-%d", n) } send100Response := func(w *io.PipeWriter, r *io.PipeReader) { defer w.Close() defer r.Close() br := bufio.NewReader(r) n := 0 for { n++ req, err := ReadRequest(br) if err == io.EOF { return } if err != nil { t.Error(err) return } slurp, err := ioutil.ReadAll(req.Body) if err != nil { t.Errorf("Server request body slurp: %v", err) return } id := req.Header.Get("Request-Id") resCode := req.Header.Get("X-Want-Response-Code") if resCode == "" { resCode = "100 Continue" if string(slurp) != reqBody(n) { t.Errorf("Server got %q, %v; want %q", slurp, err, reqBody(n)) } } body := fmt.Sprintf("Response number %d", n) v := []byte(strings.Replace(fmt.Sprintf(`HTTP/1.1 %s Date: Thu, 28 Feb 2013 17:55:41 GMT HTTP/1.1 200 OK Content-Type: text/html Echo-Request-Id: %s Content-Length: %d %s`, resCode, id, len(body), body), "\n", "\r\n", -1)) w.Write(v) if id == reqID(numReqs) { return } } } tr := &Transport{ Dial: func(n, addr string) (net.Conn, error) { sr, sw := io.Pipe() // server read/write cr, cw := io.Pipe() // client read/write conn := &rwTestConn{ Reader: cr, Writer: sw, closeFunc: func() error { sw.Close() cw.Close() return nil }, } go send100Response(cw, sr) return conn, nil }, DisableKeepAlives: false, } defer tr.CloseIdleConnections() c := &Client{Transport: tr} testResponse := func(req *Request, name string, wantCode int) { res, err := c.Do(req) if err != nil { t.Fatalf("%s: Do: %v", name, err) } if res.StatusCode != wantCode { t.Fatalf("%s: Response Statuscode=%d; want %d", name, res.StatusCode, wantCode) } if id, idBack := req.Header.Get("Request-Id"), res.Header.Get("Echo-Request-Id"); id != "" && id != idBack { t.Errorf("%s: response id %q != request id %q", name, idBack, id) } _, err = ioutil.ReadAll(res.Body) if err != nil { t.Fatalf("%s: Slurp error: %v", name, err) } } // Few 100 responses, making sure we're not off-by-one. for i := 1; i <= numReqs; i++ { req, _ := NewRequest("POST", "http://dummy.tld/", strings.NewReader(reqBody(i))) req.Header.Set("Request-Id", reqID(i)) testResponse(req, fmt.Sprintf("100, %d/%d", i, numReqs), 200) } // And some other informational 1xx but non-100 responses, to test // we return them but don't re-use the connection. for i := 1; i <= numReqs; i++ { req, _ := NewRequest("POST", "http://other.tld/", strings.NewReader(reqBody(i))) req.Header.Set("X-Want-Response-Code", "123 Sesame Street") testResponse(req, fmt.Sprintf("123, %d/%d", i, numReqs), 123) } } type proxyFromEnvTest struct { req string // URL to fetch; blank means "http://example.com" env string noenv string want string wanterr error } func (t proxyFromEnvTest) String() string { var buf bytes.Buffer if t.env != "" { fmt.Fprintf(&buf, "http_proxy=%q", t.env) } if t.noenv != "" { fmt.Fprintf(&buf, " no_proxy=%q", t.noenv) } req := "http://example.com" if t.req != "" { req = t.req } fmt.Fprintf(&buf, " req=%q", req) return strings.TrimSpace(buf.String()) } var proxyFromEnvTests = []proxyFromEnvTest{ {env: "127.0.0.1:8080", want: "http://127.0.0.1:8080"}, {env: "cache.corp.example.com:1234", want: "http://cache.corp.example.com:1234"}, {env: "cache.corp.example.com", want: "http://cache.corp.example.com"}, {env: "https://cache.corp.example.com", want: "https://cache.corp.example.com"}, {env: "http://127.0.0.1:8080", want: "http://127.0.0.1:8080"}, {env: "https://127.0.0.1:8080", want: "https://127.0.0.1:8080"}, {want: ""}, {noenv: "example.com", req: "http://example.com/", env: "proxy", want: ""}, {noenv: ".example.com", req: "http://example.com/", env: "proxy", want: ""}, {noenv: "ample.com", req: "http://example.com/", env: "proxy", want: "http://proxy"}, {noenv: "example.com", req: "http://foo.example.com/", env: "proxy", want: ""}, {noenv: ".foo.com", req: "http://example.com/", env: "proxy", want: "http://proxy"}, } func TestProxyFromEnvironment(t *testing.T) { ResetProxyEnv() for _, tt := range proxyFromEnvTests { os.Setenv("HTTP_PROXY", tt.env) os.Setenv("NO_PROXY", tt.noenv) ResetCachedEnvironment() reqURL := tt.req if reqURL == "" { reqURL = "http://example.com" } req, _ := NewRequest("GET", reqURL, nil) url, err := ProxyFromEnvironment(req) if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.wanterr); g != e { t.Errorf("%v: got error = %q, want %q", tt, g, e) continue } if got := fmt.Sprintf("%s", url); got != tt.want { t.Errorf("%v: got URL = %q, want %q", tt, url, tt.want) } } } func TestIdleConnChannelLeak(t *testing.T) { var mu sync.Mutex var n int ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mu.Lock() n++ mu.Unlock() })) defer ts.Close() tr := &Transport{ Dial: func(netw, addr string) (net.Conn, error) { return net.Dial(netw, ts.Listener.Addr().String()) }, } defer tr.CloseIdleConnections() c := &Client{Transport: tr} // First, without keep-alives. for _, disableKeep := range []bool{true, false} { tr.DisableKeepAlives = disableKeep for i := 0; i < 5; i++ { _, err := c.Get(fmt.Sprintf("http://foo-host-%d.tld/", i)) if err != nil { t.Fatal(err) } } if got := tr.IdleConnChMapSizeForTesting(); got != 0 { t.Fatalf("ForDisableKeepAlives = %v, map size = %d; want 0", disableKeep, got) } } } // Verify the status quo: that the Client.Post function coerces its // body into a ReadCloser if it's a Closer, and that the Transport // then closes it. func TestTransportClosesRequestBody(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.Copy(ioutil.Discard, r.Body) })) defer ts.Close() tr := &Transport{} defer tr.CloseIdleConnections() cl := &Client{Transport: tr} closes := 0 res, err := cl.Post(ts.URL, "text/plain", countCloseReader{&closes, strings.NewReader("hello")}) if err != nil { t.Fatal(err) } res.Body.Close() if closes != 1 { t.Errorf("closes = %d; want 1", closes) } } func TestTransportTLSHandshakeTimeout(t *testing.T) { defer afterTest(t) if testing.Short() { t.Skip("skipping in short mode") } ln := newLocalListener(t) defer ln.Close() testdonec := make(chan struct{}) defer close(testdonec) go func() { c, err := ln.Accept() if err != nil { t.Error(err) return } <-testdonec c.Close() }() getdonec := make(chan struct{}) go func() { defer close(getdonec) tr := &Transport{ Dial: func(_, _ string) (net.Conn, error) { return net.Dial("tcp", ln.Addr().String()) }, TLSHandshakeTimeout: 250 * time.Millisecond, } cl := &Client{Transport: tr} _, err := cl.Get("https://dummy.tld/") if err == nil { t.Error("expected error") return } ue, ok := err.(*url.Error) if !ok { t.Errorf("expected url.Error; got %#v", err) return } ne, ok := ue.Err.(net.Error) if !ok { t.Errorf("expected net.Error; got %#v", err) return } if !ne.Timeout() { t.Error("expected timeout error; got %v", err) } if !strings.Contains(err.Error(), "handshake timeout") { t.Error("expected 'handshake timeout' in error; got %v", err) } }() select { case <-getdonec: case <-time.After(5 * time.Second): t.Error("test timeout; TLS handshake hung?") } } func newLocalListener(t *testing.T) net.Listener { ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { ln, err = net.Listen("tcp6", "[::1]:0") } if err != nil { t.Fatal(err) } return ln } type countCloseReader struct { n *int io.Reader } func (cr countCloseReader) Close() error { (*cr.n)++ return nil } // rgz is a gzip quine that uncompresses to itself. var rgz = []byte{ 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, 0x76, 0x65, 0x00, 0x92, 0xef, 0xe6, 0xe0, 0x60, 0x00, 0x83, 0xa2, 0xd4, 0xe4, 0xd2, 0xa2, 0xe2, 0xcc, 0xb2, 0x54, 0x06, 0x00, 0x00, 0x17, 0x00, 0xe8, 0xff, 0x92, 0xef, 0xe6, 0xe0, 0x60, 0x00, 0x83, 0xa2, 0xd4, 0xe4, 0xd2, 0xa2, 0xe2, 0xcc, 0xb2, 0x54, 0x06, 0x00, 0x00, 0x17, 0x00, 0xe8, 0xff, 0x42, 0x12, 0x46, 0x16, 0x06, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x42, 0x12, 0x46, 0x16, 0x06, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00, 0x14, 0x00, 0xeb, 0xff, 0x42, 0x12, 0x46, 0x16, 0x06, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00, 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x17, 0x00, 0xe8, 0xff, 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x17, 0x00, 0xe8, 0xff, 0x42, 0x12, 0x46, 0x16, 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08, 0x00, 0xf7, 0xff, 0x3d, 0xb1, 0x20, 0x85, 0xfa, 0x00, 0x00, 0x00, 0x42, 0x12, 0x46, 0x16, 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08, 0x00, 0xf7, 0xff, 0x3d, 0xb1, 0x20, 0x85, 0xfa, 0x00, 0x00, 0x00, 0x3d, 0xb1, 0x20, 0x85, 0xfa, 0x00, 0x00, 0x00, } ubuntu-push-0.2.1+14.04.20140423.1/http13client/transport.go0000644000015301777760000007264112325724711023470 0ustar pbusernogroup00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // HTTP client implementation. See RFC 2616. // // This is the low-level Transport implementation of RoundTripper. // The high-level interface is in client.go. package http import ( "bufio" "compress/gzip" "crypto/tls" "errors" "fmt" "io" "log" "net" "net/http" "net/url" "os" "strings" "sync" "time" ) // DefaultTransport is the default implementation of Transport and is // used by DefaultClient. It establishes network connections as needed // and caches them for reuse by subsequent calls. It uses HTTP proxies // as directed by the $HTTP_PROXY and $NO_PROXY (or $http_proxy and // $no_proxy) environment variables. var DefaultTransport RoundTripper = &Transport{ Proxy: ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, // KeepAlive: 30 * time.Second, }).Dial, TLSHandshakeTimeout: 10 * time.Second, } // DefaultMaxIdleConnsPerHost is the default value of Transport's // MaxIdleConnsPerHost. const DefaultMaxIdleConnsPerHost = 2 // Transport is an implementation of RoundTripper that supports http, // https, and http proxies (for either http or https with CONNECT). // Transport can also cache connections for future re-use. type Transport struct { idleMu sync.Mutex idleConn map[connectMethodKey][]*persistConn idleConnCh map[connectMethodKey]chan *persistConn reqMu sync.Mutex reqCanceler map[*Request]func() altMu sync.RWMutex altProto map[string]RoundTripper // nil or map of URI scheme => RoundTripper // Proxy specifies a function to return a proxy for a given // Request. If the function returns a non-nil error, the // request is aborted with the provided error. // If Proxy is nil or returns a nil *URL, no proxy is used. Proxy func(*Request) (*url.URL, error) // Dial specifies the dial function for creating TCP // connections. // If Dial is nil, net.Dial is used. Dial func(network, addr string) (net.Conn, error) // TLSClientConfig specifies the TLS configuration to use with // tls.Client. If nil, the default configuration is used. TLSClientConfig *tls.Config // TLSHandshakeTimeout specifies the maximum amount of time waiting to // wait for a TLS handshake. Zero means no timeout. TLSHandshakeTimeout time.Duration // DisableKeepAlives, if true, prevents re-use of TCP connections // between different HTTP requests. DisableKeepAlives bool // DisableCompression, if true, prevents the Transport from // requesting compression with an "Accept-Encoding: gzip" // request header when the Request contains no existing // Accept-Encoding value. If the Transport requests gzip on // its own and gets a gzipped response, it's transparently // decoded in the Response.Body. However, if the user // explicitly requested gzip it is not automatically // uncompressed. DisableCompression bool // MaxIdleConnsPerHost, if non-zero, controls the maximum idle // (keep-alive) to keep per-host. If zero, // DefaultMaxIdleConnsPerHost is used. MaxIdleConnsPerHost int // ResponseHeaderTimeout, if non-zero, specifies the amount of // time to wait for a server's response headers after fully // writing the request (including its body, if any). This // time does not include the time to read the response body. ResponseHeaderTimeout time.Duration // TODO: tunable on global max cached connections // TODO: tunable on timeout on cached connections } // ProxyFromEnvironment returns the URL of the proxy to use for a // given request, as indicated by the environment variables // $HTTP_PROXY and $NO_PROXY (or $http_proxy and $no_proxy). // An error is returned if the proxy environment is invalid. // A nil URL and nil error are returned if no proxy is defined in the // environment, or a proxy should not be used for the given request. func ProxyFromEnvironment(req *Request) (*url.URL, error) { proxy := httpProxyEnv.Get() if proxy == "" { return nil, nil } if !useProxy(canonicalAddr(req.URL)) { return nil, nil } proxyURL, err := url.Parse(proxy) if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") { // proxy was bogus. Try prepending "http://" to it and // see if that parses correctly. If not, we fall // through and complain about the original one. if proxyURL, err := url.Parse("http://" + proxy); err == nil { return proxyURL, nil } } if err != nil { return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err) } return proxyURL, nil } // ProxyURL returns a proxy function (for use in a Transport) // that always returns the same URL. func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error) { return func(*Request) (*url.URL, error) { return fixedURL, nil } } // transportRequest is a wrapper around a *Request that adds // optional extra headers to write. type transportRequest struct { *Request // original request, not to be mutated extra http.Header // extra headers to write, or nil } func (tr *transportRequest) extraHeaders() http.Header { if tr.extra == nil { tr.extra = make(http.Header) } return tr.extra } // RoundTrip implements the RoundTripper interface. // // For higher-level HTTP client support (such as handling of cookies // and redirects), see Get, Post, and the Client type. func (t *Transport) RoundTrip(req *Request) (resp *Response, err error) { if req.URL == nil { return nil, errors.New("http: nil Request.URL") } if req.Header == nil { return nil, errors.New("http: nil Request.Header") } if req.URL.Scheme != "http" && req.URL.Scheme != "https" { t.altMu.RLock() var rt RoundTripper if t.altProto != nil { rt = t.altProto[req.URL.Scheme] } t.altMu.RUnlock() if rt == nil { return nil, &badStringError{"unsupported protocol scheme", req.URL.Scheme} } return rt.RoundTrip(req) } if req.URL.Host == "" { return nil, errors.New("http: no Host in request URL") } treq := &transportRequest{Request: req} cm, err := t.connectMethodForRequest(treq) if err != nil { return nil, err } // Get the cached or newly-created connection to either the // host (for http or https), the http proxy, or the http proxy // pre-CONNECTed to https server. In any case, we'll be ready // to send it requests. pconn, err := t.getConn(req, cm) if err != nil { t.setReqCanceler(req, nil) return nil, err } return pconn.roundTrip(treq) } // RegisterProtocol registers a new protocol with scheme. // The Transport will pass requests using the given scheme to rt. // It is rt's responsibility to simulate HTTP request semantics. // // RegisterProtocol can be used by other packages to provide // implementations of protocol schemes like "ftp" or "file". func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper) { if scheme == "http" || scheme == "https" { panic("protocol " + scheme + " already registered") } t.altMu.Lock() defer t.altMu.Unlock() if t.altProto == nil { t.altProto = make(map[string]RoundTripper) } if _, exists := t.altProto[scheme]; exists { panic("protocol " + scheme + " already registered") } t.altProto[scheme] = rt } // CloseIdleConnections closes any connections which were previously // connected from previous requests but are now sitting idle in // a "keep-alive" state. It does not interrupt any connections currently // in use. func (t *Transport) CloseIdleConnections() { t.idleMu.Lock() m := t.idleConn t.idleConn = nil t.idleConnCh = nil t.idleMu.Unlock() if m == nil { return } for _, conns := range m { for _, pconn := range conns { pconn.close() } } } // CancelRequest cancels an in-flight request by closing its // connection. func (t *Transport) CancelRequest(req *Request) { t.reqMu.Lock() cancel := t.reqCanceler[req] t.reqMu.Unlock() if cancel != nil { cancel() } } // // Private implementation past this point. // var ( httpProxyEnv = &envOnce{ names: []string{"HTTP_PROXY", "http_proxy"}, } noProxyEnv = &envOnce{ names: []string{"NO_PROXY", "no_proxy"}, } ) // envOnce looks up an environment variable (optionally by multiple // names) once. It mitigates expensive lookups on some platforms // (e.g. Windows). type envOnce struct { names []string once sync.Once val string } func (e *envOnce) Get() string { e.once.Do(e.init) return e.val } func (e *envOnce) init() { for _, n := range e.names { e.val = os.Getenv(n) if e.val != "" { return } } } // reset is used by tests func (e *envOnce) reset() { e.once = sync.Once{} e.val = "" } func (t *Transport) connectMethodForRequest(treq *transportRequest) (cm connectMethod, err error) { cm.targetScheme = treq.URL.Scheme cm.targetAddr = canonicalAddr(treq.URL) if t.Proxy != nil { cm.proxyURL, err = t.Proxy(treq.Request) } return cm, nil } // proxyAuth returns the Proxy-Authorization header to set // on requests, if applicable. func (cm *connectMethod) proxyAuth() string { if cm.proxyURL == nil { return "" } if u := cm.proxyURL.User; u != nil { username := u.Username() password, _ := u.Password() return "Basic " + basicAuth(username, password) } return "" } // putIdleConn adds pconn to the list of idle persistent connections awaiting // a new request. // If pconn is no longer needed or not in a good state, putIdleConn // returns false. func (t *Transport) putIdleConn(pconn *persistConn) bool { if t.DisableKeepAlives || t.MaxIdleConnsPerHost < 0 { pconn.close() return false } if pconn.isBroken() { return false } key := pconn.cacheKey max := t.MaxIdleConnsPerHost if max == 0 { max = DefaultMaxIdleConnsPerHost } t.idleMu.Lock() waitingDialer := t.idleConnCh[key] select { case waitingDialer <- pconn: // We're done with this pconn and somebody else is // currently waiting for a conn of this type (they're // actively dialing, but this conn is ready // first). Chrome calls this socket late binding. See // https://insouciant.org/tech/connection-management-in-chromium/ t.idleMu.Unlock() return true default: if waitingDialer != nil { // They had populated this, but their dial won // first, so we can clean up this map entry. delete(t.idleConnCh, key) } } if t.idleConn == nil { t.idleConn = make(map[connectMethodKey][]*persistConn) } if len(t.idleConn[key]) >= max { t.idleMu.Unlock() pconn.close() return false } for _, exist := range t.idleConn[key] { if exist == pconn { log.Fatalf("dup idle pconn %p in freelist", pconn) } } t.idleConn[key] = append(t.idleConn[key], pconn) t.idleMu.Unlock() return true } // getIdleConnCh returns a channel to receive and return idle // persistent connection for the given connectMethod. // It may return nil, if persistent connections are not being used. func (t *Transport) getIdleConnCh(cm connectMethod) chan *persistConn { if t.DisableKeepAlives { return nil } key := cm.key() t.idleMu.Lock() defer t.idleMu.Unlock() if t.idleConnCh == nil { t.idleConnCh = make(map[connectMethodKey]chan *persistConn) } ch, ok := t.idleConnCh[key] if !ok { ch = make(chan *persistConn) t.idleConnCh[key] = ch } return ch } func (t *Transport) getIdleConn(cm connectMethod) (pconn *persistConn) { key := cm.key() t.idleMu.Lock() defer t.idleMu.Unlock() if t.idleConn == nil { return nil } for { pconns, ok := t.idleConn[key] if !ok { return nil } if len(pconns) == 1 { pconn = pconns[0] delete(t.idleConn, key) } else { // 2 or more cached connections; pop last // TODO: queue? pconn = pconns[len(pconns)-1] t.idleConn[key] = pconns[:len(pconns)-1] } if !pconn.isBroken() { return } } } func (t *Transport) setReqCanceler(r *Request, fn func()) { t.reqMu.Lock() defer t.reqMu.Unlock() if t.reqCanceler == nil { t.reqCanceler = make(map[*Request]func()) } if fn != nil { t.reqCanceler[r] = fn } else { delete(t.reqCanceler, r) } } func (t *Transport) dial(network, addr string) (c net.Conn, err error) { if t.Dial != nil { return t.Dial(network, addr) } return net.Dial(network, addr) } // getConn dials and creates a new persistConn to the target as // specified in the connectMethod. This includes doing a proxy CONNECT // and/or setting up TLS. If this doesn't return an error, the persistConn // is ready to write requests to. func (t *Transport) getConn(req *Request, cm connectMethod) (*persistConn, error) { if pc := t.getIdleConn(cm); pc != nil { return pc, nil } type dialRes struct { pc *persistConn err error } dialc := make(chan dialRes) handlePendingDial := func() { if v := <-dialc; v.err == nil { t.putIdleConn(v.pc) } } cancelc := make(chan struct{}) t.setReqCanceler(req, func() { close(cancelc) }) go func() { pc, err := t.dialConn(cm) dialc <- dialRes{pc, err} }() idleConnCh := t.getIdleConnCh(cm) select { case v := <-dialc: // Our dial finished. return v.pc, v.err case pc := <-idleConnCh: // Another request finished first and its net.Conn // became available before our dial. Or somebody // else's dial that they didn't use. // But our dial is still going, so give it away // when it finishes: go handlePendingDial() return pc, nil case <-cancelc: go handlePendingDial() return nil, errors.New("net/http: request canceled while waiting for connection") } } func (t *Transport) dialConn(cm connectMethod) (*persistConn, error) { conn, err := t.dial("tcp", cm.addr()) if err != nil { if cm.proxyURL != nil { err = fmt.Errorf("http: error connecting to proxy %s: %v", cm.proxyURL, err) } return nil, err } pa := cm.proxyAuth() pconn := &persistConn{ t: t, cacheKey: cm.key(), conn: conn, reqch: make(chan requestAndChan, 50), writech: make(chan writeRequest, 50), closech: make(chan struct{}), } switch { case cm.proxyURL == nil: // Do nothing. case cm.targetScheme == "http": pconn.isProxy = true if pa != "" { pconn.mutateHeaderFunc = func(h http.Header) { h.Set("Proxy-Authorization", pa) } } case cm.targetScheme == "https": connectReq := &Request{ Method: "CONNECT", URL: &url.URL{Opaque: cm.targetAddr}, Host: cm.targetAddr, Header: make(http.Header), } if pa != "" { connectReq.Header.Set("Proxy-Authorization", pa) } connectReq.Write(conn) // Read response. // Okay to use and discard buffered reader here, because // TLS server will not speak until spoken to. br := bufio.NewReader(conn) resp, err := ReadResponse(br, connectReq) if err != nil { conn.Close() return nil, err } if resp.StatusCode != 200 { f := strings.SplitN(resp.Status, " ", 2) conn.Close() return nil, errors.New(f[1]) } } if cm.targetScheme == "https" { // Initiate TLS and check remote host name against certificate. cfg := t.TLSClientConfig if cfg == nil || cfg.ServerName == "" { host := cm.tlsHost() if cfg == nil { cfg = &tls.Config{ServerName: host} } else { clone := *cfg // shallow clone clone.ServerName = host cfg = &clone } } plainConn := conn tlsConn := tls.Client(plainConn, cfg) errc := make(chan error, 2) var timer *time.Timer // for canceling TLS handshake if d := t.TLSHandshakeTimeout; d != 0 { timer = time.AfterFunc(d, func() { errc <- tlsHandshakeTimeoutError{} }) } go func() { err := tlsConn.Handshake() if timer != nil { timer.Stop() } errc <- err }() if err := <-errc; err != nil { plainConn.Close() return nil, err } if !cfg.InsecureSkipVerify { if err := tlsConn.VerifyHostname(cfg.ServerName); err != nil { plainConn.Close() return nil, err } } cs := tlsConn.ConnectionState() pconn.tlsState = &cs pconn.conn = tlsConn } pconn.br = bufio.NewReader(pconn.conn) pconn.bw = bufio.NewWriter(pconn.conn) go pconn.readLoop() go pconn.writeLoop() return pconn, nil } // useProxy returns true if requests to addr should use a proxy, // according to the NO_PROXY or no_proxy environment variable. // addr is always a canonicalAddr with a host and port. func useProxy(addr string) bool { if len(addr) == 0 { return true } host, _, err := net.SplitHostPort(addr) if err != nil { return false } if host == "localhost" { return false } if ip := net.ParseIP(host); ip != nil { if ip.IsLoopback() { return false } } no_proxy := noProxyEnv.Get() if no_proxy == "*" { return false } addr = strings.ToLower(strings.TrimSpace(addr)) if hasPort(addr) { addr = addr[:strings.LastIndex(addr, ":")] } for _, p := range strings.Split(no_proxy, ",") { p = strings.ToLower(strings.TrimSpace(p)) if len(p) == 0 { continue } if hasPort(p) { p = p[:strings.LastIndex(p, ":")] } if addr == p { return false } if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) { // no_proxy ".foo.com" matches "bar.foo.com" or "foo.com" return false } if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' { // no_proxy "foo.com" matches "bar.foo.com" return false } } return true } // connectMethod is the map key (in its String form) for keeping persistent // TCP connections alive for subsequent HTTP requests. // // A connect method may be of the following types: // // Cache key form Description // ----------------- ------------------------- // |http|foo.com http directly to server, no proxy // |https|foo.com https directly to server, no proxy // http://proxy.com|https|foo.com http to proxy, then CONNECT to foo.com // http://proxy.com|http http to proxy, http to anywhere after that // // Note: no support to https to the proxy yet. // type connectMethod struct { proxyURL *url.URL // nil for no proxy, else full proxy URL targetScheme string // "http" or "https" targetAddr string // Not used if proxy + http targetScheme (4th example in table) } func (cm *connectMethod) key() connectMethodKey { proxyStr := "" targetAddr := cm.targetAddr if cm.proxyURL != nil { proxyStr = cm.proxyURL.String() if cm.targetScheme == "http" { targetAddr = "" } } return connectMethodKey{ proxy: proxyStr, scheme: cm.targetScheme, addr: targetAddr, } } // addr returns the first hop "host:port" to which we need to TCP connect. func (cm *connectMethod) addr() string { if cm.proxyURL != nil { return canonicalAddr(cm.proxyURL) } return cm.targetAddr } // tlsHost returns the host name to match against the peer's // TLS certificate. func (cm *connectMethod) tlsHost() string { h := cm.targetAddr if hasPort(h) { h = h[:strings.LastIndex(h, ":")] } return h } // connectMethodKey is the map key version of connectMethod, with a // stringified proxy URL (or the empty string) instead of a pointer to // a URL. type connectMethodKey struct { proxy, scheme, addr string } func (k connectMethodKey) String() string { // Only used by tests. return fmt.Sprintf("%s|%s|%s", k.proxy, k.scheme, k.addr) } // persistConn wraps a connection, usually a persistent one // (but may be used for non-keep-alive requests as well) type persistConn struct { t *Transport cacheKey connectMethodKey conn net.Conn tlsState *tls.ConnectionState closed bool // whether conn has been closed br *bufio.Reader // from conn bw *bufio.Writer // to conn reqch chan requestAndChan // written by roundTrip; read by readLoop writech chan writeRequest // written by roundTrip; read by writeLoop closech chan struct{} // broadcast close when readLoop (TCP connection) closes isProxy bool lk sync.Mutex // guards following 3 fields numExpectedResponses int broken bool // an error has happened on this connection; marked broken so it's not reused. // mutateHeaderFunc is an optional func to modify extra // headers on each outbound request before it's written. (the // original Request given to RoundTrip is not modified) mutateHeaderFunc func(http.Header) } func (pc *persistConn) isBroken() bool { pc.lk.Lock() b := pc.broken pc.lk.Unlock() return b } func (pc *persistConn) cancelRequest() { pc.conn.Close() } var remoteSideClosedFunc func(error) bool // or nil to use default func remoteSideClosed(err error) bool { if err == io.EOF { return true } if remoteSideClosedFunc != nil { return remoteSideClosedFunc(err) } return false } func (pc *persistConn) readLoop() { defer close(pc.closech) alive := true for alive { pb, err := pc.br.Peek(1) pc.lk.Lock() if pc.numExpectedResponses == 0 { pc.closeLocked() pc.lk.Unlock() if len(pb) > 0 { log.Printf("Unsolicited response received on idle HTTP channel starting with %q; err=%v", string(pb), err) } return } pc.lk.Unlock() rc := <-pc.reqch var resp *Response if err == nil { resp, err = ReadResponse(pc.br, rc.req) if err == nil && resp.StatusCode == 100 { // Skip any 100-continue for now. // TODO(bradfitz): if rc.req had "Expect: 100-continue", // actually block the request body write and signal the // writeLoop now to begin sending it. (Issue 2184) For now we // eat it, since we're never expecting one. resp, err = ReadResponse(pc.br, rc.req) } } if resp != nil { resp.TLS = pc.tlsState } hasBody := resp != nil && rc.req.Method != "HEAD" && resp.ContentLength != 0 if err != nil { pc.close() } else { if rc.addedGzip && hasBody && resp.Header.Get("Content-Encoding") == "gzip" { resp.Header.Del("Content-Encoding") resp.Header.Del("Content-Length") resp.ContentLength = -1 gzReader, zerr := gzip.NewReader(resp.Body) if zerr != nil { pc.close() err = zerr } else { resp.Body = &readerAndCloser{gzReader, resp.Body} } } resp.Body = &bodyEOFSignal{body: resp.Body} } if err != nil || resp.Close || rc.req.Close || resp.StatusCode <= 199 { // Don't do keep-alive on error if either party requested a close // or we get an unexpected informational (1xx) response. // StatusCode 100 is already handled above. alive = false } var waitForBodyRead chan bool if hasBody { waitForBodyRead = make(chan bool, 2) resp.Body.(*bodyEOFSignal).earlyCloseFn = func() error { // Sending false here sets alive to // false and closes the connection // below. waitForBodyRead <- false return nil } resp.Body.(*bodyEOFSignal).fn = func(err error) { alive1 := alive if err != nil { alive1 = false } if alive1 && !pc.t.putIdleConn(pc) { alive1 = false } if !alive1 || pc.isBroken() { pc.close() } waitForBodyRead <- alive1 } } if alive && !hasBody { if !pc.t.putIdleConn(pc) { alive = false } } rc.ch <- responseAndError{resp, err} // Wait for the just-returned response body to be fully consumed // before we race and peek on the underlying bufio reader. if waitForBodyRead != nil { alive = <-waitForBodyRead } pc.t.setReqCanceler(rc.req, nil) if !alive { pc.close() } } } func (pc *persistConn) writeLoop() { for { select { case wr := <-pc.writech: if pc.isBroken() { wr.ch <- errors.New("http: can't write HTTP request on broken connection") continue } err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra) if err == nil { err = pc.bw.Flush() } if err != nil { pc.markBroken() } wr.ch <- err case <-pc.closech: return } } } type responseAndError struct { res *Response err error } type requestAndChan struct { req *Request ch chan responseAndError // did the Transport (as opposed to the client code) add an // Accept-Encoding gzip header? only if it we set it do // we transparently decode the gzip. addedGzip bool } // A writeRequest is sent by the readLoop's goroutine to the // writeLoop's goroutine to write a request while the read loop // concurrently waits on both the write response and the server's // reply. type writeRequest struct { req *transportRequest ch chan<- error } type httpError struct { err string timeout bool } func (e *httpError) Error() string { return e.err } func (e *httpError) Timeout() bool { return e.timeout } func (e *httpError) Temporary() bool { return true } var errTimeout error = &httpError{err: "net/http: timeout awaiting response headers", timeout: true} var errClosed error = &httpError{err: "net/http: transport closed before response was received"} func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) { pc.t.setReqCanceler(req.Request, pc.cancelRequest) pc.lk.Lock() pc.numExpectedResponses++ headerFn := pc.mutateHeaderFunc pc.lk.Unlock() if headerFn != nil { headerFn(req.extraHeaders()) } // Ask for a compressed version if the caller didn't set their // own value for Accept-Encoding. We only attempted to // uncompress the gzip stream if we were the layer that // requested it. requestedGzip := false if !pc.t.DisableCompression && req.Header.Get("Accept-Encoding") == "" && req.Method != "HEAD" { // Request gzip only, not deflate. Deflate is ambiguous and // not as universally supported anyway. // See: http://www.gzip.org/zlib/zlib_faq.html#faq38 // // Note that we don't request this for HEAD requests, // due to a bug in nginx: // http://trac.nginx.org/nginx/ticket/358 // http://golang.org/issue/5522 requestedGzip = true req.extraHeaders().Set("Accept-Encoding", "gzip") } // Write the request concurrently with waiting for a response, // in case the server decides to reply before reading our full // request body. writeErrCh := make(chan error, 1) pc.writech <- writeRequest{req, writeErrCh} resc := make(chan responseAndError, 1) pc.reqch <- requestAndChan{req.Request, resc, requestedGzip} var re responseAndError var pconnDeadCh = pc.closech var failTicker <-chan time.Time var respHeaderTimer <-chan time.Time WaitResponse: for { select { case err := <-writeErrCh: if err != nil { re = responseAndError{nil, err} pc.close() break WaitResponse } if d := pc.t.ResponseHeaderTimeout; d > 0 { respHeaderTimer = time.After(d) } case <-pconnDeadCh: // The persist connection is dead. This shouldn't // usually happen (only with Connection: close responses // with no response bodies), but if it does happen it // means either a) the remote server hung up on us // prematurely, or b) the readLoop sent us a response & // closed its closech at roughly the same time, and we // selected this case first, in which case a response // might still be coming soon. // // We can't avoid the select race in b) by using a unbuffered // resc channel instead, because then goroutines can // leak if we exit due to other errors. pconnDeadCh = nil // avoid spinning failTicker = time.After(100 * time.Millisecond) // arbitrary time to wait for resc case <-failTicker: re = responseAndError{err: errClosed} break WaitResponse case <-respHeaderTimer: pc.close() re = responseAndError{err: errTimeout} break WaitResponse case re = <-resc: break WaitResponse } } pc.lk.Lock() pc.numExpectedResponses-- pc.lk.Unlock() if re.err != nil { pc.t.setReqCanceler(req.Request, nil) } return re.res, re.err } // markBroken marks a connection as broken (so it's not reused). // It differs from close in that it doesn't close the underlying // connection for use when it's still being read. func (pc *persistConn) markBroken() { pc.lk.Lock() defer pc.lk.Unlock() pc.broken = true } func (pc *persistConn) close() { pc.lk.Lock() defer pc.lk.Unlock() pc.closeLocked() } func (pc *persistConn) closeLocked() { pc.broken = true if !pc.closed { pc.conn.Close() pc.closed = true } pc.mutateHeaderFunc = nil } var portMap = map[string]string{ "http": "80", "https": "443", } // canonicalAddr returns url.Host but always with a ":port" suffix func canonicalAddr(url *url.URL) string { addr := url.Host if !hasPort(addr) { return addr + ":" + portMap[url.Scheme] } return addr } // bodyEOFSignal wraps a ReadCloser but runs fn (if non-nil) at most // once, right before its final (error-producing) Read or Close call // returns. If earlyCloseFn is non-nil and Close is called before // io.EOF is seen, earlyCloseFn is called instead of fn, and its // return value is the return value from Close. type bodyEOFSignal struct { body io.ReadCloser mu sync.Mutex // guards following 4 fields closed bool // whether Close has been called rerr error // sticky Read error fn func(error) // error will be nil on Read io.EOF earlyCloseFn func() error // optional alt Close func used if io.EOF not seen } func (es *bodyEOFSignal) Read(p []byte) (n int, err error) { es.mu.Lock() closed, rerr := es.closed, es.rerr es.mu.Unlock() if closed { return 0, errors.New("http: read on closed response body") } if rerr != nil { return 0, rerr } n, err = es.body.Read(p) if err != nil { es.mu.Lock() defer es.mu.Unlock() if es.rerr == nil { es.rerr = err } es.condfn(err) } return } func (es *bodyEOFSignal) Close() error { es.mu.Lock() defer es.mu.Unlock() if es.closed { return nil } es.closed = true if es.earlyCloseFn != nil && es.rerr != io.EOF { return es.earlyCloseFn() } err := es.body.Close() es.condfn(err) return err } // caller must hold es.mu. func (es *bodyEOFSignal) condfn(err error) { if es.fn == nil { return } if err == io.EOF { err = nil } es.fn(err) es.fn = nil } type readerAndCloser struct { io.Reader io.Closer } type tlsHandshakeTimeoutError struct{} func (tlsHandshakeTimeoutError) Timeout() bool { return true } func (tlsHandshakeTimeoutError) Temporary() bool { return true } func (tlsHandshakeTimeoutError) Error() string { return "net/http: TLS handshake timeout" } ubuntu-push-0.2.1+14.04.20140423.1/http13client/readrequest_test.go0000644000015301777760000001510712325724711025011 0ustar pbusernogroup00000000000000// 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 http import ( "bufio" "bytes" "fmt" "io" "net/http" "net/url" "reflect" "testing" ) type reqTest struct { Raw string Req *Request Body string Trailer http.Header Error string } var noError = "" var noBody = "" var noTrailer http.Header = nil var reqTests = []reqTest{ // Baseline test; All Request fields included for template use { "GET http://www.techcrunch.com/ HTTP/1.1\r\n" + "Host: www.techcrunch.com\r\n" + "User-Agent: Fake\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + "Accept-Language: en-us,en;q=0.5\r\n" + "Accept-Encoding: gzip,deflate\r\n" + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + "Keep-Alive: 300\r\n" + "Content-Length: 7\r\n" + "Proxy-Connection: keep-alive\r\n\r\n" + "abcdef\n???", &Request{ Method: "GET", URL: &url.URL{ Scheme: "http", Host: "www.techcrunch.com", Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}, "Accept-Language": {"en-us,en;q=0.5"}, "Accept-Encoding": {"gzip,deflate"}, "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"}, "Keep-Alive": {"300"}, "Proxy-Connection": {"keep-alive"}, "Content-Length": {"7"}, "User-Agent": {"Fake"}, }, Close: false, ContentLength: 7, Host: "www.techcrunch.com", RequestURI: "http://www.techcrunch.com/", }, "abcdef\n", noTrailer, noError, }, // GET request with no body (the normal case) { "GET / HTTP/1.1\r\n" + "Host: foo.com\r\n\r\n", &Request{ Method: "GET", URL: &url.URL{ Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Close: false, ContentLength: 0, Host: "foo.com", RequestURI: "/", }, noBody, noTrailer, noError, }, // Tests that we don't parse a path that looks like a // scheme-relative URI as a scheme-relative URI. { "GET //user@host/is/actually/a/path/ HTTP/1.1\r\n" + "Host: test\r\n\r\n", &Request{ Method: "GET", URL: &url.URL{ Path: "//user@host/is/actually/a/path/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Close: false, ContentLength: 0, Host: "test", RequestURI: "//user@host/is/actually/a/path/", }, noBody, noTrailer, noError, }, // Tests a bogus abs_path on the Request-Line (RFC 2616 section 5.1.2) { "GET ../../../../etc/passwd HTTP/1.1\r\n" + "Host: test\r\n\r\n", nil, noBody, noTrailer, "parse ../../../../etc/passwd: invalid URI for request", }, // Tests missing URL: { "GET HTTP/1.1\r\n" + "Host: test\r\n\r\n", nil, noBody, noTrailer, "parse : empty url", }, // Tests chunked body with trailer: { "POST / HTTP/1.1\r\n" + "Host: foo.com\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + "3\r\nfoo\r\n" + "3\r\nbar\r\n" + "0\r\n" + "Trailer-Key: Trailer-Value\r\n" + "\r\n", &Request{ Method: "POST", URL: &url.URL{ Path: "/", }, TransferEncoding: []string{"chunked"}, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, ContentLength: -1, Host: "foo.com", RequestURI: "/", }, "foobar", http.Header{ "Trailer-Key": {"Trailer-Value"}, }, noError, }, // CONNECT request with domain name: { "CONNECT www.google.com:443 HTTP/1.1\r\n\r\n", &Request{ Method: "CONNECT", URL: &url.URL{ Host: "www.google.com:443", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Close: false, ContentLength: 0, Host: "www.google.com:443", RequestURI: "www.google.com:443", }, noBody, noTrailer, noError, }, // CONNECT request with IP address: { "CONNECT 127.0.0.1:6060 HTTP/1.1\r\n\r\n", &Request{ Method: "CONNECT", URL: &url.URL{ Host: "127.0.0.1:6060", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Close: false, ContentLength: 0, Host: "127.0.0.1:6060", RequestURI: "127.0.0.1:6060", }, noBody, noTrailer, noError, }, // CONNECT request for RPC: { "CONNECT /_goRPC_ HTTP/1.1\r\n\r\n", &Request{ Method: "CONNECT", URL: &url.URL{ Path: "/_goRPC_", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Close: false, ContentLength: 0, Host: "", RequestURI: "/_goRPC_", }, noBody, noTrailer, noError, }, // SSDP Notify request. golang.org/issue/3692 { "NOTIFY * HTTP/1.1\r\nServer: foo\r\n\r\n", &Request{ Method: "NOTIFY", URL: &url.URL{ Path: "*", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "Server": []string{"foo"}, }, Close: false, ContentLength: 0, RequestURI: "*", }, noBody, noTrailer, noError, }, // OPTIONS request. Similar to golang.org/issue/3692 { "OPTIONS * HTTP/1.1\r\nServer: foo\r\n\r\n", &Request{ Method: "OPTIONS", URL: &url.URL{ Path: "*", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "Server": []string{"foo"}, }, Close: false, ContentLength: 0, RequestURI: "*", }, noBody, noTrailer, noError, }, } func TestReadRequest(t *testing.T) { for i := range reqTests { tt := &reqTests[i] var braw bytes.Buffer braw.WriteString(tt.Raw) req, err := ReadRequest(bufio.NewReader(&braw)) if err != nil { if err.Error() != tt.Error { t.Errorf("#%d: error %q, want error %q", i, err.Error(), tt.Error) } continue } rbody := req.Body req.Body = nil diff(t, fmt.Sprintf("#%d Request", i), req, tt.Req) var bout bytes.Buffer if rbody != nil { _, err := io.Copy(&bout, rbody) if err != nil { t.Fatalf("#%d. copying body: %v", i, err) } rbody.Close() } body := bout.String() if body != tt.Body { t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) } if !reflect.DeepEqual(tt.Trailer, req.Trailer) { t.Errorf("#%d. Trailers differ.\n got: %v\nwant: %v", i, req.Trailer, tt.Trailer) } } } ubuntu-push-0.2.1+14.04.20140423.1/http13client/header.go0000644000015301777760000000253612325724711022660 0ustar pbusernogroup00000000000000// 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 http import ( "strings" ) // hasToken reports whether token appears with v, ASCII // case-insensitive, with space or comma boundaries. // token must be all lowercase. // v may contain mixed cased. func hasToken(v, token string) bool { if len(token) > len(v) || token == "" { return false } if v == token { return true } for sp := 0; sp <= len(v)-len(token); sp++ { // Check that first character is good. // The token is ASCII, so checking only a single byte // is sufficient. We skip this potential starting // position if both the first byte and its potential // ASCII uppercase equivalent (b|0x20) don't match. // False positives ('^' => '~') are caught by EqualFold. if b := v[sp]; b != token[0] && b|0x20 != token[0] { continue } // Check that start pos is on a valid token boundary. if sp > 0 && !isTokenBoundary(v[sp-1]) { continue } // Check that end pos is on a valid token boundary. if endPos := sp + len(token); endPos != len(v) && !isTokenBoundary(v[endPos]) { continue } if strings.EqualFold(v[sp:sp+len(token)], token) { return true } } return false } func isTokenBoundary(b byte) bool { return b == ' ' || b == ',' || b == '\t' } ubuntu-push-0.2.1+14.04.20140423.1/http13client/transfer.go0000644000015301777760000004271712325724711023261 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http import ( "bufio" "bytes" "errors" "fmt" "io" "io/ioutil" "net/http" "net/textproto" "strconv" "strings" "sync" ) type errorReader struct { err error } func (r *errorReader) Read(p []byte) (n int, err error) { return 0, r.err } // transferWriter inspects the fields of a user-supplied Request or Response, // sanitizes them without changing the user object and provides methods for // writing the respective header, body and trailer in wire format. type transferWriter struct { Method string Body io.Reader BodyCloser io.Closer ResponseToHEAD bool ContentLength int64 // -1 means unknown, 0 means exactly none Close bool TransferEncoding []string Trailer http.Header } func newTransferWriter(r interface{}) (t *transferWriter, err error) { t = &transferWriter{} // Extract relevant fields atLeastHTTP11 := false switch rr := r.(type) { case *Request: if rr.ContentLength != 0 && rr.Body == nil { return nil, fmt.Errorf("http: Request.ContentLength=%d with nil Body", rr.ContentLength) } t.Method = rr.Method t.Body = rr.Body t.BodyCloser = rr.Body t.ContentLength = rr.ContentLength t.Close = rr.Close t.TransferEncoding = rr.TransferEncoding t.Trailer = rr.Trailer atLeastHTTP11 = rr.ProtoAtLeast(1, 1) if t.Body != nil && len(t.TransferEncoding) == 0 && atLeastHTTP11 { if t.ContentLength == 0 { // Test to see if it's actually zero or just unset. var buf [1]byte n, rerr := io.ReadFull(t.Body, buf[:]) if rerr != nil && rerr != io.EOF { t.ContentLength = -1 t.Body = &errorReader{rerr} } else if n == 1 { // Oh, guess there is data in this Body Reader after all. // The ContentLength field just wasn't set. // Stich the Body back together again, re-attaching our // consumed byte. t.ContentLength = -1 t.Body = io.MultiReader(bytes.NewReader(buf[:]), t.Body) } else { // Body is actually empty. t.Body = nil t.BodyCloser = nil } } if t.ContentLength < 0 { t.TransferEncoding = []string{"chunked"} } } case *Response: if rr.Request != nil { t.Method = rr.Request.Method } t.Body = rr.Body t.BodyCloser = rr.Body t.ContentLength = rr.ContentLength t.Close = rr.Close t.TransferEncoding = rr.TransferEncoding t.Trailer = rr.Trailer atLeastHTTP11 = rr.ProtoAtLeast(1, 1) t.ResponseToHEAD = noBodyExpected(t.Method) } // Sanitize Body,ContentLength,TransferEncoding if t.ResponseToHEAD { t.Body = nil if chunked(t.TransferEncoding) { t.ContentLength = -1 } } else { if !atLeastHTTP11 || t.Body == nil { t.TransferEncoding = nil } if chunked(t.TransferEncoding) { t.ContentLength = -1 } else if t.Body == nil { // no chunking, no body t.ContentLength = 0 } } // Sanitize Trailer if !chunked(t.TransferEncoding) { t.Trailer = nil } return t, nil } func noBodyExpected(requestMethod string) bool { return requestMethod == "HEAD" } func (t *transferWriter) shouldSendContentLength() bool { if chunked(t.TransferEncoding) { return false } if t.ContentLength > 0 { return true } // Many servers expect a Content-Length for these methods if t.Method == "POST" || t.Method == "PUT" { return true } if t.ContentLength == 0 && isIdentity(t.TransferEncoding) { return true } return false } func (t *transferWriter) WriteHeader(w io.Writer) (err error) { if t.Close { _, err = io.WriteString(w, "Connection: close\r\n") if err != nil { return } } // Write Content-Length and/or Transfer-Encoding whose values are a // function of the sanitized field triple (Body, ContentLength, // TransferEncoding) if t.shouldSendContentLength() { io.WriteString(w, "Content-Length: ") _, err = io.WriteString(w, strconv.FormatInt(t.ContentLength, 10)+"\r\n") if err != nil { return } } else if chunked(t.TransferEncoding) { _, err = io.WriteString(w, "Transfer-Encoding: chunked\r\n") if err != nil { return } } // Write Trailer header if t.Trailer != nil { // TODO: At some point, there should be a generic mechanism for // writing long headers, using HTTP line splitting io.WriteString(w, "Trailer: ") needComma := false for k := range t.Trailer { k = http.CanonicalHeaderKey(k) switch k { case "Transfer-Encoding", "Trailer", "Content-Length": return &badStringError{"invalid Trailer key", k} } if needComma { io.WriteString(w, ",") } io.WriteString(w, k) needComma = true } _, err = io.WriteString(w, "\r\n") } return } func (t *transferWriter) WriteBody(w io.Writer) (err error) { var ncopy int64 // Write body if t.Body != nil { if chunked(t.TransferEncoding) { cw := newChunkedWriter(w) _, err = io.Copy(cw, t.Body) if err == nil { err = cw.Close() } } else if t.ContentLength == -1 { ncopy, err = io.Copy(w, t.Body) } else { ncopy, err = io.Copy(w, io.LimitReader(t.Body, t.ContentLength)) if err != nil { return err } var nextra int64 nextra, err = io.Copy(ioutil.Discard, t.Body) ncopy += nextra } if err != nil { return err } if err = t.BodyCloser.Close(); err != nil { return err } } if !t.ResponseToHEAD && t.ContentLength != -1 && t.ContentLength != ncopy { return fmt.Errorf("http: Request.ContentLength=%d with Body length %d", t.ContentLength, ncopy) } // TODO(petar): Place trailer writer code here. if chunked(t.TransferEncoding) { // Last chunk, empty trailer _, err = io.WriteString(w, "\r\n") } return } type transferReader struct { // Input Header http.Header StatusCode int RequestMethod string ProtoMajor int ProtoMinor int // Output Body io.ReadCloser ContentLength int64 TransferEncoding []string Close bool Trailer http.Header } // bodyAllowedForStatus reports whether a given response status code // permits a body. See RFC2616, section 4.4. func bodyAllowedForStatus(status int) bool { switch { case status >= 100 && status <= 199: return false case status == 204: return false case status == 304: return false } return true } // msg is *Request or *Response. func readTransfer(msg interface{}, r *bufio.Reader) (err error) { t := &transferReader{RequestMethod: "GET"} // Unify input isResponse := false switch rr := msg.(type) { case *Response: t.Header = rr.Header t.StatusCode = rr.StatusCode t.ProtoMajor = rr.ProtoMajor t.ProtoMinor = rr.ProtoMinor t.Close = shouldClose(t.ProtoMajor, t.ProtoMinor, t.Header) isResponse = true if rr.Request != nil { t.RequestMethod = rr.Request.Method } case *Request: t.Header = rr.Header t.ProtoMajor = rr.ProtoMajor t.ProtoMinor = rr.ProtoMinor // Transfer semantics for Requests are exactly like those for // Responses with status code 200, responding to a GET method t.StatusCode = 200 default: panic("unexpected type") } // Default to HTTP/1.1 if t.ProtoMajor == 0 && t.ProtoMinor == 0 { t.ProtoMajor, t.ProtoMinor = 1, 1 } // Transfer encoding, content length t.TransferEncoding, err = fixTransferEncoding(t.RequestMethod, t.Header) if err != nil { return err } realLength, err := fixLength(isResponse, t.StatusCode, t.RequestMethod, t.Header, t.TransferEncoding) if err != nil { return err } if isResponse && t.RequestMethod == "HEAD" { if n, err := parseContentLength(t.Header.Get("Content-Length")); err != nil { return err } else { t.ContentLength = n } } else { t.ContentLength = realLength } // Trailer t.Trailer, err = fixTrailer(t.Header, t.TransferEncoding) if err != nil { return err } // If there is no Content-Length or chunked Transfer-Encoding on a *Response // and the status is not 1xx, 204 or 304, then the body is unbounded. // See RFC2616, section 4.4. switch msg.(type) { case *Response: if realLength == -1 && !chunked(t.TransferEncoding) && bodyAllowedForStatus(t.StatusCode) { // Unbounded body. t.Close = true } } // Prepare body reader. ContentLength < 0 means chunked encoding // or close connection when finished, since multipart is not supported yet switch { case chunked(t.TransferEncoding): if noBodyExpected(t.RequestMethod) { t.Body = eofReader } else { t.Body = &body{src: newChunkedReader(r), hdr: msg, r: r, closing: t.Close} } case realLength == 0: t.Body = eofReader case realLength > 0: t.Body = &body{src: io.LimitReader(r, realLength), closing: t.Close} default: // realLength < 0, i.e. "Content-Length" not mentioned in header if t.Close { // Close semantics (i.e. HTTP/1.0) t.Body = &body{src: r, closing: t.Close} } else { // Persistent connection (i.e. HTTP/1.1) t.Body = eofReader } } // Unify output switch rr := msg.(type) { case *Request: rr.Body = t.Body rr.ContentLength = t.ContentLength rr.TransferEncoding = t.TransferEncoding rr.Close = t.Close rr.Trailer = t.Trailer case *Response: rr.Body = t.Body rr.ContentLength = t.ContentLength rr.TransferEncoding = t.TransferEncoding rr.Close = t.Close rr.Trailer = t.Trailer } return nil } // Checks whether chunked is part of the encodings stack func chunked(te []string) bool { return len(te) > 0 && te[0] == "chunked" } // Checks whether the encoding is explicitly "identity". func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" } // Sanitize transfer encoding func fixTransferEncoding(requestMethod string, header http.Header) ([]string, error) { raw, present := header["Transfer-Encoding"] if !present { return nil, nil } delete(header, "Transfer-Encoding") encodings := strings.Split(raw[0], ",") te := make([]string, 0, len(encodings)) // TODO: Even though we only support "identity" and "chunked" // encodings, the loop below is designed with foresight. One // invariant that must be maintained is that, if present, // chunked encoding must always come first. for _, encoding := range encodings { encoding = strings.ToLower(strings.TrimSpace(encoding)) // "identity" encoding is not recorded if encoding == "identity" { break } if encoding != "chunked" { return nil, &badStringError{"unsupported transfer encoding", encoding} } te = te[0 : len(te)+1] te[len(te)-1] = encoding } if len(te) > 1 { return nil, &badStringError{"too many transfer encodings", strings.Join(te, ",")} } if len(te) > 0 { // Chunked encoding trumps Content-Length. See RFC 2616 // Section 4.4. Currently len(te) > 0 implies chunked // encoding. delete(header, "Content-Length") return te, nil } return nil, nil } // Determine the expected body length, using RFC 2616 Section 4.4. This // function is not a method, because ultimately it should be shared by // ReadResponse and ReadRequest. func fixLength(isResponse bool, status int, requestMethod string, header http.Header, te []string) (int64, error) { // Logic based on response type or status if noBodyExpected(requestMethod) { return 0, nil } if status/100 == 1 { return 0, nil } switch status { case 204, 304: return 0, nil } // Logic based on Transfer-Encoding if chunked(te) { return -1, nil } // Logic based on Content-Length cl := strings.TrimSpace(header.Get("Content-Length")) if cl != "" { n, err := parseContentLength(cl) if err != nil { return -1, err } return n, nil } else { header.Del("Content-Length") } if !isResponse && requestMethod == "GET" { // RFC 2616 doesn't explicitly permit nor forbid an // entity-body on a GET request so we permit one if // declared, but we default to 0 here (not -1 below) // if there's no mention of a body. return 0, nil } // Body-EOF logic based on other methods (like closing, or chunked coding) return -1, nil } // Determine whether to hang up after sending a request and body, or // receiving a response and body // 'header' is the request headers func shouldClose(major, minor int, header http.Header) bool { if major < 1 { return true } else if major == 1 && minor == 0 { if !strings.Contains(strings.ToLower(header.Get("Connection")), "keep-alive") { return true } return false } else { // TODO: Should split on commas, toss surrounding white space, // and check each field. if strings.ToLower(header.Get("Connection")) == "close" { header.Del("Connection") return true } } return false } // Parse the trailer header func fixTrailer(header http.Header, te []string) (http.Header, error) { raw := header.Get("Trailer") if raw == "" { return nil, nil } header.Del("Trailer") trailer := make(http.Header) keys := strings.Split(raw, ",") for _, key := range keys { key = http.CanonicalHeaderKey(strings.TrimSpace(key)) switch key { case "Transfer-Encoding", "Trailer", "Content-Length": return nil, &badStringError{"bad trailer key", key} } trailer.Del(key) } if len(trailer) == 0 { return nil, nil } if !chunked(te) { // Trailer and no chunking return nil, ErrUnexpectedTrailer } return trailer, nil } // body turns a Reader into a ReadCloser. // Close ensures that the body has been fully read // and then reads the trailer if necessary. type body struct { src io.Reader hdr interface{} // non-nil (Response or Request) value means read trailer r *bufio.Reader // underlying wire-format reader for the trailer closing bool // is the connection to be closed after reading body? mu sync.Mutex // guards closed, and calls to Read and Close closed bool } // ErrBodyReadAfterClose is returned when reading a Request or Response // Body after the body has been closed. This typically happens when the body is // read after an HTTP Handler calls WriteHeader or Write on its // ResponseWriter. var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body") func (b *body) Read(p []byte) (n int, err error) { b.mu.Lock() defer b.mu.Unlock() if b.closed { return 0, ErrBodyReadAfterClose } return b.readLocked(p) } // Must hold b.mu. func (b *body) readLocked(p []byte) (n int, err error) { n, err = b.src.Read(p) if err == io.EOF { // Chunked case. Read the trailer. if b.hdr != nil { if e := b.readTrailer(); e != nil { err = e } b.hdr = nil } else { // If the server declared the Content-Length, our body is a LimitedReader // and we need to check whether this EOF arrived early. if lr, ok := b.src.(*io.LimitedReader); ok && lr.N > 0 { err = io.ErrUnexpectedEOF } } } // If we can return an EOF here along with the read data, do // so. This is optional per the io.Reader contract, but doing // so helps the HTTP transport code recycle its connection // earlier (since it will see this EOF itself), even if the // client doesn't do future reads or Close. if err == nil && n > 0 { if lr, ok := b.src.(*io.LimitedReader); ok && lr.N == 0 { err = io.EOF } } return n, err } var ( singleCRLF = []byte("\r\n") doubleCRLF = []byte("\r\n\r\n") ) func seeUpcomingDoubleCRLF(r *bufio.Reader) bool { for peekSize := 4; ; peekSize++ { // This loop stops when Peek returns an error, // which it does when r's buffer has been filled. buf, err := r.Peek(peekSize) if bytes.HasSuffix(buf, doubleCRLF) { return true } if err != nil { break } } return false } var errTrailerEOF = errors.New("http: unexpected EOF reading trailer") func (b *body) readTrailer() error { // The common case, since nobody uses trailers. buf, err := b.r.Peek(2) if bytes.Equal(buf, singleCRLF) { b.r.ReadByte() b.r.ReadByte() return nil } if len(buf) < 2 { return errTrailerEOF } if err != nil { return err } // Make sure there's a header terminator coming up, to prevent // a DoS with an unbounded size Trailer. It's not easy to // slip in a LimitReader here, as textproto.NewReader requires // a concrete *bufio.Reader. Also, we can't get all the way // back up to our conn's LimitedReader that *might* be backing // this bufio.Reader. Instead, a hack: we iteratively Peek up // to the bufio.Reader's max size, looking for a double CRLF. // This limits the trailer to the underlying buffer size, typically 4kB. if !seeUpcomingDoubleCRLF(b.r) { return errors.New("http: suspiciously long trailer after chunked body") } hdr, err := textproto.NewReader(b.r).ReadMIMEHeader() if err != nil { if err == io.EOF { return errTrailerEOF } return err } switch rr := b.hdr.(type) { case *Request: rr.Trailer = http.Header(hdr) case *Response: rr.Trailer = http.Header(hdr) } return nil } func (b *body) Close() error { b.mu.Lock() defer b.mu.Unlock() if b.closed { return nil } var err error switch { case b.hdr == nil && b.closing: // no trailer and closing the connection next. // no point in reading to EOF. default: // Fully consume the body, which will also lead to us reading // the trailer headers after the body, if present. _, err = io.Copy(ioutil.Discard, bodyLocked{b}) } b.closed = true return err } // bodyLocked is a io.Reader reading from a *body when its mutex is // already held. type bodyLocked struct { b *body } func (bl bodyLocked) Read(p []byte) (n int, err error) { if bl.b.closed { return 0, ErrBodyReadAfterClose } return bl.b.readLocked(p) } // parseContentLength trims whitespace from s and returns -1 if no value // is set, or the value if it's >= 0. func parseContentLength(cl string) (int64, error) { cl = strings.TrimSpace(cl) if cl == "" { return -1, nil } n, err := strconv.ParseInt(cl, 10, 64) if err != nil || n < 0 { return 0, &badStringError{"bad Content-Length", cl} } return n, nil } ubuntu-push-0.2.1+14.04.20140423.1/http13client/cookie.go0000644000015301777760000001606012325724711022676 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http import ( "log" "net" "net/http" "strconv" "strings" "time" ) // This implementation is done according to RFC 6265: // // http://tools.ietf.org/html/rfc6265 // readSetCookies parses all "Set-Cookie" values from // the header h and returns the successfully parsed Cookies. func readSetCookies(h http.Header) []*http.Cookie { cookies := []*http.Cookie{} for _, line := range h["Set-Cookie"] { parts := strings.Split(strings.TrimSpace(line), ";") if len(parts) == 1 && parts[0] == "" { continue } parts[0] = strings.TrimSpace(parts[0]) j := strings.Index(parts[0], "=") if j < 0 { continue } name, value := parts[0][:j], parts[0][j+1:] if !isCookieNameValid(name) { continue } value, success := parseCookieValue(value) if !success { continue } c := &http.Cookie{ Name: name, Value: value, Raw: line, } for i := 1; i < len(parts); i++ { parts[i] = strings.TrimSpace(parts[i]) if len(parts[i]) == 0 { continue } attr, val := parts[i], "" if j := strings.Index(attr, "="); j >= 0 { attr, val = attr[:j], attr[j+1:] } lowerAttr := strings.ToLower(attr) parseCookieValueFn := parseCookieValue if lowerAttr == "expires" { parseCookieValueFn = parseCookieExpiresValue } val, success = parseCookieValueFn(val) if !success { c.Unparsed = append(c.Unparsed, parts[i]) continue } switch lowerAttr { case "secure": c.Secure = true continue case "httponly": c.HttpOnly = true continue case "domain": c.Domain = val continue case "max-age": secs, err := strconv.Atoi(val) if err != nil || secs != 0 && val[0] == '0' { break } if secs <= 0 { c.MaxAge = -1 } else { c.MaxAge = secs } continue case "expires": c.RawExpires = val exptime, err := time.Parse(time.RFC1123, val) if err != nil { exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) if err != nil { c.Expires = time.Time{} break } } c.Expires = exptime.UTC() continue case "path": c.Path = val continue } c.Unparsed = append(c.Unparsed, parts[i]) } cookies = append(cookies, c) } return cookies } // readCookies parses all "Cookie" values from the header h and // returns the successfully parsed Cookies. // // if filter isn't empty, only cookies of that name are returned func readCookies(h http.Header, filter string) []*http.Cookie { cookies := []*http.Cookie{} lines, ok := h["Cookie"] if !ok { return cookies } for _, line := range lines { parts := strings.Split(strings.TrimSpace(line), ";") if len(parts) == 1 && parts[0] == "" { continue } // Per-line attributes parsedPairs := 0 for i := 0; i < len(parts); i++ { parts[i] = strings.TrimSpace(parts[i]) if len(parts[i]) == 0 { continue } name, val := parts[i], "" if j := strings.Index(name, "="); j >= 0 { name, val = name[:j], name[j+1:] } if !isCookieNameValid(name) { continue } if filter != "" && filter != name { continue } val, success := parseCookieValue(val) if !success { continue } cookies = append(cookies, &http.Cookie{Name: name, Value: val}) parsedPairs++ } } return cookies } // validCookieDomain returns wheter v is a valid cookie domain-value. func validCookieDomain(v string) bool { if isCookieDomainName(v) { return true } if net.ParseIP(v) != nil && !strings.Contains(v, ":") { return true } return false } // isCookieDomainName returns whether s is a valid domain name or a valid // domain name with a leading dot '.'. It is almost a direct copy of // package net's isDomainName. func isCookieDomainName(s string) bool { if len(s) == 0 { return false } if len(s) > 255 { return false } if s[0] == '.' { // A cookie a domain attribute may start with a leading dot. s = s[1:] } last := byte('.') ok := false // Ok once we've seen a letter. partlen := 0 for i := 0; i < len(s); i++ { c := s[i] switch { default: return false case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z': // No '_' allowed here (in contrast to package net). ok = true partlen++ case '0' <= c && c <= '9': // fine partlen++ case c == '-': // Byte before dash cannot be dot. if last == '.' { return false } partlen++ case c == '.': // Byte before dot cannot be dot, dash. if last == '.' || last == '-' { return false } if partlen > 63 || partlen == 0 { return false } partlen = 0 } last = c } if last == '-' || partlen > 63 { return false } return ok } var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") func sanitizeCookieName(n string) string { return cookieNameSanitizer.Replace(n) } // http://tools.ietf.org/html/rfc6265#section-4.1.1 // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E // ; US-ASCII characters excluding CTLs, // ; whitespace DQUOTE, comma, semicolon, // ; and backslash func sanitizeCookieValue(v string) string { return sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) } func validCookieValueByte(b byte) bool { return 0x20 < b && b < 0x7f && b != '"' && b != ',' && b != ';' && b != '\\' } // path-av = "Path=" path-value // path-value = func sanitizeCookiePath(v string) string { return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v) } func validCookiePathByte(b byte) bool { return 0x20 <= b && b < 0x7f && b != ';' } func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string { ok := true for i := 0; i < len(v); i++ { if valid(v[i]) { continue } log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName) ok = false break } if ok { return v } buf := make([]byte, 0, len(v)) for i := 0; i < len(v); i++ { if b := v[i]; valid(b) { buf = append(buf, b) } } return string(buf) } func unquoteCookieValue(v string) string { if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { return v[1 : len(v)-1] } return v } func isCookieByte(c byte) bool { switch { case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: return true } return false } func isCookieExpiresByte(c byte) (ok bool) { return isCookieByte(c) || c == ',' || c == ' ' } func parseCookieValue(raw string) (string, bool) { return parseCookieValueUsing(raw, isCookieByte) } func parseCookieExpiresValue(raw string) (string, bool) { return parseCookieValueUsing(raw, isCookieExpiresByte) } func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) { raw = unquoteCookieValue(raw) for i := 0; i < len(raw); i++ { if !validByte(raw[i]) { return "", false } } return raw, true } func isCookieNameValid(raw string) bool { return strings.IndexFunc(raw, isNotToken) < 0 } ubuntu-push-0.2.1+14.04.20140423.1/http13client/request_test.go0000644000015301777760000003753212325724711024163 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http_test import ( "bufio" "bytes" "fmt" "io" "io/ioutil" . "launchpad.net/ubuntu-push/http13client" "mime/multipart" "net/http" "net/http/httptest" "net/url" "os" "reflect" "regexp" "strings" "testing" ) func TestQuery(t *testing.T) { req := &Request{Method: "GET"} req.URL, _ = url.Parse("http://www.google.com/search?q=foo&q=bar") if q := req.FormValue("q"); q != "foo" { t.Errorf(`req.FormValue("q") = %q, want "foo"`, q) } } func TestPostQuery(t *testing.T) { req, _ := NewRequest("POST", "http://www.google.com/search?q=foo&q=bar&both=x&prio=1&empty=not", strings.NewReader("z=post&both=y&prio=2&empty=")) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") if q := req.FormValue("q"); q != "foo" { t.Errorf(`req.FormValue("q") = %q, want "foo"`, q) } if z := req.FormValue("z"); z != "post" { t.Errorf(`req.FormValue("z") = %q, want "post"`, z) } if bq, found := req.PostForm["q"]; found { t.Errorf(`req.PostForm["q"] = %q, want no entry in map`, bq) } if bz := req.PostFormValue("z"); bz != "post" { t.Errorf(`req.PostFormValue("z") = %q, want "post"`, bz) } if qs := req.Form["q"]; !reflect.DeepEqual(qs, []string{"foo", "bar"}) { t.Errorf(`req.Form["q"] = %q, want ["foo", "bar"]`, qs) } if both := req.Form["both"]; !reflect.DeepEqual(both, []string{"y", "x"}) { t.Errorf(`req.Form["both"] = %q, want ["y", "x"]`, both) } if prio := req.FormValue("prio"); prio != "2" { t.Errorf(`req.FormValue("prio") = %q, want "2" (from body)`, prio) } if empty := req.FormValue("empty"); empty != "" { t.Errorf(`req.FormValue("empty") = %q, want "" (from body)`, empty) } } func TestPatchQuery(t *testing.T) { req, _ := NewRequest("PATCH", "http://www.google.com/search?q=foo&q=bar&both=x&prio=1&empty=not", strings.NewReader("z=post&both=y&prio=2&empty=")) req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") if q := req.FormValue("q"); q != "foo" { t.Errorf(`req.FormValue("q") = %q, want "foo"`, q) } if z := req.FormValue("z"); z != "post" { t.Errorf(`req.FormValue("z") = %q, want "post"`, z) } if bq, found := req.PostForm["q"]; found { t.Errorf(`req.PostForm["q"] = %q, want no entry in map`, bq) } if bz := req.PostFormValue("z"); bz != "post" { t.Errorf(`req.PostFormValue("z") = %q, want "post"`, bz) } if qs := req.Form["q"]; !reflect.DeepEqual(qs, []string{"foo", "bar"}) { t.Errorf(`req.Form["q"] = %q, want ["foo", "bar"]`, qs) } if both := req.Form["both"]; !reflect.DeepEqual(both, []string{"y", "x"}) { t.Errorf(`req.Form["both"] = %q, want ["y", "x"]`, both) } if prio := req.FormValue("prio"); prio != "2" { t.Errorf(`req.FormValue("prio") = %q, want "2" (from body)`, prio) } if empty := req.FormValue("empty"); empty != "" { t.Errorf(`req.FormValue("empty") = %q, want "" (from body)`, empty) } } type stringMap map[string][]string type parseContentTypeTest struct { shouldError bool contentType stringMap } var parseContentTypeTests = []parseContentTypeTest{ {false, stringMap{"Content-Type": {"text/plain"}}}, // Empty content type is legal - shoult be treated as // application/octet-stream (RFC 2616, section 7.2.1) {false, stringMap{}}, {true, stringMap{"Content-Type": {"text/plain; boundary="}}}, {false, stringMap{"Content-Type": {"application/unknown"}}}, } func TestParseFormUnknownContentType(t *testing.T) { for i, test := range parseContentTypeTests { req := &Request{ Method: "POST", Header: http.Header(test.contentType), Body: ioutil.NopCloser(strings.NewReader("body")), } err := req.ParseForm() switch { case err == nil && test.shouldError: t.Errorf("test %d should have returned error", i) case err != nil && !test.shouldError: t.Errorf("test %d should not have returned error, got %v", i, err) } } } func TestParseFormInitializeOnError(t *testing.T) { nilBody, _ := NewRequest("POST", "http://www.google.com/search?q=foo", nil) tests := []*Request{ nilBody, {Method: "GET", URL: nil}, } for i, req := range tests { err := req.ParseForm() if req.Form == nil { t.Errorf("%d. Form not initialized, error %v", i, err) } if req.PostForm == nil { t.Errorf("%d. PostForm not initialized, error %v", i, err) } } } func TestMultipartReader(t *testing.T) { req := &Request{ Method: "POST", Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, Body: ioutil.NopCloser(new(bytes.Buffer)), } multipart, err := req.MultipartReader() if multipart == nil { t.Errorf("expected multipart; error: %v", err) } req.Header = http.Header{"Content-Type": {"text/plain"}} multipart, err = req.MultipartReader() if multipart != nil { t.Errorf("unexpected multipart for text/plain") } } func TestRedirect(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/": w.Header().Set("Location", "/foo/") w.WriteHeader(http.StatusSeeOther) case "/foo/": fmt.Fprintf(w, "foo") default: w.WriteHeader(http.StatusBadRequest) } })) defer ts.Close() var end = regexp.MustCompile("/foo/$") r, err := Get(ts.URL) if err != nil { t.Fatal(err) } r.Body.Close() url := r.Request.URL.String() if r.StatusCode != 200 || !end.MatchString(url) { t.Fatalf("Get got status %d at %q, want 200 matching /foo/$", r.StatusCode, url) } } func TestSetBasicAuth(t *testing.T) { r, _ := NewRequest("GET", "http://example.com/", nil) r.SetBasicAuth("Aladdin", "open sesame") if g, e := r.Header.Get("Authorization"), "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="; g != e { t.Errorf("got header %q, want %q", g, e) } } func TestMultipartRequest(t *testing.T) { // Test that we can read the values and files of a // multipart request with FormValue and FormFile, // and that ParseMultipartForm can be called multiple times. req := newTestMultipartRequest(t) if err := req.ParseMultipartForm(25); err != nil { t.Fatal("ParseMultipartForm first call:", err) } defer req.MultipartForm.RemoveAll() validateTestMultipartContents(t, req, false) if err := req.ParseMultipartForm(25); err != nil { t.Fatal("ParseMultipartForm second call:", err) } validateTestMultipartContents(t, req, false) } func TestMultipartRequestAuto(t *testing.T) { // Test that FormValue and FormFile automatically invoke // ParseMultipartForm and return the right values. req := newTestMultipartRequest(t) defer func() { if req.MultipartForm != nil { req.MultipartForm.RemoveAll() } }() validateTestMultipartContents(t, req, true) } func TestEmptyMultipartRequest(t *testing.T) { // Test that FormValue and FormFile automatically invoke // ParseMultipartForm and return the right values. req, err := NewRequest("GET", "/", nil) if err != nil { t.Errorf("NewRequest err = %q", err) } testMissingFile(t, req) } // Test that ParseMultipartForm errors if called // after MultipartReader on the same request. func TestParseMultipartFormOrder(t *testing.T) { req := newTestMultipartRequest(t) if _, err := req.MultipartReader(); err != nil { t.Fatalf("MultipartReader: %v", err) } if err := req.ParseMultipartForm(1024); err == nil { t.Fatal("expected an error from ParseMultipartForm after call to MultipartReader") } } // Test that MultipartReader errors if called // after ParseMultipartForm on the same request. func TestMultipartReaderOrder(t *testing.T) { req := newTestMultipartRequest(t) if err := req.ParseMultipartForm(25); err != nil { t.Fatalf("ParseMultipartForm: %v", err) } defer req.MultipartForm.RemoveAll() if _, err := req.MultipartReader(); err == nil { t.Fatal("expected an error from MultipartReader after call to ParseMultipartForm") } } // Test that FormFile errors if called after // MultipartReader on the same request. func TestFormFileOrder(t *testing.T) { req := newTestMultipartRequest(t) if _, err := req.MultipartReader(); err != nil { t.Fatalf("MultipartReader: %v", err) } if _, _, err := req.FormFile(""); err == nil { t.Fatal("expected an error from FormFile after call to MultipartReader") } } var readRequestErrorTests = []struct { in string err error }{ {"GET / HTTP/1.1\r\nheader:foo\r\n\r\n", nil}, {"GET / HTTP/1.1\r\nheader:foo\r\n", io.ErrUnexpectedEOF}, {"", io.EOF}, } func TestReadRequestErrors(t *testing.T) { for i, tt := range readRequestErrorTests { _, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.in))) if err != tt.err { t.Errorf("%d. got error = %v; want %v", i, err, tt.err) } } } func TestNewRequestHost(t *testing.T) { req, err := NewRequest("GET", "http://localhost:1234/", nil) if err != nil { t.Fatal(err) } if req.Host != "localhost:1234" { t.Errorf("Host = %q; want localhost:1234", req.Host) } } func TestNewRequestContentLength(t *testing.T) { readByte := func(r io.Reader) io.Reader { var b [1]byte r.Read(b[:]) return r } tests := []struct { r io.Reader want int64 }{ {bytes.NewReader([]byte("123")), 3}, {bytes.NewBuffer([]byte("1234")), 4}, {strings.NewReader("12345"), 5}, // Not detected: {struct{ io.Reader }{strings.NewReader("xyz")}, 0}, {io.NewSectionReader(strings.NewReader("x"), 0, 6), 0}, {readByte(io.NewSectionReader(strings.NewReader("xy"), 0, 6)), 0}, } for _, tt := range tests { req, err := NewRequest("POST", "http://localhost/", tt.r) if err != nil { t.Fatal(err) } if req.ContentLength != tt.want { t.Errorf("ContentLength(%T) = %d; want %d", tt.r, req.ContentLength, tt.want) } } } var parseHTTPVersionTests = []struct { vers string major, minor int ok bool }{ {"HTTP/0.9", 0, 9, true}, {"HTTP/1.0", 1, 0, true}, {"HTTP/1.1", 1, 1, true}, {"HTTP/3.14", 3, 14, true}, {"HTTP", 0, 0, false}, {"HTTP/one.one", 0, 0, false}, {"HTTP/1.1/", 0, 0, false}, {"HTTP/-1,0", 0, 0, false}, {"HTTP/0,-1", 0, 0, false}, {"HTTP/", 0, 0, false}, {"HTTP/1,1", 0, 0, false}, } func TestParseHTTPVersion(t *testing.T) { for _, tt := range parseHTTPVersionTests { major, minor, ok := ParseHTTPVersion(tt.vers) if ok != tt.ok || major != tt.major || minor != tt.minor { type version struct { major, minor int ok bool } t.Errorf("failed to parse %q, expected: %#v, got %#v", tt.vers, version{tt.major, tt.minor, tt.ok}, version{major, minor, ok}) } } } type logWrites struct { t *testing.T dst *[]string } func (l logWrites) WriteByte(c byte) error { l.t.Fatalf("unexpected WriteByte call") return nil } func (l logWrites) Write(p []byte) (n int, err error) { *l.dst = append(*l.dst, string(p)) return len(p), nil } func TestRequestWriteBufferedWriter(t *testing.T) { got := []string{} req, _ := NewRequest("GET", "http://foo.com/", nil) req.Write(logWrites{t, &got}) want := []string{ "GET / HTTP/1.1\r\n", "Host: foo.com\r\n", "User-Agent: " + DefaultUserAgent + "\r\n", "\r\n", } if !reflect.DeepEqual(got, want) { t.Errorf("Writes = %q\n Want = %q", got, want) } } func testMissingFile(t *testing.T, req *Request) { f, fh, err := req.FormFile("missing") if f != nil { t.Errorf("FormFile file = %v, want nil", f) } if fh != nil { t.Errorf("FormFile file header = %q, want nil", fh) } if err != ErrMissingFile { t.Errorf("FormFile err = %q, want ErrMissingFile", err) } } func newTestMultipartRequest(t *testing.T) *Request { b := strings.NewReader(strings.Replace(message, "\n", "\r\n", -1)) req, err := NewRequest("POST", "/", b) if err != nil { t.Fatal("NewRequest:", err) } ctype := fmt.Sprintf(`multipart/form-data; boundary="%s"`, boundary) req.Header.Set("Content-type", ctype) return req } func validateTestMultipartContents(t *testing.T, req *Request, allMem bool) { if g, e := req.FormValue("texta"), textaValue; g != e { t.Errorf("texta value = %q, want %q", g, e) } if g, e := req.FormValue("textb"), textbValue; g != e { t.Errorf("textb value = %q, want %q", g, e) } if g := req.FormValue("missing"); g != "" { t.Errorf("missing value = %q, want empty string", g) } assertMem := func(n string, fd multipart.File) { if _, ok := fd.(*os.File); ok { t.Error(n, " is *os.File, should not be") } } fda := testMultipartFile(t, req, "filea", "filea.txt", fileaContents) defer fda.Close() assertMem("filea", fda) fdb := testMultipartFile(t, req, "fileb", "fileb.txt", filebContents) defer fdb.Close() if allMem { assertMem("fileb", fdb) } else { if _, ok := fdb.(*os.File); !ok { t.Errorf("fileb has unexpected underlying type %T", fdb) } } testMissingFile(t, req) } func testMultipartFile(t *testing.T, req *Request, key, expectFilename, expectContent string) multipart.File { f, fh, err := req.FormFile(key) if err != nil { t.Fatalf("FormFile(%q): %q", key, err) } if fh.Filename != expectFilename { t.Errorf("filename = %q, want %q", fh.Filename, expectFilename) } var b bytes.Buffer _, err = io.Copy(&b, f) if err != nil { t.Fatal("copying contents:", err) } if g := b.String(); g != expectContent { t.Errorf("contents = %q, want %q", g, expectContent) } return f } const ( fileaContents = "This is a test file." filebContents = "Another test file." textaValue = "foo" textbValue = "bar" boundary = `MyBoundary` ) const message = ` --MyBoundary Content-Disposition: form-data; name="filea"; filename="filea.txt" Content-Type: text/plain ` + fileaContents + ` --MyBoundary Content-Disposition: form-data; name="fileb"; filename="fileb.txt" Content-Type: text/plain ` + filebContents + ` --MyBoundary Content-Disposition: form-data; name="texta" ` + textaValue + ` --MyBoundary Content-Disposition: form-data; name="textb" ` + textbValue + ` --MyBoundary-- ` func benchmarkReadRequest(b *testing.B, request string) { request = request + "\n" // final \n request = strings.Replace(request, "\n", "\r\n", -1) // expand \n to \r\n b.SetBytes(int64(len(request))) r := bufio.NewReader(&infiniteReader{buf: []byte(request)}) b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { _, err := ReadRequest(r) if err != nil { b.Fatalf("failed to read request: %v", err) } } } // infiniteReader satisfies Read requests as if the contents of buf // loop indefinitely. type infiniteReader struct { buf []byte offset int } func (r *infiniteReader) Read(b []byte) (int, error) { n := copy(b, r.buf[r.offset:]) r.offset = (r.offset + n) % len(r.buf) return n, nil } func BenchmarkReadRequestChrome(b *testing.B) { // https://github.com/felixge/node-http-perf/blob/master/fixtures/get.http benchmarkReadRequest(b, `GET / HTTP/1.1 Host: localhost:8080 Connection: keep-alive Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 Cookie: __utma=1.1978842379.1323102373.1323102373.1323102373.1; EPi:NumberOfVisits=1,2012-02-28T13:42:18; CrmSession=5b707226b9563e1bc69084d07a107c98; plushContainerWidth=100%25; plushNoTopMenu=0; hudson_auto_refresh=false `) } func BenchmarkReadRequestCurl(b *testing.B) { // curl http://localhost:8080/ benchmarkReadRequest(b, `GET / HTTP/1.1 User-Agent: curl/7.27.0 Host: localhost:8080 Accept: */* `) } func BenchmarkReadRequestApachebench(b *testing.B) { // ab -n 1 -c 1 http://localhost:8080/ benchmarkReadRequest(b, `GET / HTTP/1.0 Host: localhost:8080 User-Agent: ApacheBench/2.3 Accept: */* `) } func BenchmarkReadRequestSiege(b *testing.B) { // siege -r 1 -c 1 http://localhost:8080/ benchmarkReadRequest(b, `GET / HTTP/1.1 Host: localhost:8080 Accept: */* Accept-Encoding: gzip User-Agent: JoeDog/1.00 [en] (X11; I; Siege 2.70) Connection: keep-alive `) } func BenchmarkReadRequestWrk(b *testing.B) { // wrk -t 1 -r 1 -c 1 http://localhost:8080/ benchmarkReadRequest(b, `GET / HTTP/1.1 Host: localhost:8080 `) } ubuntu-push-0.2.1+14.04.20140423.1/http13client/proxy_test.go0000644000015301777760000000413212325724711023642 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http import ( "net/url" "os" "testing" ) // TODO(mattn): // test ProxyAuth var UseProxyTests = []struct { host string match bool }{ // Never proxy localhost: {"localhost:80", false}, {"127.0.0.1", false}, {"127.0.0.2", false}, {"[::1]", false}, {"[::2]", true}, // not a loopback address {"barbaz.net", false}, // match as .barbaz.net {"foobar.com", false}, // have a port but match {"foofoobar.com", true}, // not match as a part of foobar.com {"baz.com", true}, // not match as a part of barbaz.com {"localhost.net", true}, // not match as suffix of address {"local.localhost", true}, // not match as prefix as address {"barbarbaz.net", true}, // not match because NO_PROXY have a '.' {"www.foobar.com", false}, // match because NO_PROXY includes "foobar.com" } func TestUseProxy(t *testing.T) { ResetProxyEnv() os.Setenv("NO_PROXY", "foobar.com, .barbaz.net") for _, test := range UseProxyTests { if useProxy(test.host+":80") != test.match { t.Errorf("useProxy(%v) = %v, want %v", test.host, !test.match, test.match) } } } var cacheKeysTests = []struct { proxy string scheme string addr string key string }{ {"", "http", "foo.com", "|http|foo.com"}, {"", "https", "foo.com", "|https|foo.com"}, {"http://foo.com", "http", "foo.com", "http://foo.com|http|"}, {"http://foo.com", "https", "foo.com", "http://foo.com|https|foo.com"}, } func TestCacheKeys(t *testing.T) { for _, tt := range cacheKeysTests { var proxy *url.URL if tt.proxy != "" { u, err := url.Parse(tt.proxy) if err != nil { t.Fatal(err) } proxy = u } cm := connectMethod{proxy, tt.scheme, tt.addr} if got := cm.key().String(); got != tt.key { t.Fatalf("{%q, %q, %q} cache key = %q; want %q", tt.proxy, tt.scheme, tt.addr, got, tt.key) } } } func ResetProxyEnv() { for _, v := range []string{"HTTP_PROXY", "http_proxy", "NO_PROXY", "no_proxy"} { os.Setenv(v, "") } ResetCachedEnvironment() } ubuntu-push-0.2.1+14.04.20140423.1/http13client/chunked_test.go0000644000015301777760000001005312325724711024101 0ustar pbusernogroup00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // This code is duplicated in net/http and net/http/httputil. // Please make any changes in both files. package http import ( "bufio" "bytes" "fmt" "io" "io/ioutil" "strings" "testing" ) func TestChunk(t *testing.T) { var b bytes.Buffer w := newChunkedWriter(&b) const chunk1 = "hello, " const chunk2 = "world! 0123456789abcdef" w.Write([]byte(chunk1)) w.Write([]byte(chunk2)) w.Close() if g, e := b.String(), "7\r\nhello, \r\n17\r\nworld! 0123456789abcdef\r\n0\r\n"; g != e { t.Fatalf("chunk writer wrote %q; want %q", g, e) } r := newChunkedReader(&b) data, err := ioutil.ReadAll(r) if err != nil { t.Logf(`data: "%s"`, data) t.Fatalf("ReadAll from reader: %v", err) } if g, e := string(data), chunk1+chunk2; g != e { t.Errorf("chunk reader read %q; want %q", g, e) } } func TestChunkReadMultiple(t *testing.T) { // Bunch of small chunks, all read together. { var b bytes.Buffer w := newChunkedWriter(&b) w.Write([]byte("foo")) w.Write([]byte("bar")) w.Close() r := newChunkedReader(&b) buf := make([]byte, 10) n, err := r.Read(buf) if n != 6 || err != io.EOF { t.Errorf("Read = %d, %v; want 6, EOF", n, err) } buf = buf[:n] if string(buf) != "foobar" { t.Errorf("Read = %q; want %q", buf, "foobar") } } // One big chunk followed by a little chunk, but the small bufio.Reader size // should prevent the second chunk header from being read. { var b bytes.Buffer w := newChunkedWriter(&b) // fillBufChunk is 11 bytes + 3 bytes header + 2 bytes footer = 16 bytes, // the same as the bufio ReaderSize below (the minimum), so even // though we're going to try to Read with a buffer larger enough to also // receive "foo", the second chunk header won't be read yet. const fillBufChunk = "0123456789a" const shortChunk = "foo" w.Write([]byte(fillBufChunk)) w.Write([]byte(shortChunk)) w.Close() r := newChunkedReader(bufio.NewReaderSize(&b, 16)) buf := make([]byte, len(fillBufChunk)+len(shortChunk)) n, err := r.Read(buf) if n != len(fillBufChunk) || err != nil { t.Errorf("Read = %d, %v; want %d, nil", n, err, len(fillBufChunk)) } buf = buf[:n] if string(buf) != fillBufChunk { t.Errorf("Read = %q; want %q", buf, fillBufChunk) } n, err = r.Read(buf) if n != len(shortChunk) || err != io.EOF { t.Errorf("Read = %d, %v; want %d, EOF", n, err, len(shortChunk)) } } // And test that we see an EOF chunk, even though our buffer is already full: { r := newChunkedReader(bufio.NewReader(strings.NewReader("3\r\nfoo\r\n0\r\n"))) buf := make([]byte, 3) n, err := r.Read(buf) if n != 3 || err != io.EOF { t.Errorf("Read = %d, %v; want 3, EOF", n, err) } if string(buf) != "foo" { t.Errorf("buf = %q; want foo", buf) } } } func TestChunkReaderAllocs(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") } var buf bytes.Buffer w := newChunkedWriter(&buf) a, b, c := []byte("aaaaaa"), []byte("bbbbbbbbbbbb"), []byte("cccccccccccccccccccccccc") w.Write(a) w.Write(b) w.Write(c) w.Close() readBuf := make([]byte, len(a)+len(b)+len(c)+1) byter := bytes.NewReader(buf.Bytes()) bufr := bufio.NewReader(byter) mallocs := testing.AllocsPerRun(100, func() { byter.Seek(0, 0) bufr.Reset(byter) r := newChunkedReader(bufr) n, err := io.ReadFull(r, readBuf) if n != len(readBuf)-1 { t.Fatalf("read %d bytes; want %d", n, len(readBuf)-1) } if err != io.ErrUnexpectedEOF { t.Fatalf("read error = %v; want ErrUnexpectedEOF", err) } }) if mallocs > 1.5 { t.Errorf("mallocs = %v; want 1", mallocs) } } func TestParseHexUint(t *testing.T) { for i := uint64(0); i <= 1234; i++ { line := []byte(fmt.Sprintf("%x", i)) got, err := parseHexUint(line) if err != nil { t.Fatalf("on %d: %v", i, err) } if got != i { t.Errorf("for input %q = %d; want %d", line, got, i) } } _, err := parseHexUint([]byte("bogus")) if err == nil { t.Error("expected error on bogus input") } } ubuntu-push-0.2.1+14.04.20140423.1/http13client/transfer_test.go0000644000015301777760000000275412325724711024315 0ustar pbusernogroup00000000000000// Copyright 2012 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 http import ( "bufio" "io" "strings" "testing" ) func TestBodyReadBadTrailer(t *testing.T) { b := &body{ src: strings.NewReader("foobar"), hdr: true, // force reading the trailer r: bufio.NewReader(strings.NewReader("")), } buf := make([]byte, 7) n, err := b.Read(buf[:3]) got := string(buf[:n]) if got != "foo" || err != nil { t.Fatalf(`first Read = %d (%q), %v; want 3 ("foo")`, n, got, err) } n, err = b.Read(buf[:]) got = string(buf[:n]) if got != "bar" || err != nil { t.Fatalf(`second Read = %d (%q), %v; want 3 ("bar")`, n, got, err) } n, err = b.Read(buf[:]) got = string(buf[:n]) if err == nil { t.Errorf("final Read was successful (%q), expected error from trailer read", got) } } func TestFinalChunkedBodyReadEOF(t *testing.T) { res, err := ReadResponse(bufio.NewReader(strings.NewReader( "HTTP/1.1 200 OK\r\n"+ "Transfer-Encoding: chunked\r\n"+ "\r\n"+ "0a\r\n"+ "Body here\n\r\n"+ "09\r\n"+ "continued\r\n"+ "0\r\n"+ "\r\n")), nil) if err != nil { t.Fatal(err) } want := "Body here\ncontinued" buf := make([]byte, len(want)) n, err := res.Body.Read(buf) if n != len(want) || err != io.EOF { t.Logf("body = %#v", res.Body) t.Errorf("Read = %v, %v; want %d, EOF", n, err, len(want)) } if string(buf) != want { t.Errorf("buf = %q; want %q", buf, want) } } ubuntu-push-0.2.1+14.04.20140423.1/http13client/serve_test.go0000644000015301777760000000230612325724711023606 0ustar pbusernogroup00000000000000// 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 http_test import ( "io" "net" "time" ) type dummyAddr string func (a dummyAddr) Network() string { return string(a) } func (a dummyAddr) String() string { return string(a) } type noopConn struct{} func (noopConn) LocalAddr() net.Addr { return dummyAddr("local-addr") } func (noopConn) RemoteAddr() net.Addr { return dummyAddr("remote-addr") } func (noopConn) SetDeadline(t time.Time) error { return nil } func (noopConn) SetReadDeadline(t time.Time) error { return nil } func (noopConn) SetWriteDeadline(t time.Time) error { return nil } type rwTestConn struct { io.Reader io.Writer noopConn closeFunc func() error // called if non-nil closec chan bool // else, if non-nil, send value to it on close } func (c *rwTestConn) Close() error { if c.closeFunc != nil { return c.closeFunc() } select { case c.closec <- true: default: } return nil } type neverEnding byte func (b neverEnding) Read(p []byte) (n int, err error) { for i := range p { p[i] = byte(b) } return len(p), nil } ubuntu-push-0.2.1+14.04.20140423.1/http13client/lex.go0000644000015301777760000000257312325724711022221 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http // This file deals with lexical matters of HTTP var isTokenTable = [127]bool{ '!': true, '#': true, '$': true, '%': true, '&': true, '\'': true, '*': true, '+': true, '-': true, '.': true, '0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, 'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true, 'H': true, 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true, 'O': true, 'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true, 'W': true, 'V': true, 'X': true, 'Y': true, 'Z': true, '^': true, '_': true, '`': true, 'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true, 'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true, 'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true, 'x': true, 'y': true, 'z': true, '|': true, '~': true, } func isToken(r rune) bool { i := int(r) return i < len(isTokenTable) && isTokenTable[i] } func isNotToken(r rune) bool { return !isToken(r) } ubuntu-push-0.2.1+14.04.20140423.1/http13client/server.go0000644000015301777760000000271612325724711022736 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http import ( "fmt" "io" "io/ioutil" "log" "net" "strings" "sync" ) // eofReader is a non-nil io.ReadCloser that always returns EOF. // It embeds a *strings.Reader so it still has a WriteTo method // and io.Copy won't need a buffer. var eofReader = &struct { *strings.Reader io.Closer }{ strings.NewReader(""), ioutil.NopCloser(nil), } // loggingConn is used for debugging. type loggingConn struct { name string net.Conn } var ( uniqNameMu sync.Mutex uniqNameNext = make(map[string]int) ) func newLoggingConn(baseName string, c net.Conn) net.Conn { uniqNameMu.Lock() defer uniqNameMu.Unlock() uniqNameNext[baseName]++ return &loggingConn{ name: fmt.Sprintf("%s-%d", baseName, uniqNameNext[baseName]), Conn: c, } } func (c *loggingConn) Write(p []byte) (n int, err error) { log.Printf("%s.Write(%d) = ....", c.name, len(p)) n, err = c.Conn.Write(p) log.Printf("%s.Write(%d) = %d, %v", c.name, len(p), n, err) return } func (c *loggingConn) Read(p []byte) (n int, err error) { log.Printf("%s.Read(%d) = ....", c.name, len(p)) n, err = c.Conn.Read(p) log.Printf("%s.Read(%d) = %d, %v", c.name, len(p), n, err) return } func (c *loggingConn) Close() (err error) { log.Printf("%s.Close() = ...", c.name) err = c.Conn.Close() log.Printf("%s.Close() = %v", c.name, err) return } ubuntu-push-0.2.1+14.04.20140423.1/http13client/LICENSE0000644000015301777760000000270712325724711022106 0ustar pbusernogroup00000000000000Copyright (c) 2012 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ubuntu-push-0.2.1+14.04.20140423.1/http13client/export_test.go0000644000015301777760000000234012325724711024001 0ustar pbusernogroup00000000000000// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Bridge package to expose http internals to tests in the http_test // package. package http import ( "net" ) func NewLoggingConn(baseName string, c net.Conn) net.Conn { return newLoggingConn(baseName, c) } func (t *Transport) NumPendingRequestsForTesting() int { t.reqMu.Lock() defer t.reqMu.Unlock() return len(t.reqCanceler) } func (t *Transport) IdleConnKeysForTesting() (keys []string) { keys = make([]string, 0) t.idleMu.Lock() defer t.idleMu.Unlock() if t.idleConn == nil { return } for key := range t.idleConn { keys = append(keys, key.String()) } return } func (t *Transport) IdleConnCountForTesting(cacheKey string) int { t.idleMu.Lock() defer t.idleMu.Unlock() if t.idleConn == nil { return 0 } for k, conns := range t.idleConn { if k.String() == cacheKey { return len(conns) } } return 0 } func (t *Transport) IdleConnChMapSizeForTesting() int { t.idleMu.Lock() defer t.idleMu.Unlock() return len(t.idleConnCh) } func ResetCachedEnvironment() { httpProxyEnv.reset() noProxyEnv.reset() } var DefaultUserAgent = defaultUserAgent ubuntu-push-0.2.1+14.04.20140423.1/http13client/cookie_test.go0000644000015301777760000001710012325724711023731 0ustar pbusernogroup00000000000000// 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 http import ( "bytes" "encoding/json" "fmt" "log" "net/http" "os" "reflect" "strings" "testing" "time" ) var writeSetCookiesTests = []struct { Cookie *http.Cookie Raw string }{ { &http.Cookie{Name: "cookie-1", Value: "v$1"}, "cookie-1=v$1", }, { &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}, "cookie-2=two; Max-Age=3600", }, { &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"}, "cookie-3=three; Domain=example.com", }, { &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"}, "cookie-4=four; Path=/restricted/", }, { &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"}, "cookie-5=five", }, { &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"}, "cookie-6=six", }, { &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"}, "cookie-7=seven; Domain=127.0.0.1", }, { &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, "cookie-8=eight", }, } func TestWriteSetCookies(t *testing.T) { defer log.SetOutput(os.Stderr) var logbuf bytes.Buffer log.SetOutput(&logbuf) for i, tt := range writeSetCookiesTests { if g, e := tt.Cookie.String(), tt.Raw; g != e { t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, e, g) continue } } if got, sub := logbuf.String(), "dropping domain attribute"; !strings.Contains(got, sub) { t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) } } type headerOnlyResponseWriter http.Header func (ho headerOnlyResponseWriter) Header() http.Header { return http.Header(ho) } func (ho headerOnlyResponseWriter) Write([]byte) (int, error) { panic("NOIMPL") } func (ho headerOnlyResponseWriter) WriteHeader(int) { panic("NOIMPL") } func TestSetCookie(t *testing.T) { m := make(http.Header) http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"}) http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}) if l := len(m["Set-Cookie"]); l != 2 { t.Fatalf("expected %d cookies, got %d", 2, l) } if g, e := m["Set-Cookie"][0], "cookie-1=one; Path=/restricted/"; g != e { t.Errorf("cookie #1: want %q, got %q", e, g) } if g, e := m["Set-Cookie"][1], "cookie-2=two; Max-Age=3600"; g != e { t.Errorf("cookie #2: want %q, got %q", e, g) } } var addCookieTests = []struct { Cookies []*http.Cookie Raw string }{ { []*http.Cookie{}, "", }, { []*http.Cookie{{Name: "cookie-1", Value: "v$1"}}, "cookie-1=v$1", }, { []*http.Cookie{ {Name: "cookie-1", Value: "v$1"}, {Name: "cookie-2", Value: "v$2"}, {Name: "cookie-3", Value: "v$3"}, }, "cookie-1=v$1; cookie-2=v$2; cookie-3=v$3", }, } func TestAddCookie(t *testing.T) { for i, tt := range addCookieTests { req, _ := NewRequest("GET", "http://example.com/", nil) for _, c := range tt.Cookies { req.AddCookie(c) } if g := req.Header.Get("Cookie"); g != tt.Raw { t.Errorf("Test %d:\nwant: %s\n got: %s\n", i, tt.Raw, g) continue } } } var readSetCookiesTests = []struct { Header http.Header Cookies []*http.Cookie }{ { http.Header{"Set-Cookie": {"Cookie-1=v$1"}}, []*http.Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, }, { http.Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, []*http.Cookie{{ Name: "NID", Value: "99=YsDT5i3E-CXax-", Path: "/", Domain: ".google.ch", HttpOnly: true, Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC), RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT", Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly", }}, }, { http.Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, []*http.Cookie{{ Name: ".ASPXAUTH", Value: "7E3AA", Path: "/", Expires: time.Date(2012, 3, 7, 14, 25, 6, 0, time.UTC), RawExpires: "Wed, 07-Mar-2012 14:25:06 GMT", HttpOnly: true, Raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly", }}, }, { http.Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, []*http.Cookie{{ Name: "ASP.NET_SessionId", Value: "foo", Path: "/", HttpOnly: true, Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly", }}, }, // TODO(bradfitz): users have reported seeing this in the // wild, but do browsers handle it? RFC 6265 just says "don't // do that" (section 3) and then never mentions header folding // again. // Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly, .ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, } func toJSON(v interface{}) string { b, err := json.Marshal(v) if err != nil { return fmt.Sprintf("%#v", v) } return string(b) } func TestReadSetCookies(t *testing.T) { for i, tt := range readSetCookiesTests { for n := 0; n < 2; n++ { // to verify readSetCookies doesn't mutate its input c := readSetCookies(tt.Header) if !reflect.DeepEqual(c, tt.Cookies) { t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.Cookies)) continue } } } } var readCookiesTests = []struct { Header http.Header Filter string Cookies []*http.Cookie }{ { http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, "", []*http.Cookie{ {Name: "Cookie-1", Value: "v$1"}, {Name: "c2", Value: "v2"}, }, }, { http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, "c2", []*http.Cookie{ {Name: "c2", Value: "v2"}, }, }, { http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, "", []*http.Cookie{ {Name: "Cookie-1", Value: "v$1"}, {Name: "c2", Value: "v2"}, }, }, { http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, "c2", []*http.Cookie{ {Name: "c2", Value: "v2"}, }, }, } func TestReadCookies(t *testing.T) { for i, tt := range readCookiesTests { for n := 0; n < 2; n++ { // to verify readCookies doesn't mutate its input c := readCookies(tt.Header, tt.Filter) if !reflect.DeepEqual(c, tt.Cookies) { t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.Cookies)) continue } } } } func TestCookieSanitizeValue(t *testing.T) { defer log.SetOutput(os.Stderr) var logbuf bytes.Buffer log.SetOutput(&logbuf) tests := []struct { in, want string }{ {"foo", "foo"}, {"foo bar", "foobar"}, {"\x00\x7e\x7f\x80", "\x7e"}, {`"withquotes"`, "withquotes"}, } for _, tt := range tests { if got := sanitizeCookieValue(tt.in); got != tt.want { t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want) } } if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) } } func TestCookieSanitizePath(t *testing.T) { defer log.SetOutput(os.Stderr) var logbuf bytes.Buffer log.SetOutput(&logbuf) tests := []struct { in, want string }{ {"/path", "/path"}, {"/path with space/", "/path with space/"}, {"/just;no;semicolon\x00orstuff/", "/justnosemicolonorstuff/"}, } for _, tt := range tests { if got := sanitizeCookiePath(tt.in); got != tt.want { t.Errorf("sanitizeCookiePath(%q) = %q; want %q", tt.in, got, tt.want) } } if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) { t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got) } } ubuntu-push-0.2.1+14.04.20140423.1/http13client/_patches/0000755000015301777760000000000012325725172022663 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/http13client/_patches/fix_status.patch0000644000015301777760000000721712325724711026102 0ustar pbusernogroup00000000000000=== modified file 'http13client/client.go' --- http13client/client.go 2014-03-19 23:13:58 +0000 +++ http13client/client.go 2014-03-19 23:38:11 +0000 @@ -210,7 +210,7 @@ // automatically redirect. func shouldRedirectGet(statusCode int) bool { switch statusCode { - case StatusMovedPermanently, StatusFound, StatusSeeOther, StatusTemporaryRedirect: + case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect: return true } return false @@ -220,7 +220,7 @@ // automatically redirect. func shouldRedirectPost(statusCode int) bool { switch statusCode { - case StatusFound, StatusSeeOther: + case http.StatusFound, http.StatusSeeOther: return true } return false === modified file 'http13client/client_test.go' --- http13client/client_test.go 2014-03-19 23:13:58 +0000 +++ http13client/client_test.go 2014-03-19 23:39:48 +0000 @@ -202,7 +202,7 @@ } } if n < 15 { - http.Redirect(w, r, fmt.Sprintf("/?n=%d", n+1), StatusFound) + http.Redirect(w, r, fmt.Sprintf("/?n=%d", n+1), http.StatusFound) return } fmt.Fprintf(w, "n=%d", n) @@ -324,7 +324,7 @@ } if r.URL.Path == "/" { http.SetCookie(w, expectedCookies[1]) - http.Redirect(w, r, "/second", StatusMovedPermanently) + http.Redirect(w, r, "/second", http.StatusMovedPermanently) } else { http.SetCookie(w, expectedCookies[2]) w.Write([]byte("hello")) @@ -783,7 +783,7 @@ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { sawRoot <- true - http.Redirect(w, r, "/slow", StatusFound) + http.Redirect(w, r, "/slow", http.StatusFound) return } if r.URL.Path == "/slow" { @@ -844,7 +844,7 @@ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { saw <- r.RemoteAddr if r.URL.Path == "/" { - http.Redirect(w, r, "/foo", StatusFound) // which includes a body + http.Redirect(w, r, "/foo", http.StatusFound) // which includes a body } })) defer ts.Close() === modified file 'http13client/request_test.go' --- http13client/request_test.go 2014-03-19 23:13:58 +0000 +++ http13client/request_test.go 2014-03-19 23:40:12 +0000 @@ -164,11 +164,11 @@ switch r.URL.Path { case "/": w.Header().Set("Location", "/foo/") - w.WriteHeader(StatusSeeOther) + w.WriteHeader(http.StatusSeeOther) case "/foo/": fmt.Fprintf(w, "foo") default: - w.WriteHeader(StatusBadRequest) + w.WriteHeader(http.StatusBadRequest) } })) defer ts.Close() === modified file 'http13client/response.go' --- http13client/response.go 2014-03-19 23:13:58 +0000 +++ http13client/response.go 2014-03-19 23:38:56 +0000 @@ -204,9 +204,8 @@ // Status line text := r.Status if text == "" { - var ok bool - text, ok = statusText[r.StatusCode] - if !ok { + text = http.StatusText(r.StatusCode) + if text == "" { text = "status code " + strconv.Itoa(r.StatusCode) } } === modified file 'http13client/transport_test.go' --- http13client/transport_test.go 2014-03-19 23:13:58 +0000 +++ http13client/transport_test.go 2014-03-19 23:40:39 +0000 @@ -968,7 +968,7 @@ defer afterTest(t) const deniedMsg = "sorry, denied." ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, deniedMsg, StatusUnauthorized) + http.Error(w, deniedMsg, http.StatusUnauthorized) })) defer ts.Close() tr := &Transport{} @@ -992,7 +992,7 @@ func TestChunkedNoContent(t *testing.T) { defer afterTest(t) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(StatusNoContent) + w.WriteHeader(http.StatusNoContent) })) defer ts.Close() ubuntu-push-0.2.1+14.04.20140423.1/http13client/_patches/no_keepalive.patch0000644000015301777760000000051712325724711026346 0ustar pbusernogroup00000000000000--- a/transport.go 2014-03-19 19:53:49.401406590 +0000 +++ b/transport.go 2014-03-19 19:54:05.817611304 +0000 @@ -34,7 +34,7 @@ Proxy: ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, + // KeepAlive: 30 * time.Second, }).Dial, TLSHandshakeTimeout: 10 * time.Second, } ubuntu-push-0.2.1+14.04.20140423.1/http13client/_patches/empty_server.patch0000644000015301777760000041435012325724711026435 0ustar pbusernogroup00000000000000=== modified file 'http13client/serve_test.go' --- http13client/serve_test.go 2014-03-19 21:38:56 +0000 +++ http13client/serve_test.go 2014-03-19 22:27:37 +0000 @@ -2,60 +2,15 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// End-to-end serving tests - package http_test import ( - "bufio" - "bytes" - "crypto/tls" - "errors" - "fmt" "io" - "io/ioutil" - "log" "net" - . "launchpad.net/ubuntu-push/http13client" - "net/http/httptest" - "net/http/httputil" - "net/url" - "os" - "os/exec" - "reflect" - "runtime" - "strconv" - "strings" - "sync" - "sync/atomic" - "syscall" - "testing" "time" ) type dummyAddr string -type oneConnListener struct { - conn net.Conn -} - -func (l *oneConnListener) Accept() (c net.Conn, err error) { - c = l.conn - if c == nil { - err = io.EOF - return - } - err = nil - l.conn = nil - return -} - -func (l *oneConnListener) Close() error { - return nil -} - -func (l *oneConnListener) Addr() net.Addr { - return dummyAddr("test-address") -} func (a dummyAddr) Network() string { return string(a) @@ -93,1289 +48,6 @@ return nil } -type testConn struct { - readBuf bytes.Buffer - writeBuf bytes.Buffer - closec chan bool // if non-nil, send value to it on close - noopConn -} - -func (c *testConn) Read(b []byte) (int, error) { - return c.readBuf.Read(b) -} - -func (c *testConn) Write(b []byte) (int, error) { - return c.writeBuf.Write(b) -} - -func (c *testConn) Close() error { - select { - case c.closec <- true: - default: - } - return nil -} - -// reqBytes treats req as a request (with \n delimiters) and returns it with \r\n delimiters, -// ending in \r\n\r\n -func reqBytes(req string) []byte { - return []byte(strings.Replace(strings.TrimSpace(req), "\n", "\r\n", -1) + "\r\n\r\n") -} - -type handlerTest struct { - handler Handler -} - -func newHandlerTest(h Handler) handlerTest { - return handlerTest{h} -} - -func (ht handlerTest) rawResponse(req string) string { - reqb := reqBytes(req) - var output bytes.Buffer - conn := &rwTestConn{ - Reader: bytes.NewReader(reqb), - Writer: &output, - closec: make(chan bool, 1), - } - ln := &oneConnListener{conn: conn} - go Serve(ln, ht.handler) - <-conn.closec - return output.String() -} - -func TestConsumingBodyOnNextConn(t *testing.T) { - conn := new(testConn) - for i := 0; i < 2; i++ { - conn.readBuf.Write([]byte( - "POST / HTTP/1.1\r\n" + - "Host: test\r\n" + - "Content-Length: 11\r\n" + - "\r\n" + - "foo=1&bar=1")) - } - - reqNum := 0 - ch := make(chan *Request) - servech := make(chan error) - listener := &oneConnListener{conn} - handler := func(res ResponseWriter, req *Request) { - reqNum++ - ch <- req - } - - go func() { - servech <- Serve(listener, HandlerFunc(handler)) - }() - - var req *Request - req = <-ch - if req == nil { - t.Fatal("Got nil first request.") - } - if req.Method != "POST" { - t.Errorf("For request #1's method, got %q; expected %q", - req.Method, "POST") - } - - req = <-ch - if req == nil { - t.Fatal("Got nil first request.") - } - if req.Method != "POST" { - t.Errorf("For request #2's method, got %q; expected %q", - req.Method, "POST") - } - - if serveerr := <-servech; serveerr != io.EOF { - t.Errorf("Serve returned %q; expected EOF", serveerr) - } -} - -type stringHandler string - -func (s stringHandler) ServeHTTP(w ResponseWriter, r *Request) { - w.Header().Set("Result", string(s)) -} - -var handlers = []struct { - pattern string - msg string -}{ - {"/", "Default"}, - {"/someDir/", "someDir"}, - {"someHost.com/someDir/", "someHost.com/someDir"}, -} - -var vtests = []struct { - url string - expected string -}{ - {"http://localhost/someDir/apage", "someDir"}, - {"http://localhost/otherDir/apage", "Default"}, - {"http://someHost.com/someDir/apage", "someHost.com/someDir"}, - {"http://otherHost.com/someDir/apage", "someDir"}, - {"http://otherHost.com/aDir/apage", "Default"}, - // redirections for trees - {"http://localhost/someDir", "/someDir/"}, - {"http://someHost.com/someDir", "/someDir/"}, -} - -func TestHostHandlers(t *testing.T) { - defer afterTest(t) - mux := NewServeMux() - for _, h := range handlers { - mux.Handle(h.pattern, stringHandler(h.msg)) - } - ts := httptest.NewServer(mux) - defer ts.Close() - - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - cc := httputil.NewClientConn(conn, nil) - for _, vt := range vtests { - var r *Response - var req Request - if req.URL, err = url.Parse(vt.url); err != nil { - t.Errorf("cannot parse url: %v", err) - continue - } - if err := cc.Write(&req); err != nil { - t.Errorf("writing request: %v", err) - continue - } - r, err := cc.Read(&req) - if err != nil { - t.Errorf("reading response: %v", err) - continue - } - switch r.StatusCode { - case StatusOK: - s := r.Header.Get("Result") - if s != vt.expected { - t.Errorf("Get(%q) = %q, want %q", vt.url, s, vt.expected) - } - case StatusMovedPermanently: - s := r.Header.Get("Location") - if s != vt.expected { - t.Errorf("Get(%q) = %q, want %q", vt.url, s, vt.expected) - } - default: - t.Errorf("Get(%q) unhandled status code %d", vt.url, r.StatusCode) - } - } -} - -var serveMuxRegister = []struct { - pattern string - h Handler -}{ - {"/dir/", serve(200)}, - {"/search", serve(201)}, - {"codesearch.google.com/search", serve(202)}, - {"codesearch.google.com/", serve(203)}, - {"example.com/", HandlerFunc(checkQueryStringHandler)}, -} - -// serve returns a handler that sends a response with the given code. -func serve(code int) HandlerFunc { - return func(w ResponseWriter, r *Request) { - w.WriteHeader(code) - } -} - -// checkQueryStringHandler checks if r.URL.RawQuery has the same value -// as the URL excluding the scheme and the query string and sends 200 -// response code if it is, 500 otherwise. -func checkQueryStringHandler(w ResponseWriter, r *Request) { - u := *r.URL - u.Scheme = "http" - u.Host = r.Host - u.RawQuery = "" - if "http://"+r.URL.RawQuery == u.String() { - w.WriteHeader(200) - } else { - w.WriteHeader(500) - } -} - -var serveMuxTests = []struct { - method string - host string - path string - code int - pattern string -}{ - {"GET", "google.com", "/", 404, ""}, - {"GET", "google.com", "/dir", 301, "/dir/"}, - {"GET", "google.com", "/dir/", 200, "/dir/"}, - {"GET", "google.com", "/dir/file", 200, "/dir/"}, - {"GET", "google.com", "/search", 201, "/search"}, - {"GET", "google.com", "/search/", 404, ""}, - {"GET", "google.com", "/search/foo", 404, ""}, - {"GET", "codesearch.google.com", "/search", 202, "codesearch.google.com/search"}, - {"GET", "codesearch.google.com", "/search/", 203, "codesearch.google.com/"}, - {"GET", "codesearch.google.com", "/search/foo", 203, "codesearch.google.com/"}, - {"GET", "codesearch.google.com", "/", 203, "codesearch.google.com/"}, - {"GET", "images.google.com", "/search", 201, "/search"}, - {"GET", "images.google.com", "/search/", 404, ""}, - {"GET", "images.google.com", "/search/foo", 404, ""}, - {"GET", "google.com", "/../search", 301, "/search"}, - {"GET", "google.com", "/dir/..", 301, ""}, - {"GET", "google.com", "/dir/..", 301, ""}, - {"GET", "google.com", "/dir/./file", 301, "/dir/"}, - - // The /foo -> /foo/ redirect applies to CONNECT requests - // but the path canonicalization does not. - {"CONNECT", "google.com", "/dir", 301, "/dir/"}, - {"CONNECT", "google.com", "/../search", 404, ""}, - {"CONNECT", "google.com", "/dir/..", 200, "/dir/"}, - {"CONNECT", "google.com", "/dir/..", 200, "/dir/"}, - {"CONNECT", "google.com", "/dir/./file", 200, "/dir/"}, -} - -func TestServeMuxHandler(t *testing.T) { - mux := NewServeMux() - for _, e := range serveMuxRegister { - mux.Handle(e.pattern, e.h) - } - - for _, tt := range serveMuxTests { - r := &Request{ - Method: tt.method, - Host: tt.host, - URL: &url.URL{ - Path: tt.path, - }, - } - h, pattern := mux.Handler(r) - rr := httptest.NewRecorder() - h.ServeHTTP(rr, r) - if pattern != tt.pattern || rr.Code != tt.code { - t.Errorf("%s %s %s = %d, %q, want %d, %q", tt.method, tt.host, tt.path, rr.Code, pattern, tt.code, tt.pattern) - } - } -} - -var serveMuxTests2 = []struct { - method string - host string - url string - code int - redirOk bool -}{ - {"GET", "google.com", "/", 404, false}, - {"GET", "example.com", "/test/?example.com/test/", 200, false}, - {"GET", "example.com", "test/?example.com/test/", 200, true}, -} - -// TestServeMuxHandlerRedirects tests that automatic redirects generated by -// mux.Handler() shouldn't clear the request's query string. -func TestServeMuxHandlerRedirects(t *testing.T) { - mux := NewServeMux() - for _, e := range serveMuxRegister { - mux.Handle(e.pattern, e.h) - } - - for _, tt := range serveMuxTests2 { - tries := 1 - turl := tt.url - for tries > 0 { - u, e := url.Parse(turl) - if e != nil { - t.Fatal(e) - } - r := &Request{ - Method: tt.method, - Host: tt.host, - URL: u, - } - h, _ := mux.Handler(r) - rr := httptest.NewRecorder() - h.ServeHTTP(rr, r) - if rr.Code != 301 { - if rr.Code != tt.code { - t.Errorf("%s %s %s = %d, want %d", tt.method, tt.host, tt.url, rr.Code, tt.code) - } - break - } - if !tt.redirOk { - t.Errorf("%s %s %s, unexpected redirect", tt.method, tt.host, tt.url) - break - } - turl = rr.HeaderMap.Get("Location") - tries-- - } - if tries < 0 { - t.Errorf("%s %s %s, too many redirects", tt.method, tt.host, tt.url) - } - } -} - -// Tests for http://code.google.com/p/go/issues/detail?id=900 -func TestMuxRedirectLeadingSlashes(t *testing.T) { - paths := []string{"//foo.txt", "///foo.txt", "/../../foo.txt"} - for _, path := range paths { - req, err := ReadRequest(bufio.NewReader(strings.NewReader("GET " + path + " HTTP/1.1\r\nHost: test\r\n\r\n"))) - if err != nil { - t.Errorf("%s", err) - } - mux := NewServeMux() - resp := httptest.NewRecorder() - - mux.ServeHTTP(resp, req) - - if loc, expected := resp.Header().Get("Location"), "/foo.txt"; loc != expected { - t.Errorf("Expected Location header set to %q; got %q", expected, loc) - return - } - - if code, expected := resp.Code, StatusMovedPermanently; code != expected { - t.Errorf("Expected response code of StatusMovedPermanently; got %d", code) - return - } - } -} - -func TestServerTimeouts(t *testing.T) { - if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") - } - defer afterTest(t) - reqNum := 0 - ts := httptest.NewUnstartedServer(HandlerFunc(func(res ResponseWriter, req *Request) { - reqNum++ - fmt.Fprintf(res, "req=%d", reqNum) - })) - ts.Config.ReadTimeout = 250 * time.Millisecond - ts.Config.WriteTimeout = 250 * time.Millisecond - ts.Start() - defer ts.Close() - - // Hit the HTTP server successfully. - tr := &Transport{DisableKeepAlives: true} // they interfere with this test - defer tr.CloseIdleConnections() - c := &Client{Transport: tr} - r, err := c.Get(ts.URL) - if err != nil { - t.Fatalf("http Get #1: %v", err) - } - got, _ := ioutil.ReadAll(r.Body) - expected := "req=1" - if string(got) != expected { - t.Errorf("Unexpected response for request #1; got %q; expected %q", - string(got), expected) - } - - // Slow client that should timeout. - t1 := time.Now() - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatalf("Dial: %v", err) - } - buf := make([]byte, 1) - n, err := conn.Read(buf) - latency := time.Since(t1) - if n != 0 || err != io.EOF { - t.Errorf("Read = %v, %v, wanted %v, %v", n, err, 0, io.EOF) - } - if latency < 200*time.Millisecond /* fudge from 250 ms above */ { - t.Errorf("got EOF after %s, want >= %s", latency, 200*time.Millisecond) - } - - // Hit the HTTP server successfully again, verifying that the - // previous slow connection didn't run our handler. (that we - // get "req=2", not "req=3") - r, err = Get(ts.URL) - if err != nil { - t.Fatalf("http Get #2: %v", err) - } - got, _ = ioutil.ReadAll(r.Body) - expected = "req=2" - if string(got) != expected { - t.Errorf("Get #2 got %q, want %q", string(got), expected) - } - - if !testing.Short() { - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer conn.Close() - go io.Copy(ioutil.Discard, conn) - for i := 0; i < 5; i++ { - _, err := conn.Write([]byte("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")) - if err != nil { - t.Fatalf("on write %d: %v", i, err) - } - time.Sleep(ts.Config.ReadTimeout / 2) - } - } -} - -// golang.org/issue/4741 -- setting only a write timeout that triggers -// shouldn't cause a handler to block forever on reads (next HTTP -// request) that will never happen. -func TestOnlyWriteTimeout(t *testing.T) { - if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") - } - defer afterTest(t) - var conn net.Conn - var afterTimeoutErrc = make(chan error, 1) - ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, req *Request) { - buf := make([]byte, 512<<10) - _, err := w.Write(buf) - if err != nil { - t.Errorf("handler Write error: %v", err) - return - } - conn.SetWriteDeadline(time.Now().Add(-30 * time.Second)) - _, err = w.Write(buf) - afterTimeoutErrc <- err - })) - ts.Listener = trackLastConnListener{ts.Listener, &conn} - ts.Start() - defer ts.Close() - - tr := &Transport{DisableKeepAlives: false} - defer tr.CloseIdleConnections() - c := &Client{Transport: tr} - - errc := make(chan error) - go func() { - res, err := c.Get(ts.URL) - if err != nil { - errc <- err - return - } - _, err = io.Copy(ioutil.Discard, res.Body) - errc <- err - }() - select { - case err := <-errc: - if err == nil { - t.Errorf("expected an error from Get request") - } - case <-time.After(5 * time.Second): - t.Fatal("timeout waiting for Get error") - } - if err := <-afterTimeoutErrc; err == nil { - t.Error("expected write error after timeout") - } -} - -// trackLastConnListener tracks the last net.Conn that was accepted. -type trackLastConnListener struct { - net.Listener - last *net.Conn // destination -} - -func (l trackLastConnListener) Accept() (c net.Conn, err error) { - c, err = l.Listener.Accept() - *l.last = c - return -} - -// TestIdentityResponse verifies that a handler can unset -func TestIdentityResponse(t *testing.T) { - defer afterTest(t) - handler := HandlerFunc(func(rw ResponseWriter, req *Request) { - rw.Header().Set("Content-Length", "3") - rw.Header().Set("Transfer-Encoding", req.FormValue("te")) - switch { - case req.FormValue("overwrite") == "1": - _, err := rw.Write([]byte("foo TOO LONG")) - if err != ErrContentLength { - t.Errorf("expected ErrContentLength; got %v", err) - } - case req.FormValue("underwrite") == "1": - rw.Header().Set("Content-Length", "500") - rw.Write([]byte("too short")) - default: - rw.Write([]byte("foo")) - } - }) - - ts := httptest.NewServer(handler) - defer ts.Close() - - // Note: this relies on the assumption (which is true) that - // Get sends HTTP/1.1 or greater requests. Otherwise the - // server wouldn't have the choice to send back chunked - // responses. - for _, te := range []string{"", "identity"} { - url := ts.URL + "/?te=" + te - res, err := Get(url) - if err != nil { - t.Fatalf("error with Get of %s: %v", url, err) - } - if cl, expected := res.ContentLength, int64(3); cl != expected { - t.Errorf("for %s expected res.ContentLength of %d; got %d", url, expected, cl) - } - if cl, expected := res.Header.Get("Content-Length"), "3"; cl != expected { - t.Errorf("for %s expected Content-Length header of %q; got %q", url, expected, cl) - } - if tl, expected := len(res.TransferEncoding), 0; tl != expected { - t.Errorf("for %s expected len(res.TransferEncoding) of %d; got %d (%v)", - url, expected, tl, res.TransferEncoding) - } - res.Body.Close() - } - - // Verify that ErrContentLength is returned - url := ts.URL + "/?overwrite=1" - res, err := Get(url) - if err != nil { - t.Fatalf("error with Get of %s: %v", url, err) - } - res.Body.Close() - - // Verify that the connection is closed when the declared Content-Length - // is larger than what the handler wrote. - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatalf("error dialing: %v", err) - } - _, err = conn.Write([]byte("GET /?underwrite=1 HTTP/1.1\r\nHost: foo\r\n\r\n")) - if err != nil { - t.Fatalf("error writing: %v", err) - } - - // The ReadAll will hang for a failing test, so use a Timer to - // fail explicitly. - goTimeout(t, 2*time.Second, func() { - got, _ := ioutil.ReadAll(conn) - expectedSuffix := "\r\n\r\ntoo short" - if !strings.HasSuffix(string(got), expectedSuffix) { - t.Errorf("Expected output to end with %q; got response body %q", - expectedSuffix, string(got)) - } - }) -} - -func testTCPConnectionCloses(t *testing.T, req string, h Handler) { - defer afterTest(t) - s := httptest.NewServer(h) - defer s.Close() - - conn, err := net.Dial("tcp", s.Listener.Addr().String()) - if err != nil { - t.Fatal("dial error:", err) - } - defer conn.Close() - - _, err = fmt.Fprint(conn, req) - if err != nil { - t.Fatal("print error:", err) - } - - r := bufio.NewReader(conn) - res, err := ReadResponse(r, &Request{Method: "GET"}) - if err != nil { - t.Fatal("ReadResponse error:", err) - } - - didReadAll := make(chan bool, 1) - go func() { - select { - case <-time.After(5 * time.Second): - t.Error("body not closed after 5s") - return - case <-didReadAll: - } - }() - - _, err = ioutil.ReadAll(r) - if err != nil { - t.Fatal("read error:", err) - } - didReadAll <- true - - if !res.Close { - t.Errorf("Response.Close = false; want true") - } -} - -// TestServeHTTP10Close verifies that HTTP/1.0 requests won't be kept alive. -func TestServeHTTP10Close(t *testing.T) { - testTCPConnectionCloses(t, "GET / HTTP/1.0\r\n\r\n", HandlerFunc(func(w ResponseWriter, r *Request) { - ServeFile(w, r, "testdata/file") - })) -} - -// TestClientCanClose verifies that clients can also force a connection to close. -func TestClientCanClose(t *testing.T) { - testTCPConnectionCloses(t, "GET / HTTP/1.1\r\nConnection: close\r\n\r\n", HandlerFunc(func(w ResponseWriter, r *Request) { - // Nothing. - })) -} - -// TestHandlersCanSetConnectionClose verifies that handlers can force a connection to close, -// even for HTTP/1.1 requests. -func TestHandlersCanSetConnectionClose11(t *testing.T) { - testTCPConnectionCloses(t, "GET / HTTP/1.1\r\n\r\n", HandlerFunc(func(w ResponseWriter, r *Request) { - w.Header().Set("Connection", "close") - })) -} - -func TestHandlersCanSetConnectionClose10(t *testing.T) { - testTCPConnectionCloses(t, "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n", HandlerFunc(func(w ResponseWriter, r *Request) { - w.Header().Set("Connection", "close") - })) -} - -func TestSetsRemoteAddr(t *testing.T) { - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - fmt.Fprintf(w, "%s", r.RemoteAddr) - })) - defer ts.Close() - - res, err := Get(ts.URL) - if err != nil { - t.Fatalf("Get error: %v", err) - } - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatalf("ReadAll error: %v", err) - } - ip := string(body) - if !strings.HasPrefix(ip, "127.0.0.1:") && !strings.HasPrefix(ip, "[::1]:") { - t.Fatalf("Expected local addr; got %q", ip) - } -} - -func TestChunkedResponseHeaders(t *testing.T) { - defer afterTest(t) - log.SetOutput(ioutil.Discard) // is noisy otherwise - defer log.SetOutput(os.Stderr) - - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - w.Header().Set("Content-Length", "intentional gibberish") // we check that this is deleted - w.(Flusher).Flush() - fmt.Fprintf(w, "I am a chunked response.") - })) - defer ts.Close() - - res, err := Get(ts.URL) - if err != nil { - t.Fatalf("Get error: %v", err) - } - defer res.Body.Close() - if g, e := res.ContentLength, int64(-1); g != e { - t.Errorf("expected ContentLength of %d; got %d", e, g) - } - if g, e := res.TransferEncoding, []string{"chunked"}; !reflect.DeepEqual(g, e) { - t.Errorf("expected TransferEncoding of %v; got %v", e, g) - } - if _, haveCL := res.Header["Content-Length"]; haveCL { - t.Errorf("Unexpected Content-Length") - } -} - -// Test304Responses verifies that 304s don't declare that they're -// chunking in their response headers and aren't allowed to produce -// output. -func Test304Responses(t *testing.T) { - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - w.WriteHeader(StatusNotModified) - _, err := w.Write([]byte("illegal body")) - if err != ErrBodyNotAllowed { - t.Errorf("on Write, expected ErrBodyNotAllowed, got %v", err) - } - })) - defer ts.Close() - res, err := Get(ts.URL) - if err != nil { - t.Error(err) - } - if len(res.TransferEncoding) > 0 { - t.Errorf("expected no TransferEncoding; got %v", res.TransferEncoding) - } - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Error(err) - } - if len(body) > 0 { - t.Errorf("got unexpected body %q", string(body)) - } -} - -// TestHeadResponses verifies that all MIME type sniffing and Content-Length -// counting of GET requests also happens on HEAD requests. -func TestHeadResponses(t *testing.T) { - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - _, err := w.Write([]byte("")) - if err != nil { - t.Errorf("ResponseWriter.Write: %v", err) - } - - // Also exercise the ReaderFrom path - _, err = io.Copy(w, strings.NewReader("789a")) - if err != nil { - t.Errorf("Copy(ResponseWriter, ...): %v", err) - } - })) - defer ts.Close() - res, err := Head(ts.URL) - if err != nil { - t.Error(err) - } - if len(res.TransferEncoding) > 0 { - t.Errorf("expected no TransferEncoding; got %v", res.TransferEncoding) - } - if ct := res.Header.Get("Content-Type"); ct != "text/html; charset=utf-8" { - t.Errorf("Content-Type: %q; want text/html; charset=utf-8", ct) - } - if v := res.ContentLength; v != 10 { - t.Errorf("Content-Length: %d; want 10", v) - } - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Error(err) - } - if len(body) > 0 { - t.Errorf("got unexpected body %q", string(body)) - } -} - -func TestTLSHandshakeTimeout(t *testing.T) { - if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") - } - defer afterTest(t) - ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) - errc := make(chanWriter, 10) // but only expecting 1 - ts.Config.ReadTimeout = 250 * time.Millisecond - ts.Config.ErrorLog = log.New(errc, "", 0) - ts.StartTLS() - defer ts.Close() - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer conn.Close() - goTimeout(t, 10*time.Second, func() { - var buf [1]byte - n, err := conn.Read(buf[:]) - if err == nil || n != 0 { - t.Errorf("Read = %d, %v; want an error and no bytes", n, err) - } - }) - select { - case v := <-errc: - if !strings.Contains(v, "timeout") && !strings.Contains(v, "TLS handshake") { - t.Errorf("expected a TLS handshake timeout error; got %q", v) - } - case <-time.After(5 * time.Second): - t.Errorf("timeout waiting for logged error") - } -} - -func TestTLSServer(t *testing.T) { - defer afterTest(t) - ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { - if r.TLS != nil { - w.Header().Set("X-TLS-Set", "true") - if r.TLS.HandshakeComplete { - w.Header().Set("X-TLS-HandshakeComplete", "true") - } - } - })) - ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) - defer ts.Close() - - // Connect an idle TCP connection to this server before we run - // our real tests. This idle connection used to block forever - // in the TLS handshake, preventing future connections from - // being accepted. It may prevent future accidental blocking - // in newConn. - idleConn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer idleConn.Close() - goTimeout(t, 10*time.Second, func() { - if !strings.HasPrefix(ts.URL, "https://") { - t.Errorf("expected test TLS server to start with https://, got %q", ts.URL) - return - } - noVerifyTransport := &Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - } - client := &Client{Transport: noVerifyTransport} - res, err := client.Get(ts.URL) - if err != nil { - t.Error(err) - return - } - if res == nil { - t.Errorf("got nil Response") - return - } - defer res.Body.Close() - if res.Header.Get("X-TLS-Set") != "true" { - t.Errorf("expected X-TLS-Set response header") - return - } - if res.Header.Get("X-TLS-HandshakeComplete") != "true" { - t.Errorf("expected X-TLS-HandshakeComplete header") - } - }) -} - -type serverExpectTest struct { - contentLength int // of request body - expectation string // e.g. "100-continue" - readBody bool // whether handler should read the body (if false, sends StatusUnauthorized) - expectedResponse string // expected substring in first line of http response -} - -var serverExpectTests = []serverExpectTest{ - // Normal 100-continues, case-insensitive. - {100, "100-continue", true, "100 Continue"}, - {100, "100-cOntInUE", true, "100 Continue"}, - - // No 100-continue. - {100, "", true, "200 OK"}, - - // 100-continue but requesting client to deny us, - // so it never reads the body. - {100, "100-continue", false, "401 Unauthorized"}, - // Likewise without 100-continue: - {100, "", false, "401 Unauthorized"}, - - // Non-standard expectations are failures - {0, "a-pony", false, "417 Expectation Failed"}, - - // Expect-100 requested but no body - {0, "100-continue", true, "400 Bad Request"}, -} - -// Tests that the server responds to the "Expect" request header -// correctly. -func TestServerExpect(t *testing.T) { - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - // Note using r.FormValue("readbody") because for POST - // requests that would read from r.Body, which we only - // conditionally want to do. - if strings.Contains(r.URL.RawQuery, "readbody=true") { - ioutil.ReadAll(r.Body) - w.Write([]byte("Hi")) - } else { - w.WriteHeader(StatusUnauthorized) - } - })) - defer ts.Close() - - runTest := func(test serverExpectTest) { - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatalf("Dial: %v", err) - } - defer conn.Close() - - // Only send the body immediately if we're acting like an HTTP client - // that doesn't send 100-continue expectations. - writeBody := test.contentLength > 0 && strings.ToLower(test.expectation) != "100-continue" - - go func() { - _, err := fmt.Fprintf(conn, "POST /?readbody=%v HTTP/1.1\r\n"+ - "Connection: close\r\n"+ - "Content-Length: %d\r\n"+ - "Expect: %s\r\nHost: foo\r\n\r\n", - test.readBody, test.contentLength, test.expectation) - if err != nil { - t.Errorf("On test %#v, error writing request headers: %v", test, err) - return - } - if writeBody { - body := strings.Repeat("A", test.contentLength) - _, err = fmt.Fprint(conn, body) - if err != nil { - if !test.readBody { - // Server likely already hung up on us. - // See larger comment below. - t.Logf("On test %#v, acceptable error writing request body: %v", test, err) - return - } - t.Errorf("On test %#v, error writing request body: %v", test, err) - } - } - }() - bufr := bufio.NewReader(conn) - line, err := bufr.ReadString('\n') - if err != nil { - if writeBody && !test.readBody { - // This is an acceptable failure due to a possible TCP race: - // We were still writing data and the server hung up on us. A TCP - // implementation may send a RST if our request body data was known - // to be lost, which may trigger our reads to fail. - // See RFC 1122 page 88. - t.Logf("On test %#v, acceptable error from ReadString: %v", test, err) - return - } - t.Fatalf("On test %#v, ReadString: %v", test, err) - } - if !strings.Contains(line, test.expectedResponse) { - t.Errorf("On test %#v, got first line = %q; want %q", test, line, test.expectedResponse) - } - } - - for _, test := range serverExpectTests { - runTest(test) - } -} - -// Under a ~256KB (maxPostHandlerReadBytes) threshold, the server -// should consume client request bodies that a handler didn't read. -func TestServerUnreadRequestBodyLittle(t *testing.T) { - conn := new(testConn) - body := strings.Repeat("x", 100<<10) - conn.readBuf.Write([]byte(fmt.Sprintf( - "POST / HTTP/1.1\r\n"+ - "Host: test\r\n"+ - "Content-Length: %d\r\n"+ - "\r\n", len(body)))) - conn.readBuf.Write([]byte(body)) - - done := make(chan bool) - - ls := &oneConnListener{conn} - go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) { - defer close(done) - if conn.readBuf.Len() < len(body)/2 { - t.Errorf("on request, read buffer length is %d; expected about 100 KB", conn.readBuf.Len()) - } - rw.WriteHeader(200) - rw.(Flusher).Flush() - if g, e := conn.readBuf.Len(), 0; g != e { - t.Errorf("after WriteHeader, read buffer length is %d; want %d", g, e) - } - if c := rw.Header().Get("Connection"); c != "" { - t.Errorf(`Connection header = %q; want ""`, c) - } - })) - <-done -} - -// Over a ~256KB (maxPostHandlerReadBytes) threshold, the server -// should ignore client request bodies that a handler didn't read -// and close the connection. -func TestServerUnreadRequestBodyLarge(t *testing.T) { - conn := new(testConn) - body := strings.Repeat("x", 1<<20) - conn.readBuf.Write([]byte(fmt.Sprintf( - "POST / HTTP/1.1\r\n"+ - "Host: test\r\n"+ - "Content-Length: %d\r\n"+ - "\r\n", len(body)))) - conn.readBuf.Write([]byte(body)) - conn.closec = make(chan bool, 1) - - ls := &oneConnListener{conn} - go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) { - if conn.readBuf.Len() < len(body)/2 { - t.Errorf("on request, read buffer length is %d; expected about 1MB", conn.readBuf.Len()) - } - rw.WriteHeader(200) - rw.(Flusher).Flush() - if conn.readBuf.Len() < len(body)/2 { - t.Errorf("post-WriteHeader, read buffer length is %d; expected about 1MB", conn.readBuf.Len()) - } - })) - <-conn.closec - - if res := conn.writeBuf.String(); !strings.Contains(res, "Connection: close") { - t.Errorf("Expected a Connection: close header; got response: %s", res) - } -} - -func TestTimeoutHandler(t *testing.T) { - defer afterTest(t) - sendHi := make(chan bool, 1) - writeErrors := make(chan error, 1) - sayHi := HandlerFunc(func(w ResponseWriter, r *Request) { - <-sendHi - _, werr := w.Write([]byte("hi")) - writeErrors <- werr - }) - timeout := make(chan time.Time, 1) // write to this to force timeouts - ts := httptest.NewServer(NewTestTimeoutHandler(sayHi, timeout)) - defer ts.Close() - - // Succeed without timing out: - sendHi <- true - res, err := Get(ts.URL) - if err != nil { - t.Error(err) - } - if g, e := res.StatusCode, StatusOK; g != e { - t.Errorf("got res.StatusCode %d; expected %d", g, e) - } - body, _ := ioutil.ReadAll(res.Body) - if g, e := string(body), "hi"; g != e { - t.Errorf("got body %q; expected %q", g, e) - } - if g := <-writeErrors; g != nil { - t.Errorf("got unexpected Write error on first request: %v", g) - } - - // Times out: - timeout <- time.Time{} - res, err = Get(ts.URL) - if err != nil { - t.Error(err) - } - if g, e := res.StatusCode, StatusServiceUnavailable; g != e { - t.Errorf("got res.StatusCode %d; expected %d", g, e) - } - body, _ = ioutil.ReadAll(res.Body) - if !strings.Contains(string(body), "Timeout") { - t.Errorf("expected timeout body; got %q", string(body)) - } - - // Now make the previously-timed out handler speak again, - // which verifies the panic is handled: - sendHi <- true - if g, e := <-writeErrors, ErrHandlerTimeout; g != e { - t.Errorf("expected Write error of %v; got %v", e, g) - } -} - -// Verifies we don't path.Clean() on the wrong parts in redirects. -func TestRedirectMunging(t *testing.T) { - req, _ := NewRequest("GET", "http://example.com/", nil) - - resp := httptest.NewRecorder() - Redirect(resp, req, "/foo?next=http://bar.com/", 302) - if g, e := resp.Header().Get("Location"), "/foo?next=http://bar.com/"; g != e { - t.Errorf("Location header was %q; want %q", g, e) - } - - resp = httptest.NewRecorder() - Redirect(resp, req, "http://localhost:8080/_ah/login?continue=http://localhost:8080/", 302) - if g, e := resp.Header().Get("Location"), "http://localhost:8080/_ah/login?continue=http://localhost:8080/"; g != e { - t.Errorf("Location header was %q; want %q", g, e) - } -} - -func TestRedirectBadPath(t *testing.T) { - // This used to crash. It's not valid input (bad path), but it - // shouldn't crash. - rr := httptest.NewRecorder() - req := &Request{ - Method: "GET", - URL: &url.URL{ - Scheme: "http", - Path: "not-empty-but-no-leading-slash", // bogus - }, - } - Redirect(rr, req, "", 304) - if rr.Code != 304 { - t.Errorf("Code = %d; want 304", rr.Code) - } -} - -// TestZeroLengthPostAndResponse exercises an optimization done by the Transport: -// when there is no body (either because the method doesn't permit a body, or an -// explicit Content-Length of zero is present), then the transport can re-use the -// connection immediately. But when it re-uses the connection, it typically closes -// the previous request's body, which is not optimal for zero-lengthed bodies, -// as the client would then see http.ErrBodyReadAfterClose and not 0, io.EOF. -func TestZeroLengthPostAndResponse(t *testing.T) { - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { - all, err := ioutil.ReadAll(r.Body) - if err != nil { - t.Fatalf("handler ReadAll: %v", err) - } - if len(all) != 0 { - t.Errorf("handler got %d bytes; expected 0", len(all)) - } - rw.Header().Set("Content-Length", "0") - })) - defer ts.Close() - - req, err := NewRequest("POST", ts.URL, strings.NewReader("")) - if err != nil { - t.Fatal(err) - } - req.ContentLength = 0 - - var resp [5]*Response - for i := range resp { - resp[i], err = DefaultClient.Do(req) - if err != nil { - t.Fatalf("client post #%d: %v", i, err) - } - } - - for i := range resp { - all, err := ioutil.ReadAll(resp[i].Body) - if err != nil { - t.Fatalf("req #%d: client ReadAll: %v", i, err) - } - if len(all) != 0 { - t.Errorf("req #%d: client got %d bytes; expected 0", i, len(all)) - } - } -} - -func TestHandlerPanicNil(t *testing.T) { - testHandlerPanic(t, false, nil) -} - -func TestHandlerPanic(t *testing.T) { - testHandlerPanic(t, false, "intentional death for testing") -} - -func TestHandlerPanicWithHijack(t *testing.T) { - testHandlerPanic(t, true, "intentional death for testing") -} - -func testHandlerPanic(t *testing.T, withHijack bool, panicValue interface{}) { - defer afterTest(t) - // Unlike the other tests that set the log output to ioutil.Discard - // to quiet the output, this test uses a pipe. The pipe serves three - // purposes: - // - // 1) The log.Print from the http server (generated by the caught - // panic) will go to the pipe instead of stderr, making the - // output quiet. - // - // 2) We read from the pipe to verify that the handler - // actually caught the panic and logged something. - // - // 3) The blocking Read call prevents this TestHandlerPanic - // function from exiting before the HTTP server handler - // finishes crashing. If this text function exited too - // early (and its defer log.SetOutput(os.Stderr) ran), - // then the crash output could spill into the next test. - pr, pw := io.Pipe() - log.SetOutput(pw) - defer log.SetOutput(os.Stderr) - defer pw.Close() - - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - if withHijack { - rwc, _, err := w.(Hijacker).Hijack() - if err != nil { - t.Logf("unexpected error: %v", err) - } - defer rwc.Close() - } - panic(panicValue) - })) - defer ts.Close() - - // Do a blocking read on the log output pipe so its logging - // doesn't bleed into the next test. But wait only 5 seconds - // for it. - done := make(chan bool, 1) - go func() { - buf := make([]byte, 4<<10) - _, err := pr.Read(buf) - pr.Close() - if err != nil && err != io.EOF { - t.Error(err) - } - done <- true - }() - - _, err := Get(ts.URL) - if err == nil { - t.Logf("expected an error") - } - - if panicValue == nil { - return - } - - select { - case <-done: - return - case <-time.After(5 * time.Second): - t.Fatal("expected server handler to log an error") - } -} - -func TestNoDate(t *testing.T) { - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - w.Header()["Date"] = nil - })) - defer ts.Close() - res, err := Get(ts.URL) - if err != nil { - t.Fatal(err) - } - _, present := res.Header["Date"] - if present { - t.Fatalf("Expected no Date header; got %v", res.Header["Date"]) - } -} - -func TestStripPrefix(t *testing.T) { - defer afterTest(t) - h := HandlerFunc(func(w ResponseWriter, r *Request) { - w.Header().Set("X-Path", r.URL.Path) - }) - ts := httptest.NewServer(StripPrefix("/foo", h)) - defer ts.Close() - - res, err := Get(ts.URL + "/foo/bar") - if err != nil { - t.Fatal(err) - } - if g, e := res.Header.Get("X-Path"), "/bar"; g != e { - t.Errorf("test 1: got %s, want %s", g, e) - } - res.Body.Close() - - res, err = Get(ts.URL + "/bar") - if err != nil { - t.Fatal(err) - } - if g, e := res.StatusCode, 404; g != e { - t.Errorf("test 2: got status %v, want %v", g, e) - } - res.Body.Close() -} - -func TestRequestLimit(t *testing.T) { - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - t.Fatalf("didn't expect to get request in Handler") - })) - defer ts.Close() - req, _ := NewRequest("GET", ts.URL, nil) - var bytesPerHeader = len("header12345: val12345\r\n") - for i := 0; i < ((DefaultMaxHeaderBytes+4096)/bytesPerHeader)+1; i++ { - req.Header.Set(fmt.Sprintf("header%05d", i), fmt.Sprintf("val%05d", i)) - } - res, err := DefaultClient.Do(req) - if err != nil { - // Some HTTP clients may fail on this undefined behavior (server replying and - // closing the connection while the request is still being written), but - // we do support it (at least currently), so we expect a response below. - t.Fatalf("Do: %v", err) - } - defer res.Body.Close() - if res.StatusCode != 413 { - t.Fatalf("expected 413 response status; got: %d %s", res.StatusCode, res.Status) - } -} - type neverEnding byte func (b neverEnding) Read(p []byte) (n int, err error) { @@ -1384,1344 +56,3 @@ } return len(p), nil } - -type countReader struct { - r io.Reader - n *int64 -} - -func (cr countReader) Read(p []byte) (n int, err error) { - n, err = cr.r.Read(p) - atomic.AddInt64(cr.n, int64(n)) - return -} - -func TestRequestBodyLimit(t *testing.T) { - defer afterTest(t) - const limit = 1 << 20 - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - r.Body = MaxBytesReader(w, r.Body, limit) - n, err := io.Copy(ioutil.Discard, r.Body) - if err == nil { - t.Errorf("expected error from io.Copy") - } - if n != limit { - t.Errorf("io.Copy = %d, want %d", n, limit) - } - })) - defer ts.Close() - - nWritten := new(int64) - req, _ := NewRequest("POST", ts.URL, io.LimitReader(countReader{neverEnding('a'), nWritten}, limit*200)) - - // Send the POST, but don't care it succeeds or not. The - // remote side is going to reply and then close the TCP - // connection, and HTTP doesn't really define if that's - // allowed or not. Some HTTP clients will get the response - // and some (like ours, currently) will complain that the - // request write failed, without reading the response. - // - // But that's okay, since what we're really testing is that - // the remote side hung up on us before we wrote too much. - _, _ = DefaultClient.Do(req) - - if atomic.LoadInt64(nWritten) > limit*100 { - t.Errorf("handler restricted the request body to %d bytes, but client managed to write %d", - limit, nWritten) - } -} - -// TestClientWriteShutdown tests that if the client shuts down the write -// side of their TCP connection, the server doesn't send a 400 Bad Request. -func TestClientWriteShutdown(t *testing.T) { - if runtime.GOOS == "plan9" { - t.Skip("skipping test; see http://golang.org/issue/7237") - } - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) - defer ts.Close() - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatalf("Dial: %v", err) - } - err = conn.(*net.TCPConn).CloseWrite() - if err != nil { - t.Fatalf("Dial: %v", err) - } - donec := make(chan bool) - go func() { - defer close(donec) - bs, err := ioutil.ReadAll(conn) - if err != nil { - t.Fatalf("ReadAll: %v", err) - } - got := string(bs) - if got != "" { - t.Errorf("read %q from server; want nothing", got) - } - }() - select { - case <-donec: - case <-time.After(10 * time.Second): - t.Fatalf("timeout") - } -} - -// Tests that chunked server responses that write 1 byte at a time are -// buffered before chunk headers are added, not after chunk headers. -func TestServerBufferedChunking(t *testing.T) { - conn := new(testConn) - conn.readBuf.Write([]byte("GET / HTTP/1.1\r\n\r\n")) - conn.closec = make(chan bool, 1) - ls := &oneConnListener{conn} - go Serve(ls, HandlerFunc(func(rw ResponseWriter, req *Request) { - rw.(Flusher).Flush() // force the Header to be sent, in chunking mode, not counting the length - rw.Write([]byte{'x'}) - rw.Write([]byte{'y'}) - rw.Write([]byte{'z'}) - })) - <-conn.closec - if !bytes.HasSuffix(conn.writeBuf.Bytes(), []byte("\r\n\r\n3\r\nxyz\r\n0\r\n\r\n")) { - t.Errorf("response didn't end with a single 3 byte 'xyz' chunk; got:\n%q", - conn.writeBuf.Bytes()) - } -} - -// Tests that the server flushes its response headers out when it's -// ignoring the response body and waits a bit before forcefully -// closing the TCP connection, causing the client to get a RST. -// See http://golang.org/issue/3595 -func TestServerGracefulClose(t *testing.T) { - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - Error(w, "bye", StatusUnauthorized) - })) - defer ts.Close() - - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - const bodySize = 5 << 20 - req := []byte(fmt.Sprintf("POST / HTTP/1.1\r\nHost: foo.com\r\nContent-Length: %d\r\n\r\n", bodySize)) - for i := 0; i < bodySize; i++ { - req = append(req, 'x') - } - writeErr := make(chan error) - go func() { - _, err := conn.Write(req) - writeErr <- err - }() - br := bufio.NewReader(conn) - lineNum := 0 - for { - line, err := br.ReadString('\n') - if err == io.EOF { - break - } - if err != nil { - t.Fatalf("ReadLine: %v", err) - } - lineNum++ - if lineNum == 1 && !strings.Contains(line, "401 Unauthorized") { - t.Errorf("Response line = %q; want a 401", line) - } - } - // Wait for write to finish. This is a broken pipe on both - // Darwin and Linux, but checking this isn't the point of - // the test. - <-writeErr -} - -func TestCaseSensitiveMethod(t *testing.T) { - defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - if r.Method != "get" { - t.Errorf(`Got method %q; want "get"`, r.Method) - } - })) - defer ts.Close() - req, _ := NewRequest("get", ts.URL, nil) - res, err := DefaultClient.Do(req) - if err != nil { - t.Error(err) - return - } - res.Body.Close() -} - -// TestContentLengthZero tests that for both an HTTP/1.0 and HTTP/1.1 -// request (both keep-alive), when a Handler never writes any -// response, the net/http package adds a "Content-Length: 0" response -// header. -func TestContentLengthZero(t *testing.T) { - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {})) - defer ts.Close() - - for _, version := range []string{"HTTP/1.0", "HTTP/1.1"} { - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatalf("error dialing: %v", err) - } - _, err = fmt.Fprintf(conn, "GET / %v\r\nConnection: keep-alive\r\nHost: foo\r\n\r\n", version) - if err != nil { - t.Fatalf("error writing: %v", err) - } - req, _ := NewRequest("GET", "/", nil) - res, err := ReadResponse(bufio.NewReader(conn), req) - if err != nil { - t.Fatalf("error reading response: %v", err) - } - if te := res.TransferEncoding; len(te) > 0 { - t.Errorf("For version %q, Transfer-Encoding = %q; want none", version, te) - } - if cl := res.ContentLength; cl != 0 { - t.Errorf("For version %q, Content-Length = %v; want 0", version, cl) - } - conn.Close() - } -} - -func TestCloseNotifier(t *testing.T) { - defer afterTest(t) - gotReq := make(chan bool, 1) - sawClose := make(chan bool, 1) - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { - gotReq <- true - cc := rw.(CloseNotifier).CloseNotify() - <-cc - sawClose <- true - })) - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatalf("error dialing: %v", err) - } - diec := make(chan bool) - go func() { - _, err = fmt.Fprintf(conn, "GET / HTTP/1.1\r\nConnection: keep-alive\r\nHost: foo\r\n\r\n") - if err != nil { - t.Fatal(err) - } - <-diec - conn.Close() - }() -For: - for { - select { - case <-gotReq: - diec <- true - case <-sawClose: - break For - case <-time.After(5 * time.Second): - t.Fatal("timeout") - } - } - ts.Close() -} - -func TestCloseNotifierChanLeak(t *testing.T) { - defer afterTest(t) - req := reqBytes("GET / HTTP/1.0\nHost: golang.org") - for i := 0; i < 20; i++ { - var output bytes.Buffer - conn := &rwTestConn{ - Reader: bytes.NewReader(req), - Writer: &output, - closec: make(chan bool, 1), - } - ln := &oneConnListener{conn: conn} - handler := HandlerFunc(func(rw ResponseWriter, r *Request) { - // Ignore the return value and never read from - // it, testing that we don't leak goroutines - // on the sending side: - _ = rw.(CloseNotifier).CloseNotify() - }) - go Serve(ln, handler) - <-conn.closec - } -} - -func TestOptions(t *testing.T) { - uric := make(chan string, 2) // only expect 1, but leave space for 2 - mux := NewServeMux() - mux.HandleFunc("/", func(w ResponseWriter, r *Request) { - uric <- r.RequestURI - }) - ts := httptest.NewServer(mux) - defer ts.Close() - - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - - // An OPTIONS * request should succeed. - _, err = conn.Write([]byte("OPTIONS * HTTP/1.1\r\nHost: foo.com\r\n\r\n")) - if err != nil { - t.Fatal(err) - } - br := bufio.NewReader(conn) - res, err := ReadResponse(br, &Request{Method: "OPTIONS"}) - if err != nil { - t.Fatal(err) - } - if res.StatusCode != 200 { - t.Errorf("Got non-200 response to OPTIONS *: %#v", res) - } - - // A GET * request on a ServeMux should fail. - _, err = conn.Write([]byte("GET * HTTP/1.1\r\nHost: foo.com\r\n\r\n")) - if err != nil { - t.Fatal(err) - } - res, err = ReadResponse(br, &Request{Method: "GET"}) - if err != nil { - t.Fatal(err) - } - if res.StatusCode != 400 { - t.Errorf("Got non-400 response to GET *: %#v", res) - } - - res, err = Get(ts.URL + "/second") - if err != nil { - t.Fatal(err) - } - res.Body.Close() - if got := <-uric; got != "/second" { - t.Errorf("Handler saw request for %q; want /second", got) - } -} - -// Tests regarding the ordering of Write, WriteHeader, Header, and -// Flush calls. In Go 1.0, rw.WriteHeader immediately flushed the -// (*response).header to the wire. In Go 1.1, the actual wire flush is -// delayed, so we could maybe tack on a Content-Length and better -// Content-Type after we see more (or all) of the output. To preserve -// compatibility with Go 1, we need to be careful to track which -// headers were live at the time of WriteHeader, so we write the same -// ones, even if the handler modifies them (~erroneously) after the -// first Write. -func TestHeaderToWire(t *testing.T) { - tests := []struct { - name string - handler func(ResponseWriter, *Request) - check func(output string) error - }{ - { - name: "write without Header", - handler: func(rw ResponseWriter, r *Request) { - rw.Write([]byte("hello world")) - }, - check: func(got string) error { - if !strings.Contains(got, "Content-Length:") { - return errors.New("no content-length") - } - if !strings.Contains(got, "Content-Type: text/plain") { - return errors.New("no content-length") - } - return nil - }, - }, - { - name: "Header mutation before write", - handler: func(rw ResponseWriter, r *Request) { - h := rw.Header() - h.Set("Content-Type", "some/type") - rw.Write([]byte("hello world")) - h.Set("Too-Late", "bogus") - }, - check: func(got string) error { - if !strings.Contains(got, "Content-Length:") { - return errors.New("no content-length") - } - if !strings.Contains(got, "Content-Type: some/type") { - return errors.New("wrong content-type") - } - if strings.Contains(got, "Too-Late") { - return errors.New("don't want too-late header") - } - return nil - }, - }, - { - name: "write then useless Header mutation", - handler: func(rw ResponseWriter, r *Request) { - rw.Write([]byte("hello world")) - rw.Header().Set("Too-Late", "Write already wrote headers") - }, - check: func(got string) error { - if strings.Contains(got, "Too-Late") { - return errors.New("header appeared from after WriteHeader") - } - return nil - }, - }, - { - name: "flush then write", - handler: func(rw ResponseWriter, r *Request) { - rw.(Flusher).Flush() - rw.Write([]byte("post-flush")) - rw.Header().Set("Too-Late", "Write already wrote headers") - }, - check: func(got string) error { - if !strings.Contains(got, "Transfer-Encoding: chunked") { - return errors.New("not chunked") - } - if strings.Contains(got, "Too-Late") { - return errors.New("header appeared from after WriteHeader") - } - return nil - }, - }, - { - name: "header then flush", - handler: func(rw ResponseWriter, r *Request) { - rw.Header().Set("Content-Type", "some/type") - rw.(Flusher).Flush() - rw.Write([]byte("post-flush")) - rw.Header().Set("Too-Late", "Write already wrote headers") - }, - check: func(got string) error { - if !strings.Contains(got, "Transfer-Encoding: chunked") { - return errors.New("not chunked") - } - if strings.Contains(got, "Too-Late") { - return errors.New("header appeared from after WriteHeader") - } - if !strings.Contains(got, "Content-Type: some/type") { - return errors.New("wrong content-length") - } - return nil - }, - }, - { - name: "sniff-on-first-write content-type", - handler: func(rw ResponseWriter, r *Request) { - rw.Write([]byte("some html")) - rw.Header().Set("Content-Type", "x/wrong") - }, - check: func(got string) error { - if !strings.Contains(got, "Content-Type: text/html") { - return errors.New("wrong content-length; want html") - } - return nil - }, - }, - { - name: "explicit content-type wins", - handler: func(rw ResponseWriter, r *Request) { - rw.Header().Set("Content-Type", "some/type") - rw.Write([]byte("some html")) - }, - check: func(got string) error { - if !strings.Contains(got, "Content-Type: some/type") { - return errors.New("wrong content-length; want html") - } - return nil - }, - }, - { - name: "empty handler", - handler: func(rw ResponseWriter, r *Request) { - }, - check: func(got string) error { - if !strings.Contains(got, "Content-Type: text/plain") { - return errors.New("wrong content-length; want text/plain") - } - if !strings.Contains(got, "Content-Length: 0") { - return errors.New("want 0 content-length") - } - return nil - }, - }, - { - name: "only Header, no write", - handler: func(rw ResponseWriter, r *Request) { - rw.Header().Set("Some-Header", "some-value") - }, - check: func(got string) error { - if !strings.Contains(got, "Some-Header") { - return errors.New("didn't get header") - } - return nil - }, - }, - { - name: "WriteHeader call", - handler: func(rw ResponseWriter, r *Request) { - rw.WriteHeader(404) - rw.Header().Set("Too-Late", "some-value") - }, - check: func(got string) error { - if !strings.Contains(got, "404") { - return errors.New("wrong status") - } - if strings.Contains(got, "Some-Header") { - return errors.New("shouldn't have seen Too-Late") - } - return nil - }, - }, - } - for _, tc := range tests { - ht := newHandlerTest(HandlerFunc(tc.handler)) - got := ht.rawResponse("GET / HTTP/1.1\nHost: golang.org") - if err := tc.check(got); err != nil { - t.Errorf("%s: %v\nGot response:\n%s", tc.name, err, got) - } - } -} - -// goTimeout runs f, failing t if f takes more than ns to complete. -func goTimeout(t *testing.T, d time.Duration, f func()) { - ch := make(chan bool, 2) - timer := time.AfterFunc(d, func() { - t.Errorf("Timeout expired after %v", d) - ch <- true - }) - defer timer.Stop() - go func() { - defer func() { ch <- true }() - f() - }() - <-ch -} - -type errorListener struct { - errs []error -} - -func (l *errorListener) Accept() (c net.Conn, err error) { - if len(l.errs) == 0 { - return nil, io.EOF - } - err = l.errs[0] - l.errs = l.errs[1:] - return -} - -func (l *errorListener) Close() error { - return nil -} - -func (l *errorListener) Addr() net.Addr { - return dummyAddr("test-address") -} - -func TestAcceptMaxFds(t *testing.T) { - log.SetOutput(ioutil.Discard) // is noisy otherwise - defer log.SetOutput(os.Stderr) - - ln := &errorListener{[]error{ - &net.OpError{ - Op: "accept", - Err: syscall.EMFILE, - }}} - err := Serve(ln, HandlerFunc(HandlerFunc(func(ResponseWriter, *Request) {}))) - if err != io.EOF { - t.Errorf("got error %v, want EOF", err) - } -} - -func TestWriteAfterHijack(t *testing.T) { - req := reqBytes("GET / HTTP/1.1\nHost: golang.org") - var buf bytes.Buffer - wrotec := make(chan bool, 1) - conn := &rwTestConn{ - Reader: bytes.NewReader(req), - Writer: &buf, - closec: make(chan bool, 1), - } - handler := HandlerFunc(func(rw ResponseWriter, r *Request) { - conn, bufrw, err := rw.(Hijacker).Hijack() - if err != nil { - t.Error(err) - return - } - go func() { - bufrw.Write([]byte("[hijack-to-bufw]")) - bufrw.Flush() - conn.Write([]byte("[hijack-to-conn]")) - conn.Close() - wrotec <- true - }() - }) - ln := &oneConnListener{conn: conn} - go Serve(ln, handler) - <-conn.closec - <-wrotec - if g, w := buf.String(), "[hijack-to-bufw][hijack-to-conn]"; g != w { - t.Errorf("wrote %q; want %q", g, w) - } -} - -func TestDoubleHijack(t *testing.T) { - req := reqBytes("GET / HTTP/1.1\nHost: golang.org") - var buf bytes.Buffer - conn := &rwTestConn{ - Reader: bytes.NewReader(req), - Writer: &buf, - closec: make(chan bool, 1), - } - handler := HandlerFunc(func(rw ResponseWriter, r *Request) { - conn, _, err := rw.(Hijacker).Hijack() - if err != nil { - t.Error(err) - return - } - _, _, err = rw.(Hijacker).Hijack() - if err == nil { - t.Errorf("got err = nil; want err != nil") - } - conn.Close() - }) - ln := &oneConnListener{conn: conn} - go Serve(ln, handler) - <-conn.closec -} - -// http://code.google.com/p/go/issues/detail?id=5955 -// Note that this does not test the "request too large" -// exit path from the http server. This is intentional; -// not sending Connection: close is just a minor wire -// optimization and is pointless if dealing with a -// badly behaved client. -func TestHTTP10ConnectionHeader(t *testing.T) { - defer afterTest(t) - - mux := NewServeMux() - mux.Handle("/", HandlerFunc(func(resp ResponseWriter, req *Request) {})) - ts := httptest.NewServer(mux) - defer ts.Close() - - // net/http uses HTTP/1.1 for requests, so write requests manually - tests := []struct { - req string // raw http request - expect []string // expected Connection header(s) - }{ - { - req: "GET / HTTP/1.0\r\n\r\n", - expect: nil, - }, - { - req: "OPTIONS * HTTP/1.0\r\n\r\n", - expect: nil, - }, - { - req: "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n", - expect: []string{"keep-alive"}, - }, - } - - for _, tt := range tests { - conn, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatal("dial err:", err) - } - - _, err = fmt.Fprint(conn, tt.req) - if err != nil { - t.Fatal("conn write err:", err) - } - - resp, err := ReadResponse(bufio.NewReader(conn), &Request{Method: "GET"}) - if err != nil { - t.Fatal("ReadResponse err:", err) - } - conn.Close() - resp.Body.Close() - - got := resp.Header["Connection"] - if !reflect.DeepEqual(got, tt.expect) { - t.Errorf("wrong Connection headers for request %q. Got %q expect %q", tt.req, got, tt.expect) - } - } -} - -// See golang.org/issue/5660 -func TestServerReaderFromOrder(t *testing.T) { - defer afterTest(t) - pr, pw := io.Pipe() - const size = 3 << 20 - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { - rw.Header().Set("Content-Type", "text/plain") // prevent sniffing path - done := make(chan bool) - go func() { - io.Copy(rw, pr) - close(done) - }() - time.Sleep(25 * time.Millisecond) // give Copy a chance to break things - n, err := io.Copy(ioutil.Discard, req.Body) - if err != nil { - t.Errorf("handler Copy: %v", err) - return - } - if n != size { - t.Errorf("handler Copy = %d; want %d", n, size) - } - pw.Write([]byte("hi")) - pw.Close() - <-done - })) - defer ts.Close() - - req, err := NewRequest("POST", ts.URL, io.LimitReader(neverEnding('a'), size)) - if err != nil { - t.Fatal(err) - } - res, err := DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - } - all, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - res.Body.Close() - if string(all) != "hi" { - t.Errorf("Body = %q; want hi", all) - } -} - -// Issue 6157, Issue 6685 -func TestCodesPreventingContentTypeAndBody(t *testing.T) { - for _, code := range []int{StatusNotModified, StatusNoContent, StatusContinue} { - ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { - if r.URL.Path == "/header" { - w.Header().Set("Content-Length", "123") - } - w.WriteHeader(code) - if r.URL.Path == "/more" { - w.Write([]byte("stuff")) - } - })) - for _, req := range []string{ - "GET / HTTP/1.0", - "GET /header HTTP/1.0", - "GET /more HTTP/1.0", - "GET / HTTP/1.1", - "GET /header HTTP/1.1", - "GET /more HTTP/1.1", - } { - got := ht.rawResponse(req) - wantStatus := fmt.Sprintf("%d %s", code, StatusText(code)) - if !strings.Contains(got, wantStatus) { - t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, req, got) - } else if strings.Contains(got, "Content-Length") { - t.Errorf("Code %d: Got a Content-Length from %q: %s", code, req, got) - } else if strings.Contains(got, "stuff") { - t.Errorf("Code %d: Response contains a body from %q: %s", code, req, got) - } - } - } -} - -// Issue 6995 -// A server Handler can receive a Request, and then turn around and -// give a copy of that Request.Body out to the Transport (e.g. any -// proxy). So then two people own that Request.Body (both the server -// and the http client), and both think they can close it on failure. -// Therefore, all incoming server requests Bodies need to be thread-safe. -func TestTransportAndServerSharedBodyRace(t *testing.T) { - defer afterTest(t) - - const bodySize = 1 << 20 - - unblockBackend := make(chan bool) - backend := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { - io.CopyN(rw, req.Body, bodySize/2) - <-unblockBackend - })) - defer backend.Close() - - backendRespc := make(chan *Response, 1) - proxy := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { - if req.RequestURI == "/foo" { - rw.Write([]byte("bar")) - return - } - req2, _ := NewRequest("POST", backend.URL, req.Body) - req2.ContentLength = bodySize - - bresp, err := DefaultClient.Do(req2) - if err != nil { - t.Errorf("Proxy outbound request: %v", err) - return - } - _, err = io.CopyN(ioutil.Discard, bresp.Body, bodySize/4) - if err != nil { - t.Errorf("Proxy copy error: %v", err) - return - } - backendRespc <- bresp // to close later - - // Try to cause a race: Both the DefaultTransport and the proxy handler's Server - // will try to read/close req.Body (aka req2.Body) - DefaultTransport.(*Transport).CancelRequest(req2) - rw.Write([]byte("OK")) - })) - defer proxy.Close() - - req, _ := NewRequest("POST", proxy.URL, io.LimitReader(neverEnding('a'), bodySize)) - res, err := DefaultClient.Do(req) - if err != nil { - t.Fatalf("Original request: %v", err) - } - - // Cleanup, so we don't leak goroutines. - res.Body.Close() - close(unblockBackend) - (<-backendRespc).Body.Close() -} - -// Test that a hanging Request.Body.Read from another goroutine can't -// cause the Handler goroutine's Request.Body.Close to block. -func TestRequestBodyCloseDoesntBlock(t *testing.T) { - t.Skipf("Skipping known issue; see golang.org/issue/7121") - if testing.Short() { - t.Skip("skipping in -short mode") - } - defer afterTest(t) - - readErrCh := make(chan error, 1) - errCh := make(chan error, 2) - - server := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { - go func(body io.Reader) { - _, err := body.Read(make([]byte, 100)) - readErrCh <- err - }(req.Body) - time.Sleep(500 * time.Millisecond) - })) - defer server.Close() - - closeConn := make(chan bool) - defer close(closeConn) - go func() { - conn, err := net.Dial("tcp", server.Listener.Addr().String()) - if err != nil { - errCh <- err - return - } - defer conn.Close() - _, err = conn.Write([]byte("POST / HTTP/1.1\r\nConnection: close\r\nHost: foo\r\nContent-Length: 100000\r\n\r\n")) - if err != nil { - errCh <- err - return - } - // And now just block, making the server block on our - // 100000 bytes of body that will never arrive. - <-closeConn - }() - select { - case err := <-readErrCh: - if err == nil { - t.Error("Read was nil. Expected error.") - } - case err := <-errCh: - t.Error(err) - case <-time.After(5 * time.Second): - t.Error("timeout") - } -} - -func TestResponseWriterWriteStringAllocs(t *testing.T) { - ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) { - if r.URL.Path == "/s" { - io.WriteString(w, "Hello world") - } else { - w.Write([]byte("Hello world")) - } - })) - before := testing.AllocsPerRun(50, func() { ht.rawResponse("GET / HTTP/1.0") }) - after := testing.AllocsPerRun(50, func() { ht.rawResponse("GET /s HTTP/1.0") }) - if int(after) >= int(before) { - t.Errorf("WriteString allocs of %v >= Write allocs of %v", after, before) - } -} - -func TestAppendTime(t *testing.T) { - var b [len(TimeFormat)]byte - t1 := time.Date(2013, 9, 21, 15, 41, 0, 0, time.FixedZone("CEST", 2*60*60)) - res := ExportAppendTime(b[:0], t1) - t2, err := ParseTime(string(res)) - if err != nil { - t.Fatalf("Error parsing time: %s", err) - } - if !t1.Equal(t2) { - t.Fatalf("Times differ; expected: %v, got %v (%s)", t1, t2, string(res)) - } -} - -func TestServerConnState(t *testing.T) { - defer afterTest(t) - handler := map[string]func(w ResponseWriter, r *Request){ - "/": func(w ResponseWriter, r *Request) { - fmt.Fprintf(w, "Hello.") - }, - "/close": func(w ResponseWriter, r *Request) { - w.Header().Set("Connection", "close") - fmt.Fprintf(w, "Hello.") - }, - "/hijack": func(w ResponseWriter, r *Request) { - c, _, _ := w.(Hijacker).Hijack() - c.Write([]byte("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nHello.")) - c.Close() - }, - "/hijack-panic": func(w ResponseWriter, r *Request) { - c, _, _ := w.(Hijacker).Hijack() - c.Write([]byte("HTTP/1.0 200 OK\r\nConnection: close\r\n\r\nHello.")) - c.Close() - panic("intentional panic") - }, - } - ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) { - handler[r.URL.Path](w, r) - })) - defer ts.Close() - - var mu sync.Mutex // guard stateLog and connID - var stateLog = map[int][]ConnState{} - var connID = map[net.Conn]int{} - - ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) - ts.Config.ConnState = func(c net.Conn, state ConnState) { - if c == nil { - t.Error("nil conn seen in state %s", state) - return - } - mu.Lock() - defer mu.Unlock() - id, ok := connID[c] - if !ok { - id = len(connID) + 1 - connID[c] = id - } - stateLog[id] = append(stateLog[id], state) - } - ts.Start() - - mustGet(t, ts.URL+"/") - mustGet(t, ts.URL+"/close") - - mustGet(t, ts.URL+"/") - mustGet(t, ts.URL+"/", "Connection", "close") - - mustGet(t, ts.URL+"/hijack") - mustGet(t, ts.URL+"/hijack-panic") - - // New->Closed - { - c, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - c.Close() - } - - // New->Active->Closed - { - c, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - if _, err := io.WriteString(c, "BOGUS REQUEST\r\n\r\n"); err != nil { - t.Fatal(err) - } - c.Close() - } - - // New->Idle->Closed - { - c, err := net.Dial("tcp", ts.Listener.Addr().String()) - if err != nil { - t.Fatal(err) - } - if _, err := io.WriteString(c, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n"); err != nil { - t.Fatal(err) - } - res, err := ReadResponse(bufio.NewReader(c), nil) - if err != nil { - t.Fatal(err) - } - if _, err := io.Copy(ioutil.Discard, res.Body); err != nil { - t.Fatal(err) - } - c.Close() - } - - want := map[int][]ConnState{ - 1: []ConnState{StateNew, StateActive, StateIdle, StateActive, StateClosed}, - 2: []ConnState{StateNew, StateActive, StateIdle, StateActive, StateClosed}, - 3: []ConnState{StateNew, StateActive, StateHijacked}, - 4: []ConnState{StateNew, StateActive, StateHijacked}, - 5: []ConnState{StateNew, StateClosed}, - 6: []ConnState{StateNew, StateActive, StateClosed}, - 7: []ConnState{StateNew, StateActive, StateIdle, StateClosed}, - } - logString := func(m map[int][]ConnState) string { - var b bytes.Buffer - for id, l := range m { - fmt.Fprintf(&b, "Conn %d: ", id) - for _, s := range l { - fmt.Fprintf(&b, "%s ", s) - } - b.WriteString("\n") - } - return b.String() - } - - for i := 0; i < 5; i++ { - time.Sleep(time.Duration(i) * 50 * time.Millisecond) - mu.Lock() - match := reflect.DeepEqual(stateLog, want) - mu.Unlock() - if match { - return - } - } - - mu.Lock() - t.Errorf("Unexpected events.\nGot log: %s\n Want: %s\n", logString(stateLog), logString(want)) - mu.Unlock() -} - -func mustGet(t *testing.T, url string, headers ...string) { - req, err := NewRequest("GET", url, nil) - if err != nil { - t.Fatal(err) - } - for len(headers) > 0 { - req.Header.Add(headers[0], headers[1]) - headers = headers[2:] - } - res, err := DefaultClient.Do(req) - if err != nil { - t.Errorf("Error fetching %s: %v", url, err) - return - } - _, err = ioutil.ReadAll(res.Body) - defer res.Body.Close() - if err != nil { - t.Errorf("Error reading %s: %v", url, err) - } -} - -func TestServerKeepAlivesEnabled(t *testing.T) { - defer afterTest(t) - ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) - ts.Config.SetKeepAlivesEnabled(false) - ts.Start() - defer ts.Close() - res, err := Get(ts.URL) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - if !res.Close { - t.Errorf("Body.Close == false; want true") - } -} - -func TestServerConnStateNew(t *testing.T) { - sawNew := false // if the test is buggy, we'll race on this variable. - srv := &Server{ - ConnState: func(c net.Conn, state ConnState) { - if state == StateNew { - sawNew = true // testing that this write isn't racy - } - }, - Handler: HandlerFunc(func(w ResponseWriter, r *Request) {}), // irrelevant - } - srv.Serve(&oneConnListener{ - conn: &rwTestConn{ - Reader: strings.NewReader("GET / HTTP/1.1\r\nHost: foo\r\n\r\n"), - Writer: ioutil.Discard, - }, - }) - if !sawNew { // testing that this read isn't racy - t.Error("StateNew not seen") - } -} - -func BenchmarkClientServer(b *testing.B) { - b.ReportAllocs() - b.StopTimer() - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { - fmt.Fprintf(rw, "Hello world.\n") - })) - defer ts.Close() - b.StartTimer() - - for i := 0; i < b.N; i++ { - res, err := Get(ts.URL) - if err != nil { - b.Fatal("Get:", err) - } - all, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - b.Fatal("ReadAll:", err) - } - body := string(all) - if body != "Hello world.\n" { - b.Fatal("Got body:", body) - } - } - - b.StopTimer() -} - -// A benchmark for profiling the server without the HTTP client code. -// The client code runs in a subprocess. -// -// For use like: -// $ go test -c -// $ ./http.test -test.run=XX -test.bench=BenchmarkServer -test.benchtime=15s -test.cpuprofile=http.prof -// $ go tool pprof http.test http.prof -// (pprof) web -func BenchmarkServer(b *testing.B) { - b.ReportAllocs() - // Child process mode; - if url := os.Getenv("TEST_BENCH_SERVER_URL"); url != "" { - n, err := strconv.Atoi(os.Getenv("TEST_BENCH_CLIENT_N")) - if err != nil { - panic(err) - } - for i := 0; i < n; i++ { - res, err := Get(url) - if err != nil { - log.Panicf("Get: %v", err) - } - all, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - log.Panicf("ReadAll: %v", err) - } - body := string(all) - if body != "Hello world.\n" { - log.Panicf("Got body: %q", body) - } - } - os.Exit(0) - return - } - - var res = []byte("Hello world.\n") - b.StopTimer() - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { - rw.Header().Set("Content-Type", "text/html; charset=utf-8") - rw.Write(res) - })) - defer ts.Close() - b.StartTimer() - - cmd := exec.Command(os.Args[0], "-test.run=XXXX", "-test.bench=BenchmarkServer") - cmd.Env = append([]string{ - fmt.Sprintf("TEST_BENCH_CLIENT_N=%d", b.N), - fmt.Sprintf("TEST_BENCH_SERVER_URL=%s", ts.URL), - }, os.Environ()...) - out, err := cmd.CombinedOutput() - if err != nil { - b.Errorf("Test failure: %v, with output: %s", err, out) - } -} - -func BenchmarkServerFakeConnNoKeepAlive(b *testing.B) { - b.ReportAllocs() - req := reqBytes(`GET / HTTP/1.0 -Host: golang.org -Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 -User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17 -Accept-Encoding: gzip,deflate,sdch -Accept-Language: en-US,en;q=0.8 -Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 -`) - res := []byte("Hello world!\n") - - conn := &testConn{ - // testConn.Close will not push into the channel - // if it's full. - closec: make(chan bool, 1), - } - handler := HandlerFunc(func(rw ResponseWriter, r *Request) { - rw.Header().Set("Content-Type", "text/html; charset=utf-8") - rw.Write(res) - }) - ln := new(oneConnListener) - for i := 0; i < b.N; i++ { - conn.readBuf.Reset() - conn.writeBuf.Reset() - conn.readBuf.Write(req) - ln.conn = conn - Serve(ln, handler) - <-conn.closec - } -} - -// repeatReader reads content count times, then EOFs. -type repeatReader struct { - content []byte - count int - off int -} - -func (r *repeatReader) Read(p []byte) (n int, err error) { - if r.count <= 0 { - return 0, io.EOF - } - n = copy(p, r.content[r.off:]) - r.off += n - if r.off == len(r.content) { - r.count-- - r.off = 0 - } - return -} - -func BenchmarkServerFakeConnWithKeepAlive(b *testing.B) { - b.ReportAllocs() - - req := reqBytes(`GET / HTTP/1.1 -Host: golang.org -Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 -User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17 -Accept-Encoding: gzip,deflate,sdch -Accept-Language: en-US,en;q=0.8 -Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 -`) - res := []byte("Hello world!\n") - - conn := &rwTestConn{ - Reader: &repeatReader{content: req, count: b.N}, - Writer: ioutil.Discard, - closec: make(chan bool, 1), - } - handled := 0 - handler := HandlerFunc(func(rw ResponseWriter, r *Request) { - handled++ - rw.Header().Set("Content-Type", "text/html; charset=utf-8") - rw.Write(res) - }) - ln := &oneConnListener{conn: conn} - go Serve(ln, handler) - <-conn.closec - if b.N != handled { - b.Errorf("b.N=%d but handled %d", b.N, handled) - } -} - -// same as above, but representing the most simple possible request -// and handler. Notably: the handler does not call rw.Header(). -func BenchmarkServerFakeConnWithKeepAliveLite(b *testing.B) { - b.ReportAllocs() - - req := reqBytes(`GET / HTTP/1.1 -Host: golang.org -`) - res := []byte("Hello world!\n") - - conn := &rwTestConn{ - Reader: &repeatReader{content: req, count: b.N}, - Writer: ioutil.Discard, - closec: make(chan bool, 1), - } - handled := 0 - handler := HandlerFunc(func(rw ResponseWriter, r *Request) { - handled++ - rw.Write(res) - }) - ln := &oneConnListener{conn: conn} - go Serve(ln, handler) - <-conn.closec - if b.N != handled { - b.Errorf("b.N=%d but handled %d", b.N, handled) - } -} - -const someResponse = "some response" - -// A Response that's just no bigger than 2KB, the buffer-before-chunking threshold. -var response = bytes.Repeat([]byte(someResponse), 2<<10/len(someResponse)) - -// Both Content-Type and Content-Length set. Should be no buffering. -func BenchmarkServerHandlerTypeLen(b *testing.B) { - benchmarkHandler(b, HandlerFunc(func(w ResponseWriter, r *Request) { - w.Header().Set("Content-Type", "text/html") - w.Header().Set("Content-Length", strconv.Itoa(len(response))) - w.Write(response) - })) -} - -// A Content-Type is set, but no length. No sniffing, but will count the Content-Length. -func BenchmarkServerHandlerNoLen(b *testing.B) { - benchmarkHandler(b, HandlerFunc(func(w ResponseWriter, r *Request) { - w.Header().Set("Content-Type", "text/html") - w.Write(response) - })) -} - -// A Content-Length is set, but the Content-Type will be sniffed. -func BenchmarkServerHandlerNoType(b *testing.B) { - benchmarkHandler(b, HandlerFunc(func(w ResponseWriter, r *Request) { - w.Header().Set("Content-Length", strconv.Itoa(len(response))) - w.Write(response) - })) -} - -// Neither a Content-Type or Content-Length, so sniffed and counted. -func BenchmarkServerHandlerNoHeader(b *testing.B) { - benchmarkHandler(b, HandlerFunc(func(w ResponseWriter, r *Request) { - w.Write(response) - })) -} - -func benchmarkHandler(b *testing.B, h Handler) { - b.ReportAllocs() - req := reqBytes(`GET / HTTP/1.1 -Host: golang.org -`) - conn := &rwTestConn{ - Reader: &repeatReader{content: req, count: b.N}, - Writer: ioutil.Discard, - closec: make(chan bool, 1), - } - handled := 0 - handler := HandlerFunc(func(rw ResponseWriter, r *Request) { - handled++ - h.ServeHTTP(rw, r) - }) - ln := &oneConnListener{conn: conn} - go Serve(ln, handler) - <-conn.closec - if b.N != handled { - b.Errorf("b.N=%d but handled %d", b.N, handled) - } -} - -func BenchmarkServerHijack(b *testing.B) { - b.ReportAllocs() - req := reqBytes(`GET / HTTP/1.1 -Host: golang.org -`) - h := HandlerFunc(func(w ResponseWriter, r *Request) { - conn, _, err := w.(Hijacker).Hijack() - if err != nil { - panic(err) - } - conn.Close() - }) - conn := &rwTestConn{ - Writer: ioutil.Discard, - closec: make(chan bool, 1), - } - ln := &oneConnListener{conn: conn} - for i := 0; i < b.N; i++ { - conn.Reader = bytes.NewReader(req) - ln.conn = conn - Serve(ln, h) - <-conn.closec - } -} === modified file 'http13client/server.go' --- http13client/server.go 2014-03-19 20:20:19 +0000 +++ http13client/server.go 2014-03-19 22:27:37 +0000 @@ -2,1984 +2,17 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// HTTP server. See RFC 2616. - package http import ( - "bufio" - "crypto/tls" - "errors" "fmt" "io" "io/ioutil" "log" "net" - "net/url" - "os" - "path" - "runtime" - "strconv" "strings" "sync" - "sync/atomic" - "time" -) - -// Errors introduced by the HTTP server. -var ( - ErrWriteAfterFlush = errors.New("Conn.Write called after Flush") - ErrBodyNotAllowed = errors.New("http: request method or response status code does not allow body") - ErrHijacked = errors.New("Conn has been hijacked") - ErrContentLength = errors.New("Conn.Write wrote more than the declared Content-Length") -) - -// Objects implementing the Handler interface can be -// registered to serve a particular path or subtree -// in the HTTP server. -// -// ServeHTTP should write reply headers and data to the ResponseWriter -// and then return. Returning signals that the request is finished -// and that the HTTP server can move on to the next request on -// the connection. -type Handler interface { - ServeHTTP(ResponseWriter, *Request) -} - -// A ResponseWriter interface is used by an HTTP handler to -// construct an HTTP response. -type ResponseWriter interface { - // Header returns the header map that will be sent by WriteHeader. - // Changing the header after a call to WriteHeader (or Write) has - // no effect. - Header() Header - - // Write writes the data to the connection as part of an HTTP reply. - // If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK) - // before writing the data. If the Header does not contain a - // Content-Type line, Write adds a Content-Type set to the result of passing - // the initial 512 bytes of written data to DetectContentType. - Write([]byte) (int, error) - - // WriteHeader sends an HTTP response header with status code. - // If WriteHeader is not called explicitly, the first call to Write - // will trigger an implicit WriteHeader(http.StatusOK). - // Thus explicit calls to WriteHeader are mainly used to - // send error codes. - WriteHeader(int) -} - -// The Flusher interface is implemented by ResponseWriters that allow -// an HTTP handler to flush buffered data to the client. -// -// Note that even for ResponseWriters that support Flush, -// if the client is connected through an HTTP proxy, -// the buffered data may not reach the client until the response -// completes. -type Flusher interface { - // Flush sends any buffered data to the client. - Flush() -} - -// The Hijacker interface is implemented by ResponseWriters that allow -// an HTTP handler to take over the connection. -type Hijacker interface { - // Hijack lets the caller take over the connection. - // After a call to Hijack(), the HTTP server library - // will not do anything else with the connection. - // It becomes the caller's responsibility to manage - // and close the connection. - Hijack() (net.Conn, *bufio.ReadWriter, error) -} - -// The CloseNotifier interface is implemented by ResponseWriters which -// allow detecting when the underlying connection has gone away. -// -// This mechanism can be used to cancel long operations on the server -// if the client has disconnected before the response is ready. -type CloseNotifier interface { - // CloseNotify returns a channel that receives a single value - // when the client connection has gone away. - CloseNotify() <-chan bool -} - -// A conn represents the server side of an HTTP connection. -type conn struct { - remoteAddr string // network address of remote side - server *Server // the Server on which the connection arrived - rwc net.Conn // i/o connection - sr liveSwitchReader // where the LimitReader reads from; usually the rwc - lr *io.LimitedReader // io.LimitReader(sr) - buf *bufio.ReadWriter // buffered(lr,rwc), reading from bufio->limitReader->sr->rwc - tlsState *tls.ConnectionState // or nil when not using TLS - - mu sync.Mutex // guards the following - clientGone bool // if client has disconnected mid-request - closeNotifyc chan bool // made lazily - hijackedv bool // connection has been hijacked by handler -} - -func (c *conn) hijacked() bool { - c.mu.Lock() - defer c.mu.Unlock() - return c.hijackedv -} - -func (c *conn) hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) { - c.mu.Lock() - defer c.mu.Unlock() - if c.hijackedv { - return nil, nil, ErrHijacked - } - if c.closeNotifyc != nil { - return nil, nil, errors.New("http: Hijack is incompatible with use of CloseNotifier") - } - c.hijackedv = true - rwc = c.rwc - buf = c.buf - c.rwc = nil - c.buf = nil - c.setState(rwc, StateHijacked) - return -} - -func (c *conn) closeNotify() <-chan bool { - c.mu.Lock() - defer c.mu.Unlock() - if c.closeNotifyc == nil { - c.closeNotifyc = make(chan bool, 1) - if c.hijackedv { - // to obey the function signature, even though - // it'll never receive a value. - return c.closeNotifyc - } - pr, pw := io.Pipe() - - readSource := c.sr.r - c.sr.Lock() - c.sr.r = pr - c.sr.Unlock() - go func() { - _, err := io.Copy(pw, readSource) - if err == nil { - err = io.EOF - } - pw.CloseWithError(err) - c.noteClientGone() - }() - } - return c.closeNotifyc -} - -func (c *conn) noteClientGone() { - c.mu.Lock() - defer c.mu.Unlock() - if c.closeNotifyc != nil && !c.clientGone { - c.closeNotifyc <- true - } - c.clientGone = true -} - -// A switchReader can have its Reader changed at runtime. -// It's not safe for concurrent Reads and switches. -type switchReader struct { - io.Reader -} - -// A switchWriter can have its Writer changed at runtime. -// It's not safe for concurrent Writes and switches. -type switchWriter struct { - io.Writer -} - -// A liveSwitchReader is a switchReader that's safe for concurrent -// reads and switches, if its mutex is held. -type liveSwitchReader struct { - sync.Mutex - r io.Reader -} - -func (sr *liveSwitchReader) Read(p []byte) (n int, err error) { - sr.Lock() - r := sr.r - sr.Unlock() - return r.Read(p) -} - -// This should be >= 512 bytes for DetectContentType, -// but otherwise it's somewhat arbitrary. -const bufferBeforeChunkingSize = 2048 - -// chunkWriter writes to a response's conn buffer, and is the writer -// wrapped by the response.bufw buffered writer. -// -// chunkWriter also is responsible for finalizing the Header, including -// conditionally setting the Content-Type and setting a Content-Length -// in cases where the handler's final output is smaller than the buffer -// size. It also conditionally adds chunk headers, when in chunking mode. -// -// See the comment above (*response).Write for the entire write flow. -type chunkWriter struct { - res *response - - // header is either nil or a deep clone of res.handlerHeader - // at the time of res.WriteHeader, if res.WriteHeader is - // called and extra buffering is being done to calculate - // Content-Type and/or Content-Length. - header Header - - // wroteHeader tells whether the header's been written to "the - // wire" (or rather: w.conn.buf). this is unlike - // (*response).wroteHeader, which tells only whether it was - // logically written. - wroteHeader bool - - // set by the writeHeader method: - chunking bool // using chunked transfer encoding for reply body -} - -var ( - crlf = []byte("\r\n") - colonSpace = []byte(": ") -) - -func (cw *chunkWriter) Write(p []byte) (n int, err error) { - if !cw.wroteHeader { - cw.writeHeader(p) - } - if cw.res.req.Method == "HEAD" { - // Eat writes. - return len(p), nil - } - if cw.chunking { - _, err = fmt.Fprintf(cw.res.conn.buf, "%x\r\n", len(p)) - if err != nil { - cw.res.conn.rwc.Close() - return - } - } - n, err = cw.res.conn.buf.Write(p) - if cw.chunking && err == nil { - _, err = cw.res.conn.buf.Write(crlf) - } - if err != nil { - cw.res.conn.rwc.Close() - } - return -} - -func (cw *chunkWriter) flush() { - if !cw.wroteHeader { - cw.writeHeader(nil) - } - cw.res.conn.buf.Flush() -} - -func (cw *chunkWriter) close() { - if !cw.wroteHeader { - cw.writeHeader(nil) - } - if cw.chunking { - // zero EOF chunk, trailer key/value pairs (currently - // unsupported in Go's server), followed by a blank - // line. - cw.res.conn.buf.WriteString("0\r\n\r\n") - } -} - -// A response represents the server side of an HTTP response. -type response struct { - conn *conn - req *Request // request for this response - wroteHeader bool // reply header has been (logically) written - wroteContinue bool // 100 Continue response was written - - w *bufio.Writer // buffers output in chunks to chunkWriter - cw chunkWriter - sw *switchWriter // of the bufio.Writer, for return to putBufioWriter - - // handlerHeader is the Header that Handlers get access to, - // which may be retained and mutated even after WriteHeader. - // handlerHeader is copied into cw.header at WriteHeader - // time, and privately mutated thereafter. - handlerHeader Header - calledHeader bool // handler accessed handlerHeader via Header - - written int64 // number of bytes written in body - contentLength int64 // explicitly-declared Content-Length; or -1 - status int // status code passed to WriteHeader - - // close connection after this reply. set on request and - // updated after response from handler if there's a - // "Connection: keep-alive" response header and a - // Content-Length. - closeAfterReply bool - - // requestBodyLimitHit is set by requestTooLarge when - // maxBytesReader hits its max size. It is checked in - // WriteHeader, to make sure we don't consume the - // remaining request body to try to advance to the next HTTP - // request. Instead, when this is set, we stop reading - // subsequent requests on this connection and stop reading - // input from it. - requestBodyLimitHit bool - - handlerDone bool // set true when the handler exits - - // Buffers for Date and Content-Length - dateBuf [len(TimeFormat)]byte - clenBuf [10]byte -} - -// requestTooLarge is called by maxBytesReader when too much input has -// been read from the client. -func (w *response) requestTooLarge() { - w.closeAfterReply = true - w.requestBodyLimitHit = true - if !w.wroteHeader { - w.Header().Set("Connection", "close") - } -} - -// needsSniff reports whether a Content-Type still needs to be sniffed. -func (w *response) needsSniff() bool { - _, haveType := w.handlerHeader["Content-Type"] - return !w.cw.wroteHeader && !haveType && w.written < sniffLen -} - -// writerOnly hides an io.Writer value's optional ReadFrom method -// from io.Copy. -type writerOnly struct { - io.Writer -} - -func srcIsRegularFile(src io.Reader) (isRegular bool, err error) { - switch v := src.(type) { - case *os.File: - fi, err := v.Stat() - if err != nil { - return false, err - } - return fi.Mode().IsRegular(), nil - case *io.LimitedReader: - return srcIsRegularFile(v.R) - default: - return - } -} - -// ReadFrom is here to optimize copying from an *os.File regular file -// to a *net.TCPConn with sendfile. -func (w *response) ReadFrom(src io.Reader) (n int64, err error) { - // Our underlying w.conn.rwc is usually a *TCPConn (with its - // own ReadFrom method). If not, or if our src isn't a regular - // file, just fall back to the normal copy method. - rf, ok := w.conn.rwc.(io.ReaderFrom) - regFile, err := srcIsRegularFile(src) - if err != nil { - return 0, err - } - if !ok || !regFile { - return io.Copy(writerOnly{w}, src) - } - - // sendfile path: - - if !w.wroteHeader { - w.WriteHeader(StatusOK) - } - - if w.needsSniff() { - n0, err := io.Copy(writerOnly{w}, io.LimitReader(src, sniffLen)) - n += n0 - if err != nil { - return n, err - } - } - - w.w.Flush() // get rid of any previous writes - w.cw.flush() // make sure Header is written; flush data to rwc - - // Now that cw has been flushed, its chunking field is guaranteed initialized. - if !w.cw.chunking && w.bodyAllowed() { - n0, err := rf.ReadFrom(src) - n += n0 - w.written += n0 - return n, err - } - - n0, err := io.Copy(writerOnly{w}, src) - n += n0 - return n, err -} - -// noLimit is an effective infinite upper bound for io.LimitedReader -const noLimit int64 = (1 << 63) - 1 - -// debugServerConnections controls whether all server connections are wrapped -// with a verbose logging wrapper. -const debugServerConnections = false - -// Create new connection from rwc. -func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) { - c = new(conn) - c.remoteAddr = rwc.RemoteAddr().String() - c.server = srv - c.rwc = rwc - if debugServerConnections { - c.rwc = newLoggingConn("server", c.rwc) - } - c.sr = liveSwitchReader{r: c.rwc} - c.lr = io.LimitReader(&c.sr, noLimit).(*io.LimitedReader) - br := newBufioReader(c.lr) - bw := newBufioWriterSize(c.rwc, 4<<10) - c.buf = bufio.NewReadWriter(br, bw) - return c, nil -} - -// TODO: use a sync.Cache instead -var ( - bufioReaderCache = make(chan *bufio.Reader, 4) - bufioWriterCache2k = make(chan *bufio.Writer, 4) - bufioWriterCache4k = make(chan *bufio.Writer, 4) -) - -func bufioWriterCache(size int) chan *bufio.Writer { - switch size { - case 2 << 10: - return bufioWriterCache2k - case 4 << 10: - return bufioWriterCache4k - } - return nil -} - -func newBufioReader(r io.Reader) *bufio.Reader { - select { - case p := <-bufioReaderCache: - p.Reset(r) - return p - default: - return bufio.NewReader(r) - } -} - -func putBufioReader(br *bufio.Reader) { - br.Reset(nil) - select { - case bufioReaderCache <- br: - default: - } -} - -func newBufioWriterSize(w io.Writer, size int) *bufio.Writer { - select { - case p := <-bufioWriterCache(size): - p.Reset(w) - return p - default: - return bufio.NewWriterSize(w, size) - } -} - -func putBufioWriter(bw *bufio.Writer) { - bw.Reset(nil) - select { - case bufioWriterCache(bw.Available()) <- bw: - default: - } -} - -// DefaultMaxHeaderBytes is the maximum permitted size of the headers -// in an HTTP request. -// This can be overridden by setting Server.MaxHeaderBytes. -const DefaultMaxHeaderBytes = 1 << 20 // 1 MB - -func (srv *Server) maxHeaderBytes() int { - if srv.MaxHeaderBytes > 0 { - return srv.MaxHeaderBytes - } - return DefaultMaxHeaderBytes -} - -func (srv *Server) initialLimitedReaderSize() int64 { - return int64(srv.maxHeaderBytes()) + 4096 // bufio slop -} - -// wrapper around io.ReaderCloser which on first read, sends an -// HTTP/1.1 100 Continue header -type expectContinueReader struct { - resp *response - readCloser io.ReadCloser - closed bool -} - -func (ecr *expectContinueReader) Read(p []byte) (n int, err error) { - if ecr.closed { - return 0, ErrBodyReadAfterClose - } - if !ecr.resp.wroteContinue && !ecr.resp.conn.hijacked() { - ecr.resp.wroteContinue = true - ecr.resp.conn.buf.WriteString("HTTP/1.1 100 Continue\r\n\r\n") - ecr.resp.conn.buf.Flush() - } - return ecr.readCloser.Read(p) -} - -func (ecr *expectContinueReader) Close() error { - ecr.closed = true - return ecr.readCloser.Close() -} - -// TimeFormat is the time format to use with -// time.Parse and time.Time.Format when parsing -// or generating times in HTTP headers. -// It is like time.RFC1123 but hard codes GMT as the time zone. -const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT" - -// appendTime is a non-allocating version of []byte(t.UTC().Format(TimeFormat)) -func appendTime(b []byte, t time.Time) []byte { - const days = "SunMonTueWedThuFriSat" - const months = "JanFebMarAprMayJunJulAugSepOctNovDec" - - t = t.UTC() - yy, mm, dd := t.Date() - hh, mn, ss := t.Clock() - day := days[3*t.Weekday():] - mon := months[3*(mm-1):] - - return append(b, - day[0], day[1], day[2], ',', ' ', - byte('0'+dd/10), byte('0'+dd%10), ' ', - mon[0], mon[1], mon[2], ' ', - byte('0'+yy/1000), byte('0'+(yy/100)%10), byte('0'+(yy/10)%10), byte('0'+yy%10), ' ', - byte('0'+hh/10), byte('0'+hh%10), ':', - byte('0'+mn/10), byte('0'+mn%10), ':', - byte('0'+ss/10), byte('0'+ss%10), ' ', - 'G', 'M', 'T') -} - -var errTooLarge = errors.New("http: request too large") - -// Read next request from connection. -func (c *conn) readRequest() (w *response, err error) { - if c.hijacked() { - return nil, ErrHijacked - } - - if d := c.server.ReadTimeout; d != 0 { - c.rwc.SetReadDeadline(time.Now().Add(d)) - } - if d := c.server.WriteTimeout; d != 0 { - defer func() { - c.rwc.SetWriteDeadline(time.Now().Add(d)) - }() - } - - c.lr.N = c.server.initialLimitedReaderSize() - var req *Request - if req, err = ReadRequest(c.buf.Reader); err != nil { - if c.lr.N == 0 { - return nil, errTooLarge - } - return nil, err - } - c.lr.N = noLimit - - req.RemoteAddr = c.remoteAddr - req.TLS = c.tlsState - - w = &response{ - conn: c, - req: req, - handlerHeader: make(Header), - contentLength: -1, - } - w.cw.res = w - w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize) - return w, nil -} - -func (w *response) Header() Header { - if w.cw.header == nil && w.wroteHeader && !w.cw.wroteHeader { - // Accessing the header between logically writing it - // and physically writing it means we need to allocate - // a clone to snapshot the logically written state. - w.cw.header = w.handlerHeader.clone() - } - w.calledHeader = true - return w.handlerHeader -} - -// maxPostHandlerReadBytes is the max number of Request.Body bytes not -// consumed by a handler that the server will read from the client -// in order to keep a connection alive. If there are more bytes than -// this then the server to be paranoid instead sends a "Connection: -// close" response. -// -// This number is approximately what a typical machine's TCP buffer -// size is anyway. (if we have the bytes on the machine, we might as -// well read them) -const maxPostHandlerReadBytes = 256 << 10 - -func (w *response) WriteHeader(code int) { - if w.conn.hijacked() { - w.conn.server.logf("http: response.WriteHeader on hijacked connection") - return - } - if w.wroteHeader { - w.conn.server.logf("http: multiple response.WriteHeader calls") - return - } - w.wroteHeader = true - w.status = code - - if w.calledHeader && w.cw.header == nil { - w.cw.header = w.handlerHeader.clone() - } - - if cl := w.handlerHeader.get("Content-Length"); cl != "" { - v, err := strconv.ParseInt(cl, 10, 64) - if err == nil && v >= 0 { - w.contentLength = v - } else { - w.conn.server.logf("http: invalid Content-Length of %q", cl) - w.handlerHeader.Del("Content-Length") - } - } -} - -// extraHeader is the set of headers sometimes added by chunkWriter.writeHeader. -// This type is used to avoid extra allocations from cloning and/or populating -// the response Header map and all its 1-element slices. -type extraHeader struct { - contentType string - connection string - transferEncoding string - date []byte // written if not nil - contentLength []byte // written if not nil -} - -// Sorted the same as extraHeader.Write's loop. -var extraHeaderKeys = [][]byte{ - []byte("Content-Type"), - []byte("Connection"), - []byte("Transfer-Encoding"), -} - -var ( - headerContentLength = []byte("Content-Length: ") - headerDate = []byte("Date: ") -) - -// Write writes the headers described in h to w. -// -// This method has a value receiver, despite the somewhat large size -// of h, because it prevents an allocation. The escape analysis isn't -// smart enough to realize this function doesn't mutate h. -func (h extraHeader) Write(w *bufio.Writer) { - if h.date != nil { - w.Write(headerDate) - w.Write(h.date) - w.Write(crlf) - } - if h.contentLength != nil { - w.Write(headerContentLength) - w.Write(h.contentLength) - w.Write(crlf) - } - for i, v := range []string{h.contentType, h.connection, h.transferEncoding} { - if v != "" { - w.Write(extraHeaderKeys[i]) - w.Write(colonSpace) - w.WriteString(v) - w.Write(crlf) - } - } -} - -// writeHeader finalizes the header sent to the client and writes it -// to cw.res.conn.buf. -// -// p is not written by writeHeader, but is the first chunk of the body -// that will be written. It is sniffed for a Content-Type if none is -// set explicitly. It's also used to set the Content-Length, if the -// total body size was small and the handler has already finished -// running. -func (cw *chunkWriter) writeHeader(p []byte) { - if cw.wroteHeader { - return - } - cw.wroteHeader = true - - w := cw.res - keepAlivesEnabled := w.conn.server.doKeepAlives() - isHEAD := w.req.Method == "HEAD" - - // header is written out to w.conn.buf below. Depending on the - // state of the handler, we either own the map or not. If we - // don't own it, the exclude map is created lazily for - // WriteSubset to remove headers. The setHeader struct holds - // headers we need to add. - header := cw.header - owned := header != nil - if !owned { - header = w.handlerHeader - } - var excludeHeader map[string]bool - delHeader := func(key string) { - if owned { - header.Del(key) - return - } - if _, ok := header[key]; !ok { - return - } - if excludeHeader == nil { - excludeHeader = make(map[string]bool) - } - excludeHeader[key] = true - } - var setHeader extraHeader - - // If the handler is done but never sent a Content-Length - // response header and this is our first (and last) write, set - // it, even to zero. This helps HTTP/1.0 clients keep their - // "keep-alive" connections alive. - // Exceptions: 304/204/1xx responses never get Content-Length, and if - // it was a HEAD request, we don't know the difference between - // 0 actual bytes and 0 bytes because the handler noticed it - // was a HEAD request and chose not to write anything. So for - // HEAD, the handler should either write the Content-Length or - // write non-zero bytes. If it's actually 0 bytes and the - // handler never looked at the Request.Method, we just don't - // send a Content-Length header. - if w.handlerDone && bodyAllowedForStatus(w.status) && header.get("Content-Length") == "" && (!isHEAD || len(p) > 0) { - w.contentLength = int64(len(p)) - setHeader.contentLength = strconv.AppendInt(cw.res.clenBuf[:0], int64(len(p)), 10) - } - - // If this was an HTTP/1.0 request with keep-alive and we sent a - // Content-Length back, we can make this a keep-alive response ... - if w.req.wantsHttp10KeepAlive() && keepAlivesEnabled { - sentLength := header.get("Content-Length") != "" - if sentLength && header.get("Connection") == "keep-alive" { - w.closeAfterReply = false - } - } - - // Check for a explicit (and valid) Content-Length header. - hasCL := w.contentLength != -1 - - if w.req.wantsHttp10KeepAlive() && (isHEAD || hasCL) { - _, connectionHeaderSet := header["Connection"] - if !connectionHeaderSet { - setHeader.connection = "keep-alive" - } - } else if !w.req.ProtoAtLeast(1, 1) || w.req.wantsClose() { - w.closeAfterReply = true - } - - if header.get("Connection") == "close" || !keepAlivesEnabled { - w.closeAfterReply = true - } - - // Per RFC 2616, we should consume the request body before - // replying, if the handler hasn't already done so. But we - // don't want to do an unbounded amount of reading here for - // DoS reasons, so we only try up to a threshold. - if w.req.ContentLength != 0 && !w.closeAfterReply { - ecr, isExpecter := w.req.Body.(*expectContinueReader) - if !isExpecter || ecr.resp.wroteContinue { - n, _ := io.CopyN(ioutil.Discard, w.req.Body, maxPostHandlerReadBytes+1) - if n >= maxPostHandlerReadBytes { - w.requestTooLarge() - delHeader("Connection") - setHeader.connection = "close" - } else { - w.req.Body.Close() - } - } - } - - code := w.status - if !bodyAllowedForStatus(code) { - // Must not have body. - // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers" - for _, k := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} { - delHeader(k) - } - } else { - // If no content type, apply sniffing algorithm to body. - _, haveType := header["Content-Type"] - if !haveType { - setHeader.contentType = DetectContentType(p) - } - } - - if _, ok := header["Date"]; !ok { - setHeader.date = appendTime(cw.res.dateBuf[:0], time.Now()) - } - - te := header.get("Transfer-Encoding") - hasTE := te != "" - if hasCL && hasTE && te != "identity" { - // TODO: return an error if WriteHeader gets a return parameter - // For now just ignore the Content-Length. - w.conn.server.logf("http: WriteHeader called with both Transfer-Encoding of %q and a Content-Length of %d", - te, w.contentLength) - delHeader("Content-Length") - hasCL = false - } - - if w.req.Method == "HEAD" || !bodyAllowedForStatus(code) { - // do nothing - } else if code == StatusNoContent { - delHeader("Transfer-Encoding") - } else if hasCL { - delHeader("Transfer-Encoding") - } else if w.req.ProtoAtLeast(1, 1) { - // HTTP/1.1 or greater: use chunked transfer encoding - // to avoid closing the connection at EOF. - // TODO: this blows away any custom or stacked Transfer-Encoding they - // might have set. Deal with that as need arises once we have a valid - // use case. - cw.chunking = true - setHeader.transferEncoding = "chunked" - } else { - // HTTP version < 1.1: cannot do chunked transfer - // encoding and we don't know the Content-Length so - // signal EOF by closing connection. - w.closeAfterReply = true - delHeader("Transfer-Encoding") // in case already set - } - - // Cannot use Content-Length with non-identity Transfer-Encoding. - if cw.chunking { - delHeader("Content-Length") - } - if !w.req.ProtoAtLeast(1, 0) { - return - } - - if w.closeAfterReply && (!keepAlivesEnabled || !hasToken(cw.header.get("Connection"), "close")) { - delHeader("Connection") - if w.req.ProtoAtLeast(1, 1) { - setHeader.connection = "close" - } - } - - w.conn.buf.WriteString(statusLine(w.req, code)) - cw.header.WriteSubset(w.conn.buf, excludeHeader) - setHeader.Write(w.conn.buf.Writer) - w.conn.buf.Write(crlf) -} - -// statusLines is a cache of Status-Line strings, keyed by code (for -// HTTP/1.1) or negative code (for HTTP/1.0). This is faster than a -// map keyed by struct of two fields. This map's max size is bounded -// by 2*len(statusText), two protocol types for each known official -// status code in the statusText map. -var ( - statusMu sync.RWMutex - statusLines = make(map[int]string) -) - -// statusLine returns a response Status-Line (RFC 2616 Section 6.1) -// for the given request and response status code. -func statusLine(req *Request, code int) string { - // Fast path: - key := code - proto11 := req.ProtoAtLeast(1, 1) - if !proto11 { - key = -key - } - statusMu.RLock() - line, ok := statusLines[key] - statusMu.RUnlock() - if ok { - return line - } - - // Slow path: - proto := "HTTP/1.0" - if proto11 { - proto = "HTTP/1.1" - } - codestring := strconv.Itoa(code) - text, ok := statusText[code] - if !ok { - text = "status code " + codestring - } - line = proto + " " + codestring + " " + text + "\r\n" - if ok { - statusMu.Lock() - defer statusMu.Unlock() - statusLines[key] = line - } - return line -} - -// bodyAllowed returns true if a Write is allowed for this response type. -// It's illegal to call this before the header has been flushed. -func (w *response) bodyAllowed() bool { - if !w.wroteHeader { - panic("") - } - return bodyAllowedForStatus(w.status) -} - -// The Life Of A Write is like this: -// -// Handler starts. No header has been sent. The handler can either -// write a header, or just start writing. Writing before sending a header -// sends an implicitly empty 200 OK header. -// -// If the handler didn't declare a Content-Length up front, we either -// go into chunking mode or, if the handler finishes running before -// the chunking buffer size, we compute a Content-Length and send that -// in the header instead. -// -// Likewise, if the handler didn't set a Content-Type, we sniff that -// from the initial chunk of output. -// -// The Writers are wired together like: -// -// 1. *response (the ResponseWriter) -> -// 2. (*response).w, a *bufio.Writer of bufferBeforeChunkingSize bytes -// 3. chunkWriter.Writer (whose writeHeader finalizes Content-Length/Type) -// and which writes the chunk headers, if needed. -// 4. conn.buf, a bufio.Writer of default (4kB) bytes -// 5. the rwc, the net.Conn. -// -// TODO(bradfitz): short-circuit some of the buffering when the -// initial header contains both a Content-Type and Content-Length. -// Also short-circuit in (1) when the header's been sent and not in -// chunking mode, writing directly to (4) instead, if (2) has no -// buffered data. More generally, we could short-circuit from (1) to -// (3) even in chunking mode if the write size from (1) is over some -// threshold and nothing is in (2). The answer might be mostly making -// bufferBeforeChunkingSize smaller and having bufio's fast-paths deal -// with this instead. -func (w *response) Write(data []byte) (n int, err error) { - return w.write(len(data), data, "") -} - -func (w *response) WriteString(data string) (n int, err error) { - return w.write(len(data), nil, data) -} - -// either dataB or dataS is non-zero. -func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) { - if w.conn.hijacked() { - w.conn.server.logf("http: response.Write on hijacked connection") - return 0, ErrHijacked - } - if !w.wroteHeader { - w.WriteHeader(StatusOK) - } - if lenData == 0 { - return 0, nil - } - if !w.bodyAllowed() { - return 0, ErrBodyNotAllowed - } - - w.written += int64(lenData) // ignoring errors, for errorKludge - if w.contentLength != -1 && w.written > w.contentLength { - return 0, ErrContentLength - } - if dataB != nil { - return w.w.Write(dataB) - } else { - return w.w.WriteString(dataS) - } -} - -func (w *response) finishRequest() { - w.handlerDone = true - - if !w.wroteHeader { - w.WriteHeader(StatusOK) - } - - w.w.Flush() - putBufioWriter(w.w) - w.cw.close() - w.conn.buf.Flush() - - // Close the body (regardless of w.closeAfterReply) so we can - // re-use its bufio.Reader later safely. - w.req.Body.Close() - - if w.req.MultipartForm != nil { - w.req.MultipartForm.RemoveAll() - } - - if w.req.Method != "HEAD" && w.contentLength != -1 && w.bodyAllowed() && w.contentLength != w.written { - // Did not write enough. Avoid getting out of sync. - w.closeAfterReply = true - } -} - -func (w *response) Flush() { - if !w.wroteHeader { - w.WriteHeader(StatusOK) - } - w.w.Flush() - w.cw.flush() -} - -func (c *conn) finalFlush() { - if c.buf != nil { - c.buf.Flush() - - // Steal the bufio.Reader (~4KB worth of memory) and its associated - // reader for a future connection. - putBufioReader(c.buf.Reader) - - // Steal the bufio.Writer (~4KB worth of memory) and its associated - // writer for a future connection. - putBufioWriter(c.buf.Writer) - - c.buf = nil - } -} - -// Close the connection. -func (c *conn) close() { - c.finalFlush() - if c.rwc != nil { - c.rwc.Close() - c.rwc = nil - } -} - -// rstAvoidanceDelay is the amount of time we sleep after closing the -// write side of a TCP connection before closing the entire socket. -// By sleeping, we increase the chances that the client sees our FIN -// and processes its final data before they process the subsequent RST -// from closing a connection with known unread data. -// This RST seems to occur mostly on BSD systems. (And Windows?) -// This timeout is somewhat arbitrary (~latency around the planet). -const rstAvoidanceDelay = 500 * time.Millisecond - -// closeWrite flushes any outstanding data and sends a FIN packet (if -// client is connected via TCP), signalling that we're done. We then -// pause for a bit, hoping the client processes it before `any -// subsequent RST. -// -// See http://golang.org/issue/3595 -func (c *conn) closeWriteAndWait() { - c.finalFlush() - if tcp, ok := c.rwc.(*net.TCPConn); ok { - tcp.CloseWrite() - } - time.Sleep(rstAvoidanceDelay) -} - -// validNPN reports whether the proto is not a blacklisted Next -// Protocol Negotiation protocol. Empty and built-in protocol types -// are blacklisted and can't be overridden with alternate -// implementations. -func validNPN(proto string) bool { - switch proto { - case "", "http/1.1", "http/1.0": - return false - } - return true -} - -func (c *conn) setState(nc net.Conn, state ConnState) { - if hook := c.server.ConnState; hook != nil { - hook(nc, state) - } -} - -// Serve a new connection. -func (c *conn) serve() { - origConn := c.rwc // copy it before it's set nil on Close or Hijack - defer func() { - if err := recover(); err != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf) - } - if !c.hijacked() { - c.close() - c.setState(origConn, StateClosed) - } - }() - - if tlsConn, ok := c.rwc.(*tls.Conn); ok { - if d := c.server.ReadTimeout; d != 0 { - c.rwc.SetReadDeadline(time.Now().Add(d)) - } - if d := c.server.WriteTimeout; d != 0 { - c.rwc.SetWriteDeadline(time.Now().Add(d)) - } - if err := tlsConn.Handshake(); err != nil { - c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err) - return - } - c.tlsState = new(tls.ConnectionState) - *c.tlsState = tlsConn.ConnectionState() - if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) { - if fn := c.server.TLSNextProto[proto]; fn != nil { - h := initNPNRequest{tlsConn, serverHandler{c.server}} - fn(c.server, tlsConn, h) - } - return - } - } - - for { - w, err := c.readRequest() - if c.lr.N != c.server.initialLimitedReaderSize() { - // If we read any bytes off the wire, we're active. - c.setState(c.rwc, StateActive) - } - if err != nil { - if err == errTooLarge { - // Their HTTP client may or may not be - // able to read this if we're - // responding to them and hanging up - // while they're still writing their - // request. Undefined behavior. - io.WriteString(c.rwc, "HTTP/1.1 413 Request Entity Too Large\r\n\r\n") - c.closeWriteAndWait() - break - } else if err == io.EOF { - break // Don't reply - } else if neterr, ok := err.(net.Error); ok && neterr.Timeout() { - break // Don't reply - } - io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request\r\n\r\n") - break - } - - // Expect 100 Continue support - req := w.req - if req.expectsContinue() { - if req.ProtoAtLeast(1, 1) { - // Wrap the Body reader with one that replies on the connection - req.Body = &expectContinueReader{readCloser: req.Body, resp: w} - } - if req.ContentLength == 0 { - w.Header().Set("Connection", "close") - w.WriteHeader(StatusBadRequest) - w.finishRequest() - break - } - req.Header.Del("Expect") - } else if req.Header.get("Expect") != "" { - w.sendExpectationFailed() - break - } - - // HTTP cannot have multiple simultaneous active requests.[*] - // Until the server replies to this request, it can't read another, - // so we might as well run the handler in this goroutine. - // [*] Not strictly true: HTTP pipelining. We could let them all process - // in parallel even if their responses need to be serialized. - serverHandler{c.server}.ServeHTTP(w, w.req) - if c.hijacked() { - return - } - w.finishRequest() - if w.closeAfterReply { - if w.requestBodyLimitHit { - c.closeWriteAndWait() - } - break - } - c.setState(c.rwc, StateIdle) - } -} - -func (w *response) sendExpectationFailed() { - // TODO(bradfitz): let ServeHTTP handlers handle - // requests with non-standard expectation[s]? Seems - // theoretical at best, and doesn't fit into the - // current ServeHTTP model anyway. We'd need to - // make the ResponseWriter an optional - // "ExpectReplier" interface or something. - // - // For now we'll just obey RFC 2616 14.20 which says - // "If a server receives a request containing an - // Expect field that includes an expectation- - // extension that it does not support, it MUST - // respond with a 417 (Expectation Failed) status." - w.Header().Set("Connection", "close") - w.WriteHeader(StatusExpectationFailed) - w.finishRequest() -} - -// Hijack implements the Hijacker.Hijack method. Our response is both a ResponseWriter -// and a Hijacker. -func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) { - if w.wroteHeader { - w.cw.flush() - } - // Release the bufioWriter that writes to the chunk writer, it is not - // used after a connection has been hijacked. - rwc, buf, err = w.conn.hijack() - if err == nil { - putBufioWriter(w.w) - w.w = nil - } - return rwc, buf, err -} - -func (w *response) CloseNotify() <-chan bool { - return w.conn.closeNotify() -} - -// The HandlerFunc type is an adapter to allow the use of -// ordinary functions as HTTP handlers. If f is a function -// with the appropriate signature, HandlerFunc(f) is a -// Handler object that calls f. -type HandlerFunc func(ResponseWriter, *Request) - -// ServeHTTP calls f(w, r). -func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { - f(w, r) -} - -// Helper handlers - -// Error replies to the request with the specified error message and HTTP code. -// The error message should be plain text. -func Error(w ResponseWriter, error string, code int) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(code) - fmt.Fprintln(w, error) -} - -// NotFound replies to the request with an HTTP 404 not found error. -func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) } - -// NotFoundHandler returns a simple request handler -// that replies to each request with a ``404 page not found'' reply. -func NotFoundHandler() Handler { return HandlerFunc(NotFound) } - -// StripPrefix returns a handler that serves HTTP requests -// by removing the given prefix from the request URL's Path -// and invoking the handler h. StripPrefix handles a -// request for a path that doesn't begin with prefix by -// replying with an HTTP 404 not found error. -func StripPrefix(prefix string, h Handler) Handler { - if prefix == "" { - return h - } - return HandlerFunc(func(w ResponseWriter, r *Request) { - if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) { - r.URL.Path = p - h.ServeHTTP(w, r) - } else { - NotFound(w, r) - } - }) -} - -// Redirect replies to the request with a redirect to url, -// which may be a path relative to the request path. -func Redirect(w ResponseWriter, r *Request, urlStr string, code int) { - if u, err := url.Parse(urlStr); err == nil { - // If url was relative, make absolute by - // combining with request path. - // The browser would probably do this for us, - // but doing it ourselves is more reliable. - - // NOTE(rsc): RFC 2616 says that the Location - // line must be an absolute URI, like - // "http://www.google.com/redirect/", - // not a path like "/redirect/". - // Unfortunately, we don't know what to - // put in the host name section to get the - // client to connect to us again, so we can't - // know the right absolute URI to send back. - // Because of this problem, no one pays attention - // to the RFC; they all send back just a new path. - // So do we. - oldpath := r.URL.Path - if oldpath == "" { // should not happen, but avoid a crash if it does - oldpath = "/" - } - if u.Scheme == "" { - // no leading http://server - if urlStr == "" || urlStr[0] != '/' { - // make relative path absolute - olddir, _ := path.Split(oldpath) - urlStr = olddir + urlStr - } - - var query string - if i := strings.Index(urlStr, "?"); i != -1 { - urlStr, query = urlStr[:i], urlStr[i:] - } - - // clean up but preserve trailing slash - trailing := strings.HasSuffix(urlStr, "/") - urlStr = path.Clean(urlStr) - if trailing && !strings.HasSuffix(urlStr, "/") { - urlStr += "/" - } - urlStr += query - } - } - - w.Header().Set("Location", urlStr) - w.WriteHeader(code) - - // RFC2616 recommends that a short note "SHOULD" be included in the - // response because older user agents may not understand 301/307. - // Shouldn't send the response for POST or HEAD; that leaves GET. - if r.Method == "GET" { - note := "" + statusText[code] + ".\n" - fmt.Fprintln(w, note) - } -} - -var htmlReplacer = strings.NewReplacer( - "&", "&", - "<", "<", - ">", ">", - // """ is shorter than """. - `"`, """, - // "'" is shorter than "'" and apos was not in HTML until HTML5. - "'", "'", -) - -func htmlEscape(s string) string { - return htmlReplacer.Replace(s) -} - -// Redirect to a fixed URL -type redirectHandler struct { - url string - code int -} - -func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) { - Redirect(w, r, rh.url, rh.code) -} - -// RedirectHandler returns a request handler that redirects -// each request it receives to the given url using the given -// status code. -func RedirectHandler(url string, code int) Handler { - return &redirectHandler{url, code} -} - -// ServeMux is an HTTP request multiplexer. -// It matches the URL of each incoming request against a list of registered -// patterns and calls the handler for the pattern that -// most closely matches the URL. -// -// Patterns name fixed, rooted paths, like "/favicon.ico", -// or rooted subtrees, like "/images/" (note the trailing slash). -// Longer patterns take precedence over shorter ones, so that -// if there are handlers registered for both "/images/" -// and "/images/thumbnails/", the latter handler will be -// called for paths beginning "/images/thumbnails/" and the -// former will receive requests for any other paths in the -// "/images/" subtree. -// -// Note that since a pattern ending in a slash names a rooted subtree, -// the pattern "/" matches all paths not matched by other registered -// patterns, not just the URL with Path == "/". -// -// Patterns may optionally begin with a host name, restricting matches to -// URLs on that host only. Host-specific patterns take precedence over -// general patterns, so that a handler might register for the two patterns -// "/codesearch" and "codesearch.google.com/" without also taking over -// requests for "http://www.google.com/". -// -// ServeMux also takes care of sanitizing the URL request path, -// redirecting any request containing . or .. elements to an -// equivalent .- and ..-free URL. -type ServeMux struct { - mu sync.RWMutex - m map[string]muxEntry - hosts bool // whether any patterns contain hostnames -} - -type muxEntry struct { - explicit bool - h Handler - pattern string -} - -// NewServeMux allocates and returns a new ServeMux. -func NewServeMux() *ServeMux { return &ServeMux{m: make(map[string]muxEntry)} } - -// DefaultServeMux is the default ServeMux used by Serve. -var DefaultServeMux = NewServeMux() - -// Does path match pattern? -func pathMatch(pattern, path string) bool { - if len(pattern) == 0 { - // should not happen - return false - } - n := len(pattern) - if pattern[n-1] != '/' { - return pattern == path - } - return len(path) >= n && path[0:n] == pattern -} - -// Return the canonical path for p, eliminating . and .. elements. -func cleanPath(p string) string { - if p == "" { - return "/" - } - if p[0] != '/' { - p = "/" + p - } - np := path.Clean(p) - // path.Clean removes trailing slash except for root; - // put the trailing slash back if necessary. - if p[len(p)-1] == '/' && np != "/" { - np += "/" - } - return np -} - -// Find a handler on a handler map given a path string -// Most-specific (longest) pattern wins -func (mux *ServeMux) match(path string) (h Handler, pattern string) { - var n = 0 - for k, v := range mux.m { - if !pathMatch(k, path) { - continue - } - if h == nil || len(k) > n { - n = len(k) - h = v.h - pattern = v.pattern - } - } - return -} - -// Handler returns the handler to use for the given request, -// consulting r.Method, r.Host, and r.URL.Path. It always returns -// a non-nil handler. If the path is not in its canonical form, the -// handler will be an internally-generated handler that redirects -// to the canonical path. -// -// Handler also returns the registered pattern that matches the -// request or, in the case of internally-generated redirects, -// the pattern that will match after following the redirect. -// -// If there is no registered handler that applies to the request, -// Handler returns a ``page not found'' handler and an empty pattern. -func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { - if r.Method != "CONNECT" { - if p := cleanPath(r.URL.Path); p != r.URL.Path { - _, pattern = mux.handler(r.Host, p) - url := *r.URL - url.Path = p - return RedirectHandler(url.String(), StatusMovedPermanently), pattern - } - } - - return mux.handler(r.Host, r.URL.Path) -} - -// handler is the main implementation of Handler. -// The path is known to be in canonical form, except for CONNECT methods. -func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) { - mux.mu.RLock() - defer mux.mu.RUnlock() - - // Host-specific pattern takes precedence over generic ones - if mux.hosts { - h, pattern = mux.match(host + path) - } - if h == nil { - h, pattern = mux.match(path) - } - if h == nil { - h, pattern = NotFoundHandler(), "" - } - return -} - -// ServeHTTP dispatches the request to the handler whose -// pattern most closely matches the request URL. -func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { - if r.RequestURI == "*" { - if r.ProtoAtLeast(1, 1) { - w.Header().Set("Connection", "close") - } - w.WriteHeader(StatusBadRequest) - return - } - h, _ := mux.Handler(r) - h.ServeHTTP(w, r) -} - -// Handle registers the handler for the given pattern. -// If a handler already exists for pattern, Handle panics. -func (mux *ServeMux) Handle(pattern string, handler Handler) { - mux.mu.Lock() - defer mux.mu.Unlock() - - if pattern == "" { - panic("http: invalid pattern " + pattern) - } - if handler == nil { - panic("http: nil handler") - } - if mux.m[pattern].explicit { - panic("http: multiple registrations for " + pattern) - } - - mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} - - if pattern[0] != '/' { - mux.hosts = true - } - - // Helpful behavior: - // If pattern is /tree/, insert an implicit permanent redirect for /tree. - // It can be overridden by an explicit registration. - n := len(pattern) - if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit { - // If pattern contains a host name, strip it and use remaining - // path for redirect. - path := pattern - if pattern[0] != '/' { - // In pattern, at least the last character is a '/', so - // strings.Index can't be -1. - path = pattern[strings.Index(pattern, "/"):] - } - mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(path, StatusMovedPermanently), pattern: pattern} - } -} - -// HandleFunc registers the handler function for the given pattern. -func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { - mux.Handle(pattern, HandlerFunc(handler)) -} - -// Handle registers the handler for the given pattern -// in the DefaultServeMux. -// The documentation for ServeMux explains how patterns are matched. -func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) } - -// HandleFunc registers the handler function for the given pattern -// in the DefaultServeMux. -// The documentation for ServeMux explains how patterns are matched. -func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { - DefaultServeMux.HandleFunc(pattern, handler) -} - -// Serve accepts incoming HTTP connections on the listener l, -// creating a new service goroutine for each. The service goroutines -// read requests and then call handler to reply to them. -// Handler is typically nil, in which case the DefaultServeMux is used. -func Serve(l net.Listener, handler Handler) error { - srv := &Server{Handler: handler} - return srv.Serve(l) -} - -// A Server defines parameters for running an HTTP server. -// The zero value for Server is a valid configuration. -type Server struct { - Addr string // TCP address to listen on, ":http" if empty - Handler Handler // handler to invoke, http.DefaultServeMux if nil - ReadTimeout time.Duration // maximum duration before timing out read of the request - WriteTimeout time.Duration // maximum duration before timing out write of the response - MaxHeaderBytes int // maximum size of request headers, DefaultMaxHeaderBytes if 0 - TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS - - // TLSNextProto optionally specifies a function to take over - // ownership of the provided TLS connection when an NPN - // protocol upgrade has occurred. The map key is the protocol - // name negotiated. The Handler argument should be used to - // handle HTTP requests and will initialize the Request's TLS - // and RemoteAddr if not already set. The connection is - // automatically closed when the function returns. - TLSNextProto map[string]func(*Server, *tls.Conn, Handler) - - // ConnState specifies an optional callback function that is - // called when a client connection changes state. See the - // ConnState type and associated constants for details. - ConnState func(net.Conn, ConnState) - - // ErrorLog specifies an optional logger for errors accepting - // connections and unexpected behavior from handlers. - // If nil, logging goes to os.Stderr via the log package's - // standard logger. - ErrorLog *log.Logger - - disableKeepAlives int32 // accessed atomically. -} - -// A ConnState represents the state of a client connection to a server. -// It's used by the optional Server.ConnState hook. -type ConnState int - -const ( - // StateNew represents a new connection that is expected to - // send a request immediately. Connections begin at this - // state and then transition to either StateActive or - // StateClosed. - StateNew ConnState = iota - - // StateActive represents a connection that has read 1 or more - // bytes of a request. The Server.ConnState hook for - // StateActive fires before the request has entered a handler - // and doesn't fire again until the request has been - // handled. After the request is handled, the state - // transitions to StateClosed, StateHijacked, or StateIdle. - StateActive - - // StateIdle represents a connection that has finished - // handling a request and is in the keep-alive state, waiting - // for a new request. Connections transition from StateIdle - // to either StateActive or StateClosed. - StateIdle - - // StateHijacked represents a hijacked connection. - // This is a terminal state. It does not transition to StateClosed. - StateHijacked - - // StateClosed represents a closed connection. - // This is a terminal state. Hijacked connections do not - // transition to StateClosed. - StateClosed -) - -var stateName = map[ConnState]string{ - StateNew: "new", - StateActive: "active", - StateIdle: "idle", - StateHijacked: "hijacked", - StateClosed: "closed", -} - -func (c ConnState) String() string { - return stateName[c] -} - -// serverHandler delegates to either the server's Handler or -// DefaultServeMux and also handles "OPTIONS *" requests. -type serverHandler struct { - srv *Server -} - -func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) { - handler := sh.srv.Handler - if handler == nil { - handler = DefaultServeMux - } - if req.RequestURI == "*" && req.Method == "OPTIONS" { - handler = globalOptionsHandler{} - } - handler.ServeHTTP(rw, req) -} - -// ListenAndServe listens on the TCP network address srv.Addr and then -// calls Serve to handle requests on incoming connections. If -// srv.Addr is blank, ":http" is used. -func (srv *Server) ListenAndServe() error { - addr := srv.Addr - if addr == "" { - addr = ":http" - } - ln, err := net.Listen("tcp", addr) - if err != nil { - return err - } - return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) -} - -// Serve accepts incoming connections on the Listener l, creating a -// new service goroutine for each. The service goroutines read requests and -// then call srv.Handler to reply to them. -func (srv *Server) Serve(l net.Listener) error { - defer l.Close() - var tempDelay time.Duration // how long to sleep on accept failure - for { - rw, e := l.Accept() - if e != nil { - if ne, ok := e.(net.Error); ok && ne.Temporary() { - if tempDelay == 0 { - tempDelay = 5 * time.Millisecond - } else { - tempDelay *= 2 - } - if max := 1 * time.Second; tempDelay > max { - tempDelay = max - } - srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay) - time.Sleep(tempDelay) - continue - } - return e - } - tempDelay = 0 - c, err := srv.newConn(rw) - if err != nil { - continue - } - c.setState(c.rwc, StateNew) // before Serve can return - go c.serve() - } -} - -func (s *Server) doKeepAlives() bool { - return atomic.LoadInt32(&s.disableKeepAlives) == 0 -} - -// SetKeepAlivesEnabled controls whether HTTP keep-alives are enabled. -// By default, keep-alives are always enabled. Only very -// resource-constrained environments or servers in the process of -// shutting down should disable them. -func (s *Server) SetKeepAlivesEnabled(v bool) { - if v { - atomic.StoreInt32(&s.disableKeepAlives, 0) - } else { - atomic.StoreInt32(&s.disableKeepAlives, 1) - } -} - -func (s *Server) logf(format string, args ...interface{}) { - if s.ErrorLog != nil { - s.ErrorLog.Printf(format, args...) - } else { - log.Printf(format, args...) - } -} - -// ListenAndServe listens on the TCP network address addr -// and then calls Serve with handler to handle requests -// on incoming connections. Handler is typically nil, -// in which case the DefaultServeMux is used. -// -// A trivial example server is: -// -// package main -// -// import ( -// "io" -// "launchpad.net/ubuntu-push/http13client" -// "log" -// ) -// -// // hello world, the web server -// func HelloServer(w http.ResponseWriter, req *http.Request) { -// io.WriteString(w, "hello, world!\n") -// } -// -// func main() { -// http.HandleFunc("/hello", HelloServer) -// err := http.ListenAndServe(":12345", nil) -// if err != nil { -// log.Fatal("ListenAndServe: ", err) -// } -// } -func ListenAndServe(addr string, handler Handler) error { - server := &Server{Addr: addr, Handler: handler} - return server.ListenAndServe() -} - -// ListenAndServeTLS acts identically to ListenAndServe, except that it -// expects HTTPS connections. Additionally, files containing a certificate and -// matching private key for the server must be provided. If the certificate -// is signed by a certificate authority, the certFile should be the concatenation -// of the server's certificate followed by the CA's certificate. -// -// A trivial example server is: -// -// import ( -// "log" -// "launchpad.net/ubuntu-push/http13client" -// ) -// -// func handler(w http.ResponseWriter, req *http.Request) { -// w.Header().Set("Content-Type", "text/plain") -// w.Write([]byte("This is an example server.\n")) -// } -// -// func main() { -// http.HandleFunc("/", handler) -// log.Printf("About to listen on 10443. Go to https://127.0.0.1:10443/") -// err := http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil) -// if err != nil { -// log.Fatal(err) -// } -// } -// -// One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem. -func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error { - server := &Server{Addr: addr, Handler: handler} - return server.ListenAndServeTLS(certFile, keyFile) -} - -// ListenAndServeTLS listens on the TCP network address srv.Addr and -// then calls Serve to handle requests on incoming TLS connections. -// -// Filenames containing a certificate and matching private key for -// the server must be provided. If the certificate is signed by a -// certificate authority, the certFile should be the concatenation -// of the server's certificate followed by the CA's certificate. -// -// If srv.Addr is blank, ":https" is used. -func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { - addr := srv.Addr - if addr == "" { - addr = ":https" - } - config := &tls.Config{} - if srv.TLSConfig != nil { - *config = *srv.TLSConfig - } - if config.NextProtos == nil { - config.NextProtos = []string{"http/1.1"} - } - - var err error - config.Certificates = make([]tls.Certificate, 1) - config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return err - } - - ln, err := net.Listen("tcp", addr) - if err != nil { - return err - } - - tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config) - return srv.Serve(tlsListener) -} - -// TimeoutHandler returns a Handler that runs h with the given time limit. -// -// The new Handler calls h.ServeHTTP to handle each request, but if a -// call runs for longer than its time limit, the handler responds with -// a 503 Service Unavailable error and the given message in its body. -// (If msg is empty, a suitable default message will be sent.) -// After such a timeout, writes by h to its ResponseWriter will return -// ErrHandlerTimeout. -func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler { - f := func() <-chan time.Time { - return time.After(dt) - } - return &timeoutHandler{h, f, msg} -} - -// ErrHandlerTimeout is returned on ResponseWriter Write calls -// in handlers which have timed out. -var ErrHandlerTimeout = errors.New("http: Handler timeout") - -type timeoutHandler struct { - handler Handler - timeout func() <-chan time.Time // returns channel producing a timeout - body string -} - -func (h *timeoutHandler) errorBody() string { - if h.body != "" { - return h.body - } - return "Timeout

Timeout

" -} - -func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) { - done := make(chan bool, 1) - tw := &timeoutWriter{w: w} - go func() { - h.handler.ServeHTTP(tw, r) - done <- true - }() - select { - case <-done: - return - case <-h.timeout(): - tw.mu.Lock() - defer tw.mu.Unlock() - if !tw.wroteHeader { - tw.w.WriteHeader(StatusServiceUnavailable) - tw.w.Write([]byte(h.errorBody())) - } - tw.timedOut = true - } -} - -type timeoutWriter struct { - w ResponseWriter - - mu sync.Mutex - timedOut bool - wroteHeader bool -} - -func (tw *timeoutWriter) Header() Header { - return tw.w.Header() -} - -func (tw *timeoutWriter) Write(p []byte) (int, error) { - tw.mu.Lock() - timedOut := tw.timedOut - tw.mu.Unlock() - if timedOut { - return 0, ErrHandlerTimeout - } - return tw.w.Write(p) -} - -func (tw *timeoutWriter) WriteHeader(code int) { - tw.mu.Lock() - if tw.timedOut || tw.wroteHeader { - tw.mu.Unlock() - return - } - tw.wroteHeader = true - tw.mu.Unlock() - tw.w.WriteHeader(code) -} - -// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted -// connections. It's used by ListenAndServe and ListenAndServeTLS so -// dead TCP connections (e.g. closing laptop mid-download) eventually -// go away. -type tcpKeepAliveListener struct { - *net.TCPListener -} - -func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { - tc, err := ln.AcceptTCP() - if err != nil { - return - } - tc.SetKeepAlive(true) - tc.SetKeepAlivePeriod(3 * time.Minute) - return tc, nil -} - -// globalOptionsHandler responds to "OPTIONS *" requests. -type globalOptionsHandler struct{} - -func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) { - w.Header().Set("Content-Length", "0") - if r.ContentLength != 0 { - // Read up to 4KB of OPTIONS body (as mentioned in the - // spec as being reserved for future use), but anything - // over that is considered a waste of server resources - // (or an attack) and we abort and close the connection, - // courtesy of MaxBytesReader's EOF behavior. - mb := MaxBytesReader(w, r.Body, 4<<10) - io.Copy(ioutil.Discard, mb) - } -} +) // eofReader is a non-nil io.ReadCloser that always returns EOF. // It embeds a *strings.Reader so it still has a WriteTo method @@ -1992,28 +25,6 @@ ioutil.NopCloser(nil), } -// initNPNRequest is an HTTP handler that initializes certain -// uninitialized fields in its *Request. Such partially-initialized -// Requests come from NPN protocol handlers. -type initNPNRequest struct { - c *tls.Conn - h serverHandler -} - -func (h initNPNRequest) ServeHTTP(rw ResponseWriter, req *Request) { - if req.TLS == nil { - req.TLS = &tls.ConnectionState{} - *req.TLS = h.c.ConnectionState() - } - if req.Body == nil { - req.Body = eofReader - } - if req.RemoteAddr == "" { - req.RemoteAddr = h.c.RemoteAddr().String() - } - h.h.ServeHTTP(rw, req) -} - // loggingConn is used for debugging. type loggingConn struct { name string ubuntu-push-0.2.1+14.04.20140423.1/http13client/_patches/no_serve_test_unsupported_bench.patch0000644000015301777760000000216612325724711032375 0ustar pbusernogroup00000000000000--- a/serve_test.go 2014-03-19 20:14:01.831371373 +0000 +++ b/serve_test.go 2014-03-19 20:14:47.518205363 +0000 @@ -2474,43 +2474,6 @@ b.StopTimer() } -func BenchmarkClientServerParallel4(b *testing.B) { - benchmarkClientServerParallel(b, 4) -} - -func BenchmarkClientServerParallel64(b *testing.B) { - benchmarkClientServerParallel(b, 64) -} - -func benchmarkClientServerParallel(b *testing.B, parallelism int) { - b.ReportAllocs() - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { - fmt.Fprintf(rw, "Hello world.\n") - })) - defer ts.Close() - b.ResetTimer() - b.SetParallelism(parallelism) - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - res, err := Get(ts.URL) - if err != nil { - b.Logf("Get: %v", err) - continue - } - all, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - b.Logf("ReadAll: %v", err) - continue - } - body := string(all) - if body != "Hello world.\n" { - panic("Got body: " + body) - } - } - }) -} - // A benchmark for profiling the server without the HTTP client code. // The client code runs in a subprocess. // ubuntu-push-0.2.1+14.04.20140423.1/http13client/_patches/fix_tests.patch0000644000015301777760000013434212325724711025721 0ustar pbusernogroup00000000000000=== modified file 'http13client/client_test.go' --- http13client/client_test.go 2014-03-19 21:38:56 +0000 +++ http13client/client_test.go 2014-03-19 22:27:37 +0000 @@ -15,8 +15,8 @@ "fmt" "io" "io/ioutil" - "log" "net" + "net/http" . "launchpad.net/ubuntu-push/http13client" "net/http/httptest" "net/url" @@ -27,7 +27,7 @@ "time" ) -var robotsTxtHandler = HandlerFunc(func(w ResponseWriter, r *Request) { +var robotsTxtHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Last-Modified", "sometime") fmt.Fprintf(w, "User-agent: go\nDisallow: /something/") }) @@ -193,7 +193,7 @@ func TestClientRedirects(t *testing.T) { defer afterTest(t) var ts *httptest.Server - ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { n, _ := strconv.Atoi(r.FormValue("n")) // Test Referer header. (7 is arbitrary position to test at) if n == 7 { @@ -202,7 +202,7 @@ } } if n < 15 { - Redirect(w, r, fmt.Sprintf("/?n=%d", n+1), StatusFound) + http.Redirect(w, r, fmt.Sprintf("/?n=%d", n+1), StatusFound) return } fmt.Fprintf(w, "n=%d", n) @@ -271,7 +271,7 @@ bytes.Buffer } var ts *httptest.Server - ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Lock() fmt.Fprintf(&log.Buffer, "%s %s ", r.Method, r.RequestURI) log.Unlock() @@ -312,21 +312,21 @@ } } -var expectedCookies = []*Cookie{ +var expectedCookies = []*http.Cookie{ {Name: "ChocolateChip", Value: "tasty"}, {Name: "First", Value: "Hit"}, {Name: "Second", Value: "Hit"}, } -var echoCookiesRedirectHandler = HandlerFunc(func(w ResponseWriter, r *Request) { +var echoCookiesRedirectHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for _, cookie := range r.Cookies() { - SetCookie(w, cookie) + http.SetCookie(w, cookie) } if r.URL.Path == "/" { - SetCookie(w, expectedCookies[1]) - Redirect(w, r, "/second", StatusMovedPermanently) + http.SetCookie(w, expectedCookies[1]) + http.Redirect(w, r, "/second", StatusMovedPermanently) } else { - SetCookie(w, expectedCookies[2]) + http.SetCookie(w, expectedCookies[2]) w.Write([]byte("hello")) } }) @@ -334,7 +334,7 @@ func TestClientSendsCookieFromJar(t *testing.T) { tr := &recordingTransport{} client := &Client{Transport: tr} - client.Jar = &TestJar{perURL: make(map[string][]*Cookie)} + client.Jar = &TestJar{perURL: make(map[string][]*http.Cookie)} us := "http://dummy.faketld/" u, _ := url.Parse(us) client.Jar.SetCookies(u, expectedCookies) @@ -364,19 +364,19 @@ // scope of all cookies. type TestJar struct { m sync.Mutex - perURL map[string][]*Cookie + perURL map[string][]*http.Cookie } -func (j *TestJar) SetCookies(u *url.URL, cookies []*Cookie) { +func (j *TestJar) SetCookies(u *url.URL, cookies []*http.Cookie) { j.m.Lock() defer j.m.Unlock() if j.perURL == nil { - j.perURL = make(map[string][]*Cookie) + j.perURL = make(map[string][]*http.Cookie) } j.perURL[u.Host] = cookies } -func (j *TestJar) Cookies(u *url.URL) []*Cookie { +func (j *TestJar) Cookies(u *url.URL) []*http.Cookie { j.m.Lock() defer j.m.Unlock() return j.perURL[u.Host] @@ -391,7 +391,7 @@ Jar: new(TestJar), } u, _ := url.Parse(ts.URL) - c.Jar.SetCookies(u, []*Cookie{expectedCookies[0]}) + c.Jar.SetCookies(u, []*http.Cookie{expectedCookies[0]}) resp, err := c.Get(ts.URL) if err != nil { t.Fatalf("Get: %v", err) @@ -400,7 +400,7 @@ matchReturnedCookies(t, expectedCookies, resp.Cookies()) } -func matchReturnedCookies(t *testing.T, expected, given []*Cookie) { +func matchReturnedCookies(t *testing.T, expected, given []*http.Cookie) { if len(given) != len(expected) { t.Logf("Received cookies: %v", given) t.Errorf("Expected %d cookies, got %d", len(expected), len(given)) @@ -421,14 +421,14 @@ func TestJarCalls(t *testing.T) { defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pathSuffix := r.RequestURI[1:] if r.RequestURI == "/nosetcookie" { return // dont set cookies for this path } - SetCookie(w, &Cookie{Name: "name" + pathSuffix, Value: "val" + pathSuffix}) + http.SetCookie(w, &http.Cookie{Name: "name" + pathSuffix, Value: "val" + pathSuffix}) if r.RequestURI == "/" { - Redirect(w, r, "http://secondhost.fake/secondpath", 302) + http.Redirect(w, r, "http://secondhost.fake/secondpath", 302) } })) defer ts.Close() @@ -468,11 +468,11 @@ log bytes.Buffer } -func (j *RecordingJar) SetCookies(u *url.URL, cookies []*Cookie) { +func (j *RecordingJar) SetCookies(u *url.URL, cookies []*http.Cookie) { j.logf("SetCookie(%q, %v)\n", u, cookies) } -func (j *RecordingJar) Cookies(u *url.URL) []*Cookie { +func (j *RecordingJar) Cookies(u *url.URL) []*http.Cookie { j.logf("Cookies(%q)\n", u) return nil } @@ -486,11 +486,11 @@ func TestStreamingGet(t *testing.T) { defer afterTest(t) say := make(chan string) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - w.(Flusher).Flush() + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.(http.Flusher).Flush() for str := range say { w.Write([]byte(str)) - w.(Flusher).Flush() + w.(http.Flusher).Flush() } })) defer ts.Close() @@ -536,7 +536,7 @@ // don't send a TCP packet per line of the http request + body. func TestClientWrites(t *testing.T) { defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { })) defer ts.Close() @@ -568,46 +568,6 @@ } } -func TestClientInsecureTransport(t *testing.T) { - defer afterTest(t) - ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { - w.Write([]byte("Hello")) - })) - errc := make(chanWriter, 10) // but only expecting 1 - ts.Config.ErrorLog = log.New(errc, "", 0) - defer ts.Close() - - // TODO(bradfitz): add tests for skipping hostname checks too? - // would require a new cert for testing, and probably - // redundant with these tests. - for _, insecure := range []bool{true, false} { - tr := &Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: insecure, - }, - } - defer tr.CloseIdleConnections() - c := &Client{Transport: tr} - res, err := c.Get(ts.URL) - if (err == nil) != insecure { - t.Errorf("insecure=%v: got unexpected err=%v", insecure, err) - } - if res != nil { - res.Body.Close() - } - } - - select { - case v := <-errc: - if !strings.Contains(v, "TLS handshake error") { - t.Errorf("expected an error log message containing 'TLS handshake error'; got %q", v) - } - case <-time.After(5 * time.Second): - t.Errorf("timeout waiting for logged error") - } - -} - func TestClientErrorWithRequestURI(t *testing.T) { defer afterTest(t) req, _ := NewRequest("GET", "http://localhost:1234/", nil) @@ -639,7 +599,7 @@ func TestClientWithCorrectTLSServerName(t *testing.T) { defer afterTest(t) - ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.TLS.ServerName != "127.0.0.1" { t.Errorf("expected client to set ServerName 127.0.0.1, got: %q", r.TLS.ServerName) } @@ -652,33 +612,6 @@ } } -func TestClientWithIncorrectTLSServerName(t *testing.T) { - defer afterTest(t) - ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {})) - defer ts.Close() - errc := make(chanWriter, 10) // but only expecting 1 - ts.Config.ErrorLog = log.New(errc, "", 0) - - trans := newTLSTransport(t, ts) - trans.TLSClientConfig.ServerName = "badserver" - c := &Client{Transport: trans} - _, err := c.Get(ts.URL) - if err == nil { - t.Fatalf("expected an error") - } - if !strings.Contains(err.Error(), "127.0.0.1") || !strings.Contains(err.Error(), "badserver") { - t.Errorf("wanted error mentioning 127.0.0.1 and badserver; got error: %v", err) - } - select { - case v := <-errc: - if !strings.Contains(v, "TLS handshake error") { - t.Errorf("expected an error log message containing 'TLS handshake error'; got %q", v) - } - case <-time.After(5 * time.Second): - t.Errorf("timeout waiting for logged error") - } -} - // Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName // when not empty. // @@ -690,7 +623,7 @@ // The httptest.Server has a cert with "example.com" as its name. func TestTransportUsesTLSConfigServerName(t *testing.T) { defer afterTest(t) - ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello")) })) defer ts.Close() @@ -711,7 +644,7 @@ func TestResponseSetsTLSConnectionState(t *testing.T) { defer afterTest(t) - ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Hello")) })) defer ts.Close() @@ -739,7 +672,7 @@ // Verify Response.ContentLength is populated. http://golang.org/issue/4126 func TestClientHeadContentLength(t *testing.T) { defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if v := r.FormValue("cl"); v != "" { w.Header().Set("Content-Length", v) } @@ -775,7 +708,7 @@ func TestEmptyPasswordAuth(t *testing.T) { defer afterTest(t) gopher := "gopher" - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { auth := r.Header.Get("Authorization") if strings.HasPrefix(auth, "Basic ") { encoded := auth[6:] @@ -847,15 +780,15 @@ defer afterTest(t) sawRoot := make(chan bool, 1) sawSlow := make(chan bool, 1) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/" { sawRoot <- true - Redirect(w, r, "/slow", StatusFound) + http.Redirect(w, r, "/slow", StatusFound) return } if r.URL.Path == "/slow" { w.Write([]byte("Hello")) - w.(Flusher).Flush() + w.(http.Flusher).Flush() sawSlow <- true time.Sleep(2 * time.Second) return @@ -908,10 +841,10 @@ func TestClientRedirectEatsBody(t *testing.T) { defer afterTest(t) saw := make(chan string, 2) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { saw <- r.RemoteAddr if r.URL.Path == "/" { - Redirect(w, r, "/foo", StatusFound) // which includes a body + http.Redirect(w, r, "/foo", StatusFound) // which includes a body } })) defer ts.Close() === modified file 'http13client/cookie_test.go' --- http13client/cookie_test.go 2014-03-19 20:20:19 +0000 +++ http13client/cookie_test.go 2014-03-19 22:27:37 +0000 @@ -9,6 +9,7 @@ "encoding/json" "fmt" "log" + "net/http" "os" "reflect" "strings" @@ -17,39 +18,39 @@ ) var writeSetCookiesTests = []struct { - Cookie *Cookie + Cookie *http.Cookie Raw string }{ { - &Cookie{Name: "cookie-1", Value: "v$1"}, + &http.Cookie{Name: "cookie-1", Value: "v$1"}, "cookie-1=v$1", }, { - &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}, + &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}, "cookie-2=two; Max-Age=3600", }, { - &Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"}, + &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"}, "cookie-3=three; Domain=example.com", }, { - &Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"}, + &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"}, "cookie-4=four; Path=/restricted/", }, { - &Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"}, + &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"}, "cookie-5=five", }, { - &Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"}, + &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"}, "cookie-6=six", }, { - &Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"}, + &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"}, "cookie-7=seven; Domain=127.0.0.1", }, { - &Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, + &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"}, "cookie-8=eight", }, } @@ -71,10 +72,10 @@ } } -type headerOnlyResponseWriter Header +type headerOnlyResponseWriter http.Header -func (ho headerOnlyResponseWriter) Header() Header { - return Header(ho) +func (ho headerOnlyResponseWriter) Header() http.Header { + return http.Header(ho) } func (ho headerOnlyResponseWriter) Write([]byte) (int, error) { @@ -86,9 +87,9 @@ } func TestSetCookie(t *testing.T) { - m := make(Header) - SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"}) - SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}) + m := make(http.Header) + http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"}) + http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600}) if l := len(m["Set-Cookie"]); l != 2 { t.Fatalf("expected %d cookies, got %d", 2, l) } @@ -101,19 +102,19 @@ } var addCookieTests = []struct { - Cookies []*Cookie + Cookies []*http.Cookie Raw string }{ { - []*Cookie{}, + []*http.Cookie{}, "", }, { - []*Cookie{{Name: "cookie-1", Value: "v$1"}}, + []*http.Cookie{{Name: "cookie-1", Value: "v$1"}}, "cookie-1=v$1", }, { - []*Cookie{ + []*http.Cookie{ {Name: "cookie-1", Value: "v$1"}, {Name: "cookie-2", Value: "v$2"}, {Name: "cookie-3", Value: "v$3"}, @@ -136,16 +137,16 @@ } var readSetCookiesTests = []struct { - Header Header - Cookies []*Cookie + Header http.Header + Cookies []*http.Cookie }{ { - Header{"Set-Cookie": {"Cookie-1=v$1"}}, - []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, + http.Header{"Set-Cookie": {"Cookie-1=v$1"}}, + []*http.Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}}, }, { - Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, - []*Cookie{{ + http.Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}}, + []*http.Cookie{{ Name: "NID", Value: "99=YsDT5i3E-CXax-", Path: "/", @@ -157,8 +158,8 @@ }}, }, { - Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, - []*Cookie{{ + http.Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}}, + []*http.Cookie{{ Name: ".ASPXAUTH", Value: "7E3AA", Path: "/", @@ -169,8 +170,8 @@ }}, }, { - Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, - []*Cookie{{ + http.Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}}, + []*http.Cookie{{ Name: "ASP.NET_SessionId", Value: "foo", Path: "/", @@ -207,37 +208,37 @@ } var readCookiesTests = []struct { - Header Header + Header http.Header Filter string - Cookies []*Cookie + Cookies []*http.Cookie }{ { - Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, - "", - []*Cookie{ - {Name: "Cookie-1", Value: "v$1"}, - {Name: "c2", Value: "v2"}, - }, - }, - { - Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, - "c2", - []*Cookie{ - {Name: "c2", Value: "v2"}, - }, - }, - { - Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, - "", - []*Cookie{ - {Name: "Cookie-1", Value: "v$1"}, - {Name: "c2", Value: "v2"}, - }, - }, - { - Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, - "c2", - []*Cookie{ + http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, + "", + []*http.Cookie{ + {Name: "Cookie-1", Value: "v$1"}, + {Name: "c2", Value: "v2"}, + }, + }, + { + http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}}, + "c2", + []*http.Cookie{ + {Name: "c2", Value: "v2"}, + }, + }, + { + http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, + "", + []*http.Cookie{ + {Name: "Cookie-1", Value: "v$1"}, + {Name: "c2", Value: "v2"}, + }, + }, + { + http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}}, + "c2", + []*http.Cookie{ {Name: "c2", Value: "v2"}, }, }, === modified file 'http13client/export_test.go' --- http13client/export_test.go 2014-03-19 20:20:19 +0000 +++ http13client/export_test.go 2014-03-19 22:27:37 +0000 @@ -9,15 +9,12 @@ import ( "net" - "time" ) func NewLoggingConn(baseName string, c net.Conn) net.Conn { return newLoggingConn(baseName, c) } -var ExportAppendTime = appendTime - func (t *Transport) NumPendingRequestsForTesting() int { t.reqMu.Lock() defer t.reqMu.Unlock() @@ -57,13 +54,6 @@ return len(t.idleConnCh) } -func NewTestTimeoutHandler(handler Handler, ch <-chan time.Time) Handler { - f := func() <-chan time.Time { - return ch - } - return &timeoutHandler{handler, f, ""} -} - func ResetCachedEnvironment() { httpProxyEnv.reset() noProxyEnv.reset() === modified file 'http13client/header_test.go' --- http13client/header_test.go 2014-03-19 20:20:19 +0000 +++ http13client/header_test.go 2014-03-19 22:27:37 +0000 @@ -6,19 +6,20 @@ import ( "bytes" + "net/http" "runtime" "testing" "time" ) var headerWriteTests = []struct { - h Header + h http.Header exclude map[string]bool expected string }{ - {Header{}, nil, ""}, + {http.Header{}, nil, ""}, { - Header{ + http.Header{ "Content-Type": {"text/html; charset=UTF-8"}, "Content-Length": {"0"}, }, @@ -26,14 +27,14 @@ "Content-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\n", }, { - Header{ + http.Header{ "Content-Length": {"0", "1", "2"}, }, nil, "Content-Length: 0\r\nContent-Length: 1\r\nContent-Length: 2\r\n", }, { - Header{ + http.Header{ "Expires": {"-1"}, "Content-Length": {"0"}, "Content-Encoding": {"gzip"}, @@ -42,7 +43,7 @@ "Content-Encoding: gzip\r\nExpires: -1\r\n", }, { - Header{ + http.Header{ "Expires": {"-1"}, "Content-Length": {"0", "1", "2"}, "Content-Encoding": {"gzip"}, @@ -51,7 +52,7 @@ "Content-Encoding: gzip\r\nExpires: -1\r\n", }, { - Header{ + http.Header{ "Expires": {"-1"}, "Content-Length": {"0"}, "Content-Encoding": {"gzip"}, @@ -60,7 +61,7 @@ "", }, { - Header{ + http.Header{ "Nil": nil, "Empty": {}, "Blank": {""}, @@ -71,7 +72,7 @@ }, // Tests header sorting when over the insertion sort threshold side: { - Header{ + http.Header{ "k1": {"1a", "1b"}, "k2": {"2a", "2b"}, "k3": {"3a", "3b"}, @@ -101,21 +102,21 @@ } var parseTimeTests = []struct { - h Header + h http.Header err bool }{ - {Header{"Date": {""}}, true}, - {Header{"Date": {"invalid"}}, true}, - {Header{"Date": {"1994-11-06T08:49:37Z00:00"}}, true}, - {Header{"Date": {"Sun, 06 Nov 1994 08:49:37 GMT"}}, false}, - {Header{"Date": {"Sunday, 06-Nov-94 08:49:37 GMT"}}, false}, - {Header{"Date": {"Sun Nov 6 08:49:37 1994"}}, false}, + {http.Header{"Date": {""}}, true}, + {http.Header{"Date": {"invalid"}}, true}, + {http.Header{"Date": {"1994-11-06T08:49:37Z00:00"}}, true}, + {http.Header{"Date": {"Sun, 06 Nov 1994 08:49:37 GMT"}}, false}, + {http.Header{"Date": {"Sunday, 06-Nov-94 08:49:37 GMT"}}, false}, + {http.Header{"Date": {"Sun Nov 6 08:49:37 1994"}}, false}, } func TestParseTime(t *testing.T) { expect := time.Date(1994, 11, 6, 8, 49, 37, 0, time.UTC) for i, test := range parseTimeTests { - d, err := ParseTime(test.h.Get("Date")) + d, err := http.ParseTime(test.h.Get("Date")) if err != nil { if !test.err { t.Errorf("#%d:\n got err: %v", i, err) @@ -175,7 +176,7 @@ } } -var testHeader = Header{ +var testHeader = http.Header{ "Content-Length": {"123"}, "Content-Type": {"text/plain"}, "Date": {"some date at some time Z"}, @@ -196,9 +197,9 @@ if testing.Short() { t.Skip("skipping alloc test in short mode") } - if raceEnabled { + /*if raceEnabled { t.Skip("skipping test under race detector") - } + }*/ if runtime.GOMAXPROCS(0) > 1 { t.Skip("skipping; GOMAXPROCS>1") } === modified file 'http13client/npn_test.go' --- http13client/npn_test.go 2014-03-19 21:38:56 +0000 +++ http13client/npn_test.go 2014-03-19 22:27:37 +0000 @@ -11,13 +11,14 @@ "io" "io/ioutil" . "launchpad.net/ubuntu-push/http13client" + "net/http" "net/http/httptest" "strings" "testing" ) func TestNextProtoUpgrade(t *testing.T) { - ts := httptest.NewUnstartedServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "path=%s,proto=", r.URL.Path) if r.TLS != nil { w.Write([]byte(r.TLS.NegotiatedProtocol)) @@ -32,7 +33,7 @@ ts.TLS = &tls.Config{ NextProtos: []string{"unhandled-proto", "tls-0.9"}, } - ts.Config.TLSNextProto = map[string]func(*Server, *tls.Conn, Handler){ + ts.Config.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){ "tls-0.9": handleTLSProtocol09, } ts.StartTLS() @@ -90,7 +91,7 @@ // handleTLSProtocol09 implements the HTTP/0.9 protocol over TLS, for the // TestNextProtoUpgrade test. -func handleTLSProtocol09(srv *Server, conn *tls.Conn, h Handler) { +func handleTLSProtocol09(srv *http.Server, conn *tls.Conn, h http.Handler) { br := bufio.NewReader(conn) line, err := br.ReadString('\n') if err != nil { @@ -101,18 +102,18 @@ if path == line { return } - req, _ := NewRequest("GET", path, nil) + req, _ := http.NewRequest("GET", path, nil) req.Proto = "HTTP/0.9" req.ProtoMajor = 0 req.ProtoMinor = 9 - rw := &http09Writer{conn, make(Header)} + rw := &http09Writer{conn, make(http.Header)} h.ServeHTTP(rw, req) } type http09Writer struct { io.Writer - h Header + h http.Header } -func (w http09Writer) Header() Header { return w.h } +func (w http09Writer) Header() http.Header { return w.h } func (w http09Writer) WriteHeader(int) {} // no headers === modified file 'http13client/readrequest_test.go' --- http13client/readrequest_test.go 2014-03-19 20:20:19 +0000 +++ http13client/readrequest_test.go 2014-03-19 22:27:37 +0000 @@ -9,6 +9,7 @@ "bytes" "fmt" "io" + "net/http" "net/url" "reflect" "testing" @@ -18,13 +19,13 @@ Raw string Req *Request Body string - Trailer Header + Trailer http.Header Error string } var noError = "" var noBody = "" -var noTrailer Header = nil +var noTrailer http.Header = nil var reqTests = []reqTest{ // Baseline test; All Request fields included for template use @@ -51,7 +52,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{ + Header: http.Header{ "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}, "Accept-Language": {"en-us,en;q=0.5"}, "Accept-Encoding": {"gzip,deflate"}, @@ -86,7 +87,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, Close: false, ContentLength: 0, Host: "foo.com", @@ -112,7 +113,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, Close: false, ContentLength: 0, Host: "test", @@ -163,14 +164,14 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, ContentLength: -1, Host: "foo.com", RequestURI: "/", }, "foobar", - Header{ + http.Header{ "Trailer-Key": {"Trailer-Value"}, }, noError, @@ -188,7 +189,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, Close: false, ContentLength: 0, Host: "www.google.com:443", @@ -212,7 +213,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, Close: false, ContentLength: 0, Host: "127.0.0.1:6060", @@ -236,7 +237,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, Close: false, ContentLength: 0, Host: "", @@ -259,7 +260,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{ + Header: http.Header{ "Server": []string{"foo"}, }, Close: false, @@ -283,7 +284,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{ + Header: http.Header{ "Server": []string{"foo"}, }, Close: false, === modified file 'http13client/request_test.go' --- http13client/request_test.go 2014-03-19 21:38:56 +0000 +++ http13client/request_test.go 2014-03-19 22:27:37 +0000 @@ -12,6 +12,7 @@ "io/ioutil" "mime/multipart" . "launchpad.net/ubuntu-push/http13client" + "net/http" "net/http/httptest" "net/url" "os" @@ -110,7 +111,7 @@ for i, test := range parseContentTypeTests { req := &Request{ Method: "POST", - Header: Header(test.contentType), + Header: http.Header(test.contentType), Body: ioutil.NopCloser(strings.NewReader("body")), } err := req.ParseForm() @@ -143,7 +144,7 @@ func TestMultipartReader(t *testing.T) { req := &Request{ Method: "POST", - Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, + Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}}, Body: ioutil.NopCloser(new(bytes.Buffer)), } multipart, err := req.MultipartReader() @@ -151,7 +152,7 @@ t.Errorf("expected multipart; error: %v", err) } - req.Header = Header{"Content-Type": {"text/plain"}} + req.Header = http.Header{"Content-Type": {"text/plain"}} multipart, err = req.MultipartReader() if multipart != nil { t.Errorf("unexpected multipart for text/plain") @@ -159,7 +160,7 @@ } func TestRedirect(t *testing.T) { - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/": w.Header().Set("Location", "/foo/") === modified file 'http13client/requestwrite_test.go' --- http13client/requestwrite_test.go 2014-03-19 20:20:19 +0000 +++ http13client/requestwrite_test.go 2014-03-19 22:27:37 +0000 @@ -10,6 +10,7 @@ "fmt" "io" "io/ioutil" + "net/http" "net/url" "strings" "testing" @@ -39,7 +40,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{ + Header: http.Header{ "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}, "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"}, "Accept-Encoding": {"gzip,deflate"}, @@ -85,7 +86,7 @@ }, ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, TransferEncoding: []string{"chunked"}, }, @@ -114,7 +115,7 @@ }, ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, Close: true, TransferEncoding: []string{"chunked"}, }, @@ -147,7 +148,7 @@ }, ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, Close: true, ContentLength: 6, }, @@ -177,7 +178,7 @@ Method: "POST", URL: mustParseURL("http://example.com/"), Host: "example.com", - Header: Header{ + Header: http.Header{ "Content-Length": []string{"10"}, // ignored }, ContentLength: 6, @@ -358,7 +359,7 @@ URL: mustParseURL("/foo"), ProtoMajor: 1, ProtoMinor: 0, - Header: Header{ + Header: http.Header{ "X-Foo": []string{"X-Bar"}, }, }, @@ -384,7 +385,7 @@ }, ProtoMajor: 1, ProtoMinor: 1, - Header: Header{ + Header: http.Header{ "Host": []string{"bad.example.com"}, }, }, @@ -405,7 +406,7 @@ }, ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, }, WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" + @@ -424,7 +425,7 @@ }, ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, }, WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" + @@ -444,7 +445,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{ + Header: http.Header{ "ALL-CAPS": {"x"}, }, }, @@ -474,7 +475,7 @@ } setBody() if tt.Req.Header == nil { - tt.Req.Header = make(Header) + tt.Req.Header = make(http.Header) } var braw bytes.Buffer === modified file 'http13client/response_test.go' --- http13client/response_test.go 2014-03-19 20:20:19 +0000 +++ http13client/response_test.go 2014-03-19 22:27:37 +0000 @@ -12,6 +12,7 @@ "fmt" "io" "io/ioutil" + "net/http" "net/url" "reflect" "regexp" @@ -44,7 +45,7 @@ ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), - Header: Header{ + Header: http.Header{ "Connection": {"close"}, // TODO(rsc): Delete? }, Close: true, @@ -67,7 +68,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, Request: dummyReq("GET"), Close: true, ContentLength: -1, @@ -88,7 +89,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: Header{}, + Header: http.Header{}, Request: dummyReq("GET"), Close: false, ContentLength: 0, @@ -112,7 +113,7 @@ ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), - Header: Header{ + Header: http.Header{ "Connection": {"close"}, "Content-Length": {"10"}, }, @@ -142,7 +143,7 @@ ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), - Header: Header{}, + Header: http.Header{}, Close: false, ContentLength: -1, TransferEncoding: []string{"chunked"}, @@ -169,7 +170,7 @@ ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), - Header: Header{}, + Header: http.Header{}, Close: false, ContentLength: -1, TransferEncoding: []string{"chunked"}, @@ -191,7 +192,7 @@ ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("HEAD"), - Header: Header{}, + Header: http.Header{}, TransferEncoding: []string{"chunked"}, Close: false, ContentLength: -1, @@ -213,7 +214,7 @@ ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("HEAD"), - Header: Header{"Content-Length": {"256"}}, + Header: http.Header{"Content-Length": {"256"}}, TransferEncoding: nil, Close: true, ContentLength: 256, @@ -235,7 +236,7 @@ ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("HEAD"), - Header: Header{"Content-Length": {"256"}}, + Header: http.Header{"Content-Length": {"256"}}, TransferEncoding: nil, Close: false, ContentLength: 256, @@ -256,7 +257,7 @@ ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("HEAD"), - Header: Header{}, + Header: http.Header{}, TransferEncoding: nil, Close: true, ContentLength: -1, @@ -278,7 +279,7 @@ ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), - Header: Header{ + Header: http.Header{ "Content-Length": {"0"}, }, Close: false, @@ -299,7 +300,7 @@ ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), - Header: Header{}, + Header: http.Header{}, Close: true, ContentLength: -1, }, @@ -318,7 +319,7 @@ ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), - Header: Header{}, + Header: http.Header{}, Close: true, ContentLength: -1, }, @@ -340,7 +341,7 @@ ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), - Header: Header{ + Header: http.Header{ "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"}, }, Close: true, @@ -363,7 +364,7 @@ Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, - Header: Header{ + Header: http.Header{ "Connection": {"close"}, // TODO(rsc): Delete? }, Close: true, @@ -545,7 +546,7 @@ func TestLocationResponse(t *testing.T) { for i, tt := range responseLocationTests { res := new(Response) - res.Header = make(Header) + res.Header = make(http.Header) res.Header.Set("Location", tt.location) if tt.requrl != "" { res.Request = &Request{} @@ -626,16 +627,3 @@ t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err) } } - -func TestNeedsSniff(t *testing.T) { - // needsSniff returns true with an empty response. - r := &response{} - if got, want := r.needsSniff(), true; got != want { - t.Errorf("needsSniff = %t; want %t", got, want) - } - // needsSniff returns false when Content-Type = nil. - r.handlerHeader = Header{"Content-Type": nil} - if got, want := r.needsSniff(), false; got != want { - t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want) - } -} === modified file 'http13client/responsewrite_test.go' --- http13client/responsewrite_test.go 2014-03-19 20:20:19 +0000 +++ http13client/responsewrite_test.go 2014-03-19 22:27:37 +0000 @@ -7,6 +7,7 @@ import ( "bytes" "io/ioutil" + "net/http" "strings" "testing" ) @@ -25,7 +26,7 @@ ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), - Header: Header{}, + Header: http.Header{}, Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")), ContentLength: 6, }, @@ -41,7 +42,7 @@ ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), - Header: Header{}, + Header: http.Header{}, Body: ioutil.NopCloser(strings.NewReader("abcdef")), ContentLength: -1, }, @@ -56,7 +57,7 @@ ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), - Header: Header{}, + Header: http.Header{}, Body: ioutil.NopCloser(strings.NewReader("abcdef")), ContentLength: 6, TransferEncoding: []string{"chunked"}, @@ -77,7 +78,7 @@ ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), - Header: Header{ + Header: http.Header{ "Foo": []string{" Bar\nBaz "}, }, Body: nil, === modified file 'http13client/transport_test.go' --- http13client/transport_test.go 2014-03-19 21:38:56 +0000 +++ http13client/transport_test.go 2014-03-19 22:27:37 +0000 @@ -17,8 +17,8 @@ "io/ioutil" "log" "net" - "launchpad.net/ubuntu-push/http13client" . "launchpad.net/ubuntu-push/http13client" + "net/http" "net/http/httptest" "net/url" "os" @@ -34,7 +34,7 @@ // and then verify that the final 2 responses get errors back. // hostPortHandler writes back the client's "host:port". -var hostPortHandler = HandlerFunc(func(w ResponseWriter, r *Request) { +var hostPortHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.FormValue("close") == "true" { w.Header().Set("Connection", "close") } @@ -280,7 +280,7 @@ const msg = "foobar" var addrSeen map[string]int - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { addrSeen[r.RemoteAddr]++ if r.URL.Path == "/chunked/" { w.WriteHeader(200) @@ -299,7 +299,7 @@ wantLen := []int{len(msg), -1}[pi] addrSeen = make(map[string]int) for i := 0; i < 3; i++ { - res, err := http.Get(ts.URL + path) + res, err := Get(ts.URL + path) if err != nil { t.Errorf("Get %s: %v", path, err) continue @@ -329,7 +329,7 @@ defer afterTest(t) resch := make(chan string) gotReq := make(chan bool) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotReq <- true msg := <-resch _, err := w.Write([]byte(msg)) @@ -457,12 +457,12 @@ if testing.Short() { t.Skip("skipping test in short mode") } - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Length", "5") w.Header().Set("Content-Type", "text/plain") w.Write([]byte("Hello")) - w.(Flusher).Flush() - conn, buf, _ := w.(Hijacker).Hijack() + w.(http.Flusher).Flush() + conn, buf, _ := w.(http.Hijacker).Hijack() buf.Flush() conn.Close() })) @@ -510,7 +510,7 @@ // with no bodies properly func TestTransportHeadResponses(t *testing.T) { defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "HEAD" { panic("expected HEAD; got " + r.Method) } @@ -545,7 +545,7 @@ // on responses to HEAD requests. func TestTransportHeadChunkedResponse(t *testing.T) { defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "HEAD" { panic("expected HEAD; got " + r.Method) } @@ -588,7 +588,7 @@ func TestRoundTripGzip(t *testing.T) { defer afterTest(t) const responseBody = "test response body" - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { accept := req.Header.Get("Accept-Encoding") if expect := req.FormValue("expect_accept"); accept != expect { t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q", @@ -647,7 +647,7 @@ defer afterTest(t) const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" const nRandBytes = 1024 * 1024 - ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if req.Method == "HEAD" { if g := req.Header.Get("Accept-Encoding"); g != "" { t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g) @@ -742,11 +742,11 @@ func TestTransportProxy(t *testing.T) { defer afterTest(t) ch := make(chan string, 1) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ch <- "real server" })) defer ts.Close() - proxy := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ch <- "proxy for " + r.URL.String() })) defer proxy.Close() @@ -770,7 +770,7 @@ // Content-Encoding is removed. func TestTransportGzipRecursive(t *testing.T) { defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Encoding", "gzip") w.Write(rgz) })) @@ -802,7 +802,7 @@ defer afterTest(t) gotReqCh := make(chan bool) unblockCh := make(chan bool) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotReqCh <- true <-unblockCh w.Header().Set("Content-Length", "0") @@ -869,7 +869,7 @@ t.Skip("skipping test; see http://golang.org/issue/7237") } defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { })) defer ts.Close() @@ -912,7 +912,7 @@ c := &Client{Transport: tr} unblockCh := make(chan bool, 1) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-unblockCh tr.CloseIdleConnections() })) @@ -939,7 +939,7 @@ func TestIssue3644(t *testing.T) { defer afterTest(t) const numFoos = 5000 - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Connection", "close") for i := 0; i < numFoos; i++ { w.Write([]byte("foo ")) @@ -967,8 +967,8 @@ func TestIssue3595(t *testing.T) { defer afterTest(t) const deniedMsg = "sorry, denied." - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - Error(w, deniedMsg, StatusUnauthorized) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, deniedMsg, StatusUnauthorized) })) defer ts.Close() tr := &Transport{} @@ -991,7 +991,7 @@ // "client fails to handle requests with no body and chunked encoding" func TestChunkedNoContent(t *testing.T) { defer afterTest(t) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(StatusNoContent) })) defer ts.Close() @@ -1019,7 +1019,7 @@ maxProcs, numReqs = 4, 50 } defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs)) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%v", r.FormValue("echo")) })) defer ts.Close() @@ -1080,8 +1080,8 @@ } defer afterTest(t) const debug = false - mux := NewServeMux() - mux.HandleFunc("/get", func(w ResponseWriter, r *Request) { + mux := http.NewServeMux() + mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) { io.Copy(w, neverEnding('a')) }) ts := httptest.NewServer(mux) @@ -1144,11 +1144,11 @@ } defer afterTest(t) const debug = false - mux := NewServeMux() - mux.HandleFunc("/get", func(w ResponseWriter, r *Request) { + mux := http.NewServeMux() + mux.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) { io.Copy(w, neverEnding('a')) }) - mux.HandleFunc("/put", func(w ResponseWriter, r *Request) { + mux.HandleFunc("/put", func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() io.Copy(ioutil.Discard, r.Body) }) @@ -1214,9 +1214,9 @@ if testing.Short() { t.Skip("skipping timeout test in -short mode") } - mux := NewServeMux() - mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {}) - mux.HandleFunc("/slow", func(w ResponseWriter, r *Request) { + mux := http.NewServeMux() + mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {}) + mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) { time.Sleep(2 * time.Second) }) ts := httptest.NewServer(mux) @@ -1276,9 +1276,9 @@ t.Skip("skipping test in -short mode") } unblockc := make(chan bool) - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello") - w.(Flusher).Flush() // send headers and some body + w.(http.Flusher).Flush() // send headers and some body <-unblockc })) defer ts.Close() @@ -1386,14 +1386,14 @@ defer afterTest(t) writeErr := make(chan error, 1) msg := []byte("young\n") - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for { _, err := w.Write(msg) if err != nil { writeErr <- err return } - w.(Flusher).Flush() + w.(http.Flusher).Flush() } })) defer ts.Close() @@ -1449,7 +1449,7 @@ res := &Response{ Status: "200 OK", StatusCode: 200, - Header: make(Header), + Header: make(http.Header), Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())), } return res, nil @@ -1478,7 +1478,7 @@ defer afterTest(t) tr := &Transport{} _, err := tr.RoundTrip(&Request{ - Header: make(Header), + Header: make(http.Header), URL: &url.URL{ Scheme: "http", }, @@ -1492,14 +1492,14 @@ func TestTransportSocketLateBinding(t *testing.T) { defer afterTest(t) - mux := NewServeMux() + mux := http.NewServeMux() fooGate := make(chan bool, 1) - mux.HandleFunc("/foo", func(w ResponseWriter, r *Request) { + mux.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("foo-ipport", r.RemoteAddr) - w.(Flusher).Flush() + w.(http.Flusher).Flush() <-fooGate }) - mux.HandleFunc("/bar", func(w ResponseWriter, r *Request) { + mux.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("bar-ipport", r.RemoteAddr) }) ts := httptest.NewServer(mux) @@ -1720,7 +1720,7 @@ var mu sync.Mutex var n int - ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { mu.Lock() n++ mu.Unlock() @@ -1756,7 +1756,7 @@ // then closes it. func TestTransportClosesRequestBody(t *testing.T) { defer afterTest(t) - ts := httptest.NewServer(http.HandlerFunc(func(w ResponseWriter, r *Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.Copy(ioutil.Discard, r.Body) })) defer ts.Close() ubuntu-push-0.2.1+14.04.20140423.1/http13client/_patches/sync_pool.Rpatch0000644000015301777760000001010112325724711026022 0ustar pbusernogroup00000000000000diff -r ff4cb9143edb -r 3d2ece96ab95 src/pkg/net/http/header.go --- a/src/pkg/net/http/header.go Wed Dec 18 15:52:05 2013 -0800 +++ b/src/pkg/net/http/header.go Wed Dec 18 15:52:20 2013 -0800 @@ -9,6 +9,7 @@ "net/textproto" "sort" "strings" + "sync" "time" ) @@ -114,18 +115,15 @@ func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] } func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key } -// TODO: convert this to a sync.Cache (issue 4720) -var headerSorterCache = make(chan *headerSorter, 8) +var headerSorterPool = sync.Pool{ + New: func() interface{} { return new(headerSorter) }, +} // sortedKeyValues returns h's keys sorted in the returned kvs // slice. The headerSorter used to sort is also returned, for possible // return to headerSorterCache. func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *headerSorter) { - select { - case hs = <-headerSorterCache: - default: - hs = new(headerSorter) - } + hs = headerSorterPool.Get().(*headerSorter) if cap(hs.kvs) < len(h) { hs.kvs = make([]keyValues, 0, len(h)) } @@ -159,10 +157,7 @@ } } } - select { - case headerSorterCache <- sorter: - default: - } + headerSorterPool.Put(sorter) return nil } diff -r ff4cb9143edb -r 3d2ece96ab95 src/pkg/net/http/request.go --- a/src/pkg/net/http/request.go Wed Dec 18 15:52:05 2013 -0800 +++ b/src/pkg/net/http/request.go Wed Dec 18 15:52:20 2013 -0800 @@ -20,6 +20,7 @@ "net/url" "strconv" "strings" + "sync" ) const ( @@ -494,25 +495,20 @@ return line[:s1], line[s1+1 : s2], line[s2+1:], true } -// TODO(bradfitz): use a sync.Cache when available -var textprotoReaderCache = make(chan *textproto.Reader, 4) +var textprotoReaderPool sync.Pool func newTextprotoReader(br *bufio.Reader) *textproto.Reader { - select { - case r := <-textprotoReaderCache: - r.R = br - return r - default: - return textproto.NewReader(br) + if v := textprotoReaderPool.Get(); v != nil { + tr := v.(*textproto.Reader) + tr.R = br + return tr } + return textproto.NewReader(br) } func putTextprotoReader(r *textproto.Reader) { r.R = nil - select { - case textprotoReaderCache <- r: - default: - } + textprotoReaderPool.Put(r) } // ReadRequest reads and parses a request from b. diff -r ff4cb9143edb -r 3d2ece96ab95 src/pkg/net/http/server.go --- a/src/pkg/net/http/server.go Wed Dec 18 15:52:05 2013 -0800 +++ b/src/pkg/net/http/server.go Wed Dec 18 15:52:20 2013 -0800 @@ -435,56 +435,52 @@ return c, nil } -// TODO: use a sync.Cache instead var ( - bufioReaderCache = make(chan *bufio.Reader, 4) - bufioWriterCache2k = make(chan *bufio.Writer, 4) - bufioWriterCache4k = make(chan *bufio.Writer, 4) + bufioReaderPool sync.Pool + bufioWriter2kPool sync.Pool + bufioWriter4kPool sync.Pool ) -func bufioWriterCache(size int) chan *bufio.Writer { +func bufioWriterPool(size int) *sync.Pool { switch size { case 2 << 10: - return bufioWriterCache2k + return &bufioWriter2kPool case 4 << 10: - return bufioWriterCache4k + return &bufioWriter4kPool } return nil } func newBufioReader(r io.Reader) *bufio.Reader { - select { - case p := <-bufioReaderCache: - p.Reset(r) - return p - default: - return bufio.NewReader(r) + if v := bufioReaderPool.Get(); v != nil { + br := v.(*bufio.Reader) + br.Reset(r) + return br } + return bufio.NewReader(r) } func putBufioReader(br *bufio.Reader) { br.Reset(nil) - select { - case bufioReaderCache <- br: - default: - } + bufioReaderPool.Put(br) } func newBufioWriterSize(w io.Writer, size int) *bufio.Writer { - select { - case p := <-bufioWriterCache(size): - p.Reset(w) - return p - default: - return bufio.NewWriterSize(w, size) + pool := bufioWriterPool(size) + if pool != nil { + if v := pool.Get(); v != nil { + bw := v.(*bufio.Writer) + bw.Reset(w) + return bw + } } + return bufio.NewWriterSize(w, size) } func putBufioWriter(bw *bufio.Writer) { bw.Reset(nil) - select { - case bufioWriterCache(bw.Available()) <- bw: - default: + if pool := bufioWriterPool(bw.Available()); pool != nil { + pool.Put(bw) } } ubuntu-push-0.2.1+14.04.20140423.1/http13client/_patches/fix_code.patch0000644000015301777760000005117512325724711025473 0ustar pbusernogroup00000000000000=== modified file 'http13client/client.go' --- http13client/client.go 2014-03-19 20:20:19 +0000 +++ http13client/client.go 2014-03-19 22:27:37 +0000 @@ -17,6 +17,7 @@ "io/ioutil" "log" "net/url" + "net/http" "strings" "sync" "time" @@ -54,7 +55,7 @@ // Jar specifies the cookie jar. // If Jar is nil, cookies are not sent in requests and ignored // in responses. - Jar CookieJar + Jar http.CookieJar // Timeout specifies a time limit for requests made by this // Client. The timeout includes connection time, any @@ -177,7 +178,7 @@ // Headers, leaving it uninitialized. We guarantee to the // Transport that this has been initialized, though. if req.Header == nil { - req.Header = make(Header) + req.Header = make(http.Header) } if u := req.URL.User; u != nil { @@ -308,7 +309,7 @@ if ireq.Method == "POST" || ireq.Method == "PUT" { nreq.Method = "GET" } - nreq.Header = make(Header) + nreq.Header = make(http.Header) nreq.URL, err = base.Parse(urlStr) if err != nil { break === modified file 'http13client/cookie.go' --- http13client/cookie.go 2014-03-19 20:20:19 +0000 +++ http13client/cookie.go 2014-03-19 22:27:37 +0000 @@ -5,10 +5,9 @@ package http import ( - "bytes" - "fmt" "log" "net" + "net/http" "strconv" "strings" "time" @@ -18,30 +17,10 @@ // // http://tools.ietf.org/html/rfc6265 -// A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an -// HTTP response or the Cookie header of an HTTP request. -type Cookie struct { - Name string - Value string - Path string - Domain string - Expires time.Time - RawExpires string - - // MaxAge=0 means no 'Max-Age' attribute specified. - // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' - // MaxAge>0 means Max-Age attribute present and given in seconds - MaxAge int - Secure bool - HttpOnly bool - Raw string - Unparsed []string // Raw text of unparsed attribute-value pairs -} - // readSetCookies parses all "Set-Cookie" values from // the header h and returns the successfully parsed Cookies. -func readSetCookies(h Header) []*Cookie { - cookies := []*Cookie{} +func readSetCookies(h http.Header) []*http.Cookie { + cookies := []*http.Cookie{} for _, line := range h["Set-Cookie"] { parts := strings.Split(strings.TrimSpace(line), ";") if len(parts) == 1 && parts[0] == "" { @@ -60,7 +39,7 @@ if !success { continue } - c := &Cookie{ + c := &http.Cookie{ Name: name, Value: value, Raw: line, @@ -129,59 +108,12 @@ return cookies } -// SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. -func SetCookie(w ResponseWriter, cookie *Cookie) { - w.Header().Add("Set-Cookie", cookie.String()) -} - -// String returns the serialization of the cookie for use in a Cookie -// header (if only Name and Value are set) or a Set-Cookie response -// header (if other fields are set). -func (c *Cookie) String() string { - var b bytes.Buffer - fmt.Fprintf(&b, "%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) - if len(c.Path) > 0 { - fmt.Fprintf(&b, "; Path=%s", sanitizeCookiePath(c.Path)) - } - if len(c.Domain) > 0 { - if validCookieDomain(c.Domain) { - // A c.Domain containing illegal characters is not - // sanitized but simply dropped which turns the cookie - // into a host-only cookie. A leading dot is okay - // but won't be sent. - d := c.Domain - if d[0] == '.' { - d = d[1:] - } - fmt.Fprintf(&b, "; Domain=%s", d) - } else { - log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", - c.Domain) - } - } - if c.Expires.Unix() > 0 { - fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) - } - if c.MaxAge > 0 { - fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) - } else if c.MaxAge < 0 { - fmt.Fprintf(&b, "; Max-Age=0") - } - if c.HttpOnly { - fmt.Fprintf(&b, "; HttpOnly") - } - if c.Secure { - fmt.Fprintf(&b, "; Secure") - } - return b.String() -} - // readCookies parses all "Cookie" values from the header h and // returns the successfully parsed Cookies. // // if filter isn't empty, only cookies of that name are returned -func readCookies(h Header, filter string) []*Cookie { - cookies := []*Cookie{} +func readCookies(h http.Header, filter string) []*http.Cookie { + cookies := []*http.Cookie{} lines, ok := h["Cookie"] if !ok { return cookies @@ -213,7 +145,7 @@ if !success { continue } - cookies = append(cookies, &Cookie{Name: name, Value: val}) + cookies = append(cookies, &http.Cookie{Name: name, Value: val}) parsedPairs++ } } === modified file 'http13client/header.go' --- http13client/header.go 2014-03-19 20:20:19 +0000 +++ http13client/header.go 2014-03-19 22:27:37 +0000 @@ -5,176 +5,9 @@ package http import ( - "io" - "net/textproto" - "sort" "strings" - "time" ) -var raceEnabled = false // set by race.go - -// A Header represents the key-value pairs in an HTTP header. -type Header map[string][]string - -// Add adds the key, value pair to the header. -// It appends to any existing values associated with key. -func (h Header) Add(key, value string) { - textproto.MIMEHeader(h).Add(key, value) -} - -// Set sets the header entries associated with key to -// the single element value. It replaces any existing -// values associated with key. -func (h Header) Set(key, value string) { - textproto.MIMEHeader(h).Set(key, value) -} - -// Get gets the first value associated with the given key. -// If there are no values associated with the key, Get returns "". -// To access multiple values of a key, access the map directly -// with CanonicalHeaderKey. -func (h Header) Get(key string) string { - return textproto.MIMEHeader(h).Get(key) -} - -// get is like Get, but key must already be in CanonicalHeaderKey form. -func (h Header) get(key string) string { - if v := h[key]; len(v) > 0 { - return v[0] - } - return "" -} - -// Del deletes the values associated with key. -func (h Header) Del(key string) { - textproto.MIMEHeader(h).Del(key) -} - -// Write writes a header in wire format. -func (h Header) Write(w io.Writer) error { - return h.WriteSubset(w, nil) -} - -func (h Header) clone() Header { - h2 := make(Header, len(h)) - for k, vv := range h { - vv2 := make([]string, len(vv)) - copy(vv2, vv) - h2[k] = vv2 - } - return h2 -} - -var timeFormats = []string{ - TimeFormat, - time.RFC850, - time.ANSIC, -} - -// ParseTime parses a time header (such as the Date: header), -// trying each of the three formats allowed by HTTP/1.1: -// TimeFormat, time.RFC850, and time.ANSIC. -func ParseTime(text string) (t time.Time, err error) { - for _, layout := range timeFormats { - t, err = time.Parse(layout, text) - if err == nil { - return - } - } - return -} - -var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ") - -type writeStringer interface { - WriteString(string) (int, error) -} - -// stringWriter implements WriteString on a Writer. -type stringWriter struct { - w io.Writer -} - -func (w stringWriter) WriteString(s string) (n int, err error) { - return w.w.Write([]byte(s)) -} - -type keyValues struct { - key string - values []string -} - -// A headerSorter implements sort.Interface by sorting a []keyValues -// by key. It's used as a pointer, so it can fit in a sort.Interface -// interface value without allocation. -type headerSorter struct { - kvs []keyValues -} - -func (s *headerSorter) Len() int { return len(s.kvs) } -func (s *headerSorter) Swap(i, j int) { s.kvs[i], s.kvs[j] = s.kvs[j], s.kvs[i] } -func (s *headerSorter) Less(i, j int) bool { return s.kvs[i].key < s.kvs[j].key } - -// TODO: convert this to a sync.Cache (issue 4720) -var headerSorterCache = make(chan *headerSorter, 8) - -// sortedKeyValues returns h's keys sorted in the returned kvs -// slice. The headerSorter used to sort is also returned, for possible -// return to headerSorterCache. -func (h Header) sortedKeyValues(exclude map[string]bool) (kvs []keyValues, hs *headerSorter) { - select { - case hs = <-headerSorterCache: - default: - hs = new(headerSorter) - } - if cap(hs.kvs) < len(h) { - hs.kvs = make([]keyValues, 0, len(h)) - } - kvs = hs.kvs[:0] - for k, vv := range h { - if !exclude[k] { - kvs = append(kvs, keyValues{k, vv}) - } - } - hs.kvs = kvs - sort.Sort(hs) - return kvs, hs -} - -// WriteSubset writes a header in wire format. -// If exclude is not nil, keys where exclude[key] == true are not written. -func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error { - ws, ok := w.(writeStringer) - if !ok { - ws = stringWriter{w} - } - kvs, sorter := h.sortedKeyValues(exclude) - for _, kv := range kvs { - for _, v := range kv.values { - v = headerNewlineToSpace.Replace(v) - v = textproto.TrimString(v) - for _, s := range []string{kv.key, ": ", v, "\r\n"} { - if _, err := ws.WriteString(s); err != nil { - return err - } - } - } - } - select { - case headerSorterCache <- sorter: - default: - } - return nil -} - -// CanonicalHeaderKey returns the canonical format of the -// header key s. The canonicalization converts the first -// letter and any letter following a hyphen to upper case; -// the rest are converted to lowercase. For example, the -// canonical key for "accept-encoding" is "Accept-Encoding". -func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) } - // hasToken reports whether token appears with v, ASCII // case-insensitive, with space or comma boundaries. // token must be all lowercase. === modified file 'http13client/request.go' --- http13client/request.go 2014-03-19 20:20:19 +0000 +++ http13client/request.go 2014-03-19 22:27:37 +0000 @@ -16,6 +16,7 @@ "io/ioutil" "mime" "mime/multipart" + "net/http" "net/textproto" "net/url" "strconv" @@ -103,7 +104,7 @@ // The request parser implements this by canonicalizing the // name, making the first character and any characters // following a hyphen uppercase and the rest lowercase. - Header Header + Header http.Header // Body is the request's body. // @@ -164,7 +165,7 @@ // For server requests, Trailer is only populated after Body has been // closed or fully consumed. // Trailer support is only partially complete. - Trailer Header + Trailer http.Header // RemoteAddr allows HTTP servers and other software to record // the network address that sent the request, usually for @@ -204,7 +205,7 @@ } // Cookies parses and returns the HTTP cookies sent with the request. -func (r *Request) Cookies() []*Cookie { +func (r *Request) Cookies() []*http.Cookie { return readCookies(r.Header, "") } @@ -212,7 +213,7 @@ // Cookie returns the named cookie provided in the request or // ErrNoCookie if not found. -func (r *Request) Cookie(name string) (*Cookie, error) { +func (r *Request) Cookie(name string) (*http.Cookie, error) { for _, c := range readCookies(r.Header, name) { return c, nil } @@ -223,7 +224,7 @@ // AddCookie does not attach more than one Cookie header field. That // means all cookies, if any, are written into the same line, // separated by semicolon. -func (r *Request) AddCookie(c *Cookie) { +func (r *Request) AddCookie(c *http.Cookie) { s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) if c := r.Header.Get("Cookie"); c != "" { r.Header.Set("Cookie", c+"; "+s) @@ -326,7 +327,7 @@ } // extraHeaders may be nil -func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) error { +func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders http.Header) error { host := req.Host if host == "" { if req.URL == nil { @@ -456,7 +457,7 @@ Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, - Header: make(Header), + Header: make(http.Header), Body: rc, Host: u.Host, } @@ -571,7 +572,7 @@ if err != nil { return nil, err } - req.Header = Header(mimeHeader) + req.Header = http.Header(mimeHeader) // RFC2616: Must treat // GET /index.html HTTP/1.1 @@ -582,7 +583,7 @@ // the same. In the second case, any Host line is ignored. req.Host = req.URL.Host if req.Host == "" { - req.Host = req.Header.get("Host") + req.Host = req.Header.Get("Host") } delete(req.Header, "Host") @@ -630,12 +631,12 @@ // // MaxBytesReader prevents clients from accidentally or maliciously // sending a large request and wasting server resources. -func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser { +func MaxBytesReader(w http.ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser { return &maxBytesReader{w: w, r: r, n: n} } type maxBytesReader struct { - w ResponseWriter + w http.ResponseWriter r io.ReadCloser // underlying reader n int64 // max bytes remaining stopped bool @@ -645,9 +646,6 @@ if l.n <= 0 { if !l.stopped { l.stopped = true - if res, ok := l.w.(*response); ok { - res.requestTooLarge() - } } return 0, errors.New("http: request body too large") } @@ -852,16 +850,16 @@ } func (r *Request) expectsContinue() bool { - return hasToken(r.Header.get("Expect"), "100-continue") + return hasToken(r.Header.Get("Expect"), "100-continue") } func (r *Request) wantsHttp10KeepAlive() bool { if r.ProtoMajor != 1 || r.ProtoMinor != 0 { return false } - return hasToken(r.Header.get("Connection"), "keep-alive") + return hasToken(r.Header.Get("Connection"), "keep-alive") } func (r *Request) wantsClose() bool { - return hasToken(r.Header.get("Connection"), "close") + return hasToken(r.Header.Get("Connection"), "close") } === modified file 'http13client/response.go' --- http13client/response.go 2014-03-19 20:20:19 +0000 +++ http13client/response.go 2014-03-19 22:27:37 +0000 @@ -11,6 +11,7 @@ "crypto/tls" "errors" "io" + "net/http" "net/textproto" "net/url" "strconv" @@ -40,7 +41,7 @@ // omitted from Header. // // Keys in the map are canonicalized (see CanonicalHeaderKey). - Header Header + Header http.Header // Body represents the response body. // @@ -69,7 +70,7 @@ // Trailer maps trailer keys to values, in the same // format as the header. - Trailer Header + Trailer http.Header // The Request that was sent to obtain this Response. // Request's Body is nil (having already been consumed). @@ -84,7 +85,7 @@ } // Cookies parses and returns the cookies set in the Set-Cookie headers. -func (r *Response) Cookies() []*Cookie { +func (r *Response) Cookies() []*http.Cookie { return readSetCookies(r.Header) } @@ -153,7 +154,7 @@ } return nil, err } - resp.Header = Header(mimeHeader) + resp.Header = http.Header(mimeHeader) fixPragmaCacheControl(resp.Header) @@ -169,7 +170,7 @@ // Pragma: no-cache // like // Cache-Control: no-cache -func fixPragmaCacheControl(header Header) { +func fixPragmaCacheControl(header http.Header) { if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" { if _, presentcc := header["Cache-Control"]; !presentcc { header["Cache-Control"] = []string{"no-cache"} === modified file 'http13client/transfer.go' --- http13client/transfer.go 2014-03-19 20:20:19 +0000 +++ http13client/transfer.go 2014-03-19 22:27:37 +0000 @@ -11,6 +11,7 @@ "fmt" "io" "io/ioutil" + "net/http" "net/textproto" "strconv" "strings" @@ -36,7 +37,7 @@ ContentLength int64 // -1 means unknown, 0 means exactly none Close bool TransferEncoding []string - Trailer Header + Trailer http.Header } func newTransferWriter(r interface{}) (t *transferWriter, err error) { @@ -174,7 +175,7 @@ io.WriteString(w, "Trailer: ") needComma := false for k := range t.Trailer { - k = CanonicalHeaderKey(k) + k = http.CanonicalHeaderKey(k) switch k { case "Transfer-Encoding", "Trailer", "Content-Length": return &badStringError{"invalid Trailer key", k} @@ -237,7 +238,7 @@ type transferReader struct { // Input - Header Header + Header http.Header StatusCode int RequestMethod string ProtoMajor int @@ -247,7 +248,7 @@ ContentLength int64 TransferEncoding []string Close bool - Trailer Header + Trailer http.Header } // bodyAllowedForStatus reports whether a given response status code @@ -308,7 +309,7 @@ return err } if isResponse && t.RequestMethod == "HEAD" { - if n, err := parseContentLength(t.Header.get("Content-Length")); err != nil { + if n, err := parseContentLength(t.Header.Get("Content-Length")); err != nil { return err } else { t.ContentLength = n @@ -386,7 +387,7 @@ func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" } // Sanitize transfer encoding -func fixTransferEncoding(requestMethod string, header Header) ([]string, error) { +func fixTransferEncoding(requestMethod string, header http.Header) ([]string, error) { raw, present := header["Transfer-Encoding"] if !present { return nil, nil @@ -429,7 +430,7 @@ // Determine the expected body length, using RFC 2616 Section 4.4. This // function is not a method, because ultimately it should be shared by // ReadResponse and ReadRequest. -func fixLength(isResponse bool, status int, requestMethod string, header Header, te []string) (int64, error) { +func fixLength(isResponse bool, status int, requestMethod string, header http.Header, te []string) (int64, error) { // Logic based on response type or status if noBodyExpected(requestMethod) { @@ -449,7 +450,7 @@ } // Logic based on Content-Length - cl := strings.TrimSpace(header.get("Content-Length")) + cl := strings.TrimSpace(header.Get("Content-Length")) if cl != "" { n, err := parseContentLength(cl) if err != nil { @@ -475,18 +476,18 @@ // Determine whether to hang up after sending a request and body, or // receiving a response and body // 'header' is the request headers -func shouldClose(major, minor int, header Header) bool { +func shouldClose(major, minor int, header http.Header) bool { if major < 1 { return true } else if major == 1 && minor == 0 { - if !strings.Contains(strings.ToLower(header.get("Connection")), "keep-alive") { + if !strings.Contains(strings.ToLower(header.Get("Connection")), "keep-alive") { return true } return false } else { // TODO: Should split on commas, toss surrounding white space, // and check each field. - if strings.ToLower(header.get("Connection")) == "close" { + if strings.ToLower(header.Get("Connection")) == "close" { header.Del("Connection") return true } @@ -495,17 +496,17 @@ } // Parse the trailer header -func fixTrailer(header Header, te []string) (Header, error) { - raw := header.get("Trailer") +func fixTrailer(header http.Header, te []string) (http.Header, error) { + raw := header.Get("Trailer") if raw == "" { return nil, nil } header.Del("Trailer") - trailer := make(Header) + trailer := make(http.Header) keys := strings.Split(raw, ",") for _, key := range keys { - key = CanonicalHeaderKey(strings.TrimSpace(key)) + key = http.CanonicalHeaderKey(strings.TrimSpace(key)) switch key { case "Transfer-Encoding", "Trailer", "Content-Length": return nil, &badStringError{"bad trailer key", key} @@ -642,9 +643,9 @@ } switch rr := b.hdr.(type) { case *Request: - rr.Trailer = Header(hdr) + rr.Trailer = http.Header(hdr) case *Response: - rr.Trailer = Header(hdr) + rr.Trailer = http.Header(hdr) } return nil } === modified file 'http13client/transport.go' --- http13client/transport.go 2014-03-19 20:20:19 +0000 +++ http13client/transport.go 2014-03-19 22:27:37 +0000 @@ -18,6 +18,7 @@ "io" "log" "net" + "net/http" "net/url" "os" "strings" @@ -144,12 +145,12 @@ // optional extra headers to write. type transportRequest struct { *Request // original request, not to be mutated - extra Header // extra headers to write, or nil + extra http.Header // extra headers to write, or nil } -func (tr *transportRequest) extraHeaders() Header { +func (tr *transportRequest) extraHeaders() http.Header { if tr.extra == nil { - tr.extra = make(Header) + tr.extra = make(http.Header) } return tr.extra } @@ -512,7 +513,7 @@ case cm.targetScheme == "http": pconn.isProxy = true if pa != "" { - pconn.mutateHeaderFunc = func(h Header) { + pconn.mutateHeaderFunc = func(h http.Header) { h.Set("Proxy-Authorization", pa) } } @@ -521,7 +522,7 @@ Method: "CONNECT", URL: &url.URL{Opaque: cm.targetAddr}, Host: cm.targetAddr, - Header: make(Header), + Header: make(http.Header), } if pa != "" { connectReq.Header.Set("Proxy-Authorization", pa) @@ -735,7 +736,7 @@ // mutateHeaderFunc is an optional func to modify extra // headers on each outbound request before it's written. (the // original Request given to RoundTrip is not modified) - mutateHeaderFunc func(Header) + mutateHeaderFunc func(http.Header) } func (pc *persistConn) isBroken() bool { ubuntu-push-0.2.1+14.04.20140423.1/http13client/_patches/tweak_doc_go.patch0000644000015301777760000000244612325724711026335 0ustar pbusernogroup00000000000000=== modified file 'http13client/doc.go' --- http13client/doc.go 2014-03-19 20:20:19 +0000 +++ http13client/doc.go 2014-03-20 12:11:42 +0000 @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. /* -Package http provides HTTP client and server implementations. +Package http contains the client subset of go 1.3 development net/http. Get, Head, Post, and PostForm make HTTP (or HTTPS) requests: @@ -52,29 +52,5 @@ Clients and Transports are safe for concurrent use by multiple goroutines and for efficiency should only be created once and re-used. - -ListenAndServe starts an HTTP server with a given address and handler. -The handler is usually nil, which means to use DefaultServeMux. -Handle and HandleFunc add handlers to DefaultServeMux: - - http.Handle("/foo", fooHandler) - - http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path)) - }) - - log.Fatal(http.ListenAndServe(":8080", nil)) - -More control over the server's behavior is available by creating a -custom Server: - - s := &http.Server{ - Addr: ":8080", - Handler: myHandler, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - log.Fatal(s.ListenAndServe()) */ package http ubuntu-push-0.2.1+14.04.20140423.1/http13client/chunked.go0000644000015301777760000001174612325724711023054 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // The wire protocol for HTTP's "chunked" Transfer-Encoding. // This code is duplicated in net/http and net/http/httputil. // Please make any changes in both files. package http import ( "bufio" "bytes" "errors" "fmt" "io" ) const maxLineLength = 4096 // assumed <= bufio.defaultBufSize var ErrLineTooLong = errors.New("header line too long") // newChunkedReader returns a new chunkedReader that translates the data read from r // out of HTTP "chunked" format before returning it. // The chunkedReader returns io.EOF when the final 0-length chunk is read. // // newChunkedReader is not needed by normal applications. The http package // automatically decodes chunking when reading response bodies. func newChunkedReader(r io.Reader) io.Reader { br, ok := r.(*bufio.Reader) if !ok { br = bufio.NewReader(r) } return &chunkedReader{r: br} } type chunkedReader struct { r *bufio.Reader n uint64 // unread bytes in chunk err error buf [2]byte } func (cr *chunkedReader) beginChunk() { // chunk-size CRLF var line []byte line, cr.err = readLine(cr.r) if cr.err != nil { return } cr.n, cr.err = parseHexUint(line) if cr.err != nil { return } if cr.n == 0 { cr.err = io.EOF } } func (cr *chunkedReader) chunkHeaderAvailable() bool { n := cr.r.Buffered() if n > 0 { peek, _ := cr.r.Peek(n) return bytes.IndexByte(peek, '\n') >= 0 } return false } func (cr *chunkedReader) Read(b []uint8) (n int, err error) { for cr.err == nil { if cr.n == 0 { if n > 0 && !cr.chunkHeaderAvailable() { // We've read enough. Don't potentially block // reading a new chunk header. break } cr.beginChunk() continue } if len(b) == 0 { break } rbuf := b if uint64(len(rbuf)) > cr.n { rbuf = rbuf[:cr.n] } var n0 int n0, cr.err = cr.r.Read(rbuf) n += n0 b = b[n0:] cr.n -= uint64(n0) // If we're at the end of a chunk, read the next two // bytes to verify they are "\r\n". if cr.n == 0 && cr.err == nil { if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err == nil { if cr.buf[0] != '\r' || cr.buf[1] != '\n' { cr.err = errors.New("malformed chunked encoding") } } } } return n, cr.err } // Read a line of bytes (up to \n) from b. // Give up if the line exceeds maxLineLength. // The returned bytes are a pointer into storage in // the bufio, so they are only valid until the next bufio read. func readLine(b *bufio.Reader) (p []byte, err error) { if p, err = b.ReadSlice('\n'); err != nil { // We always know when EOF is coming. // If the caller asked for a line, there should be a line. if err == io.EOF { err = io.ErrUnexpectedEOF } else if err == bufio.ErrBufferFull { err = ErrLineTooLong } return nil, err } if len(p) >= maxLineLength { return nil, ErrLineTooLong } return trimTrailingWhitespace(p), nil } func trimTrailingWhitespace(b []byte) []byte { for len(b) > 0 && isASCIISpace(b[len(b)-1]) { b = b[:len(b)-1] } return b } func isASCIISpace(b byte) bool { return b == ' ' || b == '\t' || b == '\n' || b == '\r' } // newChunkedWriter returns a new chunkedWriter that translates writes into HTTP // "chunked" format before writing them to w. Closing the returned chunkedWriter // sends the final 0-length chunk that marks the end of the stream. // // newChunkedWriter is not needed by normal applications. The http // package adds chunking automatically if handlers don't set a // Content-Length header. Using newChunkedWriter inside a handler // would result in double chunking or chunking with a Content-Length // length, both of which are wrong. func newChunkedWriter(w io.Writer) io.WriteCloser { return &chunkedWriter{w} } // Writing to chunkedWriter translates to writing in HTTP chunked Transfer // Encoding wire format to the underlying Wire chunkedWriter. type chunkedWriter struct { Wire io.Writer } // Write the contents of data as one chunk to Wire. // NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has // a bug since it does not check for success of io.WriteString func (cw *chunkedWriter) Write(data []byte) (n int, err error) { // Don't send 0-length data. It looks like EOF for chunked encoding. if len(data) == 0 { return 0, nil } if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil { return 0, err } if n, err = cw.Wire.Write(data); err != nil { return } if n != len(data) { err = io.ErrShortWrite return } _, err = io.WriteString(cw.Wire, "\r\n") return } func (cw *chunkedWriter) Close() error { _, err := io.WriteString(cw.Wire, "0\r\n") return err } func parseHexUint(v []byte) (n uint64, err error) { for _, b := range v { n <<= 4 switch { case '0' <= b && b <= '9': b = b - '0' case 'a' <= b && b <= 'f': b = b - 'a' + 10 case 'A' <= b && b <= 'F': b = b - 'A' + 10 default: return 0, errors.New("invalid byte in chunk length") } n |= uint64(b) } return } ubuntu-push-0.2.1+14.04.20140423.1/http13client/requestwrite_test.go0000644000015301777760000003333612325724711025234 0ustar pbusernogroup00000000000000// 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 http import ( "bytes" "errors" "fmt" "io" "io/ioutil" "net/http" "net/url" "strings" "testing" ) type reqWriteTest struct { Req Request Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body // Any of these three may be empty to skip that test. WantWrite string // Request.Write WantProxy string // Request.WriteProxy WantError error // wanted error from Request.Write } var reqWriteTests = []reqWriteTest{ // HTTP/1.1 => chunked coding; no body; no trailer { Req: Request{ Method: "GET", URL: &url.URL{ Scheme: "http", Host: "www.techcrunch.com", Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"}, "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"}, "Accept-Encoding": {"gzip,deflate"}, "Accept-Language": {"en-us,en;q=0.5"}, "Keep-Alive": {"300"}, "Proxy-Connection": {"keep-alive"}, "User-Agent": {"Fake"}, }, Body: nil, Close: false, Host: "www.techcrunch.com", Form: map[string][]string{}, }, WantWrite: "GET / HTTP/1.1\r\n" + "Host: www.techcrunch.com\r\n" + "User-Agent: Fake\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + "Accept-Encoding: gzip,deflate\r\n" + "Accept-Language: en-us,en;q=0.5\r\n" + "Keep-Alive: 300\r\n" + "Proxy-Connection: keep-alive\r\n\r\n", WantProxy: "GET http://www.techcrunch.com/ HTTP/1.1\r\n" + "Host: www.techcrunch.com\r\n" + "User-Agent: Fake\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" + "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" + "Accept-Encoding: gzip,deflate\r\n" + "Accept-Language: en-us,en;q=0.5\r\n" + "Keep-Alive: 300\r\n" + "Proxy-Connection: keep-alive\r\n\r\n", }, // HTTP/1.1 => chunked coding; body; empty trailer { Req: Request{ Method: "GET", URL: &url.URL{ Scheme: "http", Host: "www.google.com", Path: "/search", }, ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, TransferEncoding: []string{"chunked"}, }, Body: []byte("abcdef"), WantWrite: "GET /search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("abcdef") + chunk(""), WantProxy: "GET http://www.google.com/search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("abcdef") + chunk(""), }, // HTTP/1.1 POST => chunked coding; body; empty trailer { Req: Request{ Method: "POST", URL: &url.URL{ Scheme: "http", Host: "www.google.com", Path: "/search", }, ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Close: true, TransferEncoding: []string{"chunked"}, }, Body: []byte("abcdef"), WantWrite: "POST /search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Connection: close\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("abcdef") + chunk(""), WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Connection: close\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("abcdef") + chunk(""), }, // HTTP/1.1 POST with Content-Length, no chunking { Req: Request{ Method: "POST", URL: &url.URL{ Scheme: "http", Host: "www.google.com", Path: "/search", }, ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Close: true, ContentLength: 6, }, Body: []byte("abcdef"), WantWrite: "POST /search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Connection: close\r\n" + "Content-Length: 6\r\n" + "\r\n" + "abcdef", WantProxy: "POST http://www.google.com/search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Connection: close\r\n" + "Content-Length: 6\r\n" + "\r\n" + "abcdef", }, // HTTP/1.1 POST with Content-Length in headers { Req: Request{ Method: "POST", URL: mustParseURL("http://example.com/"), Host: "example.com", Header: http.Header{ "Content-Length": []string{"10"}, // ignored }, ContentLength: 6, }, Body: []byte("abcdef"), WantWrite: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Content-Length: 6\r\n" + "\r\n" + "abcdef", WantProxy: "POST http://example.com/ HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Content-Length: 6\r\n" + "\r\n" + "abcdef", }, // default to HTTP/1.1 { Req: Request{ Method: "GET", URL: mustParseURL("/search"), Host: "www.google.com", }, WantWrite: "GET /search HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "\r\n", }, // Request with a 0 ContentLength and a 0 byte body. { Req: Request{ Method: "POST", URL: mustParseURL("/"), Host: "example.com", ProtoMajor: 1, ProtoMinor: 1, ContentLength: 0, // as if unset by user }, Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 0)) }, // RFC 2616 Section 14.13 says Content-Length should be specified // unless body is prohibited by the request method. // Also, nginx expects it for POST and PUT. WantWrite: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Content-Length: 0\r\n" + "\r\n", WantProxy: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Content-Length: 0\r\n" + "\r\n", }, // Request with a 0 ContentLength and a 1 byte body. { Req: Request{ Method: "POST", URL: mustParseURL("/"), Host: "example.com", ProtoMajor: 1, ProtoMinor: 1, ContentLength: 0, // as if unset by user }, Body: func() io.ReadCloser { return ioutil.NopCloser(io.LimitReader(strings.NewReader("xx"), 1)) }, WantWrite: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("x") + chunk(""), WantProxy: "POST / HTTP/1.1\r\n" + "Host: example.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + chunk("x") + chunk(""), }, // Request with a ContentLength of 10 but a 5 byte body. { Req: Request{ Method: "POST", URL: mustParseURL("/"), Host: "example.com", ProtoMajor: 1, ProtoMinor: 1, ContentLength: 10, // but we're going to send only 5 bytes }, Body: []byte("12345"), WantError: errors.New("http: Request.ContentLength=10 with Body length 5"), }, // Request with a ContentLength of 4 but an 8 byte body. { Req: Request{ Method: "POST", URL: mustParseURL("/"), Host: "example.com", ProtoMajor: 1, ProtoMinor: 1, ContentLength: 4, // but we're going to try to send 8 bytes }, Body: []byte("12345678"), WantError: errors.New("http: Request.ContentLength=4 with Body length 8"), }, // Request with a 5 ContentLength and nil body. { Req: Request{ Method: "POST", URL: mustParseURL("/"), Host: "example.com", ProtoMajor: 1, ProtoMinor: 1, ContentLength: 5, // but we'll omit the body }, WantError: errors.New("http: Request.ContentLength=5 with nil Body"), }, // Request with a 0 ContentLength and a body with 1 byte content and an error. { Req: Request{ Method: "POST", URL: mustParseURL("/"), Host: "example.com", ProtoMajor: 1, ProtoMinor: 1, ContentLength: 0, // as if unset by user }, Body: func() io.ReadCloser { err := errors.New("Custom reader error") errReader := &errorReader{err} return ioutil.NopCloser(io.MultiReader(strings.NewReader("x"), errReader)) }, WantError: errors.New("Custom reader error"), }, // Request with a 0 ContentLength and a body without content and an error. { Req: Request{ Method: "POST", URL: mustParseURL("/"), Host: "example.com", ProtoMajor: 1, ProtoMinor: 1, ContentLength: 0, // as if unset by user }, Body: func() io.ReadCloser { err := errors.New("Custom reader error") errReader := &errorReader{err} return ioutil.NopCloser(errReader) }, WantError: errors.New("Custom reader error"), }, // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host, // and doesn't add a User-Agent. { Req: Request{ Method: "GET", URL: mustParseURL("/foo"), ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "X-Foo": []string{"X-Bar"}, }, }, WantWrite: "GET /foo HTTP/1.1\r\n" + "Host: \r\n" + "User-Agent: Go 1.1 package http\r\n" + "X-Foo: X-Bar\r\n\r\n", }, // If no Request.Host and no Request.URL.Host, we send // an empty Host header, and don't use // Request.Header["Host"]. This is just testing that // we don't change Go 1.0 behavior. { Req: Request{ Method: "GET", Host: "", URL: &url.URL{ Scheme: "http", Host: "", Path: "/search", }, ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "Host": []string{"bad.example.com"}, }, }, WantWrite: "GET /search HTTP/1.1\r\n" + "Host: \r\n" + "User-Agent: Go 1.1 package http\r\n\r\n", }, // Opaque test #1 from golang.org/issue/4860 { Req: Request{ Method: "GET", URL: &url.URL{ Scheme: "http", Host: "www.google.com", Opaque: "/%2F/%2F/", }, ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, }, WantWrite: "GET /%2F/%2F/ HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n\r\n", }, // Opaque test #2 from golang.org/issue/4860 { Req: Request{ Method: "GET", URL: &url.URL{ Scheme: "http", Host: "x.google.com", Opaque: "//y.google.com/%2F/%2F/", }, ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, }, WantWrite: "GET http://y.google.com/%2F/%2F/ HTTP/1.1\r\n" + "Host: x.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n\r\n", }, // Testing custom case in header keys. Issue 5022. { Req: Request{ Method: "GET", URL: &url.URL{ Scheme: "http", Host: "www.google.com", Path: "/", }, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{ "ALL-CAPS": {"x"}, }, }, WantWrite: "GET / HTTP/1.1\r\n" + "Host: www.google.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "ALL-CAPS: x\r\n" + "\r\n", }, } func TestRequestWrite(t *testing.T) { for i := range reqWriteTests { tt := &reqWriteTests[i] setBody := func() { if tt.Body == nil { return } switch b := tt.Body.(type) { case []byte: tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b)) case func() io.ReadCloser: tt.Req.Body = b() } } setBody() if tt.Req.Header == nil { tt.Req.Header = make(http.Header) } var braw bytes.Buffer err := tt.Req.Write(&braw) if g, e := fmt.Sprintf("%v", err), fmt.Sprintf("%v", tt.WantError); g != e { t.Errorf("writing #%d, err = %q, want %q", i, g, e) continue } if err != nil { continue } if tt.WantWrite != "" { sraw := braw.String() if sraw != tt.WantWrite { t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantWrite, sraw) continue } } if tt.WantProxy != "" { setBody() var praw bytes.Buffer err = tt.Req.WriteProxy(&praw) if err != nil { t.Errorf("WriteProxy #%d: %s", i, err) continue } sraw := praw.String() if sraw != tt.WantProxy { t.Errorf("Test Proxy %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantProxy, sraw) continue } } } } type closeChecker struct { io.Reader closed bool } func (rc *closeChecker) Close() error { rc.closed = true return nil } // TestRequestWriteClosesBody tests that Request.Write does close its request.Body. // It also indirectly tests NewRequest and that it doesn't wrap an existing Closer // inside a NopCloser, and that it serializes it correctly. func TestRequestWriteClosesBody(t *testing.T) { rc := &closeChecker{Reader: strings.NewReader("my body")} req, _ := NewRequest("POST", "http://foo.com/", rc) if req.ContentLength != 0 { t.Errorf("got req.ContentLength %d, want 0", req.ContentLength) } buf := new(bytes.Buffer) req.Write(buf) if !rc.closed { t.Error("body not closed after write") } expected := "POST / HTTP/1.1\r\n" + "Host: foo.com\r\n" + "User-Agent: Go 1.1 package http\r\n" + "Transfer-Encoding: chunked\r\n\r\n" + // TODO: currently we don't buffer before chunking, so we get a // single "m" chunk before the other chunks, as this was the 1-byte // read from our MultiReader where we stiched the Body back together // after sniffing whether the Body was 0 bytes or not. chunk("m") + chunk("y body") + chunk("") if buf.String() != expected { t.Errorf("write:\n got: %s\nwant: %s", buf.String(), expected) } } func chunk(s string) string { return fmt.Sprintf("%x\r\n%s\r\n", len(s), s) } func mustParseURL(s string) *url.URL { u, err := url.Parse(s) if err != nil { panic(fmt.Sprintf("Error parsing URL %q: %v", s, err)) } return u } ubuntu-push-0.2.1+14.04.20140423.1/http13client/lex_test.go0000644000015301777760000000127412325724711023255 0ustar pbusernogroup00000000000000// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package http import ( "testing" ) func isChar(c rune) bool { return c <= 127 } func isCtl(c rune) bool { return c <= 31 || c == 127 } func isSeparator(c rune) bool { switch c { case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ', '\t': return true } return false } func TestIsToken(t *testing.T) { for i := 0; i <= 130; i++ { r := rune(i) expected := isChar(r) && !isCtl(r) && !isSeparator(r) if isToken(r) != expected { t.Errorf("isToken(0x%x) = %v", r, !expected) } } } ubuntu-push-0.2.1+14.04.20140423.1/http13client/response_test.go0000644000015301777760000003361412325724711024326 0ustar pbusernogroup00000000000000// 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 http import ( "bufio" "bytes" "compress/gzip" "crypto/rand" "fmt" "io" "io/ioutil" "net/http" "net/url" "reflect" "regexp" "strings" "testing" ) type respTest struct { Raw string Resp Response Body string } func dummyReq(method string) *Request { return &Request{Method: method} } var respTests = []respTest{ // Unchunked response without Content-Length. { "HTTP/1.0 200 OK\r\n" + "Connection: close\r\n" + "\r\n" + "Body here\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), Header: http.Header{ "Connection": {"close"}, // TODO(rsc): Delete? }, Close: true, ContentLength: -1, }, "Body here\n", }, // Unchunked HTTP/1.1 response without Content-Length or // Connection headers. { "HTTP/1.1 200 OK\r\n" + "\r\n" + "Body here\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Request: dummyReq("GET"), Close: true, ContentLength: -1, }, "Body here\n", }, // Unchunked HTTP/1.1 204 response without Content-Length. { "HTTP/1.1 204 No Content\r\n" + "\r\n" + "Body should not be read!\n", Response{ Status: "204 No Content", StatusCode: 204, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: http.Header{}, Request: dummyReq("GET"), Close: false, ContentLength: 0, }, "", }, // Unchunked response with Content-Length. { "HTTP/1.0 200 OK\r\n" + "Content-Length: 10\r\n" + "Connection: close\r\n" + "\r\n" + "Body here\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), Header: http.Header{ "Connection": {"close"}, "Content-Length": {"10"}, }, Close: true, ContentLength: 10, }, "Body here\n", }, // Chunked response without Content-Length. { "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "0a\r\n" + "Body here\n\r\n" + "09\r\n" + "continued\r\n" + "0\r\n" + "\r\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), Header: http.Header{}, Close: false, ContentLength: -1, TransferEncoding: []string{"chunked"}, }, "Body here\ncontinued", }, // Chunked response with Content-Length. { "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "Content-Length: 10\r\n" + "\r\n" + "0a\r\n" + "Body here\n\r\n" + "0\r\n" + "\r\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), Header: http.Header{}, Close: false, ContentLength: -1, TransferEncoding: []string{"chunked"}, }, "Body here\n", }, // Chunked response in response to a HEAD request { "HTTP/1.1 200 OK\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("HEAD"), Header: http.Header{}, TransferEncoding: []string{"chunked"}, Close: false, ContentLength: -1, }, "", }, // Content-Length in response to a HEAD request { "HTTP/1.0 200 OK\r\n" + "Content-Length: 256\r\n" + "\r\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("HEAD"), Header: http.Header{"Content-Length": {"256"}}, TransferEncoding: nil, Close: true, ContentLength: 256, }, "", }, // Content-Length in response to a HEAD request with HTTP/1.1 { "HTTP/1.1 200 OK\r\n" + "Content-Length: 256\r\n" + "\r\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("HEAD"), Header: http.Header{"Content-Length": {"256"}}, TransferEncoding: nil, Close: false, ContentLength: 256, }, "", }, // No Content-Length or Chunked in response to a HEAD request { "HTTP/1.0 200 OK\r\n" + "\r\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("HEAD"), Header: http.Header{}, TransferEncoding: nil, Close: true, ContentLength: -1, }, "", }, // explicit Content-Length of 0. { "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n" + "\r\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), Header: http.Header{ "Content-Length": {"0"}, }, Close: false, ContentLength: 0, }, "", }, // Status line without a Reason-Phrase, but trailing space. // (permitted by RFC 2616) { "HTTP/1.0 303 \r\n\r\n", Response{ Status: "303 ", StatusCode: 303, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), Header: http.Header{}, Close: true, ContentLength: -1, }, "", }, // Status line without a Reason-Phrase, and no trailing space. // (not permitted by RFC 2616, but we'll accept it anyway) { "HTTP/1.0 303\r\n\r\n", Response{ Status: "303 ", StatusCode: 303, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Request: dummyReq("GET"), Header: http.Header{}, Close: true, ContentLength: -1, }, "", }, // golang.org/issue/4767: don't special-case multipart/byteranges responses { `HTTP/1.1 206 Partial Content Connection: close Content-Type: multipart/byteranges; boundary=18a75608c8f47cef some body`, Response{ Status: "206 Partial Content", StatusCode: 206, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Request: dummyReq("GET"), Header: http.Header{ "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"}, }, Close: true, ContentLength: -1, }, "some body", }, // Unchunked response without Content-Length, Request is nil { "HTTP/1.0 200 OK\r\n" + "Connection: close\r\n" + "\r\n" + "Body here\n", Response{ Status: "200 OK", StatusCode: 200, Proto: "HTTP/1.0", ProtoMajor: 1, ProtoMinor: 0, Header: http.Header{ "Connection": {"close"}, // TODO(rsc): Delete? }, Close: true, ContentLength: -1, }, "Body here\n", }, } func TestReadResponse(t *testing.T) { for i, tt := range respTests { resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) if err != nil { t.Errorf("#%d: %v", i, err) continue } rbody := resp.Body resp.Body = nil diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp) var bout bytes.Buffer if rbody != nil { _, err = io.Copy(&bout, rbody) if err != nil { t.Errorf("#%d: %v", i, err) continue } rbody.Close() } body := bout.String() if body != tt.Body { t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) } } } func TestWriteResponse(t *testing.T) { for i, tt := range respTests { resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) if err != nil { t.Errorf("#%d: %v", i, err) continue } err = resp.Write(ioutil.Discard) if err != nil { t.Errorf("#%d: %v", i, err) continue } } } var readResponseCloseInMiddleTests = []struct { chunked, compressed bool }{ {false, false}, {true, false}, {true, true}, } // TestReadResponseCloseInMiddle tests that closing a body after // reading only part of its contents advances the read to the end of // the request, right up until the next request. func TestReadResponseCloseInMiddle(t *testing.T) { for _, test := range readResponseCloseInMiddleTests { fatalf := func(format string, args ...interface{}) { args = append([]interface{}{test.chunked, test.compressed}, args...) t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...) } checkErr := func(err error, msg string) { if err == nil { return } fatalf(msg+": %v", err) } var buf bytes.Buffer buf.WriteString("HTTP/1.1 200 OK\r\n") if test.chunked { buf.WriteString("Transfer-Encoding: chunked\r\n") } else { buf.WriteString("Content-Length: 1000000\r\n") } var wr io.Writer = &buf if test.chunked { wr = newChunkedWriter(wr) } if test.compressed { buf.WriteString("Content-Encoding: gzip\r\n") wr = gzip.NewWriter(wr) } buf.WriteString("\r\n") chunk := bytes.Repeat([]byte{'x'}, 1000) for i := 0; i < 1000; i++ { if test.compressed { // Otherwise this compresses too well. _, err := io.ReadFull(rand.Reader, chunk) checkErr(err, "rand.Reader ReadFull") } wr.Write(chunk) } if test.compressed { err := wr.(*gzip.Writer).Close() checkErr(err, "compressor close") } if test.chunked { buf.WriteString("0\r\n\r\n") } buf.WriteString("Next Request Here") bufr := bufio.NewReader(&buf) resp, err := ReadResponse(bufr, dummyReq("GET")) checkErr(err, "ReadResponse") expectedLength := int64(-1) if !test.chunked { expectedLength = 1000000 } if resp.ContentLength != expectedLength { fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength) } if resp.Body == nil { fatalf("nil body") } if test.compressed { gzReader, err := gzip.NewReader(resp.Body) checkErr(err, "gzip.NewReader") resp.Body = &readerAndCloser{gzReader, resp.Body} } rbuf := make([]byte, 2500) n, err := io.ReadFull(resp.Body, rbuf) checkErr(err, "2500 byte ReadFull") if n != 2500 { fatalf("ReadFull only read %d bytes", n) } if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) { fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf)) } resp.Body.Close() rest, err := ioutil.ReadAll(bufr) checkErr(err, "ReadAll on remainder") if e, g := "Next Request Here", string(rest); e != g { g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string { return fmt.Sprintf("x(repeated x%d)", len(match)) }) fatalf("remainder = %q, expected %q", g, e) } } } func diff(t *testing.T, prefix string, have, want interface{}) { hv := reflect.ValueOf(have).Elem() wv := reflect.ValueOf(want).Elem() if hv.Type() != wv.Type() { t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type()) } for i := 0; i < hv.NumField(); i++ { hf := hv.Field(i).Interface() wf := wv.Field(i).Interface() if !reflect.DeepEqual(hf, wf) { t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf) } } } type responseLocationTest struct { location string // Response's Location header or "" requrl string // Response.Request.URL or "" want string wantErr error } var responseLocationTests = []responseLocationTest{ {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil}, {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil}, {"", "http://bar.com/baz", "", ErrNoLocation}, } func TestLocationResponse(t *testing.T) { for i, tt := range responseLocationTests { res := new(Response) res.Header = make(http.Header) res.Header.Set("Location", tt.location) if tt.requrl != "" { res.Request = &Request{} var err error res.Request.URL, err = url.Parse(tt.requrl) if err != nil { t.Fatalf("bad test URL %q: %v", tt.requrl, err) } } got, err := res.Location() if tt.wantErr != nil { if err == nil { t.Errorf("%d. err=nil; want %q", i, tt.wantErr) continue } if g, e := err.Error(), tt.wantErr.Error(); g != e { t.Errorf("%d. err=%q; want %q", i, g, e) continue } continue } if err != nil { t.Errorf("%d. err=%q", i, err) continue } if g, e := got.String(), tt.want; g != e { t.Errorf("%d. Location=%q; want %q", i, g, e) } } } func TestResponseStatusStutter(t *testing.T) { r := &Response{ Status: "123 some status", StatusCode: 123, ProtoMajor: 1, ProtoMinor: 3, } var buf bytes.Buffer r.Write(&buf) if strings.Contains(buf.String(), "123 123") { t.Errorf("stutter in status: %s", buf.String()) } } func TestResponseContentLengthShortBody(t *testing.T) { const shortBody = "Short body, not 123 bytes." br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" + "Content-Length: 123\r\n" + "\r\n" + shortBody)) res, err := ReadResponse(br, &Request{Method: "GET"}) if err != nil { t.Fatal(err) } if res.ContentLength != 123 { t.Fatalf("Content-Length = %d; want 123", res.ContentLength) } var buf bytes.Buffer n, err := io.Copy(&buf, res.Body) if n != int64(len(shortBody)) { t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody) } if buf.String() != shortBody { t.Errorf("Read body %q; want %q", buf.String(), shortBody) } if err != io.ErrUnexpectedEOF { t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err) } } func TestReadResponseUnexpectedEOF(t *testing.T) { br := bufio.NewReader(strings.NewReader("HTTP/1.1 301 Moved Permanently\r\n" + "Location: http://example.com")) _, err := ReadResponse(br, nil) if err != io.ErrUnexpectedEOF { t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err) } } ubuntu-push-0.2.1+14.04.20140423.1/bus/0000755000015301777760000000000012325725172017344 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/bus/bus.go0000644000015301777760000000420412325724711020462 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package bus provides a simplified (and more testable?) interface to DBus. package bus // Here we define the Bus itself import ( "launchpad.net/go-dbus/v1" "launchpad.net/ubuntu-push/logger" ) /***************************************************************** * Bus (and its implementation) */ // This is the Bus itself. type Bus interface { String() string Endpoint(Address, logger.Logger) Endpoint } type concreteBus dbus.StandardBus // ensure concreteBus implements Bus var _ Bus = new(concreteBus) // no bus.Bus constructor, just two standard, constant, busses var ( SessionBus Bus = concreteBus(dbus.SessionBus) SystemBus Bus = concreteBus(dbus.SystemBus) ) /* public methods */ func (bus concreteBus) String() string { if bus == concreteBus(dbus.SystemBus) { return "SystemBus" } else { return "SessionBus" } } // Endpoint returns a bus endpoint. func (bus concreteBus) Endpoint(addr Address, log logger.Logger) Endpoint { return newEndpoint(bus, addr, log) } // Args helps build arguments for endpoint Call(). func Args(args ...interface{}) []interface{} { return args } /* private methods */ func (bus concreteBus) dbusType() dbus.StandardBus { return dbus.StandardBus(bus) } /***************************************************************** * Address */ // bus.Address is just a bag of configuration type Address struct { Name string Path string Interface string } var BusDaemonAddress = Address{ dbus.BUS_DAEMON_NAME, string(dbus.BUS_DAEMON_PATH), dbus.BUS_DAEMON_IFACE, } ubuntu-push-0.2.1+14.04.20140423.1/bus/bus_test.go0000644000015301777760000000273012325724711021523 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package bus import ( "fmt" "launchpad.net/go-dbus/v1" . "launchpad.net/gocheck" helpers "launchpad.net/ubuntu-push/testing" "testing" ) // hook up gocheck func TestBus(t *testing.T) { TestingT(t) } type BusSuite struct{} var _ = Suite(&BusSuite{}) // Test we stringify sanely func (s *BusSuite) TestBusType(c *C) { c.Check(fmt.Sprintf("%s", SystemBus), Equals, "SystemBus") c.Check(fmt.Sprintf("%s", SessionBus), Equals, "SessionBus") } // Test we get the right DBus bus func (s *BusSuite) TestDBusType(c *C) { c.Check(SystemBus.(concreteBus).dbusType(), DeepEquals, dbus.SystemBus) c.Check(SessionBus.(concreteBus).dbusType(), DeepEquals, dbus.SessionBus) } // Tests that we can get an endpoint back func (s *BusSuite) TestEndpoint(c *C) { endp := SystemBus.Endpoint(Address{"", "", ""}, helpers.NewTestLogger(c, "debug")) c.Assert(endp, NotNil) } ubuntu-push-0.2.1+14.04.20140423.1/bus/endpoint_test.go0000644000015301777760000000420612325724711022552 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package bus import ( . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/logger" helpers "launchpad.net/ubuntu-push/testing" "os" ) type EndpointSuite struct { log logger.Logger } var _ = Suite(&EndpointSuite{}) func (s *EndpointSuite) SetUpTest(c *C) { s.log = helpers.NewTestLogger(c, "debug") } // TODO: this is going to remain empty until go-dbus grows some // testing amenities (already talked about it with jamesh) // Tests that we can connect to the *actual* system bus. // XXX maybe connect to a mock/fake/etc bus? func (s *EndpointSuite) TestDial(c *C) { // if somebody's set up the env var, assume it's "live" if os.Getenv("DBUS_SYSTEM_BUS_ADDRESS") == "" { // otherwise, check if _, err := os.Stat("/var/run/dbus/system_bus_socket"); os.IsNotExist(err) { c.Skip("system bus not present") } } endp := newEndpoint(SystemBus, Address{"", "", ""}, s.log) c.Assert(endp.bus, IsNil) err := endp.Dial() c.Assert(err, IsNil) defer endp.Close() // yes, a second close. On purpose. c.Assert(endp.bus, NotNil) endp.Close() // the first close. If you're counting right. c.Assert(endp.bus, IsNil) // Close cleans up } // Test that if we try to connect to the session bus when no session // bus is available, we get a reasonable result (i.e., an error). func (s *EndpointSuite) TestDialCanFail(c *C) { db := "DBUS_SESSION_BUS_ADDRESS" odb := os.Getenv(db) defer os.Setenv(db, odb) os.Setenv(db, "") endp := newEndpoint(SessionBus, Address{"", "", ""}, s.log) err := endp.Dial() c.Check(err, NotNil) } ubuntu-push-0.2.1+14.04.20140423.1/bus/urldispatcher/0000755000015301777760000000000012325725172022215 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/bus/urldispatcher/urldispatcher.go0000644000015301777760000000327712325724711025424 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package urldispatcher wraps the url dispatcher's dbus api point package urldispatcher import ( "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/logger" ) // UrlDispatcher lives on a well-known bus.Address var BusAddress bus.Address = bus.Address{ Interface: "com.canonical.URLDispatcher", Path: "/com/canonical/URLDispatcher", Name: "com.canonical.URLDispatcher", } // A URLDispatcher is a simple beast, with a single method that does what it // says on the box. type URLDispatcher interface { DispatchURL(string) error } type urlDispatcher struct { endp bus.Endpoint log logger.Logger } // New builds a new URL dispatcher that uses the provided bus.Endpoint func New(endp bus.Endpoint, log logger.Logger) URLDispatcher { return &urlDispatcher{endp, log} } var _ URLDispatcher = &urlDispatcher{} // ensures it conforms func (ud *urlDispatcher) DispatchURL(url string) error { ud.log.Debugf("Dispatching %s", url) err := ud.endp.Call("DispatchURL", bus.Args(url)) if err != nil { ud.log.Errorf("Dispatch to %s failed with %s", url, err) } return err } ubuntu-push-0.2.1+14.04.20140423.1/bus/urldispatcher/urldispatcher_test.go0000644000015301777760000000274012325724711026455 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package urldispatcher import ( . "launchpad.net/gocheck" testibus "launchpad.net/ubuntu-push/bus/testing" "launchpad.net/ubuntu-push/logger" helpers "launchpad.net/ubuntu-push/testing" "launchpad.net/ubuntu-push/testing/condition" "testing" ) // hook up gocheck func TestUrldispatcher(t *testing.T) { TestingT(t) } type UDSuite struct { log logger.Logger } var _ = Suite(&UDSuite{}) func (s *UDSuite) SetUpTest(c *C) { s.log = helpers.NewTestLogger(c, "debug") } func (s *UDSuite) TestWorks(c *C) { endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{}) ud := New(endp, s.log) err := ud.DispatchURL("this") c.Check(err, IsNil) } func (s *UDSuite) TestFailsIfCallFails(c *C) { endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) ud := New(endp, s.log) err := ud.DispatchURL("this") c.Check(err, NotNil) } ubuntu-push-0.2.1+14.04.20140423.1/bus/connectivity/0000755000015301777760000000000012325725172022062 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/bus/connectivity/webchecker_test.go0000644000015301777760000001036212325724711025552 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package connectivity import ( . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/logger" helpers "launchpad.net/ubuntu-push/testing" "launchpad.net/ubuntu-push/util" "net/http" "net/http/httptest" "time" ) type WebcheckerSuite struct { timeouts []time.Duration log logger.Logger } var _ = Suite(&WebcheckerSuite{}) const ( staticText = "something ipsum dolor something" staticHash = "6155f83b471583f47c99998a472a178f" bigText = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus tincidunt vitae sapien tempus fermentum. Cras commodo augue luctu, tempus libero sit amet, laoreet lectus. Vestibulum ali justo et malesuada placerat. Pellentesque viverra luctus velit, adipiscing fermentum tortori vehicula nec. Integer tincidunt purus et pretium vestibulum. Donec portas suscipit pulvinar. Suspendisse potenti. Donec sit amet pharetra nisl, sit amet posuere orci. In feugiat elitist nec augue fringilla, a rutrum risus posuere. Aliquam erat volutpat. Morbi aliquam arcu et eleifend placeraten. Pellentesque egestas varius aliquam. In egestas nisi sed ipsum tristiquer lacinia. Sed vitae nisi non eros consectetur vestibulum vehicularum vitae. Curabitur cursus consectetur eros, in vestibulum turpis cursus at i lorem. Pellentesque ultrices arcu ut massa faucibus, e consequat sapien placerat. Maecenas quis ultricies mi. Phasellus turpis nisl, porttitor ac mi cursus, euismod imperdiet lorem. Donec facilisis est id dignissim imperdiet.` bigHash = "9bf86bce26e8f2d9c9d9bd4a98f9e668" ) // mkHandler makes an http.HandlerFunc that returns the provided text // for whatever request it's given. func mkHandler(text string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.(http.Flusher).Flush() w.Write([]byte(text)) w.(http.Flusher).Flush() } } func (s *WebcheckerSuite) SetUpSuite(c *C) { s.timeouts = util.SwapTimeouts([]time.Duration{0}) } func (s *WebcheckerSuite) TearDownSuite(c *C) { util.SwapTimeouts(s.timeouts) s.timeouts = nil } func (s *WebcheckerSuite) SetUpTest(c *C) { s.log = helpers.NewTestLogger(c, "debug") } // Webchecker sends true when everything works func (s *WebcheckerSuite) TestWorks(c *C) { ts := httptest.NewServer(mkHandler(staticText)) defer ts.Close() ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log) ch := make(chan bool, 1) ck.Webcheck(ch) c.Check(<-ch, Equals, true) } // Webchecker sends false if the download fails. func (s *WebcheckerSuite) TestActualFails(c *C) { ck := NewWebchecker("garbage://", "", 5*time.Second, s.log) ch := make(chan bool, 1) ck.Webcheck(ch) c.Check(<-ch, Equals, false) } // Webchecker sends false if the hash doesn't match func (s *WebcheckerSuite) TestHashFails(c *C) { ts := httptest.NewServer(mkHandler("")) defer ts.Close() ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log) ch := make(chan bool, 1) ck.Webcheck(ch) c.Check(<-ch, Equals, false) } // Webchecker sends false if the download is too big func (s *WebcheckerSuite) TestTooBigFails(c *C) { ts := httptest.NewServer(mkHandler(bigText)) defer ts.Close() ck := NewWebchecker(ts.URL, bigHash, 5*time.Second, s.log) ch := make(chan bool, 1) ck.Webcheck(ch) c.Check(<-ch, Equals, false) } // Webchecker sends false if the request timeouts func (s *WebcheckerSuite) TestTooSlowFails(c *C) { finish := make(chan bool) handler := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { <-finish // get stuck }) ts := httptest.NewServer(handler) defer ts.Close() defer func() { finish <- true }() ck := NewWebchecker(ts.URL, bigHash, time.Second, s.log) ch := make(chan bool, 1) ck.Webcheck(ch) c.Check(<-ch, Equals, false) } ubuntu-push-0.2.1+14.04.20140423.1/bus/connectivity/connectivity_test.go0000644000015301777760000002142312325724711026166 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package connectivity import ( "launchpad.net/go-dbus/v1" . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/bus/networkmanager" testingbus "launchpad.net/ubuntu-push/bus/testing" "launchpad.net/ubuntu-push/config" "launchpad.net/ubuntu-push/logger" helpers "launchpad.net/ubuntu-push/testing" "launchpad.net/ubuntu-push/testing/condition" "launchpad.net/ubuntu-push/util" "net/http/httptest" "testing" "time" ) // hook up gocheck func Test(t *testing.T) { TestingT(t) } type ConnSuite struct { timeouts []time.Duration log logger.Logger } var _ = Suite(&ConnSuite{}) func (s *ConnSuite) SetUpSuite(c *C) { s.timeouts = util.SwapTimeouts([]time.Duration{0, 0, 0, 0}) } func (s *ConnSuite) TearDownSuite(c *C) { util.SwapTimeouts(s.timeouts) s.timeouts = nil } func (s *ConnSuite) SetUpTest(c *C) { s.log = helpers.NewTestLogger(c, "debug") } /* tests for connectedState's Start() method */ // when given a working config and bus, Start() will work func (s *ConnSuite) TestStartWorks(c *C) { endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting)) cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} c.Check(cs.start(), Equals, networkmanager.Connecting) } // if the bus fails a couple of times, we're still OK func (s *ConnSuite) TestStartRetriesConnect(c *C) { endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting)) cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} c.Check(cs.start(), Equals, networkmanager.Connecting) c.Check(cs.connAttempts, Equals, uint32(3)) // 1 more than the Fail2Work } // when the calls to NetworkManager fails for a bit, we're still OK func (s *ConnSuite) TestStartRetriesCall(c *C) { endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting)) cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} c.Check(cs.start(), Equals, networkmanager.Connecting) c.Check(cs.connAttempts, Equals, uint32(6)) } // when some of the calls to NetworkManager fails for a bit, we're still OK func (s *ConnSuite) TestStartRetriesCall2(c *C) { cond := condition.Chain(3, condition.Work(true), 1, condition.Work(false), 1, condition.Work(true)) endp := testingbus.NewTestingEndpoint(condition.Work(true), cond, uint32(networkmanager.Connecting)) cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} c.Check(cs.start(), Equals, networkmanager.Connecting) } // when... and bear with me... the bus works, and the first call to // get network manager's state works, but then you can't establish the // watch, we recover and try again. func (s *ConnSuite) TestStartRetriesWatch(c *C) { nmcond := condition.Chain( 1, condition.Work(true), // 1 call to nm works 1, condition.Work(false), // 1 call to nm fails 0, condition.Work(true)) // and everything works from there on endp := testingbus.NewTestingEndpoint(condition.Work(true), nmcond, uint32(networkmanager.Connecting), uint32(networkmanager.ConnectedGlobal)) cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} c.Check(cs.start(), Equals, networkmanager.Connecting) c.Check(cs.connAttempts, Equals, uint32(2)) c.Check(<-cs.networkStateCh, Equals, networkmanager.Connecting) c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal) } /* tests for connectedStateStep() */ func (s *ConnSuite) TestSteps(c *C) { var webget_p condition.Interface = condition.Work(true) recheck_timeout := 50 * time.Millisecond cfg := ConnectivityConfig{ RecheckTimeout: config.ConfigTimeDuration{recheck_timeout}, } ch := make(chan networkmanager.State, 10) cs := &connectedState{ config: cfg, networkStateCh: ch, timer: time.NewTimer(time.Second), log: s.log, webget: func(ch chan<- bool) { ch <- webget_p.OK() }, lastSent: false, } ch <- networkmanager.ConnectedGlobal f, e := cs.connectedStateStep() c.Check(e, IsNil) c.Check(f, Equals, true) ch <- networkmanager.ConnectedGlobal // a ConnectedGlobal when connected signals trouble f, e = cs.connectedStateStep() c.Check(e, IsNil) c.Check(f, Equals, false) // so we assume a disconnect happened f, e = cs.connectedStateStep() c.Check(e, IsNil) c.Check(f, Equals, true) // and if the web check works, go back to connected // same scenario, but with failing web check webget_p = condition.Fail2Work(1) ch <- networkmanager.ConnectedGlobal f, e = cs.connectedStateStep() c.Check(e, IsNil) c.Check(f, Equals, false) // first false is from assuming a Connected signals trouble // the next call to Step will time out _ch := make(chan bool, 1) _t := time.NewTimer(recheck_timeout / 2) go func() { f, e := cs.connectedStateStep() c.Check(e, IsNil) _ch <- f }() select { case <-_ch: c.Fatal("test failed to timeout") case <-_t.C: } // now an recheckTimeout later, we'll get true c.Check(<-_ch, Equals, true) ch <- networkmanager.Disconnected // this should trigger a 'false' ch <- networkmanager.Disconnected // this should not ch <- networkmanager.ConnectedGlobal // this should trigger a 'true' f, e = cs.connectedStateStep() c.Check(e, IsNil) c.Check(f, Equals, false) f, e = cs.connectedStateStep() c.Check(e, IsNil) c.Check(f, Equals, true) close(ch) // this should make it error out _, e = cs.connectedStateStep() c.Check(e, NotNil) } /* tests for ConnectedState() */ // yes, this is an integration test func (s *ConnSuite) TestRun(c *C) { ts := httptest.NewServer(mkHandler(staticText)) defer ts.Close() cfg := ConnectivityConfig{ ConnectivityCheckURL: ts.URL, ConnectivityCheckMD5: staticHash, RecheckTimeout: config.ConfigTimeDuration{time.Second}, } endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.ConnectedGlobal), uint32(networkmanager.ConnectedGlobal), uint32(networkmanager.Disconnected), ) watchTicker := make(chan bool) testingbus.SetWatchTicker(endp, watchTicker) out := make(chan bool) dt := time.Second / 10 timer := time.NewTimer(dt) go ConnectedState(endp, cfg, s.log, out) var v bool expecteds := []struct { p bool s string n int }{ {false, "first state is always false", 0}, {true, "then it should be true as per ConnectedGlobal above", 0}, {false, "then, false (upon receiving the next ConnectedGlobal)", 2}, {true, "then it should be true (webcheck passed)", 0}, {false, "then it should be false (Disconnected)", 2}, {false, "then it should be false again because it's restarted", 2}, } for i, expected := range expecteds { for j := 0; j < expected.n; j++ { watchTicker <- true } timer.Reset(dt) select { case v = <-out: break case <-timer.C: c.Fatalf("Timed out before getting value (#%d: %s)", i+1, expected.s) } c.Assert(v, Equals, expected.p, Commentf(expected.s)) } } func (s *ConnSuite) TestRun4Active(c *C) { ts := httptest.NewServer(mkHandler(staticText)) defer ts.Close() cfg := ConnectivityConfig{ ConnectivityCheckURL: ts.URL, ConnectivityCheckMD5: staticHash, RecheckTimeout: config.ConfigTimeDuration{time.Second}, } endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.ConnectedGlobal), map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("hello")}}, ) watchTicker := make(chan bool) testingbus.SetWatchTicker(endp, watchTicker) out := make(chan bool) dt := time.Second / 10 timer := time.NewTimer(dt) go ConnectedState(endp, cfg, s.log, out) var v bool expecteds := []struct { p bool s string n int }{ {false, "first state is always false", 0}, {true, "then it should be true as per ConnectedGlobal above", 0}, {false, "then, false (PrimaryConnection changed)", 2}, {true, "then it should be true (webcheck passed)", 0}, } for i, expected := range expecteds { for j := 0; j < expected.n; j++ { watchTicker <- true } timer.Reset(dt) select { case v = <-out: break case <-timer.C: c.Fatalf("Timed out before getting value (#%d: %s)", i+1, expected.s) } c.Assert(v, Equals, expected.p, Commentf(expected.s)) } } ubuntu-push-0.2.1+14.04.20140423.1/bus/connectivity/webchecker.go0000644000015301777760000000461612325724711024520 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // webchecker checks whether we're actually connected by doing an http // GET to the Ubuntu connectivity check URL, // http://start.ubuntu.com/connectivity-check.html // // We could make it be https to make extra doubly sure, but it's expensive // overkill for the majority of cases. package connectivity import ( "crypto/md5" "fmt" "io" "time" http13 "launchpad.net/ubuntu-push/http13client" "launchpad.net/ubuntu-push/logger" ) // how much web would a webchecker check type Webchecker interface { // Webcheck checks whether retrieving the URL works, and if its // contents match the target. If so, then it sends true; if anything // fails, it sends false. Webcheck(chan<- bool) } type webchecker struct { log logger.Logger url string target string cli *http13.Client } // Build a webchecker for the given URL, that should match the target MD5. func NewWebchecker(url string, target string, timeout time.Duration, log logger.Logger) Webchecker { cli := &http13.Client{ Timeout: timeout, Transport: &http13.Transport{TLSHandshakeTimeout: timeout}, } return &webchecker{log, url, target, cli} } // ensure webchecker implements Webchecker var _ Webchecker = &webchecker{} func (wb *webchecker) Webcheck(ch chan<- bool) { response, err := wb.cli.Get(wb.url) if err != nil { wb.log.Errorf("While GETting %s: %v", wb.url, err) ch <- false return } defer response.Body.Close() hash := md5.New() _, err = io.CopyN(hash, response.Body, 1024) if err != io.EOF { wb.log.Errorf("Reading %s, expecting EOF, got: %v", wb.url, err) ch <- false return } sum := fmt.Sprintf("%x", hash.Sum(nil)) if sum == wb.target { wb.log.Infof("Connectivity check passed.") ch <- true } else { wb.log.Infof("Connectivity check failed: content mismatch.") ch <- false } } ubuntu-push-0.2.1+14.04.20140423.1/bus/connectivity/connectivity.go0000644000015301777760000001312112325724711025123 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package connectivity implements a single, simple stream of booleans // to answer the question "are we connected?". // // It can potentially fire two falses in a row, if a disconnected // state is followed by a dbus watch error. Other than that, it's edge // triggered. package connectivity import ( "errors" "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/bus/networkmanager" "launchpad.net/ubuntu-push/config" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/util" "time" ) // The configuration for ConnectedState, intended to be populated from a config file. type ConnectivityConfig struct { // how long to wait after a state change to make sure it's "stable" // before acting on it StabilizingTimeout config.ConfigTimeDuration `json:"stabilizing_timeout"` // How long to wait between online connectivity checks. RecheckTimeout config.ConfigTimeDuration `json:"recheck_timeout"` // The URL against which to do the connectivity check. ConnectivityCheckURL string `json:"connectivity_check_url"` // The expected MD5 of the content at the ConnectivityCheckURL ConnectivityCheckMD5 string `json:"connectivity_check_md5"` } type connectedState struct { networkStateCh <-chan networkmanager.State networkConCh <-chan string config ConnectivityConfig log logger.Logger endp bus.Endpoint connAttempts uint32 webget func(ch chan<- bool) webgetCh chan bool currentState networkmanager.State lastSent bool timer *time.Timer } // start connects to the bus, gets the initial NetworkManager state, and sets // up the watch. func (cs *connectedState) start() networkmanager.State { var initial networkmanager.State var stateCh <-chan networkmanager.State var primary string var conCh <-chan string var err error for { ar := util.NewAutoRedialer(cs.endp) cs.connAttempts += ar.Redial() nm := networkmanager.New(cs.endp, cs.log) // Get the current state. initial = nm.GetState() if initial == networkmanager.Unknown { cs.log.Debugf("Failed to get state.") goto Continue } // set up the watch stateCh, err = nm.WatchState() if err != nil { cs.log.Debugf("failed to set up the state watch: %s", err) goto Continue } primary = nm.GetPrimaryConnection() cs.log.Debugf("primary connection starts as %#v", primary) conCh, err = nm.WatchPrimaryConnection() if err != nil { cs.log.Debugf("failed to set up the connection watch: %s", err) goto Continue } cs.networkStateCh = stateCh cs.networkConCh = conCh return initial Continue: cs.endp.Close() time.Sleep(10 * time.Millisecond) // that should cool things } } // connectedStateStep takes one step forwards in the “am I connected?” // answering state machine. func (cs *connectedState) connectedStateStep() (bool, error) { stabilizingTimeout := cs.config.StabilizingTimeout.Duration recheckTimeout := cs.config.RecheckTimeout.Duration log := cs.log Loop: for { select { case <-cs.networkConCh: cs.webgetCh = nil cs.timer.Reset(stabilizingTimeout) log.Debugf("PrimaryConnection changed. Assuming disconnect.") if cs.lastSent == true { cs.lastSent = false break Loop } case v, ok := <-cs.networkStateCh: if !ok { // tear it all down and start over return false, errors.New("Got not-OK from StateChanged watch") } cs.webgetCh = nil cs.currentState = v cs.timer.Reset(stabilizingTimeout) log.Debugf("State changed to %s. Assuming disconnect.", v) if cs.lastSent == true { log.Infof("Sending 'disconnected'.") cs.lastSent = false break Loop } case <-cs.timer.C: if cs.currentState == networkmanager.ConnectedGlobal { log.Debugf("May be connected; checking...") cs.webgetCh = make(chan bool) go cs.webget(cs.webgetCh) } case connected := <-cs.webgetCh: cs.timer.Reset(recheckTimeout) log.Debugf("Connection check says: %t", connected) cs.webgetCh = nil if connected && cs.lastSent == false { log.Infof("Sending 'connected'.") cs.lastSent = true break Loop } } } return cs.lastSent, nil } // ConnectedState sends the initial NetworkManager state and changes to it // over the "out" channel. Sends "false" as soon as it detects trouble, "true" // after checking actual connectivity. // // The endpoint need not be dialed; connectivity will Dial() and Close() // it as it sees fit. func ConnectedState(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger, out chan<- bool) { wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, 10*time.Second, log) cs := &connectedState{ config: config, log: log, endp: endp, webget: wg.Webcheck, } Start: log.Infof("Sending initial 'disconnected'.") out <- false cs.lastSent = false cs.currentState = cs.start() cs.timer = time.NewTimer(cs.config.StabilizingTimeout.Duration) for { v, err := cs.connectedStateStep() if err != nil { // tear it all down and start over log.Errorf("%s", err) goto Start } out <- v } } ubuntu-push-0.2.1+14.04.20140423.1/bus/notifications/0000755000015301777760000000000012325725172022215 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/bus/notifications/raw.go0000644000015301777760000000532512325724711023340 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package notifications wraps a couple of Notifications's DBus API points: // the org.freedesktop.Notifications.Notify call, and listening for the // ActionInvoked signal. package notifications // this is the lower-level api import ( "launchpad.net/go-dbus/v1" "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/logger" ) // Notifications lives on a well-knwon bus.Address var BusAddress bus.Address = bus.Address{ Interface: "org.freedesktop.Notifications", Path: "/org/freedesktop/Notifications", Name: "org.freedesktop.Notifications", } /***************************************************************** * RawNotifications */ // convenience type for the (uint32, string) ActionInvoked signal data type RawActionReply struct { NotificationId uint32 ActionId string } // a raw notification provides a low-level interface to the f.d.o. dbus // notifications api type RawNotifications struct { bus bus.Endpoint log logger.Logger } // Raw returns a new RawNotifications that'll use the provided bus.Endpoint func Raw(endp bus.Endpoint, log logger.Logger) *RawNotifications { return &RawNotifications{endp, log} } /* public methods */ // Notify fires a notification func (raw *RawNotifications) Notify( app_name string, reuse_id uint32, icon, summary, body string, actions []string, hints map[string]*dbus.Variant, timeout int32) (uint32, error) { // that's a long argument list! Take a breather. // var res uint32 err := raw.bus.Call("Notify", bus.Args(app_name, reuse_id, icon, summary, body, actions, hints, timeout), &res) if err != nil { return 0, err } return res, nil } // WatchActions listens for ActionInvoked signals from the notification daemon // and sends them over the channel provided func (raw *RawNotifications) WatchActions() (<-chan RawActionReply, error) { ch := make(chan RawActionReply) err := raw.bus.WatchSignal("ActionInvoked", func(ns ...interface{}) { ch <- RawActionReply{ns[0].(uint32), ns[1].(string)} }, func() { close(ch) }) if err != nil { raw.log.Debugf("Failed to set up the watch: %s", err) return nil, err } return ch, nil } ubuntu-push-0.2.1+14.04.20140423.1/bus/notifications/raw_test.go0000644000015301777760000000530212325724711024372 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package notifications wraps a couple of Notifications's DBus API points: // the org.freedesktop.Notifications.Notify call, and listening for the // ActionInvoked signal. package notifications import ( . "launchpad.net/gocheck" testibus "launchpad.net/ubuntu-push/bus/testing" "launchpad.net/ubuntu-push/logger" helpers "launchpad.net/ubuntu-push/testing" "launchpad.net/ubuntu-push/testing/condition" "testing" "time" ) // hook up gocheck func TestRaw(t *testing.T) { TestingT(t) } type RawSuite struct { log logger.Logger } func (s *RawSuite) SetUpTest(c *C) { s.log = helpers.NewTestLogger(c, "debug") } var _ = Suite(&RawSuite{}) func (s *RawSuite) TestNotifies(c *C) { endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) raw := Raw(endp, s.log) nid, err := raw.Notify("", 0, "", "", "", nil, nil, 0) c.Check(err, IsNil) c.Check(nid, Equals, uint32(1)) } func (s *RawSuite) TestNotifiesFails(c *C) { endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) raw := Raw(endp, s.log) _, err := raw.Notify("", 0, "", "", "", nil, nil, 0) c.Check(err, NotNil) } func (s *RawSuite) TestNotifiesFailsWeirdly(c *C) { endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{1, 2}) raw := Raw(endp, s.log) _, err := raw.Notify("", 0, "", "", "", nil, nil, 0) c.Check(err, NotNil) } func (s *RawSuite) TestWatchActions(c *C) { endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{uint32(1), "hello"}) raw := Raw(endp, s.log) ch, err := raw.WatchActions() c.Assert(err, IsNil) // check we get the right action reply select { case p := <-ch: c.Check(p.NotificationId, Equals, uint32(1)) c.Check(p.ActionId, Equals, "hello") case <-time.NewTimer(time.Second / 10).C: c.Error("timed out?") } // and that the channel is closed if/when the watch fails _, ok := <-ch c.Check(ok, Equals, false) } func (s *RawSuite) TestWatchActionsFails(c *C) { endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) raw := Raw(endp, s.log) _, err := raw.WatchActions() c.Check(err, NotNil) } ubuntu-push-0.2.1+14.04.20140423.1/bus/endpoint.go0000644000015301777760000001312412325724711021512 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package bus // Here we define the Endpoint, which represents the DBus connection itself. import ( "errors" "fmt" "launchpad.net/go-dbus/v1" "launchpad.net/ubuntu-push/logger" ) /***************************************************************** * Endpoint (and its implementation) */ // bus.Endpoint represents the DBus connection itself. type Endpoint interface { WatchSignal(member string, f func(...interface{}), d func()) error Call(member string, args []interface{}, rvs ...interface{}) error GetProperty(property string) (interface{}, error) Dial() error Close() String() string } type endpoint struct { busT Bus bus *dbus.Connection proxy *dbus.ObjectProxy addr Address log logger.Logger } // constructor func newEndpoint(bus Bus, addr Address, log logger.Logger) *endpoint { return &endpoint{busT: bus, addr: addr, log: log} } // ensure endpoint implements Endpoint var _ Endpoint = &endpoint{} /* public methods */ // Dial() (re)establishes the connection with dbus func (endp *endpoint) Dial() error { bus, err := dbus.Connect(endp.busT.(concreteBus).dbusType()) if err != nil { return err } d := dbus.BusDaemon{bus.Object(dbus.BUS_DAEMON_NAME, dbus.BUS_DAEMON_PATH)} name := endp.addr.Name hasOwner, err := d.NameHasOwner(name) if err != nil { endp.log.Debugf("Unable to determine ownership of %#v: %v", name, err) bus.Close() return err } if !hasOwner { // maybe it's waiting to be activated? names, err := d.ListActivatableNames() if err != nil { endp.log.Debugf("%#v has no owner, and when listing activatable: %v", name, err) bus.Close() return err } found := false for _, name := range names { if name == name { found = true break } } if !found { msg := fmt.Sprintf("%#v has no owner, and not in activatables", name) endp.log.Debugf(msg) bus.Close() return errors.New(msg) } } endp.log.Infof("%#v dialed in.", name) endp.bus = bus endp.proxy = bus.Object(name, dbus.ObjectPath(endp.addr.Path)) return nil } // WatchSignal() takes a member name, sets up a watch for it (on the name, // path and interface provided when creating the endpoint), and then calls f() // with the unpacked value. If it's unable to set up the watch it returns an // error. If the watch fails once established, d() is called. Typically f() // sends the values over a channel, and d() would close the channel. func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) error { watch, err := endp.proxy.WatchSignal(endp.addr.Interface, member) if err != nil { endp.log.Debugf("Failed to set up the watch: %s", err) return err } go endp.unpackMessages(watch, f, d, member) return nil } // Call() invokes the provided member method (on the name, path and // interface provided when creating the endpoint). args can be built // using bus.Args(...). The return value is unpacked into rvs before being // returned. func (endp *endpoint) Call(member string, args []interface{}, rvs ...interface{}) error { msg, err := endp.proxy.Call(endp.addr.Interface, member, args...) if err != nil { return err } err = msg.Args(rvs...) if err != nil { return err } return nil } // GetProperty uses the org.freedesktop.DBus.Properties interface's Get method // to read a given property on the name, path and interface provided when // creating the endpoint. The return value is unpacked into a dbus.Variant, // and its value returned. func (endp *endpoint) GetProperty(property string) (interface{}, error) { msg, err := endp.proxy.Call("org.freedesktop.DBus.Properties", "Get", endp.addr.Interface, property) if err != nil { return nil, err } variantvs := endp.unpackOneMsg(msg, property) switch len(variantvs) { default: return nil, fmt.Errorf("Too many values in Properties.Get response: %d", len(variantvs)) case 0: return nil, fmt.Errorf("Not enough values in Properties.Get response: %d", len(variantvs)) case 1: // carry on } variant, ok := variantvs[0].(*dbus.Variant) if !ok { return nil, fmt.Errorf("Response from Properties.Get wasn't a *dbus.Variant") } return variant.Value, nil } // Close the connection to dbus. func (endp *endpoint) Close() { if endp.bus != nil { endp.bus.Close() endp.bus = nil endp.proxy = nil } } // String() performs advanced endpoint stringification func (endp *endpoint) String() string { return fmt.Sprintf("", endp.bus, endp.addr) } /* private methods */ // unpackOneMsg unpacks the value from the response msg func (endp *endpoint) unpackOneMsg(msg *dbus.Message, member string) []interface{} { var varmap map[string]dbus.Variant if err := msg.Args(&varmap); err != nil { return msg.AllArgs() } return []interface{}{varmap} } // unpackMessages unpacks the value from the watch func (endp *endpoint) unpackMessages(watch *dbus.SignalWatch, f func(...interface{}), d func(), member string) { for { msg, ok := <-watch.C if !ok { break } f(endp.unpackOneMsg(msg, member)...) } endp.log.Errorf("Got not-OK from %s watch", member) d() } ubuntu-push-0.2.1+14.04.20140423.1/bus/testing/0000755000015301777760000000000012325725172021021 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/bus/testing/testing_bus.go0000644000015301777760000000402312325724711023673 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package testing provides an implementation of bus.Bus and bus.Endpoint // suitable for testing. package testing // Here, the bus.Bus implementation. import ( "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/logger" "launchpad.net/ubuntu-push/testing/condition" ) /***************************************************************** * TestingBus */ type testingBus struct { endp bus.Endpoint } // Build a bus.Bus that takes a condition to determine whether it should work, // as well as a condition and series of return values for the testing // bus.Endpoint it builds. func NewTestingBus(dialTC condition.Interface, callTC condition.Interface, retvals ...interface{}) bus.Bus { return &testingBus{NewTestingEndpoint(dialTC, callTC, retvals...)} } // Build a bus.Bus that takes a condition to determine whether it should work, // as well as a condition and a series of lists of return values for the // testing bus.Endpoint it builds. func NewMultiValuedTestingBus(dialTC condition.Interface, callTC condition.Interface, retvalses ...[]interface{}) bus.Bus { return &testingBus{NewMultiValuedTestingEndpoint(dialTC, callTC, retvalses...)} } // ensure testingBus implements bus.Interface var _ bus.Bus = &testingBus{} /* public methods */ func (tb *testingBus) Endpoint(info bus.Address, log logger.Logger) bus.Endpoint { return tb.endp } func (tb *testingBus) String() string { return "" } ubuntu-push-0.2.1+14.04.20140423.1/bus/testing/testing_endpoint_test.go0000644000015301777760000001311212325724711025760 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package testing import ( . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/testing/condition" "testing" "time" ) // hook up gocheck func Test(t *testing.T) { TestingT(t) } type TestingEndpointSuite struct{} var _ = Suite(&TestingEndpointSuite{}) // Test that Call() with a positive condition returns the first return value // provided, as advertised. func (s *TestingEndpointSuite) TestCallReturnsFirstRetval(c *C) { var m, n uint32 = 42, 17 endp := NewTestingEndpoint(nil, condition.Work(true), m, n) var r uint32 e := endp.Call("what", bus.Args(), &r) c.Check(e, IsNil) c.Check(r, Equals, m) } // Test the same Call() but with multi-valued endpoint func (s *TestingEndpointSuite) TestMultiValuedCall(c *C) { var m, n uint32 = 42, 17 endp := NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{m}, []interface{}{n}) var r uint32 e := endp.Call("what", bus.Args(), &r) c.Check(e, IsNil) c.Check(r, Equals, m) } // Test that Call() with a negative condition returns an error. func (s *TestingEndpointSuite) TestCallFails(c *C) { endp := NewTestingEndpoint(nil, condition.Work(false)) e := endp.Call("what", bus.Args()) c.Check(e, NotNil) } // Test that Call() with a positive condition and no return values panics with // a helpful message. func (s *TestingEndpointSuite) TestCallPanicsWithNiceMessage(c *C) { endp := NewTestingEndpoint(nil, condition.Work(true)) var x int32 c.Check(func() { endp.Call("", bus.Args(), &x) }, PanicMatches, "No return values provided.*") } // Test that Call() updates callArgs func (s *TestingEndpointSuite) TestCallArgs(c *C) { endp := NewTestingEndpoint(nil, condition.Work(true)) err := endp.Call("what", bus.Args("is", "this", "thing")) c.Assert(err, IsNil) c.Check(GetCallArgs(endp), DeepEquals, []callArgs{{"what", []interface{}{"is", "this", "thing"}}}) } // Test that WatchSignal() with a positive condition sends the provided return // values over the channel. func (s *TestingEndpointSuite) TestWatch(c *C) { var m, n uint32 = 42, 17 endp := NewTestingEndpoint(nil, condition.Work(true), m, n) ch := make(chan uint32) e := endp.WatchSignal("what", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) }) c.Check(e, IsNil) c.Check(<-ch, Equals, m) c.Check(<-ch, Equals, n) } // Test that WatchSignal() calls the destructor callback when it runs out values func (s *TestingEndpointSuite) TestWatchDestructor(c *C) { endp := NewTestingEndpoint(nil, condition.Work(true)) ch := make(chan uint32) e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) }) c.Check(e, IsNil) _, ok := <-ch c.Check(ok, Equals, false) } // Test the endpoint can be closed func (s *TestingEndpointSuite) TestCloser(c *C) { endp := NewTestingEndpoint(nil, condition.Work(true)) endp.Close() // ... yay? } // Test that WatchSignal() with a negative condition returns an error. func (s *TestingEndpointSuite) TestWatchFails(c *C) { endp := NewTestingEndpoint(nil, condition.Work(false)) e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {}) c.Check(e, NotNil) } // Test WatchSignal can use the WatchTicker instead of a timeout (if // the former is not nil) func (s *TestingEndpointSuite) TestWatchTicker(c *C) { watchTicker := make(chan bool, 3) watchTicker <- true watchTicker <- true watchTicker <- true c.Assert(len(watchTicker), Equals, 3) endp := NewTestingEndpoint(nil, condition.Work(true), 0, 0) SetWatchTicker(endp, watchTicker) ch := make(chan int) e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) }) c.Check(e, IsNil) // wait for the destructor to be called select { case <-time.Tick(10 * time.Millisecond): c.Fatal("timed out waiting for close on channel") case <-ch: } // now if all went well, the ticker will have been tuck twice. c.Assert(len(watchTicker), Equals, 1) } // Tests that GetProperty() works func (s *TestingEndpointSuite) TestGetProperty(c *C) { var m uint32 = 42 endp := NewTestingEndpoint(nil, condition.Work(true), m) v, e := endp.GetProperty("what") c.Check(e, IsNil) c.Check(v, Equals, m) } // Tests that GetProperty() fails, too func (s *TestingEndpointSuite) TestGetPropertyFails(c *C) { endp := NewTestingEndpoint(nil, condition.Work(false)) _, e := endp.GetProperty("what") c.Check(e, NotNil) } // Tests that GetProperty() also fails if it's fed garbage func (s *TestingEndpointSuite) TestGetPropertyFailsGargling(c *C) { endp := NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{}) _, e := endp.GetProperty("what") c.Check(e, NotNil) } // Test Dial() with a non-working bus fails func (s *TestingBusSuite) TestDialNoWork(c *C) { endp := NewTestingEndpoint(condition.Work(false), nil) err := endp.Dial() c.Check(err, NotNil) } // Test testingEndpoints serialize, more or less func (s *TestingBusSuite) TestEndpointString(c *C) { endp := NewTestingEndpoint(condition.Fail2Work(2), nil, "hello there") c.Check(endp.String(), Matches, ".*Still Broken.*hello there.*") } ubuntu-push-0.2.1+14.04.20140423.1/bus/testing/testing_endpoint.go0000644000015301777760000001164312325724711024730 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package testing // Here, the bus.Endpoint implementation. import ( "errors" "fmt" "launchpad.net/go-dbus/v1" "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/testing/condition" "sync" "time" ) type callArgs struct { Member string Args []interface{} } type testingEndpoint struct { dialCond condition.Interface callCond condition.Interface retvals [][]interface{} watchTicker chan bool watchLck sync.RWMutex callArgs []callArgs callArgsLck sync.RWMutex } // Build a bus.Endpoint that calls OK() on its condition before returning // the provided return values. // // NOTE: Call() always returns the first return value; Watch() will provide // each of them in turn, irrespective of whether Call has been called. func NewMultiValuedTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvalses ...[]interface{}) bus.Endpoint { return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses} } func NewTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvals ...interface{}) bus.Endpoint { retvalses := make([][]interface{}, len(retvals)) for i, x := range retvals { retvalses[i] = []interface{}{x} } return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses} } // If SetWatchTicker is called with a non-nil watchTicker, it is used // instead of the default timeout to wait while sending values over // WatchSignal. Set it to nil again to restore default behaviour. func SetWatchTicker(tc bus.Endpoint, watchTicker chan bool) { tc.(*testingEndpoint).watchLck.Lock() tc.(*testingEndpoint).watchTicker = watchTicker tc.(*testingEndpoint).watchLck.Unlock() } // GetCallArgs returns a list of the arguments for each Call() invocation. func GetCallArgs(tc bus.Endpoint) []callArgs { tc.(*testingEndpoint).callArgsLck.RLock() defer tc.(*testingEndpoint).callArgsLck.RUnlock() return tc.(*testingEndpoint).callArgs } // See Endpoint's WatchSignal. This WatchSignal will check its condition to // decide whether to return an error, or provide each of its return values func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error { if tc.callCond.OK() { go func() { for _, v := range tc.retvals { f(v...) tc.watchLck.RLock() ticker := tc.watchTicker tc.watchLck.RUnlock() if ticker != nil { <-ticker } else { time.Sleep(10 * time.Millisecond) } } d() }() return nil } else { return errors.New("no way") } } // See Endpoint's Call. This Call will check its condition to decide whether // to return an error, or the first of its return values func (tc *testingEndpoint) Call(member string, args []interface{}, rvs ...interface{}) error { tc.callArgsLck.Lock() defer tc.callArgsLck.Unlock() tc.callArgs = append(tc.callArgs, callArgs{member, args}) if tc.callCond.OK() { expected := len(rvs) var provided int if len(tc.retvals) == 0 { if expected != 0 { panic("No return values provided!") } provided = 0 } else { provided = len(tc.retvals[0]) } if provided != expected { return errors.New("provided/expected return vals mismatch") } if provided != 0 { x := dbus.NewMethodCallMessage("", "", "", "") err := x.AppendArgs(tc.retvals[0]...) if err != nil { return err } err = x.Args(rvs...) if err != nil { return err } } return nil } else { return errors.New("no way") } } // See Endpoint's GetProperty. This one is just another name for Call. func (tc *testingEndpoint) GetProperty(property string) (interface{}, error) { var res interface{} err := tc.Call(property, bus.Args(), &res) if err != nil { return nil, err } return res, err } // See Endpoint's Dial. This one will check its dialCondition to // decide whether to return an error or not. func (endp *testingEndpoint) Dial() error { if endp.dialCond.OK() { return nil } else { return errors.New("dialCond said No.") } } // Advanced stringifobabulation func (endp *testingEndpoint) String() string { return fmt.Sprintf("&testingEndpoint{dialCond:(%s) callCond:(%s) retvals:(%#v)", endp.dialCond, endp.callCond, endp.retvals) } // see Endpoint's Close. This one does nothing. func (tc *testingEndpoint) Close() {} // ensure testingEndpoint implements bus.Endpoint var _ bus.Endpoint = &testingEndpoint{} ubuntu-push-0.2.1+14.04.20140423.1/bus/testing/testing_bus_test.go0000644000015301777760000000425212325724711024736 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package testing import ( . "launchpad.net/gocheck" "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/testing/condition" "testing" ) // hook up gocheck func BusTest(t *testing.T) { TestingT(t) } type TestingBusSuite struct{} var _ = Suite(&TestingBusSuite{}) // Test Endpoint() on a working bus returns an endpoint that looks right func (s *TestingBusSuite) TestEndpointWorks(c *C) { addr := bus.Address{"", "", ""} tb := NewTestingBus(condition.Work(true), condition.Work(false), 42, 42, 42) endp := tb.Endpoint(addr, nil) err := endp.Dial() c.Check(err, IsNil) c.Assert(endp, FitsTypeOf, &testingEndpoint{}) c.Check(endp.(*testingEndpoint).callCond.OK(), Equals, false) c.Check(endp.(*testingEndpoint).retvals, HasLen, 3) } // Test Endpoint() on a working "multi-valued" bus returns an endpoint that looks right func (s *TestingBusSuite) TestEndpointMultiValued(c *C) { addr := bus.Address{"", "", ""} tb := NewMultiValuedTestingBus(condition.Work(true), condition.Work(true), []interface{}{42, 17}, []interface{}{42, 17, 13}, []interface{}{42}, ) endpp := tb.Endpoint(addr, nil) err := endpp.Dial() c.Check(err, IsNil) endp, ok := endpp.(*testingEndpoint) c.Assert(ok, Equals, true) c.Check(endp.callCond.OK(), Equals, true) c.Assert(endp.retvals, HasLen, 3) c.Check(endp.retvals[0], HasLen, 2) c.Check(endp.retvals[1], HasLen, 3) c.Check(endp.retvals[2], HasLen, 1) } // Test TestingBus stringifies sanely func (s *TestingBusSuite) TestStringifyBus(c *C) { tb := NewTestingBus(nil, nil) c.Check(tb.String(), Matches, ".*TestingBus.*") } ubuntu-push-0.2.1+14.04.20140423.1/bus/networkmanager/0000755000015301777760000000000012325725172022370 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/bus/networkmanager/state.go0000644000015301777760000000256012325724711024040 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package networkmanager type State uint32 // the NetworkManager states, as per // https://wiki.gnome.org/Projects/NetworkManager/DBusInterface/LatestDBusAPI const ( Unknown State = iota * 10 Asleep Disconnected Disconnecting Connecting ConnectedLocal ConnectedSite ConnectedGlobal _max_state ) var names = map[State]string{ Unknown: "Unknown", Asleep: "Asleep", Disconnected: "Disconnected", Disconnecting: "Disconnecting", Connecting: "Connecting", ConnectedLocal: "Connected Local", ConnectedSite: "Connected Site", ConnectedGlobal: "Connected Global", } // give its state a descriptive stringification func (state State) String() string { if s, ok := names[state]; ok { return s } return names[Unknown] } ubuntu-push-0.2.1+14.04.20140423.1/bus/networkmanager/networkmanager_test.go0000644000015301777760000001674712325724711027017 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package networkmanager import ( "testing" "launchpad.net/go-dbus/v1" . "launchpad.net/gocheck" testingbus "launchpad.net/ubuntu-push/bus/testing" "launchpad.net/ubuntu-push/logger" helpers "launchpad.net/ubuntu-push/testing" "launchpad.net/ubuntu-push/testing/condition" ) // hook up gocheck func Test(t *testing.T) { TestingT(t) } type NMSuite struct { log logger.Logger } var _ = Suite(&NMSuite{}) func (s *NMSuite) SetUpTest(c *C) { s.log = helpers.NewTestLogger(c, "debug") } // TestNames checks that networkmanager.State objects serialize // correctly, to a point. func (s *NMSuite) TestNames(c *C) { var i State for i = 0; i < _max_state; i += 10 { c.Check(names[i], Equals, i.String()) } i = _max_state c.Check(i.String(), Equals, "Unknown") } // TestNew doesn't test much at all. If this fails, all is wrong in the world. func (s *NMSuite) TestNew(c *C) { nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(true)), s.log) c.Check(nm, NotNil) } // GetState returns the right state when everything works func (s *NMSuite) TestGetState(c *C) { nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(true), uint32(ConnectedGlobal)), s.log) state := nm.GetState() c.Check(state, Equals, ConnectedGlobal) } // GetState returns the right state when dbus fails func (s *NMSuite) TestGetStateFail(c *C) { nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false), uint32(ConnectedGlobal)), s.log) state := nm.GetState() c.Check(state, Equals, Unknown) } // GetState returns the right state when dbus works but delivers rubbish values func (s *NMSuite) TestGetStateRubbishValues(c *C) { nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(true), "Unknown"), s.log) state := nm.GetState() c.Check(state, Equals, Unknown) } // GetState returns the right state when dbus works but delivers a rubbish structure func (s *NMSuite) TestGetStateRubbishStructure(c *C) { nm := New(testingbus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{}), s.log) state := nm.GetState() c.Check(state, Equals, Unknown) } // WatchState sends a stream of States over the channel func (s *NMSuite) TestWatchState(c *C) { tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), uint32(Unknown), uint32(Asleep), uint32(ConnectedGlobal)) nm := New(tc, s.log) ch, err := nm.WatchState() c.Check(err, IsNil) l := []State{<-ch, <-ch, <-ch} c.Check(l, DeepEquals, []State{Unknown, Asleep, ConnectedGlobal}) } // WatchState returns on error if the dbus call fails func (s *NMSuite) TestWatchStateFails(c *C) { nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log) _, err := nm.WatchState() c.Check(err, NotNil) } // WatchState calls close on its channel when the watch bails func (s *NMSuite) TestWatchStateClosesOnWatchBail(c *C) { tc := testingbus.NewTestingEndpoint(nil, condition.Work(true)) nm := New(tc, s.log) ch, err := nm.WatchState() c.Check(err, IsNil) _, ok := <-ch c.Check(ok, Equals, false) } // WatchState survives rubbish values func (s *NMSuite) TestWatchStateSurvivesRubbishValues(c *C) { tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a") nm := New(tc, s.log) ch, err := nm.WatchState() c.Check(err, IsNil) _, ok := <-ch c.Check(ok, Equals, false) } // GetPrimaryConnection returns the right state when everything works func (s *NMSuite) TestGetPrimaryConnection(c *C) { nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(true), dbus.ObjectPath("/a/1")), s.log) con := nm.GetPrimaryConnection() c.Check(con, Equals, "/a/1") } // GetPrimaryConnection returns the right state when dbus fails func (s *NMSuite) TestGetPrimaryConnectionFail(c *C) { nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log) con := nm.GetPrimaryConnection() c.Check(con, Equals, "") } // GetPrimaryConnection returns the right state when dbus works but delivers rubbish values func (s *NMSuite) TestGetPrimaryConnectionRubbishValues(c *C) { nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(true), "broken"), s.log) con := nm.GetPrimaryConnection() c.Check(con, Equals, "") } // GetPrimaryConnection returns the right state when dbus works but delivers a rubbish structure func (s *NMSuite) TestGetPrimaryConnectionRubbishStructure(c *C) { nm := New(testingbus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{}), s.log) con := nm.GetPrimaryConnection() c.Check(con, Equals, "") } func mkPriConMap(priCon string) map[string]dbus.Variant { m := make(map[string]dbus.Variant) m["PrimaryConnection"] = dbus.Variant{dbus.ObjectPath(priCon)} return m } // WatchPrimaryConnection sends a stream of Connections over the channel func (s *NMSuite) TestWatchPrimaryConnection(c *C) { tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), mkPriConMap("/a/1"), mkPriConMap("/b/2"), mkPriConMap("/c/3")) nm := New(tc, s.log) ch, err := nm.WatchPrimaryConnection() c.Check(err, IsNil) l := []string{<-ch, <-ch, <-ch} c.Check(l, DeepEquals, []string{"/a/1", "/b/2", "/c/3"}) } // WatchPrimaryConnection returns on error if the dbus call fails func (s *NMSuite) TestWatchPrimaryConnectionFails(c *C) { nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log) _, err := nm.WatchPrimaryConnection() c.Check(err, NotNil) } // WatchPrimaryConnection calls close on its channel when the watch bails func (s *NMSuite) TestWatchPrimaryConnectionClosesOnWatchBail(c *C) { tc := testingbus.NewTestingEndpoint(nil, condition.Work(true)) nm := New(tc, s.log) ch, err := nm.WatchPrimaryConnection() c.Check(err, IsNil) _, ok := <-ch c.Check(ok, Equals, false) } // WatchPrimaryConnection survives rubbish values func (s *NMSuite) TestWatchPrimaryConnectionSurvivesRubbishValues(c *C) { tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a") nm := New(tc, s.log) ch, err := nm.WatchPrimaryConnection() c.Assert(err, IsNil) _, ok := <-ch c.Check(ok, Equals, false) } // WatchPrimaryConnection ignores non-PrimaryConnection PropertyChanged func (s *NMSuite) TestWatchPrimaryConnectionIgnoresIrrelephant(c *C) { tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), map[string]dbus.Variant{"foo": dbus.Variant{}}, map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}}, ) nm := New(tc, s.log) ch, err := nm.WatchPrimaryConnection() c.Assert(err, IsNil) v, ok := <-ch c.Check(ok, Equals, true) c.Check(v, Equals, "42") } // WatchPrimaryConnection ignores rubbish PrimaryConnections func (s *NMSuite) TestWatchPrimaryConnectionIgnoresRubbishValues(c *C) { tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{-12}}, map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}}, ) nm := New(tc, s.log) ch, err := nm.WatchPrimaryConnection() c.Assert(err, IsNil) v, ok := <-ch c.Check(ok, Equals, true) c.Check(v, Equals, "42") } ubuntu-push-0.2.1+14.04.20140423.1/bus/networkmanager/networkmanager.go0000644000015301777760000001015412325724711025742 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package networkmanager wraps a couple of NetworkManager's DBus API points: // the org.freedesktop.NetworkManager.state call, and listening for the // StateChange signal. package networkmanager import ( "launchpad.net/go-dbus/v1" "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/logger" ) // NetworkManager lives on a well-knwon bus.Address var BusAddress bus.Address = bus.Address{ Interface: "org.freedesktop.NetworkManager", Path: "/org/freedesktop/NetworkManager", Name: "org.freedesktop.NetworkManager", } /***************************************************************** * NetworkManager (and its implementation) */ type NetworkManager interface { // GetState fetches and returns NetworkManager's current state GetState() State // WatchState listens for changes to NetworkManager's state, and sends // them out over the channel returned. WatchState() (<-chan State, error) // GetPrimaryConnection fetches and returns NetworkManager's current // primary connection. GetPrimaryConnection() string // WatchPrimaryConnection listens for changes of NetworkManager's // Primary Connection, and sends it out over the channel returned. WatchPrimaryConnection() (<-chan string, error) } type networkManager struct { bus bus.Endpoint log logger.Logger } // New returns a new NetworkManager that'll use the provided bus.Endpoint func New(endp bus.Endpoint, log logger.Logger) NetworkManager { return &networkManager{endp, log} } // ensure networkManager implements NetworkManager var _ NetworkManager = &networkManager{} /* public methods */ func (nm *networkManager) GetState() State { s, err := nm.bus.GetProperty("state") if err != nil { nm.log.Errorf("Failed gettting current state: %s", err) nm.log.Debugf("Defaulting state to Unknown") return Unknown } v, ok := s.(uint32) if !ok { nm.log.Errorf("Got weird state: %#v", s) return Unknown } return State(v) } func (nm *networkManager) WatchState() (<-chan State, error) { ch := make(chan State) err := nm.bus.WatchSignal("StateChanged", func(ns ...interface{}) { stint, ok := ns[0].(uint32) if !ok { nm.log.Errorf("got weird state: %#v", ns[0]) return } st := State(stint) nm.log.Debugf("got state: %s", st) ch <- State(stint) }, func() { close(ch) }) if err != nil { nm.log.Debugf("Failed to set up the watch: %s", err) return nil, err } return ch, nil } func (nm *networkManager) GetPrimaryConnection() string { s, err := nm.bus.GetProperty("PrimaryConnection") if err != nil { nm.log.Errorf("Failed gettting current primary connection: %s", err) nm.log.Debugf("Defaulting primary connection to empty") return "" } v, ok := s.(dbus.ObjectPath) if !ok { nm.log.Errorf("got weird PrimaryConnection: %#v", s) return "" } return string(v) } func (nm *networkManager) WatchPrimaryConnection() (<-chan string, error) { ch := make(chan string) err := nm.bus.WatchSignal("PropertiesChanged", func(ppsi ...interface{}) { pps, ok := ppsi[0].(map[string]dbus.Variant) if !ok { nm.log.Errorf("got weird PropertiesChanged: %#v", ppsi[0]) return } v, ok := pps["PrimaryConnection"] if !ok { return } con, ok := v.Value.(dbus.ObjectPath) if !ok { nm.log.Errorf("got weird PrimaryConnection via PropertiesChanged: %#v", v) return } nm.log.Debugf("got primary connection: %s", con) ch <- string(con) }, func() { close(ch) }) if err != nil { nm.log.Debugf("Failed to set up the watch: %s", err) return nil, err } return ch, nil } ubuntu-push-0.2.1+14.04.20140423.1/bus/systemimage/0000755000015301777760000000000012325725172021673 5ustar pbusernogroup00000000000000ubuntu-push-0.2.1+14.04.20140423.1/bus/systemimage/systemimage_test.go0000644000015301777760000000327612325724711025616 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package systemimage import ( "testing" . "launchpad.net/gocheck" testibus "launchpad.net/ubuntu-push/bus/testing" "launchpad.net/ubuntu-push/logger" helpers "launchpad.net/ubuntu-push/testing" "launchpad.net/ubuntu-push/testing/condition" ) // hook up gocheck func TestSystemImage(t *testing.T) { TestingT(t) } type SISuite struct { log logger.Logger } var _ = Suite(&SISuite{}) func (s *SISuite) SetUpTest(c *C) { s.log = helpers.NewTestLogger(c, "debug") } func (s *SISuite) TestWorks(c *C) { endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}}) si := New(endp, s.log) res, err := si.Info() c.Assert(err, IsNil) c.Check(res, DeepEquals, &InfoResult{ BuildNumber: 101, Device: "mako", Channel: "daily", LastUpdate: "Unknown", VersionDetail: map[string]string{}, }) } func (s *SISuite) TestFailsIfCallFails(c *C) { endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) si := New(endp, s.log) _, err := si.Info() c.Check(err, NotNil) } ubuntu-push-0.2.1+14.04.20140423.1/bus/systemimage/systemimage.go0000644000015301777760000000374312325724711024556 0ustar pbusernogroup00000000000000/* Copyright 2013-2014 Canonical Ltd. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ // Package systemimage is a mimimal wrapper for the system-image dbus API. package systemimage import ( "launchpad.net/ubuntu-push/bus" "launchpad.net/ubuntu-push/logger" ) // system-image service lives on a well-known bus.Address var BusAddress bus.Address = bus.Address{ Interface: "com.canonical.SystemImage", Path: "/Service", Name: "com.canonical.SystemImage", } // InfoResult holds the result of the system-image service Info method. type InfoResult struct { BuildNumber int32 Device string Channel string // xxx channel_target missing LastUpdate string VersionDetail map[string]string } // A SystemImage exposes the a subset of system-image service. type SystemImage interface { Info() (*InfoResult, error) } type systemImage struct { endp bus.Endpoint log logger.Logger } // New builds a new system-image service wrapper that uses the provided bus.Endpoint func New(endp bus.Endpoint, log logger.Logger) SystemImage { return &systemImage{endp, log} } var _ SystemImage = &systemImage{} // ensures it conforms func (si *systemImage) Info() (*InfoResult, error) { si.log.Debugf("Invoking Info") res := &InfoResult{} err := si.endp.Call("Info", bus.Args(), &res.BuildNumber, &res.Device, &res.Channel, &res.LastUpdate, &res.VersionDetail) if err != nil { si.log.Errorf("Info failed: %v", err) return nil, err } return res, err } ubuntu-push-0.2.1+14.04.20140423.1/README0000644000015301777760000000172112325724711017432 0ustar pbusernogroup00000000000000Ubuntu Push Notifications ------------------------- Protocol, client, and development code for Ubuntu Push Notifications. The code expects to be checked out as launchpad.net/ubuntu-push in a Go workspace, see "go help gopath". To setup Go dependencies, install libsqlite3-dev and run: make bootstrap To run tests, install libgcrypt11-dev and libwhoopsie-dev and run: make check To produce coverage reports you need Go 1.2 (default on Trusty) and the cover tool (in the golang-go.tools package), then run: make coverage-summary for a summary report, or: for per-package HTML with annotated code in coverhtml/.html make coverage-html (it makes also textual coverhtml/.txt reports). To run the acceptance tests, change to the acceptance subdir and run: make acceptance There are build targets to build the client: make build-client building ubuntu-push-client, and to run the development server: make run-server-dev