Merge lp:~chipaca/ubuntu-push/the-merge-automatic into lp:ubuntu-push

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: 109
Merged at revision: 105
Proposed branch: lp:~chipaca/ubuntu-push/the-merge-automatic
Merge into: lp:ubuntu-push
Diff against target: 8895 lines (+4002/-1767)
75 files modified
Makefile (+2/-2)
PACKAGE_DEPS (+2/-0)
bus/connectivity/connectivity_test.go (+7/-7)
bus/endpoint.go (+14/-10)
bus/notifications/app_helper/app_helper_c.go (+41/-0)
bus/notifications/raw.go (+36/-0)
bus/notifications/raw_test.go (+43/-0)
bus/testing/testing_endpoint.go (+3/-3)
bus/testing/testing_endpoint_test.go (+6/-6)
client/client.go (+90/-90)
client/client_test.go (+113/-58)
client/gethosts/gethost_test.go (+1/-1)
client/service/common.go (+99/-0)
client/service/postal.go (+181/-0)
client/service/postal_test.go (+252/-0)
client/service/service.go (+155/-167)
client/service/service_test.go (+166/-136)
client/session/session.go (+6/-17)
client/session/session_test.go (+15/-28)
debian/changelog (+34/-0)
debian/config.json (+3/-1)
debian/control (+2/-0)
debian/rules (+6/-1)
external/README (+1/-1)
external/murmur3/murmur128.go (+2/-2)
external/murmur3/murmur32.go (+2/-2)
external/murmur3/murmur64.go (+1/-1)
external/murmur3/murmur_test.go (+9/-5)
http13client/Makefile (+2/-1)
http13client/_patches/empty_server.patch (+118/-42)
http13client/_patches/fix_code.patch (+73/-65)
http13client/_patches/fix_status.patch (+33/-20)
http13client/_patches/fix_tests.patch (+338/-384)
http13client/_using.txt (+3/-3)
http13client/client.go (+11/-3)
http13client/client_test.go (+92/-0)
http13client/cookie.go (+19/-37)
http13client/cookie_test.go (+0/-304)
http13client/request.go (+62/-50)
http13client/request_test.go (+48/-8)
http13client/response.go (+52/-5)
http13client/response_test.go (+4/-0)
http13client/responsewrite_test.go (+117/-1)
http13client/server.go (+11/-5)
http13client/transfer.go (+59/-27)
http13client/transport.go (+114/-42)
http13client/transport_test.go (+296/-17)
launch_helper/helper.go (+51/-0)
launch_helper/helper_output.go (+57/-0)
launch_helper/helper_test.go (+57/-0)
messaging/cmessaging/cmessaging.go (+71/-0)
messaging/cmessaging/cmessaging_c.go (+56/-0)
messaging/messaging.go (+50/-0)
messaging/messaging_test.go (+87/-0)
messaging/reply/reply.go (+25/-0)
nih/nih.go (+1/-1)
scripts/deps.sh (+1/-1)
scripts/dummyauth.sh (+3/-0)
scripts/register (+43/-0)
scripts/unicast (+2/-0)
server/acceptance/acceptanceclient.go (+1/-1)
server/acceptance/cmd/acceptanceclient.go (+3/-1)
server/acceptance/suites/broadcast.go (+13/-13)
server/acceptance/suites/suite.go (+4/-2)
server/acceptance/suites/unicast.go (+16/-8)
server/api/handlers.go (+179/-93)
server/api/handlers_test.go (+234/-42)
server/session/session.go (+79/-29)
server/session/session_test.go (+95/-4)
server/store/inmemory.go (+31/-0)
server/store/inmemory_test.go (+51/-0)
server/store/store.go (+10/-0)
signing-helper/signing-helper.cpp (+6/-10)
testing/helpers.go (+27/-0)
whoopsie/identifier/identifier.go (+5/-10)
To merge this branch: bzr merge lp:~chipaca/ubuntu-push/the-merge-automatic
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+224289@code.launchpad.net

Description of the change

from debian/changelog:

  [ Samuele Pedroni ]
  * Support registering tokens and sending notifications with a token
  * Register script and scripts unicast support
  * Update http13client from the actual go1.3 release
  * Avoid late pings in the face of nop exchanges
  * murmur3 upstream change of seed to 0

  [ Roberto Alsina ]
  * Make signing-helper generate a HTTP header instead of a querystring,
    and take a URL to sign.
  * Wrap libmessaging-menu to allow for persistent notifications.
  * Wrap ubuntu-app-launch start_helper / stop_helper functions.

  [ John R. Lenton ]
  * Switch dbus api to retrieve app name from dbus path.
  * Move signing bits up from session to client, for reuse by service.
  * Change AuthHelper to be a string; auth helper should now expect a
    parameter (the url to sign). Added SessionURL to config.
  * Adapt our whoopsie wrapper to whoopsie's now more correct behavior wrt
    failing to get a mac address.
  * Add registration_url to config; hook up auth bits and reg url to
    client & service.
  * Do an HTTP POST to registration_url on register.
  * Fix debian/rules so packaging-time tests pass (ugh)

  [ Guillermo Gonzalez ]
  * Split DBus service into PushService and PostalService

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2014-06-02 11:37:03 +0000
3+++ Makefile 2014-07-02 13:12:36 +0000
4@@ -36,8 +36,8 @@
5 $(GOPATH)/bin/godeps -u dependencies.tsv
6 go install $(GODEPS)
7
8-dependencies.tsv: $(TOBUILD)
9- $(GOPATH)/bin/godeps -t $(foreach i,$^,$(dir $(PROJECT)/$(i))) 2>/dev/null | cat > $@
10+dependencies.tsv:
11+ $(GOPATH)/bin/godeps -t $(TOTEST) $(foreach i,$(TOBUILD),$(dir $(PROJECT)/$(i))) 2>/dev/null | cat > $@
12
13 check:
14 go test $(TESTFLAGS) $(TOTEST)
15
16=== modified file 'PACKAGE_DEPS'
17--- PACKAGE_DEPS 2014-05-23 06:21:46 +0000
18+++ PACKAGE_DEPS 2014-07-02 13:12:36 +0000
19@@ -8,3 +8,5 @@
20 libsqlite3-dev
21 libubuntuoneauth-2.0-dev
22 libwhoopsie-dev
23+libmessaging-menu-dev
24+libubuntu-app-launch2-dev
25
26=== modified file 'bus/connectivity/connectivity_test.go'
27--- bus/connectivity/connectivity_test.go 2014-05-09 10:48:39 +0000
28+++ bus/connectivity/connectivity_test.go 2014-07-02 13:12:36 +0000
29@@ -160,13 +160,13 @@
30 return nil
31 }
32
33-func (*racyEndpoint) Close() {}
34-func (*racyEndpoint) Dial() error { return nil }
35-func (*racyEndpoint) String() string { return "racyEndpoint" }
36-func (*racyEndpoint) Call(string, []interface{}, ...interface{}) error { return nil }
37-func (*racyEndpoint) GrabName(bool) <-chan error { return nil }
38-func (*racyEndpoint) WatchMethod(bus.DispatchMap, ...interface{}) {}
39-func (*racyEndpoint) Signal(member string, args []interface{}) error { return nil }
40+func (*racyEndpoint) Close() {}
41+func (*racyEndpoint) Dial() error { return nil }
42+func (*racyEndpoint) String() string { return "racyEndpoint" }
43+func (*racyEndpoint) Call(string, []interface{}, ...interface{}) error { return nil }
44+func (*racyEndpoint) GrabName(bool) <-chan error { return nil }
45+func (*racyEndpoint) WatchMethod(bus.DispatchMap, string, ...interface{}) {}
46+func (*racyEndpoint) Signal(member string, suffix string, args []interface{}) error { return nil }
47
48 var _ bus.Endpoint = (*racyEndpoint)(nil)
49
50
51=== modified file 'bus/endpoint.go'
52--- bus/endpoint.go 2014-05-15 11:28:06 +0000
53+++ bus/endpoint.go 2014-07-02 13:12:36 +0000
54@@ -21,7 +21,9 @@
55 import (
56 "errors"
57 "fmt"
58+
59 "launchpad.net/go-dbus/v1"
60+
61 "launchpad.net/ubuntu-push/logger"
62 )
63
64@@ -29,15 +31,15 @@
65 * Endpoint (and its implementation)
66 */
67
68-type BusMethod func([]interface{}, []interface{}) ([]interface{}, error)
69+type BusMethod func(string, []interface{}, []interface{}) ([]interface{}, error)
70 type DispatchMap map[string]BusMethod
71
72 // bus.Endpoint represents the DBus connection itself.
73 type Endpoint interface {
74 GrabName(allowReplacement bool) <-chan error
75 WatchSignal(member string, f func(...interface{}), d func()) error
76- WatchMethod(DispatchMap, ...interface{})
77- Signal(string, []interface{}) error
78+ WatchMethod(DispatchMap, string, ...interface{})
79+ Signal(string, string, []interface{}) error
80 Call(member string, args []interface{}, rvs ...interface{}) error
81 GetProperty(property string) (interface{}, error)
82 Dial() error
83@@ -212,8 +214,9 @@
84 // Signal() sends out a signal called <member> containing <args>.
85 //
86 // XXX: untested
87-func (endp *endpoint) Signal(member string, args []interface{}) error {
88- msg := dbus.NewSignalMessage(dbus.ObjectPath(endp.addr.Path), endp.addr.Interface, member)
89+func (endp *endpoint) Signal(member string, suffix string, args []interface{}) error {
90+ path := dbus.ObjectPath(endp.addr.Path + suffix)
91+ msg := dbus.NewSignalMessage(path, endp.addr.Interface, member)
92 if args != nil {
93 err := msg.AppendArgs(args...)
94 if err != nil {
95@@ -234,7 +237,7 @@
96 // calls.
97 //
98 // XXX: untested
99-func (endp *endpoint) WatchMethod(dispatch DispatchMap, extra ...interface{}) {
100+func (endp *endpoint) WatchMethod(dispatch DispatchMap, suffix string, extra ...interface{}) {
101 ch := make(chan *dbus.Message)
102 go func() {
103 var reply *dbus.Message
104@@ -249,12 +252,12 @@
105 endp.log.Errorf("WatchMethod: unknown method %s", msg.Member)
106 } else {
107 args := msg.AllArgs()
108- rvals, err := meth(args, extra)
109+ rvals, err := meth(string(msg.Path), args, extra)
110 if err != nil {
111 reply = dbus.NewErrorMessage(msg, err_iface, err.Error())
112- endp.log.Errorf("WatchMethod: %s(%#v, %#v) failure: %#v", msg.Member, args, extra, err)
113+ endp.log.Errorf("WatchMethod: %s(%v, %#v, %#v) failure: %#v", msg.Member, msg.Path, args, extra, err)
114 } else {
115- endp.log.Debugf("WatchMethod: %s(%#v, %#v) success: %#v", msg.Member, args, extra, rvals)
116+ endp.log.Debugf("WatchMethod: %s(%v, %#v, %#v) success: %#v", msg.Member, msg.Path, args, extra, rvals)
117 reply = dbus.NewMethodReturnMessage(msg)
118 err = reply.AppendArgs(rvals...)
119 if err != nil {
120@@ -270,7 +273,8 @@
121
122 }
123 }()
124- endp.bus.RegisterObjectPath(dbus.ObjectPath(endp.addr.Path), ch)
125+ path := dbus.ObjectPath(endp.addr.Path + suffix)
126+ endp.bus.RegisterObjectPath(path, ch)
127 }
128
129 /*
130
131=== added directory 'bus/notifications/app_helper'
132=== added file 'bus/notifications/app_helper/app_helper_c.go'
133--- bus/notifications/app_helper/app_helper_c.go 1970-01-01 00:00:00 +0000
134+++ bus/notifications/app_helper/app_helper_c.go 2014-07-02 13:12:36 +0000
135@@ -0,0 +1,41 @@
136+/*
137+ Copyright 2013-2014 Canonical Ltd.
138+
139+ This program is free software: you can redistribute it and/or modify it
140+ under the terms of the GNU General Public License version 3, as published
141+ by the Free Software Foundation.
142+
143+ This program is distributed in the hope that it will be useful, but
144+ WITHOUT ANY WARRANTY; without even the implied warranties of
145+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
146+ PURPOSE. See the GNU General Public License for more details.
147+
148+ You should have received a copy of the GNU General Public License along
149+ with this program. If not, see <http://www.gnu.org/licenses/>.
150+*/
151+
152+// Package app_helper wraps C functions to access app information
153+package app_helper
154+
155+/*
156+#cgo pkg-config: gio-unix-2.0
157+#cgo pkg-config: gio-2.0
158+#include <stdlib.h>
159+#include <glib.h>
160+#include <gio/gdesktopappinfo.h>
161+*/
162+import "C"
163+import "unsafe"
164+
165+func AppIconFromId(appId string) string {
166+ _id := C.CString(appId)
167+ defer C.free(unsafe.Pointer(_id))
168+ _app_info := C.g_desktop_app_info_new(_id)
169+ defer C.g_app_info_delete(_app_info)
170+ _app_icon := C.g_app_info_get_icon(_app_info)
171+ defer C.g_object_unref((C.gpointer)(_app_icon))
172+ _icon_string := C.g_icon_to_string(_app_icon)
173+ defer C.free(unsafe.Pointer(_icon_string))
174+ name := C.GoString((*C.char)(_icon_string))
175+ return name
176+}
177
178=== modified file 'bus/notifications/raw.go'
179--- bus/notifications/raw.go 2014-05-20 10:50:04 +0000
180+++ bus/notifications/raw.go 2014-07-02 13:12:36 +0000
181@@ -23,9 +23,12 @@
182
183 import (
184 "errors"
185+ "fmt"
186
187 "launchpad.net/go-dbus/v1"
188 "launchpad.net/ubuntu-push/bus"
189+ c_helper "launchpad.net/ubuntu-push/bus/notifications/app_helper"
190+ "launchpad.net/ubuntu-push/launch_helper"
191 "launchpad.net/ubuntu-push/logger"
192 )
193
194@@ -96,3 +99,36 @@
195 }
196 return ch, nil
197 }
198+
199+// ShowCard displays a given card.
200+//
201+// If card.Actions has 1 action, it's an interactive notification.
202+// If card.Actions has 2 or more actions, it will show as a snap decision.
203+//
204+// WatchActions will receive something like this in the ActionId field:
205+// appId::notificationId::action.Id
206+func (raw *RawNotifications) Present(appId string, notificationId string, notification *launch_helper.Notification) (uint32, error) {
207+ if notification == nil || notification.Card == nil || !notification.Card.Popup || notification.Card.Summary == "" {
208+ raw.log.Debugf("skipping notification: nil, or nil card, or not popup, or no summary: %#v", notification)
209+ return 0, nil
210+ }
211+
212+ card := notification.Card
213+
214+ app_icon := c_helper.AppIconFromId(appId)
215+ hints := make(map[string]*dbus.Variant)
216+ hints["x-canonical-secondary-icon"] = &dbus.Variant{app_icon}
217+
218+ actions := make([]string, 2*len(card.Actions))
219+ for i, action := range card.Actions {
220+ actions[2*i] = fmt.Sprintf("%s::%s::%d", appId, notificationId, i)
221+ actions[2*i+1] = action
222+ }
223+ switch len(actions) {
224+ case 2:
225+ hints["x-canonical-switch-to-application"] = &dbus.Variant{true}
226+ case 4:
227+ hints["x-canonical-snap-decisions"] = &dbus.Variant{true}
228+ }
229+ return raw.Notify(appId, 0, card.Icon, card.Summary, card.Body, actions, hints, 30*1000)
230+}
231
232=== modified file 'bus/notifications/raw_test.go'
233--- bus/notifications/raw_test.go 2014-05-20 10:50:04 +0000
234+++ bus/notifications/raw_test.go 2014-07-02 13:12:36 +0000
235@@ -22,6 +22,7 @@
236 import (
237 . "launchpad.net/gocheck"
238 testibus "launchpad.net/ubuntu-push/bus/testing"
239+ "launchpad.net/ubuntu-push/launch_helper"
240 "launchpad.net/ubuntu-push/logger"
241 helpers "launchpad.net/ubuntu-push/testing"
242 "launchpad.net/ubuntu-push/testing/condition"
243@@ -95,3 +96,45 @@
244 _, err := raw.WatchActions()
245 c.Check(err, NotNil)
246 }
247+
248+func (s *RawSuite) TestPresentNotifies(c *C) {
249+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
250+ raw := Raw(endp, s.log)
251+ nid, err := raw.Present("firefox.desktop", "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary", Popup: true}})
252+ c.Check(err, IsNil)
253+ c.Check(nid, Equals, uint32(1))
254+}
255+
256+func (s *RawSuite) TestPresentNoNotificationDoesNotNotify(c *C) {
257+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
258+ raw := Raw(endp, s.log)
259+ nid, err := raw.Present("firefox.desktop", "notifId", nil)
260+ c.Check(err, IsNil)
261+ c.Check(nid, Equals, uint32(0))
262+}
263+
264+func (s *RawSuite) TestPresentNoCardDoesNotNotify(c *C) {
265+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
266+ raw := Raw(endp, s.log)
267+ nid, err := raw.Present("firefox.desktop", "notifId", &launch_helper.Notification{})
268+ c.Check(err, IsNil)
269+ c.Check(nid, Equals, uint32(0))
270+}
271+
272+func (s *RawSuite) TestPresentNoSummaryDoesNotNotify(c *C) {
273+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
274+ raw := Raw(endp, s.log)
275+ nid, err := raw.Present("firefox.desktop", "notifId", &launch_helper.Notification{Card: &launch_helper.Card{}})
276+ c.Check(err, IsNil)
277+ c.Check(nid, Equals, uint32(0))
278+}
279+
280+func (s *RawSuite) TestPresentNoPopupNoNotify(c *C) {
281+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
282+ raw := Raw(endp, s.log)
283+ nid, err := raw.Present("firefox.desktop", "notifId", &launch_helper.Notification{Card: &launch_helper.Card{Summary: "summary"}})
284+ c.Check(err, IsNil)
285+ c.Check(nid, Equals, uint32(0))
286+}
287+
288+// XXX Missing test about ShowCard manipulating Actions and hints correctly.
289
290=== modified file 'bus/testing/testing_endpoint.go'
291--- bus/testing/testing_endpoint.go 2014-05-15 12:27:32 +0000
292+++ bus/testing/testing_endpoint.go 2014-07-02 13:12:36 +0000
293@@ -187,7 +187,7 @@
294 return nil
295 }
296
297-func (tc *testingEndpoint) WatchMethod(dispatch bus.DispatchMap, extra ...interface{}) {
298+func (tc *testingEndpoint) WatchMethod(dispatch bus.DispatchMap, suffix string, extra ...interface{}) {
299 tc.callArgsLck.Lock()
300 defer tc.callArgsLck.Unlock()
301
302@@ -196,12 +196,12 @@
303 tc.callArgs = append(tc.callArgs, args)
304 }
305
306-func (tc *testingEndpoint) Signal(member string, args []interface{}) error {
307+func (tc *testingEndpoint) Signal(member string, suffix string, args []interface{}) error {
308 tc.callArgsLck.Lock()
309 defer tc.callArgsLck.Unlock()
310
311 callargs := callArgs{Member: "::Signal"}
312- callargs.Args = append(callargs.Args, member, args)
313+ callargs.Args = append(callargs.Args, member, suffix, args)
314 tc.callArgs = append(tc.callArgs, callargs)
315
316 return nil
317
318=== modified file 'bus/testing/testing_endpoint_test.go'
319--- bus/testing/testing_endpoint_test.go 2014-06-02 10:04:34 +0000
320+++ bus/testing/testing_endpoint_test.go 2014-07-02 13:12:36 +0000
321@@ -214,24 +214,24 @@
322 // Test that Signal updates callArgs
323 func (s *TestingEndpointSuite) TestSignalUpdatesCallArgs(c *C) {
324 endp := NewTestingEndpoint(nil, condition.Work(true))
325- endp.Signal("hello", []interface{}{"world"})
326- endp.Signal("hello", []interface{}{"there"})
327+ endp.Signal("hello", "", []interface{}{"world"})
328+ endp.Signal("hello", "/potato", []interface{}{"there"})
329 c.Check(GetCallArgs(endp), DeepEquals, []callArgs{
330 {
331 Member: "::Signal",
332- Args: []interface{}{"hello", []interface{}{"world"}},
333+ Args: []interface{}{"hello", "", []interface{}{"world"}},
334 }, {
335 Member: "::Signal",
336- Args: []interface{}{"hello", []interface{}{"there"}},
337+ Args: []interface{}{"hello", "/potato", []interface{}{"there"}},
338 }})
339 }
340
341 // Test that WatchMethod updates callArgs
342 func (s *TestingEndpointSuite) TestWatchMethodUpdatesCallArgs(c *C) {
343 endp := NewTestingEndpoint(nil, condition.Work(true))
344- foo := func([]interface{}, []interface{}) ([]interface{}, error) { return nil, nil }
345+ foo := func(string, []interface{}, []interface{}) ([]interface{}, error) { return nil, nil }
346 foomp := bus.DispatchMap{"foo": foo}
347- endp.WatchMethod(foomp)
348+ endp.WatchMethod(foomp, "/*")
349 c.Check(GetCallArgs(endp), DeepEquals, []callArgs{
350 {
351 Member: "::WatchMethod",
352
353=== modified file 'client/client.go'
354--- client/client.go 2014-05-21 10:03:18 +0000
355+++ client/client.go 2014-07-02 13:12:36 +0000
356@@ -22,16 +22,14 @@
357 "crypto/sha256"
358 "encoding/base64"
359 "encoding/hex"
360- "encoding/json"
361 "encoding/pem"
362 "errors"
363 "fmt"
364 "io/ioutil"
365 "os"
366+ "os/exec"
367 "strings"
368
369- "launchpad.net/go-dbus/v1"
370-
371 "launchpad.net/ubuntu-push/bus"
372 "launchpad.net/ubuntu-push/bus/connectivity"
373 "launchpad.net/ubuntu-push/bus/networkmanager"
374@@ -63,40 +61,38 @@
375 // The PEM-encoded server certificate
376 CertPEMFile string `json:"cert_pem_file"`
377 // How to invoke the auth helper
378- AuthHelper []string `json:"auth_helper"`
379+ AuthHelper string `json:"auth_helper"`
380+ SessionURL string `json:"session_url"`
381+ RegistrationURL string `json:"registration_url"`
382 // The logging level (one of "debug", "info", "error")
383 LogLevel logger.ConfigLogLevel `json:"log_level"`
384 }
385
386 // PushClient is the Ubuntu Push Notifications client-side daemon.
387 type PushClient struct {
388- leveldbPath string
389- configPath string
390- config ClientConfig
391- log logger.Logger
392- pem []byte
393- idder identifier.Id
394- deviceId string
395- notificationsEndp bus.Endpoint
396- urlDispatcherEndp bus.Endpoint
397- connectivityEndp bus.Endpoint
398- systemImageEndp bus.Endpoint
399- systemImageInfo *systemimage.InfoResult
400- connCh chan bool
401- hasConnectivity bool
402- actionsCh <-chan notifications.RawActionReply
403- session *session.ClientSession
404- sessionConnectedCh chan uint32
405- serviceEndpoint bus.Endpoint
406- service *service.Service
407+ leveldbPath string
408+ configPath string
409+ config ClientConfig
410+ log logger.Logger
411+ pem []byte
412+ idder identifier.Id
413+ deviceId string
414+ notificationsEndp bus.Endpoint
415+ urlDispatcherEndp bus.Endpoint
416+ connectivityEndp bus.Endpoint
417+ systemImageEndp bus.Endpoint
418+ systemImageInfo *systemimage.InfoResult
419+ connCh chan bool
420+ hasConnectivity bool
421+ actionsCh <-chan notifications.RawActionReply
422+ session *session.ClientSession
423+ sessionConnectedCh chan uint32
424+ pushServiceEndpoint bus.Endpoint
425+ pushService *service.PushService
426+ postalServiceEndpoint bus.Endpoint
427+ postalService *service.PostalService
428 }
429
430-var (
431- system_update_url = "settings:///system/system-update"
432- ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::"
433- ACTION_ID_BROADCAST = ACTION_ID_SNOWFLAKE + system_update_url
434-)
435-
436 // Creates a new Ubuntu Push Notifications client-side daemon that will use
437 // the given configuration file.
438 func NewPushClient(configPath string, leveldbPath string) *PushClient {
439@@ -128,10 +124,12 @@
440
441 // overridden for testing
442 client.idder = identifier.New()
443- client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log)
444 client.urlDispatcherEndp = bus.SessionBus.Endpoint(urldispatcher.BusAddress, client.log)
445 client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log)
446 client.systemImageEndp = bus.SystemBus.Endpoint(systemimage.BusAddress, client.log)
447+ if client.notificationsEndp == nil {
448+ client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log)
449+ }
450
451 client.connCh = make(chan bool, 1)
452 client.sessionConnectedCh = make(chan uint32, 1)
453@@ -160,7 +158,27 @@
454 ExpectAllRepairedTime: client.config.ExpectAllRepairedTime.TimeDuration(),
455 PEM: client.pem,
456 Info: info,
457- AuthHelper: client.config.AuthHelper,
458+ AuthGetter: client.getAuthorization,
459+ AuthURL: client.config.SessionURL,
460+ }
461+}
462+
463+// getAuthorization gets the authorization blob to send to the server
464+func (client *PushClient) getAuthorization(url string) string {
465+ client.log.Debugf("getting authorization for %s", url)
466+ // using a helper, for now at least
467+ if len(client.config.AuthHelper) == 0 {
468+ // do nothing if helper is unset or empty
469+ return ""
470+ }
471+
472+ auth, err := exec.Command(client.config.AuthHelper, url).Output()
473+ if err != nil {
474+ // For now we just log the error, as we don't want to block unauthorized users
475+ client.log.Errorf("unable to get the authorization token from the account: %v", err)
476+ return ""
477+ } else {
478+ return strings.TrimSpace(string(auth))
479 }
480 }
481
482@@ -185,12 +203,10 @@
483 go connectivity.ConnectedState(client.connectivityEndp,
484 client.config.ConnectivityConfig, client.log, client.connCh)
485 iniCh := make(chan uint32)
486- go func() { iniCh <- util.NewAutoRedialer(client.notificationsEndp).Redial() }()
487 go func() { iniCh <- util.NewAutoRedialer(client.urlDispatcherEndp).Redial() }()
488 go func() { iniCh <- util.NewAutoRedialer(client.systemImageEndp).Redial() }()
489 <-iniCh
490 <-iniCh
491- <-iniCh
492
493 sysimg := systemimage.New(client.systemImageEndp, client.log)
494 info, err := sysimg.Info()
495@@ -198,8 +214,11 @@
496 return err
497 }
498 client.systemImageInfo = info
499+ return err
500+}
501
502- actionsCh, err := notifications.Raw(client.notificationsEndp, client.log).WatchActions()
503+func (client *PushClient) takePostalServiceBus() error {
504+ actionsCh, err := client.postalService.TakeTheBus()
505 client.actionsCh = actionsCh
506 return err
507 }
508@@ -296,28 +315,12 @@
509 return false
510 }
511
512-func (client *PushClient) sendNotification(action_id, icon, summary, body string) (uint32, error) {
513- a := []string{action_id, "Switch to app"} // action value not visible on the phone
514- h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}
515- nots := notifications.Raw(client.notificationsEndp, client.log)
516- return nots.Notify(
517- "ubuntu-push-client", // app name
518- uint32(0), // id
519- icon, // icon
520- summary, // summary
521- body, // body
522- a, // actions
523- h, // hints
524- int32(10*1000), // timeout (ms)
525- )
526-}
527-
528 // handleBroadcastNotification deals with receiving a broadcast notification
529 func (client *PushClient) handleBroadcastNotification(msg *session.BroadcastNotification) error {
530 if !client.filterBroadcastNotification(msg) {
531 return nil
532 }
533- not_id, err := client.sendNotification(ACTION_ID_BROADCAST,
534+ not_id, err := client.postalService.SendNotification(service.ACTION_ID_BROADCAST,
535 "update_manager_icon", "There's an updated system image.",
536 "Tap to open the system updater.")
537 if err != nil {
538@@ -331,7 +334,7 @@
539 // handleUnicastNotification deals with receiving a unicast notification
540 func (client *PushClient) handleUnicastNotification(msg *protocol.Notification) error {
541 client.log.Debugf("sending notification %#v for %#v.", msg.MsgId, msg.AppId)
542- return client.service.Inject(msg.AppId, string(msg.Payload))
543+ return client.postalService.Inject(msg.AppId, msg.MsgId, string(msg.Payload))
544 }
545
546 // handleClick deals with the user clicking a notification
547@@ -342,7 +345,7 @@
548 //
549 // From ACM's SIGPLAN publication, (September, 1982), Article
550 // "Epigrams in Programming", by Alan J. Perlis of Yale University.
551- url := strings.TrimPrefix(action_id, ACTION_ID_SNOWFLAKE)
552+ url := strings.TrimPrefix(action_id, service.ACTION_ID_SNOWFLAKE)
553 if len(url) == len(action_id) || len(url) == 0 {
554 // it didn't start with the prefix
555 return nil
556@@ -392,52 +395,49 @@
557 client.handleErr)
558 }
559
560-// these are the currently supported fields of a unicast message
561-type UnicastMessage struct {
562- Icon string `json:"icon"`
563- Body string `json:"body"`
564- Summary string `json:"summary"`
565- URL string `json:"url"`
566- Blob json.RawMessage `json:"blob"`
567-}
568-
569-func (client *PushClient) messageHandler(message []byte) error {
570- var umsg = new(UnicastMessage)
571- err := json.Unmarshal(message, &umsg)
572- if err != nil {
573- client.log.Errorf("unable to unmarshal message: %v", err)
574- return err
575- }
576-
577- not_id, err := client.sendNotification(
578- ACTION_ID_SNOWFLAKE+umsg.URL,
579- umsg.Icon, umsg.Summary, umsg.Body)
580-
581- if err != nil {
582- client.log.Errorf("showing notification: %s", err)
583- return err
584- }
585- client.log.Debugf("got notification id %d", not_id)
586- return nil
587-}
588-
589 func (client *PushClient) startService() error {
590- if client.serviceEndpoint == nil {
591- client.serviceEndpoint = bus.SessionBus.Endpoint(service.BusAddress, client.log)
592- }
593-
594- client.service = service.NewService(client.serviceEndpoint, client.log)
595- client.service.SetMessageHandler(client.messageHandler)
596- return client.service.Start()
597+ if client.pushServiceEndpoint == nil {
598+ client.pushServiceEndpoint = bus.SessionBus.Endpoint(service.PushServiceBusAddress, client.log)
599+ }
600+ if client.postalServiceEndpoint == nil {
601+ client.postalServiceEndpoint = bus.SessionBus.Endpoint(service.PostalServiceBusAddress, client.log)
602+ }
603+
604+ client.pushService = service.NewPushService(client.pushServiceEndpoint, client.log)
605+ client.pushService.SetRegistrationURL(client.config.RegistrationURL)
606+ client.pushService.SetAuthGetter(client.getAuthorization)
607+ client.pushService.SetDeviceId(client.deviceId)
608+ if err := client.pushService.Start(); err != nil {
609+ return err
610+ }
611+ return nil
612+}
613+
614+func (client *PushClient) setupPostalService() error {
615+ if client.notificationsEndp == nil {
616+ client.notificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, client.log)
617+ }
618+ client.postalService = service.NewPostalService(client.postalServiceEndpoint, client.notificationsEndp, client.log)
619+ return nil
620+}
621+
622+func (client *PushClient) startPostalService() error {
623+ if err := client.postalService.Start(); err != nil {
624+ return err
625+ }
626+ return nil
627 }
628
629 // Start calls doStart with the "real" starters
630 func (client *PushClient) Start() error {
631 return client.doStart(
632 client.configure,
633+ client.getDeviceId,
634 client.startService,
635- client.getDeviceId,
636+ client.setupPostalService,
637+ client.startPostalService,
638 client.takeTheBus,
639+ client.takePostalServiceBus,
640 client.initSession,
641 )
642 }
643
644=== modified file 'client/client_test.go'
645--- client/client_test.go 2014-05-20 13:56:37 +0000
646+++ client/client_test.go 2014-07-02 13:12:36 +0000
647@@ -37,6 +37,7 @@
648 "launchpad.net/ubuntu-push/bus/notifications"
649 "launchpad.net/ubuntu-push/bus/systemimage"
650 testibus "launchpad.net/ubuntu-push/bus/testing"
651+ "launchpad.net/ubuntu-push/client/service"
652 "launchpad.net/ubuntu-push/client/session"
653 "launchpad.net/ubuntu-push/client/session/seenstate"
654 "launchpad.net/ubuntu-push/config"
655@@ -103,11 +104,13 @@
656 "stabilizing_timeout": "0ms",
657 "connectivity_check_url": "",
658 "connectivity_check_md5": "",
659- "addr": ":0",
660- "cert_pem_file": pem_file,
661- "recheck_timeout": "3h",
662- "auth_helper": []string{},
663- "log_level": "debug",
664+ "addr": ":0",
665+ "cert_pem_file": pem_file,
666+ "recheck_timeout": "3h",
667+ "auth_helper": "",
668+ "session_url": "xyzzy://",
669+ "registration_url": "reg://",
670+ "log_level": "debug",
671 }
672 for k, v := range overrides {
673 cfgMap[k] = v
674@@ -257,7 +260,7 @@
675
676 func (cs *clientSuite) TestDeriveSessionConfig(c *C) {
677 cs.writeTestConfig(map[string]interface{}{
678- "auth_helper": []string{"auth", "helper"},
679+ "auth_helper": "auth helper",
680 })
681 info := map[string]interface{}{
682 "foo": 1,
683@@ -272,7 +275,8 @@
684 ExpectAllRepairedTime: 30 * time.Minute,
685 PEM: cli.pem,
686 Info: info,
687- AuthHelper: []string{"auth", "helper"},
688+ AuthGetter: func(string) string { return "" },
689+ AuthURL: "xyzzy://",
690 }
691 // sanity check that we are looking at all fields
692 vExpected := reflect.ValueOf(expected)
693@@ -284,6 +288,11 @@
694 }
695 // finally compare
696 conf := cli.deriveSessionConfig(info)
697+ // compare authGetter by string
698+ c.Check(fmt.Sprintf("%v", conf.AuthGetter), Equals, fmt.Sprintf("%v", cli.getAuthorization))
699+ // and set it to nil
700+ conf.AuthGetter = nil
701+ expected.AuthGetter = nil
702 c.Check(conf, DeepEquals, expected)
703 }
704
705@@ -292,28 +301,54 @@
706 ******************************************************************/
707
708 func (cs *clientSuite) TestStartServiceWorks(c *C) {
709+ cs.writeTestConfig(map[string]interface{}{
710+ "auth_helper": helpers.ScriptAbsPath("dummyauth.sh"),
711+ })
712 cli := NewPushClient(cs.configPath, cs.leveldbPath)
713+ cli.configure()
714 cli.log = cs.log
715- cli.serviceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil)
716- c.Check(cli.service, IsNil)
717+ cli.deviceId = "fake-id"
718+ cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil)
719+ cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil)
720+ c.Check(cli.pushService, IsNil)
721 c.Check(cli.startService(), IsNil)
722- c.Assert(cli.service, NotNil)
723- c.Check(cli.service.IsRunning(), Equals, true)
724- c.Check(cli.service.GetMessageHandler(), NotNil)
725- cli.service.Stop()
726+ c.Assert(cli.pushService, NotNil)
727+ c.Check(cli.pushService.IsRunning(), Equals, true)
728+ c.Check(cli.pushService.GetDeviceId(), Equals, "fake-id")
729+ c.Check(cli.pushService.GetRegistrationAuthorization(), Equals, "hello reg://")
730+ c.Assert(cli.setupPostalService(), IsNil)
731+ c.Assert(cli.startPostalService(), IsNil)
732+ c.Check(cli.postalService.IsRunning(), Equals, true)
733+ cli.pushService.Stop()
734+ cli.postalService.Stop()
735 }
736
737 func (cs *clientSuite) TestStartServiceErrorsOnNilLog(c *C) {
738 cli := NewPushClient(cs.configPath, cs.leveldbPath)
739 c.Check(cli.log, IsNil)
740 c.Check(cli.startService(), NotNil)
741+ c.Assert(cli.setupPostalService(), IsNil)
742+ c.Assert(cli.startPostalService(), NotNil)
743 }
744
745-func (cs *clientSuite) TestStartServiceErrorsOnBusDialFail(c *C) {
746+func (cs *clientSuite) TestStartServiceErrorsOnBusDialPushFail(c *C) {
747 cli := NewPushClient(cs.configPath, cs.leveldbPath)
748 cli.log = cs.log
749- cli.serviceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil)
750+ cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil)
751+ cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil)
752 c.Check(cli.startService(), NotNil)
753+ c.Assert(cli.setupPostalService(), IsNil)
754+ c.Assert(cli.startPostalService(), NotNil)
755+}
756+
757+func (cs *clientSuite) TestStartServiceErrorsOnBusDialPostalFail(c *C) {
758+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
759+ cli.log = cs.log
760+ cli.pushServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), nil)
761+ cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(false), nil)
762+ c.Check(cli.startService(), IsNil)
763+ c.Assert(cli.setupPostalService(), IsNil)
764+ c.Assert(cli.startPostalService(), NotNil)
765 }
766
767 /*****************************************************************
768@@ -386,6 +421,8 @@
769 cli.systemImageEndp = siEndp
770
771 c.Assert(cli.takeTheBus(), IsNil)
772+ c.Assert(cli.setupPostalService(), IsNil)
773+ c.Assert(cli.takePostalServiceBus(), IsNil)
774 // the notifications and urldispatcher endpoints retried until connected
775 c.Check(nCond.OK(), Equals, true)
776 c.Check(uCond.OK(), Equals, true)
777@@ -415,6 +452,8 @@
778 cli.systemImageEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))
779
780 c.Check(cli.takeTheBus(), NotNil)
781+ c.Assert(cli.setupPostalService(), IsNil)
782+ c.Assert(cli.takePostalServiceBus(), NotNil)
783 c.Check(cli.actionsCh, IsNil)
784 }
785
786@@ -608,6 +647,8 @@
787 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
788 cli.notificationsEndp = endp
789 cli.log = cs.log
790+ cli.postalServiceEndpoint = testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
791+ cli.setupPostalService()
792 c.Check(cli.handleBroadcastNotification(positiveBroadcastNotification), IsNil)
793 // check we sent the notification
794 args := testibus.GetCallArgs(endp)
795@@ -635,6 +676,8 @@
796 cli.log = cs.log
797 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
798 cli.notificationsEndp = endp
799+ cli.postalServiceEndpoint = testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
800+ cli.setupPostalService()
801 c.Check(cli.handleBroadcastNotification(positiveBroadcastNotification), NotNil)
802 }
803
804@@ -642,19 +685,24 @@
805 handleUnicastNotification tests
806 ******************************************************************/
807
808-var notif = &protocol.Notification{AppId: "hello", Payload: []byte(`{"url": "xyzzy"}`), MsgId: "42"}
809+var payload = `{"message": "aGVsbG8=", "notification": {"card": {"icon": "icon-value", "summary": "summary-value", "body": "body-value", "actions": []}}}`
810+var notif = &protocol.Notification{AppId: "hello", Payload: []byte(payload), MsgId: "42"}
811
812 func (cs *clientSuite) TestHandleUcastNotification(c *C) {
813 cli := NewPushClient(cs.configPath, cs.leveldbPath)
814 svcEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
815+ postEndp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
816 cli.log = cs.log
817- cli.serviceEndpoint = svcEndp
818+ cli.pushServiceEndpoint = svcEndp
819+ cli.postalServiceEndpoint = postEndp
820 notsEndp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
821 cli.notificationsEndp = notsEndp
822 c.Assert(cli.startService(), IsNil)
823+ c.Assert(cli.setupPostalService(), IsNil)
824+ c.Assert(cli.startPostalService(), IsNil)
825 c.Check(cli.handleUnicastNotification(notif), IsNil)
826 // check we sent the notification
827- args := testibus.GetCallArgs(svcEndp)
828+ args := testibus.GetCallArgs(postEndp)
829 c.Assert(len(args), Not(Equals), 0)
830 c.Check(args[len(args)-1].Member, Equals, "::Signal")
831 c.Check(cs.log.Captured(), Matches, `(?m).*sending notification "42" for "hello".*`)
832@@ -675,14 +723,14 @@
833 args := testibus.GetCallArgs(endp)
834 c.Assert(args, HasLen, 0)
835 // check we worked with the right action id
836- c.Check(cli.handleClick(ACTION_ID_BROADCAST), IsNil)
837+ c.Check(cli.handleClick(service.ACTION_ID_BROADCAST), IsNil)
838 // check we sent the notification
839 args = testibus.GetCallArgs(endp)
840 c.Assert(args, HasLen, 1)
841 c.Check(args[0].Member, Equals, "DispatchURL")
842- c.Check(args[0].Args, DeepEquals, []interface{}{system_update_url})
843+ c.Check(args[0].Args, DeepEquals, []interface{}{service.SystemUpdateUrl})
844 // check we worked with the right action id
845- c.Check(cli.handleClick(ACTION_ID_SNOWFLAKE+"foo"), IsNil)
846+ c.Check(cli.handleClick(service.ACTION_ID_SNOWFLAKE+"foo"), IsNil)
847 // check we sent the notification
848 args = testibus.GetCallArgs(endp)
849 c.Assert(args, HasLen, 2)
850@@ -810,6 +858,8 @@
851 cli.connectivityEndp = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true),
852 uint32(networkmanager.ConnectedGlobal))
853 cli.systemImageInfo = siInfoRes
854+ cli.postalServiceEndpoint = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
855+ cli.setupPostalService()
856 c.Assert(cli.initSession(), IsNil)
857
858 cli.session.BroadcastCh = make(chan *session.BroadcastNotification)
859@@ -829,7 +879,7 @@
860 c.Check(cs.log.Captured(), Matches, "(?ms).*Session connected after 42 attempts$")
861
862 // * actionsCh to the click handler/url dispatcher
863- aCh <- notifications.RawActionReply{ActionId: ACTION_ID_BROADCAST}
864+ aCh <- notifications.RawActionReply{ActionId: service.ACTION_ID_BROADCAST}
865 tick()
866 uargs := testibus.GetCallArgs(cli.urlDispatcherEndp)
867 c.Assert(uargs, HasLen, 1)
868@@ -879,7 +929,7 @@
869 cli := NewPushClient(cs.configPath, cs.leveldbPath)
870 // before start, everything sucks:
871 // no service,
872- c.Check(cli.service, IsNil)
873+ c.Check(cli.pushService, IsNil)
874 // no config,
875 c.Check(string(cli.config.Addr), Equals, "")
876 // no device id,
877@@ -904,9 +954,10 @@
878 // and a bus,
879 c.Check(cli.notificationsEndp, NotNil)
880 // and a service,
881- c.Check(cli.service, NotNil)
882+ c.Check(cli.pushService, NotNil)
883 // and everthying us just peachy!
884- cli.service.Stop() // cleanup
885+ cli.pushService.Stop() // cleanup
886+ cli.postalService.Stop() // cleanup
887 }
888
889 func (cs *clientSuite) TestStartCanFail(c *C) {
890@@ -917,37 +968,41 @@
891 c.Check(err, NotNil)
892 }
893
894-func (cs *clientSuite) TestMessageHandler(c *C) {
895- cli := NewPushClient(cs.configPath, cs.leveldbPath)
896- endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
897- cli.notificationsEndp = endp
898- cli.log = cs.log
899- err := cli.messageHandler([]byte(`{"icon": "icon-value", "summary": "summary-value", "body": "body-value"}`))
900- c.Assert(err, IsNil)
901- args := testibus.GetCallArgs(endp)
902- c.Assert(args, HasLen, 1)
903- c.Check(args[0].Member, Equals, "Notify")
904- c.Check(args[0].Args[0], Equals, "ubuntu-push-client")
905- c.Check(args[0].Args[2], Equals, "icon-value")
906- c.Check(args[0].Args[3], Equals, "summary-value")
907- c.Check(args[0].Args[4], Equals, "body-value")
908-}
909-
910-func (cs *clientSuite) TestMessageHandlerReportsUnmarshalErrors(c *C) {
911- cli := NewPushClient(cs.configPath, cs.leveldbPath)
912- cli.log = cs.log
913-
914- err := cli.messageHandler([]byte(`{"broken`))
915- c.Check(err, NotNil)
916- c.Check(cs.log.Captured(), Matches, "(?msi).*unable to unmarshal message:.*")
917-}
918-
919-func (cs *clientSuite) TestMessageHandlerReportsFailedNotifies(c *C) {
920- cli := NewPushClient(cs.configPath, cs.leveldbPath)
921- endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
922- cli.notificationsEndp = endp
923- cli.log = cs.log
924- err := cli.messageHandler([]byte(`{}`))
925- c.Assert(err, NotNil)
926- c.Check(cs.log.Captured(), Matches, "(?msi).*showing notification: no way$")
927+func (cs *clientSuite) TestInitSessionErr(c *C) {
928+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
929+ cli.log = cs.log
930+ cli.systemImageInfo = siInfoRes
931+ // change the cli.pem value so initSession fails
932+ cli.pem = []byte("foo")
933+ c.Assert(cli.initSession(), NotNil)
934+}
935+
936+/*****************************************************************
937+ getAuthorization() tests
938+******************************************************************/
939+
940+func (cs *clientSuite) TestGetAuthorizationIgnoresErrors(c *C) {
941+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
942+ cli.configure()
943+ cli.config.AuthHelper = "/no/such/executable"
944+
945+ c.Check(cli.getAuthorization("xyzzy://"), Equals, "")
946+}
947+
948+func (cs *clientSuite) TestGetAuthorizationGetsIt(c *C) {
949+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
950+ cli.configure()
951+ cli.config.AuthHelper = helpers.ScriptAbsPath("dummyauth.sh")
952+
953+ c.Check(cli.getAuthorization("xyzzy://"), Equals, "hello xyzzy://")
954+}
955+
956+func (cs *clientSuite) TestGetAuthorizationWorksIfUnsetOrNil(c *C) {
957+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
958+ cli.log = cs.log
959+
960+ c.Assert(cli.config, NotNil)
961+ c.Check(cli.getAuthorization("xyzzy://"), Equals, "")
962+ cli.configure()
963+ c.Check(cli.getAuthorization("xyzzy://"), Equals, "")
964 }
965
966=== modified file 'client/gethosts/gethost_test.go'
967--- client/gethosts/gethost_test.go 2014-05-02 10:42:16 +0000
968+++ client/gethosts/gethost_test.go 2014-07-02 13:12:36 +0000
969@@ -59,7 +59,7 @@
970 res, err := gh.Get()
971 c.Assert(err, IsNil)
972 c.Check(*res, DeepEquals,
973- Host{Domain: "example.com", Hosts: []string{"http://c1130408a700afe0"}})
974+ Host{Domain: "example.com", Hosts: []string{"http://bdd2ae7116c85a45"}})
975 }
976
977 func (s *getHostsSuite) TestGetTimeout(c *C) {
978
979=== added file 'client/service/common.go'
980--- client/service/common.go 1970-01-01 00:00:00 +0000
981+++ client/service/common.go 2014-07-02 13:12:36 +0000
982@@ -0,0 +1,99 @@
983+/*
984+ Copyright 2014 Canonical Ltd.
985+
986+ This program is free software: you can redistribute it and/or modify it
987+ under the terms of the GNU General Public License version 3, as published
988+ by the Free Software Foundation.
989+
990+ This program is distributed in the hope that it will be useful, but
991+ WITHOUT ANY WARRANTY; without even the implied warranties of
992+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
993+ PURPOSE. See the GNU General Public License for more details.
994+
995+ You should have received a copy of the GNU General Public License along
996+ with this program. If not, see <http://www.gnu.org/licenses/>.
997+*/
998+
999+// package service implements the dbus-level service with which client
1000+// applications are expected to interact.
1001+package service
1002+
1003+import (
1004+ "errors"
1005+ "sync"
1006+
1007+ "launchpad.net/ubuntu-push/bus"
1008+ "launchpad.net/ubuntu-push/logger"
1009+)
1010+
1011+type DBusService struct {
1012+ lock sync.RWMutex
1013+ state ServiceState
1014+ Log logger.Logger
1015+ Bus bus.Endpoint
1016+}
1017+
1018+// the service can be in a numnber of states
1019+type ServiceState uint8
1020+
1021+const (
1022+ StateUnknown ServiceState = iota
1023+ StateRunning // Start() has been successfully called
1024+ StateFinished // Stop() has been successfully called
1025+)
1026+
1027+var (
1028+ NotConfigured = errors.New("not configured")
1029+ AlreadyStarted = errors.New("already started")
1030+ BadArgCount = errors.New("Wrong number of arguments")
1031+ BadArgType = errors.New("Bad argument type")
1032+)
1033+
1034+// IsRunning() returns whether the service's state is StateRunning
1035+func (svc *DBusService) IsRunning() bool {
1036+ svc.lock.RLock()
1037+ defer svc.lock.RUnlock()
1038+ return svc.state == StateRunning
1039+}
1040+
1041+// Start() dials the bus, grab the name, and listens for method calls.
1042+func (svc *DBusService) Start(dispatchMap bus.DispatchMap, busAddr bus.Address) error {
1043+ svc.lock.Lock()
1044+ defer svc.lock.Unlock()
1045+ if svc.state != StateUnknown {
1046+ return AlreadyStarted
1047+ }
1048+ if svc.Log == nil || svc.Bus == nil {
1049+ return NotConfigured
1050+ }
1051+ err := svc.Bus.Dial()
1052+ if err != nil {
1053+ return err
1054+ }
1055+ ch := svc.Bus.GrabName(true)
1056+ log := svc.Log
1057+ go func() {
1058+ for err := range ch {
1059+ if !svc.IsRunning() {
1060+ break
1061+ }
1062+ if err != nil {
1063+ log.Fatalf("name channel for %s got: %v",
1064+ busAddr.Name, err)
1065+ }
1066+ }
1067+ }()
1068+ svc.Bus.WatchMethod(dispatchMap, "/*", svc)
1069+ svc.state = StateRunning
1070+ return nil
1071+}
1072+
1073+// Stop() closes the bus and sets the state to StateFinished
1074+func (svc *DBusService) Stop() {
1075+ svc.lock.Lock()
1076+ defer svc.lock.Unlock()
1077+ if svc.Bus != nil {
1078+ svc.Bus.Close()
1079+ }
1080+ svc.state = StateFinished
1081+}
1082
1083=== added file 'client/service/postal.go'
1084--- client/service/postal.go 1970-01-01 00:00:00 +0000
1085+++ client/service/postal.go 2014-07-02 13:12:36 +0000
1086@@ -0,0 +1,181 @@
1087+/*
1088+ Copyright 2013-2014 Canonical Ltd.
1089+
1090+ This program is free software: you can redistribute it and/or modify it
1091+ under the terms of the GNU General Public License version 3, as published
1092+ by the Free Software Foundation.
1093+
1094+ This program is distributed in the hope that it will be useful, but
1095+ WITHOUT ANY WARRANTY; without even the implied warranties of
1096+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1097+ PURPOSE. See the GNU General Public License for more details.
1098+
1099+ You should have received a copy of the GNU General Public License along
1100+ with this program. If not, see <http://www.gnu.org/licenses/>.
1101+*/
1102+
1103+package service
1104+
1105+import (
1106+ "strings"
1107+
1108+ "code.google.com/p/go-uuid/uuid"
1109+ "launchpad.net/go-dbus/v1"
1110+
1111+ "launchpad.net/ubuntu-push/bus"
1112+ "launchpad.net/ubuntu-push/bus/notifications"
1113+ "launchpad.net/ubuntu-push/launch_helper"
1114+ "launchpad.net/ubuntu-push/logger"
1115+ "launchpad.net/ubuntu-push/messaging"
1116+ "launchpad.net/ubuntu-push/nih"
1117+ "launchpad.net/ubuntu-push/util"
1118+)
1119+
1120+// PostalService is the dbus api
1121+type PostalService struct {
1122+ DBusService
1123+ mbox map[string][]string
1124+ msgHandler func(string, string, *launch_helper.HelperOutput) error
1125+ HelperLauncher launch_helper.HelperLauncher
1126+ messagingMenu *messaging.MessagingMenu
1127+ notificationsEndp bus.Endpoint
1128+}
1129+
1130+var (
1131+ PostalServiceBusAddress = bus.Address{
1132+ Interface: "com.ubuntu.Postal",
1133+ Path: "/com/ubuntu/Postal",
1134+ Name: "com.ubuntu.Postal",
1135+ }
1136+)
1137+
1138+var (
1139+ SystemUpdateUrl = "settings:///system/system-update"
1140+ ACTION_ID_SNOWFLAKE = "::ubuntu-push-client::"
1141+ ACTION_ID_BROADCAST = ACTION_ID_SNOWFLAKE + SystemUpdateUrl
1142+)
1143+
1144+// NewPostalService() builds a new service and returns it.
1145+func NewPostalService(busEndp bus.Endpoint, notificationsEndp bus.Endpoint, log logger.Logger) *PostalService {
1146+ var svc = &PostalService{}
1147+ svc.Log = log
1148+ svc.Bus = busEndp
1149+ svc.messagingMenu = messaging.New(log)
1150+ svc.HelperLauncher = launch_helper.NewTrivialHelperLauncher(log)
1151+ svc.notificationsEndp = notificationsEndp
1152+ svc.msgHandler = svc.messageHandler
1153+ return svc
1154+}
1155+
1156+// SetMessageHandler() sets the message-handling callback
1157+func (svc *PostalService) SetMessageHandler(callback func(string, string, *launch_helper.HelperOutput) error) {
1158+ svc.lock.RLock()
1159+ defer svc.lock.RUnlock()
1160+ svc.msgHandler = callback
1161+}
1162+
1163+// GetMessageHandler() returns the (possibly nil) messaging handler callback
1164+func (svc *PostalService) GetMessageHandler() func(string, string, *launch_helper.HelperOutput) error {
1165+ svc.lock.RLock()
1166+ defer svc.lock.RUnlock()
1167+ return svc.msgHandler
1168+}
1169+
1170+// Start() dials the bus, grab the name, and listens for method calls.
1171+func (svc *PostalService) Start() error {
1172+ return svc.DBusService.Start(bus.DispatchMap{
1173+ "Notifications": svc.notifications,
1174+ "Inject": svc.inject,
1175+ }, PostalServiceBusAddress)
1176+}
1177+
1178+func (svc *PostalService) TakeTheBus() (<-chan notifications.RawActionReply, error) {
1179+ iniCh := make(chan uint32)
1180+ go func() { iniCh <- util.NewAutoRedialer(svc.notificationsEndp).Redial() }()
1181+ <-iniCh
1182+ actionsCh, err := notifications.Raw(svc.notificationsEndp, svc.Log).WatchActions()
1183+ return actionsCh, err
1184+}
1185+
1186+func (svc *PostalService) notifications(path string, args, _ []interface{}) ([]interface{}, error) {
1187+ if len(args) != 0 {
1188+ return nil, BadArgCount
1189+ }
1190+ appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:])))
1191+
1192+ svc.lock.Lock()
1193+ defer svc.lock.Unlock()
1194+
1195+ if svc.mbox == nil {
1196+ return []interface{}{[]string(nil)}, nil
1197+ }
1198+ msgs := svc.mbox[appname]
1199+ delete(svc.mbox, appname)
1200+
1201+ return []interface{}{msgs}, nil
1202+}
1203+
1204+var newNid = uuid.New
1205+
1206+func (svc *PostalService) inject(path string, args, _ []interface{}) ([]interface{}, error) {
1207+ if len(args) != 1 {
1208+ return nil, BadArgCount
1209+ }
1210+ notif, ok := args[0].(string)
1211+ if !ok {
1212+ return nil, BadArgType
1213+ }
1214+ appname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:])))
1215+
1216+ nid := newNid()
1217+
1218+ return nil, svc.Inject(appname, nid, notif)
1219+}
1220+
1221+// Inject() signals to an application over dbus that a notification
1222+// has arrived.
1223+func (svc *PostalService) Inject(appname string, nid string, notif string) error {
1224+ svc.lock.Lock()
1225+ defer svc.lock.Unlock()
1226+ if svc.mbox == nil {
1227+ svc.mbox = make(map[string][]string)
1228+ }
1229+ output := svc.HelperLauncher.Run(appname, []byte(notif))
1230+ // XXX also track the nid in the mbox
1231+ svc.mbox[appname] = append(svc.mbox[appname], string(output.Message))
1232+
1233+ if svc.msgHandler != nil {
1234+ err := svc.msgHandler(appname, nid, output)
1235+ if err != nil {
1236+ svc.DBusService.Log.Errorf("msgHandler returned %v", err)
1237+ return err
1238+ }
1239+ svc.DBusService.Log.Debugf("call to msgHandler successful")
1240+ }
1241+
1242+ return svc.Bus.Signal("Notification", "/"+string(nih.Quote([]byte(appname))), []interface{}{appname})
1243+}
1244+
1245+func (svc *PostalService) messageHandler(appname string, nid string, output *launch_helper.HelperOutput) error {
1246+ svc.messagingMenu.Present(appname, nid, output.Notification)
1247+ nots := notifications.Raw(svc.notificationsEndp, svc.Log)
1248+ _, err := nots.Present(appname, nid, output.Notification)
1249+
1250+ return err
1251+}
1252+
1253+func (svc *PostalService) SendNotification(action_id, icon, summary, body string) (uint32, error) {
1254+ a := []string{action_id, "Switch to app"} // action value not visible on the phone
1255+ h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}}
1256+ nots := notifications.Raw(svc.notificationsEndp, svc.Log)
1257+ return nots.Notify(
1258+ "ubuntu-push-client", // app name
1259+ uint32(0), // id
1260+ icon, // icon
1261+ summary, // summary
1262+ body, // body
1263+ a, // actions
1264+ h, // hints
1265+ int32(10*1000), // timeout (ms)
1266+ )
1267+}
1268
1269=== added file 'client/service/postal_test.go'
1270--- client/service/postal_test.go 1970-01-01 00:00:00 +0000
1271+++ client/service/postal_test.go 2014-07-02 13:12:36 +0000
1272@@ -0,0 +1,252 @@
1273+/*
1274+ Copyright 2014 Canonical Ltd.
1275+
1276+ This program is free software: you can redistribute it and/or modify it
1277+ under the terms of the GNU General Public License version 3, as published
1278+ by the Free Software Foundation.
1279+
1280+ This program is distributed in the hope that it will be useful, but
1281+ WITHOUT ANY WARRANTY; without even the implied warranties of
1282+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1283+ PURPOSE. See the GNU General Public License for more details.
1284+
1285+ You should have received a copy of the GNU General Public License along
1286+ with this program. If not, see <http://www.gnu.org/licenses/>.
1287+*/
1288+
1289+package service
1290+
1291+import (
1292+ "errors"
1293+
1294+ . "launchpad.net/gocheck"
1295+
1296+ "launchpad.net/ubuntu-push/bus"
1297+ testibus "launchpad.net/ubuntu-push/bus/testing"
1298+ "launchpad.net/ubuntu-push/launch_helper"
1299+ helpers "launchpad.net/ubuntu-push/testing"
1300+ "launchpad.net/ubuntu-push/testing/condition"
1301+)
1302+
1303+type postalSuite struct {
1304+ log *helpers.TestLogger
1305+ bus bus.Endpoint
1306+ notifBus bus.Endpoint
1307+}
1308+
1309+var _ = Suite(&postalSuite{})
1310+
1311+func (ss *postalSuite) SetUpTest(c *C) {
1312+ ss.log = helpers.NewTestLogger(c, "debug")
1313+ ss.bus = testibus.NewTestingEndpoint(condition.Work(true), nil)
1314+ ss.notifBus = testibus.NewTestingEndpoint(condition.Work(true), nil)
1315+}
1316+
1317+func (ss *postalSuite) TestStart(c *C) {
1318+ svc := NewPostalService(ss.bus, ss.notifBus, ss.log)
1319+ c.Check(svc.IsRunning(), Equals, false)
1320+ c.Check(svc.Start(), IsNil)
1321+ c.Check(svc.IsRunning(), Equals, true)
1322+ svc.Stop()
1323+}
1324+
1325+func (ss *postalSuite) TestStartTwice(c *C) {
1326+ svc := NewPostalService(ss.bus, ss.notifBus, ss.log)
1327+ c.Check(svc.Start(), IsNil)
1328+ c.Check(svc.Start(), Equals, AlreadyStarted)
1329+ svc.Stop()
1330+}
1331+
1332+func (ss *postalSuite) TestStartNoLog(c *C) {
1333+ svc := NewPostalService(ss.bus, ss.notifBus, nil)
1334+ c.Check(svc.Start(), Equals, NotConfigured)
1335+}
1336+
1337+func (ss *postalSuite) TestStartNoBus(c *C) {
1338+ svc := NewPostalService(nil, ss.notifBus, ss.log)
1339+ c.Check(svc.Start(), Equals, NotConfigured)
1340+}
1341+
1342+func (ss *postalSuite) TestTakeTheBustFail(c *C) {
1343+ nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(false), []interface{}{uint32(1), "hello"})
1344+ svc := NewPostalService(ss.bus, nEndp, ss.log)
1345+ _, err := svc.TakeTheBus()
1346+ c.Check(err, NotNil)
1347+}
1348+
1349+func (ss *postalSuite) TestTakeTheBustOk(c *C) {
1350+ nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(true), []interface{}{uint32(1), "hello"})
1351+ svc := NewPostalService(ss.bus, nEndp, ss.log)
1352+ _, err := svc.TakeTheBus()
1353+ c.Check(err, IsNil)
1354+}
1355+
1356+func (ss *postalSuite) TestStartFailsOnBusDialFailure(c *C) {
1357+ bus := testibus.NewTestingEndpoint(condition.Work(false), nil)
1358+ svc := NewPostalService(bus, ss.notifBus, ss.log)
1359+ c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)
1360+ svc.Stop()
1361+}
1362+
1363+func (ss *postalSuite) TestStartGrabsName(c *C) {
1364+ svc := NewPostalService(ss.bus, ss.notifBus, ss.log)
1365+ c.Assert(svc.Start(), IsNil)
1366+ callArgs := testibus.GetCallArgs(ss.bus)
1367+ defer svc.Stop()
1368+ c.Assert(callArgs, NotNil)
1369+ c.Check(callArgs[0].Member, Equals, "::GrabName")
1370+}
1371+
1372+func (ss *postalSuite) TestStopClosesBus(c *C) {
1373+ svc := NewPostalService(ss.bus, ss.notifBus, ss.log)
1374+ c.Assert(svc.Start(), IsNil)
1375+ svc.Stop()
1376+ callArgs := testibus.GetCallArgs(ss.bus)
1377+ c.Assert(callArgs, NotNil)
1378+ c.Check(callArgs[len(callArgs)-1].Member, Equals, "::Close")
1379+}
1380+
1381+//
1382+// Injection tests
1383+
1384+func (ss *postalSuite) TestInjectWorks(c *C) {
1385+ svc := NewPostalService(ss.bus, ss.notifBus, ss.log)
1386+ svc.msgHandler = nil
1387+ rvs, err := svc.inject("/hello", []interface{}{"world"}, nil)
1388+ c.Assert(err, IsNil)
1389+ c.Check(rvs, IsNil)
1390+ rvs, err = svc.inject("/hello", []interface{}{"there"}, nil)
1391+ c.Assert(err, IsNil)
1392+ c.Check(rvs, IsNil)
1393+ c.Assert(svc.mbox, HasLen, 1)
1394+ c.Assert(svc.mbox["hello"], HasLen, 2)
1395+ c.Check(svc.mbox["hello"][0], Equals, "world")
1396+ c.Check(svc.mbox["hello"][1], Equals, "there")
1397+
1398+ // and check it fired the right signal (twice)
1399+ callArgs := testibus.GetCallArgs(ss.bus)
1400+ c.Assert(callArgs, HasLen, 2)
1401+ c.Check(callArgs[0].Member, Equals, "::Signal")
1402+ c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Notification", "/hello", []interface{}{"hello"}})
1403+ c.Check(callArgs[1], DeepEquals, callArgs[0])
1404+}
1405+
1406+func (ss *postalSuite) TestInjectFailsIfInjectFails(c *C) {
1407+ bus := testibus.NewTestingEndpoint(condition.Work(true),
1408+ condition.Work(false))
1409+ svc := NewPostalService(bus, ss.notifBus, ss.log)
1410+ svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return errors.New("fail") })
1411+ _, err := svc.inject("/hello", []interface{}{"xyzzy"}, nil)
1412+ c.Check(err, NotNil)
1413+}
1414+
1415+func (ss *postalSuite) TestInjectFailsIfBadArgs(c *C) {
1416+ for i, s := range []struct {
1417+ args []interface{}
1418+ errt error
1419+ }{
1420+ {nil, BadArgCount},
1421+ {[]interface{}{}, BadArgCount},
1422+ {[]interface{}{1}, BadArgType},
1423+ {[]interface{}{1, 2}, BadArgCount},
1424+ } {
1425+ reg, err := new(PostalService).inject("", s.args, nil)
1426+ c.Check(reg, IsNil, Commentf("iteration #%d", i))
1427+ c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
1428+ }
1429+}
1430+
1431+//
1432+// Notifications tests
1433+func (ss *postalSuite) TestNotificationsWorks(c *C) {
1434+ svc := NewPostalService(ss.bus, ss.notifBus, ss.log)
1435+ nots, err := svc.notifications("/hello", nil, nil)
1436+ c.Assert(err, IsNil)
1437+ c.Assert(nots, NotNil)
1438+ c.Assert(nots, HasLen, 1)
1439+ c.Check(nots[0], HasLen, 0)
1440+ if svc.mbox == nil {
1441+ svc.mbox = make(map[string][]string)
1442+ }
1443+ svc.mbox["hello"] = append(svc.mbox["hello"], "this", "thing")
1444+ nots, err = svc.notifications("/hello", nil, nil)
1445+ c.Assert(err, IsNil)
1446+ c.Assert(nots, NotNil)
1447+ c.Assert(nots, HasLen, 1)
1448+ c.Check(nots[0], DeepEquals, []string{"this", "thing"})
1449+}
1450+
1451+func (ss *postalSuite) TestNotificationsFailsIfBadArgs(c *C) {
1452+ reg, err := new(PostalService).notifications("/foo", []interface{}{1}, nil)
1453+ c.Check(reg, IsNil)
1454+ c.Check(err, Equals, BadArgCount)
1455+}
1456+
1457+func (ss *postalSuite) TestMessageHandlerPublicAPI(c *C) {
1458+ svc := new(PostalService)
1459+ c.Assert(svc.msgHandler, IsNil)
1460+ var ext = &launch_helper.HelperOutput{}
1461+ e := errors.New("Hello")
1462+ f := func(app string, nid string, s *launch_helper.HelperOutput) error { ext = s; return e }
1463+ c.Check(svc.GetMessageHandler(), IsNil)
1464+ svc.SetMessageHandler(f)
1465+ c.Check(svc.GetMessageHandler(), NotNil)
1466+ hOutput := &launch_helper.HelperOutput{[]byte("37"), nil}
1467+ c.Check(svc.msgHandler("", "", hOutput), Equals, e)
1468+ c.Check(ext, DeepEquals, hOutput)
1469+}
1470+
1471+func (ss *postalSuite) TestInjectCallsMessageHandler(c *C) {
1472+ var ext = &launch_helper.HelperOutput{}
1473+ svc := NewPostalService(ss.bus, ss.notifBus, ss.log)
1474+ f := func(app string, nid string, s *launch_helper.HelperOutput) error { ext = s; return nil }
1475+ svc.SetMessageHandler(f)
1476+ c.Check(svc.Inject("stuff", "thing", "{}"), IsNil)
1477+ c.Check(ext, DeepEquals, &launch_helper.HelperOutput{})
1478+ err := errors.New("ouch")
1479+ svc.SetMessageHandler(func(string, string, *launch_helper.HelperOutput) error { return err })
1480+ c.Check(svc.Inject("stuff", "", "{}"), Equals, err)
1481+}
1482+
1483+func (ss *postalSuite) TestMessageHandler(c *C) {
1484+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1))
1485+ svc := NewPostalService(ss.bus, endp, ss.log)
1486+ card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true}
1487+ output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card}}
1488+ err := svc.messageHandler("xyzzy", "", output)
1489+ c.Assert(err, IsNil)
1490+ args := testibus.GetCallArgs(endp)
1491+ c.Assert(args, HasLen, 1)
1492+ c.Check(args[0].Member, Equals, "Notify")
1493+ c.Check(args[0].Args[0], Equals, "xyzzy")
1494+ c.Check(args[0].Args[2], Equals, "icon-value")
1495+ c.Check(args[0].Args[3], Equals, "summary-value")
1496+ c.Check(args[0].Args[4], Equals, "body-value")
1497+}
1498+
1499+func (ss *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) {
1500+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
1501+ svc := NewPostalService(ss.bus, endp, ss.log)
1502+ card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true}
1503+ notif := &launch_helper.Notification{Card: card}
1504+ output := &launch_helper.HelperOutput{Notification: notif}
1505+ err := svc.messageHandler("", "", output)
1506+ c.Assert(err, NotNil)
1507+}
1508+
1509+func (ss *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) {
1510+ svc := NewPostalService(ss.bus, ss.notifBus, ss.log)
1511+ output := &launch_helper.HelperOutput{[]byte(`broken`), nil}
1512+ err := svc.messageHandler("", "", output)
1513+ c.Check(err, IsNil)
1514+ c.Check(ss.log.Captured(), Matches, "(?msi).*skipping notification: nil.*")
1515+}
1516+
1517+func (ss *postalSuite) TestMessageHandlerReportsButIgnoresNilNotifies(c *C) {
1518+ endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
1519+ svc := NewPostalService(ss.bus, endp, ss.log)
1520+ output := &launch_helper.HelperOutput{[]byte(`{}`), nil}
1521+ err := svc.messageHandler("", "", output)
1522+ c.Assert(err, IsNil)
1523+ c.Check(ss.log.Captured(), Matches, "(?msi).*skipping notification: nil.*")
1524+}
1525
1526=== modified file 'client/service/service.go'
1527--- client/service/service.go 2014-05-21 10:03:18 +0000
1528+++ client/service/service.go 2014-07-02 13:12:36 +0000
1529@@ -14,196 +14,184 @@
1530 with this program. If not, see <http://www.gnu.org/licenses/>.
1531 */
1532
1533-// package service implements the dbus-level service with which client
1534-// applications are expected to interact.
1535 package service
1536
1537 import (
1538+ "bytes"
1539+ "encoding/json"
1540 "errors"
1541+ "fmt"
1542+ "io/ioutil"
1543+ "net/http"
1544 "os"
1545- "sync"
1546+ "strings"
1547
1548 "launchpad.net/ubuntu-push/bus"
1549+ http13 "launchpad.net/ubuntu-push/http13client"
1550 "launchpad.net/ubuntu-push/logger"
1551+ "launchpad.net/ubuntu-push/nih"
1552 )
1553
1554-// Service is the dbus api
1555-type Service struct {
1556- lock sync.RWMutex
1557- state ServiceState
1558- mbox map[string][]string
1559- msgHandler func([]byte) error
1560- Log logger.Logger
1561- Bus bus.Endpoint
1562+// PushService is the dbus api
1563+type PushService struct {
1564+ DBusService
1565+ regURL string
1566+ deviceId string
1567+ authGetter func(string) string
1568+ httpCli http13.Client
1569 }
1570
1571-// the service can be in a numnber of states
1572-type ServiceState uint8
1573-
1574-const (
1575- StateUnknown ServiceState = iota
1576- StateRunning // Start() has been successfully called
1577- StateFinished // Stop() has been successfully called
1578-)
1579-
1580 var (
1581- NotConfigured = errors.New("not configured")
1582- AlreadyStarted = errors.New("already started")
1583- BusAddress = bus.Address{
1584+ PushServiceBusAddress = bus.Address{
1585 Interface: "com.ubuntu.PushNotifications",
1586 Path: "/com/ubuntu/PushNotifications",
1587 Name: "com.ubuntu.PushNotifications",
1588 }
1589 )
1590
1591-// NewService() builds a new service and returns it.
1592-func NewService(bus bus.Endpoint, log logger.Logger) *Service {
1593- return &Service{Log: log, Bus: bus}
1594-}
1595-
1596-// SetMessageHandler() sets the message-handling callback
1597-func (svc *Service) SetMessageHandler(callback func([]byte) error) {
1598- svc.lock.Lock()
1599- defer svc.lock.Unlock()
1600- svc.msgHandler = callback
1601-}
1602-
1603-// GetMessageHandler() returns the (possibly nil) messaging handler callback
1604-func (svc *Service) GetMessageHandler() func([]byte) error {
1605- svc.lock.RLock()
1606- defer svc.lock.RUnlock()
1607- return svc.msgHandler
1608-}
1609-
1610-// IsRunning() returns whether the service's state is StateRunning
1611-func (svc *Service) IsRunning() bool {
1612- svc.lock.RLock()
1613- defer svc.lock.RUnlock()
1614- return svc.state == StateRunning
1615-}
1616-
1617-// Start() dials the bus, grab the name, and listens for method calls.
1618-func (svc *Service) Start() error {
1619- svc.lock.Lock()
1620- defer svc.lock.Unlock()
1621- if svc.state != StateUnknown {
1622- return AlreadyStarted
1623- }
1624- if svc.Log == nil || svc.Bus == nil {
1625- return NotConfigured
1626- }
1627- err := svc.Bus.Dial()
1628- if err != nil {
1629- return err
1630- }
1631- ch := svc.Bus.GrabName(true)
1632- log := svc.Log
1633- go func() {
1634- for err := range ch {
1635- if !svc.IsRunning() {
1636- break
1637- }
1638- if err != nil {
1639- log.Fatalf("name channel for %s got: %v",
1640- BusAddress.Name, err)
1641- }
1642- }
1643- }()
1644- svc.Bus.WatchMethod(bus.DispatchMap{
1645- "Register": svc.register,
1646- "Notifications": svc.notifications,
1647- "Inject": svc.inject,
1648- }, svc)
1649- svc.state = StateRunning
1650- return nil
1651-}
1652-
1653-// Stop() closes the bus and sets the state to StateFinished
1654-func (svc *Service) Stop() {
1655- svc.lock.Lock()
1656- defer svc.lock.Unlock()
1657- if svc.Bus != nil {
1658- svc.Bus.Close()
1659- }
1660- svc.state = StateFinished
1661+// NewPushService() builds a new service and returns it.
1662+func NewPushService(bus bus.Endpoint, log logger.Logger) *PushService {
1663+ var svc = &PushService{}
1664+ svc.Log = log
1665+ svc.Bus = bus
1666+ return svc
1667+}
1668+
1669+// SetRegistrationURL() sets the registration url for the service
1670+func (svc *PushService) SetRegistrationURL(url string) {
1671+ svc.lock.Lock()
1672+ defer svc.lock.Unlock()
1673+ svc.regURL = url
1674+}
1675+
1676+// SetAuthGetter() sets the authorization getter for the service
1677+func (svc *PushService) SetAuthGetter(authGetter func(string) string) {
1678+ svc.lock.Lock()
1679+ defer svc.lock.Unlock()
1680+ svc.authGetter = authGetter
1681+}
1682+
1683+// getRegistrationAuthorization() returns the authorization header for
1684+// POSTing to the registration HTTP endpoint
1685+//
1686+// (this is for calling with the lock held)
1687+func (svc *PushService) getRegistrationAuthorization() string {
1688+ if svc.authGetter != nil && svc.regURL != "" {
1689+ return svc.authGetter(svc.regURL)
1690+ } else {
1691+ return ""
1692+ }
1693+}
1694+
1695+// GetRegistrationAuthorization() returns the authorization header for
1696+// POSTing to the registration HTTP endpoint
1697+func (svc *PushService) GetRegistrationAuthorization() string {
1698+ svc.lock.RLock()
1699+ defer svc.lock.RUnlock()
1700+ return svc.getRegistrationAuthorization()
1701+}
1702+
1703+// SetDeviceId() sets the device id
1704+func (svc *PushService) SetDeviceId(deviceId string) {
1705+ svc.lock.Lock()
1706+ defer svc.lock.Unlock()
1707+ svc.deviceId = deviceId
1708+}
1709+
1710+// GetDeviceId() returns the device id
1711+func (svc *PushService) GetDeviceId() string {
1712+ svc.lock.RLock()
1713+ defer svc.lock.RUnlock()
1714+ return svc.deviceId
1715+}
1716+
1717+func (svc *PushService) Start() error {
1718+ return svc.DBusService.Start(bus.DispatchMap{
1719+ "Register": svc.register,
1720+ }, PushServiceBusAddress)
1721 }
1722
1723 var (
1724- BadArgCount = errors.New("Wrong number of arguments")
1725- BadArgType = errors.New("Bad argument type")
1726+ BadServer = errors.New("Bad server")
1727+ BadRequest = errors.New("Bad request")
1728+ BadToken = errors.New("Bad token")
1729+ BadAuth = errors.New("Bad auth")
1730 )
1731
1732-func (svc *Service) register(args []interface{}, _ []interface{}) ([]interface{}, error) {
1733- if len(args) != 1 {
1734- return nil, BadArgCount
1735- }
1736- appname, ok := args[0].(string)
1737- if !ok {
1738- return nil, BadArgType
1739- }
1740-
1741- rv := os.Getenv("PUSH_REG_" + appname)
1742- if rv == "" {
1743- rv = "this-is-an-opaque-block-of-random-bits-i-promise"
1744- }
1745-
1746- return []interface{}{rv}, nil
1747-}
1748-
1749-func (svc *Service) notifications(args []interface{}, _ []interface{}) ([]interface{}, error) {
1750- if len(args) != 1 {
1751- return nil, BadArgCount
1752- }
1753- appname, ok := args[0].(string)
1754- if !ok {
1755- return nil, BadArgType
1756- }
1757-
1758- svc.lock.Lock()
1759- defer svc.lock.Unlock()
1760-
1761- if svc.mbox == nil {
1762- return []interface{}{[]string(nil)}, nil
1763- }
1764- msgs := svc.mbox[appname]
1765- delete(svc.mbox, appname)
1766-
1767- return []interface{}{msgs}, nil
1768-}
1769-
1770-func (svc *Service) inject(args []interface{}, _ []interface{}) ([]interface{}, error) {
1771- if len(args) != 2 {
1772- return nil, BadArgCount
1773- }
1774- appname, ok := args[0].(string)
1775- if !ok {
1776- return nil, BadArgType
1777- }
1778- notif, ok := args[1].(string)
1779- if !ok {
1780- return nil, BadArgType
1781- }
1782-
1783- return nil, svc.Inject(appname, notif)
1784-}
1785-
1786-// Inject() signals to an application over dbus that a notification
1787-// has arrived.
1788-func (svc *Service) Inject(appname string, notif string) error {
1789- svc.lock.Lock()
1790- defer svc.lock.Unlock()
1791- if svc.mbox == nil {
1792- svc.mbox = make(map[string][]string)
1793- }
1794- svc.mbox[appname] = append(svc.mbox[appname], notif)
1795- if svc.msgHandler != nil {
1796- err := svc.msgHandler([]byte(notif))
1797- if err != nil {
1798- svc.Log.Errorf("msgHandler returned %v", err)
1799- return err
1800+type registrationRequest struct {
1801+ DeviceId string `json:"deviceid"`
1802+ AppId string `json:"appid"`
1803+}
1804+
1805+type registrationReply struct {
1806+ Token string `json:"token"` // the bit we're after
1807+ Ok bool `json:"ok"` // only ever true or absent
1808+ Error string `json:"error"` // these two only used for debugging
1809+ Message string `json:"message"` //
1810+}
1811+
1812+func (svc *PushService) register(path string, args, _ []interface{}) ([]interface{}, error) {
1813+ svc.lock.RLock()
1814+ defer svc.lock.RUnlock()
1815+ if len(args) != 0 {
1816+ return nil, BadArgCount
1817+ }
1818+ raw_appname := path[strings.LastIndex(path, "/")+1:]
1819+ appname := string(nih.Unquote([]byte(raw_appname)))
1820+
1821+ rv := os.Getenv("PUSH_REG_" + raw_appname)
1822+ if rv != "" {
1823+ return []interface{}{rv}, nil
1824+ }
1825+
1826+ req_body, err := json.Marshal(registrationRequest{svc.deviceId, appname})
1827+ if err != nil {
1828+ return nil, fmt.Errorf("unable to marshal register request body: %v", err)
1829+ }
1830+ req, err := http13.NewRequest("POST", svc.regURL, bytes.NewReader(req_body))
1831+ if err != nil {
1832+ return nil, fmt.Errorf("unable to build register request: %v", err)
1833+ }
1834+ auth := svc.getRegistrationAuthorization()
1835+ if auth == "" {
1836+ return nil, BadAuth
1837+ }
1838+ req.Header.Add("Authorization", auth)
1839+ req.Header.Add("Content-Type", "application/json")
1840+
1841+ resp, err := svc.httpCli.Do(req)
1842+ if err != nil {
1843+ return nil, fmt.Errorf("unable to request registration: %v", err)
1844+ }
1845+ defer resp.Body.Close()
1846+ if resp.StatusCode != http.StatusOK {
1847+ svc.Log.Errorf("register endpoint replied %d", resp.StatusCode)
1848+ switch {
1849+ case resp.StatusCode >= http.StatusInternalServerError:
1850+ // XXX retry on 503
1851+ return nil, BadServer
1852+ default:
1853+ return nil, BadRequest
1854 }
1855- svc.Log.Debugf("call to msgHandler successful")
1856- }
1857-
1858- return svc.Bus.Signal("Notification", []interface{}{appname})
1859+ }
1860+ // errors below here Can't Happen (tm).
1861+ body, err := ioutil.ReadAll(resp.Body)
1862+ if err != nil {
1863+ svc.Log.Errorf("Reading response body: %v", err)
1864+ return nil, err
1865+ }
1866+
1867+ var reply registrationReply
1868+ err = json.Unmarshal(body, &reply)
1869+ if err != nil {
1870+ svc.Log.Errorf("Unmarshalling response body: %v", err)
1871+ return nil, fmt.Errorf("unable to unmarshal register response: %v", err)
1872+ }
1873+
1874+ if !reply.Ok || reply.Token == "" {
1875+ svc.Log.Errorf("Unexpected response: %#v", reply)
1876+ return nil, BadToken
1877+ }
1878+
1879+ return []interface{}{reply.Token}, nil
1880 }
1881
1882=== modified file 'client/service/service_test.go'
1883--- client/service/service_test.go 2014-05-21 10:05:24 +0000
1884+++ client/service/service_test.go 2014-07-02 13:12:36 +0000
1885@@ -17,7 +17,10 @@
1886 package service
1887
1888 import (
1889- "errors"
1890+ "encoding/json"
1891+ "fmt"
1892+ "net/http"
1893+ "net/http/httptest"
1894 "os"
1895 "testing"
1896
1897@@ -45,7 +48,7 @@
1898 }
1899
1900 func (ss *serviceSuite) TestStart(c *C) {
1901- svc := NewService(ss.bus, ss.log)
1902+ svc := NewPushService(ss.bus, ss.log)
1903 c.Check(svc.IsRunning(), Equals, false)
1904 c.Check(svc.Start(), IsNil)
1905 c.Check(svc.IsRunning(), Equals, true)
1906@@ -53,31 +56,31 @@
1907 }
1908
1909 func (ss *serviceSuite) TestStartTwice(c *C) {
1910- svc := NewService(ss.bus, ss.log)
1911+ svc := NewPushService(ss.bus, ss.log)
1912 c.Check(svc.Start(), IsNil)
1913 c.Check(svc.Start(), Equals, AlreadyStarted)
1914 svc.Stop()
1915 }
1916
1917 func (ss *serviceSuite) TestStartNoLog(c *C) {
1918- svc := NewService(ss.bus, nil)
1919+ svc := NewPushService(ss.bus, nil)
1920 c.Check(svc.Start(), Equals, NotConfigured)
1921 }
1922
1923 func (ss *serviceSuite) TestStartNoBus(c *C) {
1924- svc := NewService(nil, ss.log)
1925+ svc := NewPushService(nil, ss.log)
1926 c.Check(svc.Start(), Equals, NotConfigured)
1927 }
1928
1929 func (ss *serviceSuite) TestStartFailsOnBusDialFailure(c *C) {
1930 bus := testibus.NewTestingEndpoint(condition.Work(false), nil)
1931- svc := NewService(bus, ss.log)
1932+ svc := NewPushService(bus, ss.log)
1933 c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)
1934 svc.Stop()
1935 }
1936
1937 func (ss *serviceSuite) TestStartGrabsName(c *C) {
1938- svc := NewService(ss.bus, ss.log)
1939+ svc := NewPushService(ss.bus, ss.log)
1940 c.Assert(svc.Start(), IsNil)
1941 callArgs := testibus.GetCallArgs(ss.bus)
1942 defer svc.Stop()
1943@@ -86,7 +89,7 @@
1944 }
1945
1946 func (ss *serviceSuite) TestStopClosesBus(c *C) {
1947- svc := NewService(ss.bus, ss.log)
1948+ svc := NewPushService(ss.bus, ss.log)
1949 c.Assert(svc.Start(), IsNil)
1950 svc.Stop()
1951 callArgs := testibus.GetCallArgs(ss.bus)
1952@@ -96,36 +99,75 @@
1953
1954 // registration tests
1955
1956+func (ss *serviceSuite) TestSetRegURLWorks(c *C) {
1957+ svc := NewPushService(ss.bus, ss.log)
1958+ c.Check(svc.regURL, Equals, "")
1959+ svc.SetRegistrationURL("xyzzy://")
1960+ c.Check(svc.regURL, Equals, "xyzzy://")
1961+}
1962+
1963+func (ss *serviceSuite) TestSetAuthGetterWorks(c *C) {
1964+ svc := NewPushService(ss.bus, ss.log)
1965+ c.Check(svc.authGetter, IsNil)
1966+ f := func(string) string { return "" }
1967+ svc.SetAuthGetter(f)
1968+ c.Check(fmt.Sprintf("%#v", svc.authGetter), Equals, fmt.Sprintf("%#v", f))
1969+}
1970+
1971+func (ss *serviceSuite) TestGetRegAuthWorks(c *C) {
1972+ svc := NewPushService(ss.bus, ss.log)
1973+ svc.SetRegistrationURL("xyzzy://")
1974+ ch := make(chan string, 1)
1975+ f := func(s string) string { ch <- s; return "Auth " + s }
1976+ svc.SetAuthGetter(f)
1977+ c.Check(svc.getRegistrationAuthorization(), Equals, "Auth xyzzy://")
1978+ c.Assert(len(ch), Equals, 1)
1979+ c.Check(<-ch, Equals, "xyzzy://")
1980+}
1981+
1982+func (ss *serviceSuite) TestGetRegAuthDoesNotPanic(c *C) {
1983+ svc := NewPushService(ss.bus, ss.log)
1984+ c.Check(svc.getRegistrationAuthorization(), Equals, "")
1985+}
1986+
1987 func (ss *serviceSuite) TestRegistrationFailsIfBadArgs(c *C) {
1988- for i, s := range []struct {
1989- args []interface{}
1990- errt error
1991- }{
1992- {nil, BadArgCount}, // no args
1993- {[]interface{}{}, BadArgCount}, // still no args
1994- {[]interface{}{42}, BadArgType}, // bad arg type
1995- {[]interface{}{1, 2}, BadArgCount}, // too many args
1996- } {
1997- reg, err := new(Service).register(s.args, nil)
1998- c.Check(reg, IsNil, Commentf("iteration #%d", i))
1999- c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
2000- }
2001+ reg, err := new(PushService).register("", []interface{}{1}, nil)
2002+ c.Check(reg, IsNil)
2003+ c.Check(err, Equals, BadArgCount)
2004 }
2005
2006 func (ss *serviceSuite) TestRegistrationWorks(c *C) {
2007- reg, err := new(Service).register([]interface{}{"this"}, nil)
2008+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2009+ buf := make([]byte, 256)
2010+ n, e := r.Body.Read(buf)
2011+ c.Assert(e, IsNil)
2012+ req := registrationRequest{}
2013+ c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
2014+ c.Check(req, DeepEquals, registrationRequest{"fake-device-id", "an-app-id"})
2015+
2016+ w.Header().Set("Content-Type", "application/json")
2017+ fmt.Fprintln(w, `{"ok":true,"token":"blob-of-bytes"}`)
2018+ }))
2019+ defer ts.Close()
2020+
2021+ svc := NewPushService(ss.bus, ss.log)
2022+ svc.SetAuthGetter(func(string) string { return "tok" })
2023+ svc.SetRegistrationURL(ts.URL)
2024+ svc.SetDeviceId("fake-device-id")
2025+ // this'll check (un)quoting, too
2026+ reg, err := svc.register("/an_2dapp_2did", nil, nil)
2027+ c.Assert(err, IsNil)
2028 c.Assert(reg, HasLen, 1)
2029 regs, ok := reg[0].(string)
2030 c.Check(ok, Equals, true)
2031- c.Check(regs, Not(Equals), "")
2032- c.Check(err, IsNil)
2033+ c.Check(regs, Equals, "blob-of-bytes")
2034 }
2035
2036 func (ss *serviceSuite) TestRegistrationOverrideWorks(c *C) {
2037 os.Setenv("PUSH_REG_stuff", "42")
2038 defer os.Setenv("PUSH_REG_stuff", "")
2039
2040- reg, err := new(Service).register([]interface{}{"stuff"}, nil)
2041+ reg, err := new(PushService).register("/stuff", nil, nil)
2042 c.Assert(reg, HasLen, 1)
2043 regs, ok := reg[0].(string)
2044 c.Check(ok, Equals, true)
2045@@ -133,115 +175,103 @@
2046 c.Check(err, IsNil)
2047 }
2048
2049-//
2050-// Injection tests
2051-
2052-func (ss *serviceSuite) TestInjectWorks(c *C) {
2053- svc := NewService(ss.bus, ss.log)
2054- rvs, err := svc.inject([]interface{}{"hello", "world"}, nil)
2055- c.Assert(err, IsNil)
2056- c.Check(rvs, IsNil)
2057- rvs, err = svc.inject([]interface{}{"hello", "there"}, nil)
2058- c.Assert(err, IsNil)
2059- c.Check(rvs, IsNil)
2060- c.Assert(svc.mbox, HasLen, 1)
2061- c.Assert(svc.mbox["hello"], HasLen, 2)
2062- c.Check(svc.mbox["hello"][0], Equals, "world")
2063- c.Check(svc.mbox["hello"][1], Equals, "there")
2064-
2065- // and check it fired the right signal (twice)
2066- callArgs := testibus.GetCallArgs(ss.bus)
2067- c.Assert(callArgs, HasLen, 2)
2068- c.Check(callArgs[0].Member, Equals, "::Signal")
2069- c.Check(callArgs[0].Args, DeepEquals, []interface{}{"Notification", []interface{}{"hello"}})
2070- c.Check(callArgs[1], DeepEquals, callArgs[0])
2071-}
2072-
2073-func (ss *serviceSuite) TestInjectFailsIfInjectFails(c *C) {
2074- bus := testibus.NewTestingEndpoint(condition.Work(true),
2075- condition.Work(false))
2076- svc := NewService(bus, ss.log)
2077- svc.SetMessageHandler(func([]byte) error { return errors.New("fail") })
2078- _, err := svc.inject([]interface{}{"hello", "xyzzy"}, nil)
2079- c.Check(err, NotNil)
2080-}
2081-
2082-func (ss *serviceSuite) TestInjectFailsIfBadArgs(c *C) {
2083- for i, s := range []struct {
2084- args []interface{}
2085- errt error
2086- }{
2087- {nil, BadArgCount},
2088- {[]interface{}{}, BadArgCount},
2089- {[]interface{}{1}, BadArgCount},
2090- {[]interface{}{1, 2}, BadArgType},
2091- {[]interface{}{"1", 2}, BadArgType},
2092- {[]interface{}{1, "2"}, BadArgType},
2093- {[]interface{}{1, 2, 3}, BadArgCount},
2094- } {
2095- reg, err := new(Service).inject(s.args, nil)
2096- c.Check(reg, IsNil, Commentf("iteration #%d", i))
2097- c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
2098- }
2099-}
2100-
2101-//
2102-// Notifications tests
2103-func (ss *serviceSuite) TestNotificationsWorks(c *C) {
2104- svc := NewService(ss.bus, ss.log)
2105- nots, err := svc.notifications([]interface{}{"hello"}, nil)
2106- c.Assert(err, IsNil)
2107- c.Assert(nots, NotNil)
2108- c.Assert(nots, HasLen, 1)
2109- c.Check(nots[0], HasLen, 0)
2110- if svc.mbox == nil {
2111- svc.mbox = make(map[string][]string)
2112- }
2113- svc.mbox["hello"] = append(svc.mbox["hello"], "this", "thing")
2114- nots, err = svc.notifications([]interface{}{"hello"}, nil)
2115- c.Assert(err, IsNil)
2116- c.Assert(nots, NotNil)
2117- c.Assert(nots, HasLen, 1)
2118- c.Check(nots[0], DeepEquals, []string{"this", "thing"})
2119-}
2120-
2121-func (ss *serviceSuite) TestNotificationsFailsIfBadArgs(c *C) {
2122- for i, s := range []struct {
2123- args []interface{}
2124- errt error
2125- }{
2126- {nil, BadArgCount}, // no args
2127- {[]interface{}{}, BadArgCount}, // still no args
2128- {[]interface{}{42}, BadArgType}, // bad arg type
2129- {[]interface{}{1, 2}, BadArgCount}, // too many args
2130- } {
2131- reg, err := new(Service).notifications(s.args, nil)
2132- c.Check(reg, IsNil, Commentf("iteration #%d", i))
2133- c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
2134- }
2135-}
2136-
2137-func (ss *serviceSuite) TestMessageHandler(c *C) {
2138- svc := new(Service)
2139- c.Assert(svc.msgHandler, IsNil)
2140- var ext = []byte{}
2141- e := errors.New("Hello")
2142- f := func(s []byte) error { ext = s; return e }
2143- c.Check(svc.GetMessageHandler(), IsNil)
2144- svc.SetMessageHandler(f)
2145- c.Check(svc.GetMessageHandler(), NotNil)
2146- c.Check(svc.msgHandler([]byte("37")), Equals, e)
2147- c.Check(ext, DeepEquals, []byte("37"))
2148-}
2149-
2150-func (ss *serviceSuite) TestInjectCallsMessageHandler(c *C) {
2151- var ext = []byte{}
2152- svc := NewService(ss.bus, ss.log)
2153- f := func(s []byte) error { ext = s; return nil }
2154- svc.SetMessageHandler(f)
2155- c.Check(svc.Inject("stuff", "{}"), IsNil)
2156- c.Check(ext, DeepEquals, []byte("{}"))
2157- err := errors.New("ouch")
2158- svc.SetMessageHandler(func([]byte) error { return err })
2159- c.Check(svc.Inject("stuff", "{}"), Equals, err)
2160+func (ss *serviceSuite) TestRegistrationFailsOnBadReqURL(c *C) {
2161+ svc := NewPushService(ss.bus, ss.log)
2162+ svc.SetRegistrationURL("%gh")
2163+ reg, err := svc.register("thing", nil, nil)
2164+ c.Check(reg, IsNil)
2165+ c.Check(err, ErrorMatches, "unable to build register request: .*")
2166+}
2167+
2168+func (ss *serviceSuite) TestRegistrationFailsOnBadAuth(c *C) {
2169+ svc := NewPushService(ss.bus, ss.log)
2170+ // ... no auth added
2171+ reg, err := svc.register("thing", nil, nil)
2172+ c.Check(reg, IsNil)
2173+ c.Check(err, Equals, BadAuth)
2174+}
2175+
2176+func (ss *serviceSuite) TestRegistrationFailsOnNoServer(c *C) {
2177+ svc := NewPushService(ss.bus, ss.log)
2178+ svc.SetRegistrationURL("xyzzy://")
2179+ svc.SetAuthGetter(func(string) string { return "tok" })
2180+ reg, err := svc.register("thing", nil, nil)
2181+ c.Check(reg, IsNil)
2182+ c.Check(err, ErrorMatches, "unable to request registration: .*")
2183+}
2184+
2185+func (ss *serviceSuite) TestRegistrationFailsOn40x(c *C) {
2186+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2187+ http.Error(w, "I'm a teapot", 418)
2188+ }))
2189+ defer ts.Close()
2190+
2191+ svc := NewPushService(ss.bus, ss.log)
2192+ svc.SetAuthGetter(func(string) string { return "tok" })
2193+ svc.SetRegistrationURL(ts.URL)
2194+ reg, err := svc.register("/thing", nil, nil)
2195+ c.Check(err, Equals, BadRequest)
2196+ c.Check(reg, IsNil)
2197+}
2198+
2199+func (ss *serviceSuite) TestRegistrationFailsOn50x(c *C) {
2200+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2201+ http.Error(w, "Not implemented", 501)
2202+ }))
2203+ defer ts.Close()
2204+
2205+ svc := NewPushService(ss.bus, ss.log)
2206+ svc.SetAuthGetter(func(string) string { return "tok" })
2207+ svc.SetRegistrationURL(ts.URL)
2208+ reg, err := svc.register("/thing", nil, nil)
2209+ c.Check(err, Equals, BadServer)
2210+ c.Check(reg, IsNil)
2211+}
2212+
2213+func (ss *serviceSuite) TestRegistrationFailsOnBadJSON(c *C) {
2214+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2215+ buf := make([]byte, 256)
2216+ n, e := r.Body.Read(buf)
2217+ c.Assert(e, IsNil)
2218+ req := registrationRequest{}
2219+ c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
2220+ c.Check(req, DeepEquals, registrationRequest{"fake-device-id", "an-app-id"})
2221+
2222+ w.Header().Set("Content-Type", "application/json")
2223+ fmt.Fprintln(w, `{`)
2224+ }))
2225+ defer ts.Close()
2226+
2227+ svc := NewPushService(ss.bus, ss.log)
2228+ svc.SetAuthGetter(func(string) string { return "tok" })
2229+ svc.SetRegistrationURL(ts.URL)
2230+ svc.SetDeviceId("fake-device-id")
2231+ // this'll check (un)quoting, too
2232+ reg, err := svc.register("/an_2dapp_2did", nil, nil)
2233+ c.Check(reg, IsNil)
2234+ c.Check(err, ErrorMatches, "unable to unmarshal register response: .*")
2235+}
2236+
2237+func (ss *serviceSuite) TestRegistrationFailsOnBadJSONDocument(c *C) {
2238+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2239+ buf := make([]byte, 256)
2240+ n, e := r.Body.Read(buf)
2241+ c.Assert(e, IsNil)
2242+ req := registrationRequest{}
2243+ c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
2244+ c.Check(req, DeepEquals, registrationRequest{"fake-device-id", "an-app-id"})
2245+
2246+ w.Header().Set("Content-Type", "application/json")
2247+ fmt.Fprintln(w, `{"bananas": "very yes"}`)
2248+ }))
2249+ defer ts.Close()
2250+
2251+ svc := NewPushService(ss.bus, ss.log)
2252+ svc.SetAuthGetter(func(string) string { return "tok" })
2253+ svc.SetRegistrationURL(ts.URL)
2254+ svc.SetDeviceId("fake-device-id")
2255+ // this'll check (un)quoting, too
2256+ reg, err := svc.register("/an_2dapp_2did", nil, nil)
2257+ c.Check(reg, IsNil)
2258+ c.Check(err, Equals, BadToken)
2259 }
2260
2261=== modified file 'client/session/session.go'
2262--- client/session/session.go 2014-05-23 05:59:38 +0000
2263+++ client/session/session.go 2014-07-02 13:12:36 +0000
2264@@ -26,7 +26,6 @@
2265 "fmt"
2266 "math/rand"
2267 "net"
2268- "os/exec"
2269 "strings"
2270 "sync"
2271 "sync/atomic"
2272@@ -87,7 +86,8 @@
2273 ExpectAllRepairedTime time.Duration
2274 PEM []byte
2275 Info map[string]interface{}
2276- AuthHelper []string
2277+ AuthGetter func(string) string
2278+ AuthURL string
2279 }
2280
2281 // ClientSession holds a client<->server session and its configuration.
2282@@ -244,21 +244,10 @@
2283 // addAuthorization gets the authorization blob to send to the server
2284 // and adds it to the session.
2285 func (sess *ClientSession) addAuthorization() error {
2286- sess.Log.Debugf("adding authorization")
2287- // using a helper, for now at least
2288- if len(sess.AuthHelper) == 0 {
2289- // do nothing if helper is unset or empty
2290- return nil
2291- }
2292-
2293- auth, err := exec.Command(sess.AuthHelper[0], sess.AuthHelper[1:]...).Output()
2294- if err != nil {
2295- // For now we just log the error, as we don't want to block unauthorized users
2296- sess.Log.Errorf("unable to get the authorization token from the account: %v", err)
2297- } else {
2298- sess.auth = strings.TrimSpace(string(auth))
2299- }
2300-
2301+ if sess.AuthGetter != nil {
2302+ sess.Log.Debugf("adding authorization")
2303+ sess.auth = sess.AuthGetter(sess.AuthURL)
2304+ }
2305 return nil
2306 }
2307
2308
2309=== modified file 'client/session/session_test.go'
2310--- client/session/session_test.go 2014-05-23 05:59:38 +0000
2311+++ client/session/session_test.go 2014-07-02 13:12:36 +0000
2312@@ -353,34 +353,21 @@
2313 ****************************************************************/
2314
2315 func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {
2316- sess := &ClientSession{Log: cs.log}
2317- sess.AuthHelper = []string{"echo", "some auth"}
2318- c.Assert(sess.auth, Equals, "")
2319- err := sess.addAuthorization()
2320- c.Assert(err, IsNil)
2321- c.Check(sess.auth, Equals, "some auth")
2322-}
2323-
2324-func (cs *clientSessionSuite) TestAddAuthorizationIgnoresErrors(c *C) {
2325- sess := &ClientSession{Log: cs.log}
2326- sess.AuthHelper = []string{"sh", "-c", "echo hello; false"}
2327-
2328- c.Assert(sess.auth, Equals, "")
2329- err := sess.addAuthorization()
2330- c.Assert(err, IsNil)
2331- c.Check(sess.auth, Equals, "")
2332-}
2333-
2334-func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnsetOrNil(c *C) {
2335- sess := &ClientSession{Log: cs.log}
2336- sess.AuthHelper = nil
2337- c.Assert(sess.auth, Equals, "")
2338- err := sess.addAuthorization()
2339- c.Assert(err, IsNil)
2340- c.Check(sess.auth, Equals, "")
2341-
2342- sess.AuthHelper = []string{}
2343- err = sess.addAuthorization()
2344+ url := "xyzzy://"
2345+ sess := &ClientSession{Log: cs.log}
2346+ sess.AuthGetter = func(url string) string { return url + " auth'ed" }
2347+ sess.AuthURL = url
2348+ c.Assert(sess.auth, Equals, "")
2349+ err := sess.addAuthorization()
2350+ c.Assert(err, IsNil)
2351+ c.Check(sess.auth, Equals, "xyzzy:// auth'ed")
2352+}
2353+
2354+func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnset(c *C) {
2355+ sess := &ClientSession{Log: cs.log}
2356+ sess.AuthGetter = nil
2357+ c.Assert(sess.auth, Equals, "")
2358+ err := sess.addAuthorization()
2359 c.Assert(err, IsNil)
2360 c.Check(sess.auth, Equals, "")
2361 }
2362
2363=== modified file 'debian/changelog'
2364--- debian/changelog 2014-06-05 09:42:22 +0000
2365+++ debian/changelog 2014-07-02 13:12:36 +0000
2366@@ -1,3 +1,37 @@
2367+ubuntu-push (0.42ubuntu1) UNRELEASED; urgency=medium
2368+
2369+ [ Samuele Pedroni ]
2370+ * Support registering tokens and sending notifications with a token
2371+ * Register script and scripts unicast support
2372+ * Update http13client from the actual go1.3 release
2373+ * Avoid late pings in the face of nop exchanges
2374+ * murmur3 upstream change of seed to 0
2375+
2376+ [ Roberto Alsina ]
2377+ * Make signing-helper generate a HTTP header instead of a querystring,
2378+ and take a URL to sign.
2379+ * Wrap libmessaging-menu to allow for persistent notifications.
2380+ * Wrap ubuntu-app-launch start_helper / stop_helper functions.
2381+
2382+ [ John R. Lenton ]
2383+ * Switch dbus api to retrieve app name from dbus path.
2384+ * Move signing bits up from session to client, for reuse by service.
2385+ * Change AuthHelper to be a string; auth helper should now expect a
2386+ parameter (the url to sign). Added SessionURL to config.
2387+ * Adapt our whoopsie wrapper to whoopsie's now more correct behavior wrt
2388+ failing to get a mac address.
2389+ * Add registration_url to config; hook up auth bits and reg url to
2390+ client & service.
2391+ * Do an HTTP POST to registration_url on register.
2392+ * Fix debian/rules so packaging-time tests pass (ugh)
2393+ * Refactoring notification providers.
2394+ * Get the small messaging-menu wrapper working (thanks larsu & dednick)
2395+
2396+ [ Guillermo Gonzalez ]
2397+ * Split DBus service into PushService and PostalService
2398+
2399+ -- John R. Lenton <john.lenton@canonical.com> Wed, 02 Jul 2014 13:49:01 +0100
2400+
2401 ubuntu-push (0.3+14.10.20140605-0ubuntu1) utopic; urgency=medium
2402
2403 [ John Lenton ]
2404
2405=== modified file 'debian/config.json'
2406--- debian/config.json 2014-06-04 12:18:42 +0000
2407+++ debian/config.json 2014-07-02 13:12:36 +0000
2408@@ -1,5 +1,7 @@
2409 {
2410- "auth_helper": [],
2411+ "auth_helper": "/usr/lib/ubuntu-push-client/signing-helper",
2412+ "session_url": "https://push.ubuntu.com/",
2413+ "registration_url": "https://push.ubuntu.com/register",
2414 "connect_timeout": "20s",
2415 "exchange_timeout": "30s",
2416 "hosts_cache_expiry": "12h",
2417
2418=== modified file 'debian/control'
2419--- debian/control 2014-06-02 10:01:13 +0000
2420+++ debian/control 2014-07-02 13:12:36 +0000
2421@@ -14,7 +14,9 @@
2422 golang-uuid-dev,
2423 libgcrypt11-dev,
2424 libglib2.0-dev (>= 2.31.6),
2425+ libmessaging-menu-dev,
2426 libwhoopsie-dev,
2427+ libubuntu-app-launch2-dev,
2428 libubuntuoneauth-2.0-dev,
2429 libdbus-1-dev,
2430 libnih-dbus-dev,
2431
2432=== modified file 'debian/rules'
2433--- debian/rules 2014-05-02 12:42:27 +0000
2434+++ debian/rules 2014-07-02 13:12:36 +0000
2435@@ -5,10 +5,15 @@
2436 export UBUNTU_PUSH_TEST_RESOURCES_ROOT := $(CURDIR)
2437
2438 override_dh_auto_build:
2439- cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1)
2440 dh_auto_build --buildsystem=golang
2441 (cd signing-helper && cmake . && make)
2442
2443+# overriding dh_auto_test because the http13client tests don't all pass on go < 1.3
2444+# (should go away once we ship go 1.3)
2445+override_dh_auto_test:
2446+ cd $$( find ./ -type d -regex '\./[^/]*/src/launchpad.net' -printf "%h\n" | head -n1) && \
2447+ env GOPATH=$$(cd ..; pwd) go test $$(env GOPATH=$$(cd ..; pwd) go list $(DH_GOPKG)/... | grep -v acceptance | grep -v http13client )
2448+
2449 override_dh_install:
2450 dh_install -Xusr/bin/cmd -Xusr/bin/dev --fail-missing
2451
2452
2453=== modified file 'external/README'
2454--- external/README 2014-03-24 15:37:36 +0000
2455+++ external/README 2014-07-02 13:12:36 +0000
2456@@ -1,3 +1,3 @@
2457 Directly included vendorized small packages.
2458
2459-* murmor3 comes from import at lp:~ubuntu-push-hackers/ubuntu-push/murmur at revno 10
2460+* murmur3 comes from import at lp:~ubuntu-push-hackers/ubuntu-push/murmur at revno 12
2461
2462=== modified file 'external/murmur3/murmur128.go'
2463--- external/murmur3/murmur128.go 2014-03-24 15:31:42 +0000
2464+++ external/murmur3/murmur128.go 2014-07-02 13:12:36 +0000
2465@@ -40,7 +40,7 @@
2466
2467 func (d *digest128) Size() int { return 16 }
2468
2469-func (d *digest128) reset() { d.h1, d.h2 = 1, 1 }
2470+func (d *digest128) reset() { d.h1, d.h2 = 0, 0 }
2471
2472 func (d *digest128) Sum(b []byte) []byte {
2473 h1, h2 := d.h1, d.h2
2474@@ -182,7 +182,7 @@
2475 // hasher.Write(data)
2476 // return hasher.Sum128()
2477 func Sum128(data []byte) (h1 uint64, h2 uint64) {
2478- d := &digest128{h1: 1, h2: 1}
2479+ d := &digest128{h1: 0, h2: 0}
2480 d.tail = d.bmix(data)
2481 d.clen = len(data)
2482 return d.Sum128()
2483
2484=== modified file 'external/murmur3/murmur32.go'
2485--- external/murmur3/murmur32.go 2014-03-24 15:31:42 +0000
2486+++ external/murmur3/murmur32.go 2014-07-02 13:12:36 +0000
2487@@ -33,7 +33,7 @@
2488
2489 func (d *digest32) Size() int { return 4 }
2490
2491-func (d *digest32) reset() { d.h1 = 1 }
2492+func (d *digest32) reset() { d.h1 = 0 }
2493
2494 func (d *digest32) Sum(b []byte) []byte {
2495 h := d.h1
2496@@ -104,7 +104,7 @@
2497 // return hasher.Sum32()
2498 func Sum32(data []byte) uint32 {
2499
2500- var h1 uint32 = 1
2501+ var h1 uint32 = 0
2502
2503 nblocks := len(data) / 4
2504 var p uintptr
2505
2506=== modified file 'external/murmur3/murmur64.go'
2507--- external/murmur3/murmur64.go 2014-03-24 15:31:42 +0000
2508+++ external/murmur3/murmur64.go 2014-07-02 13:12:36 +0000
2509@@ -37,7 +37,7 @@
2510 // hasher.Write(data)
2511 // return hasher.Sum64()
2512 func Sum64(data []byte) uint64 {
2513- d := &digest128{h1: 1, h2: 1}
2514+ d := &digest128{h1: 0, h2: 0}
2515 d.tail = d.bmix(data)
2516 d.clen = len(data)
2517 h1, _ := d.Sum128()
2518
2519=== modified file 'external/murmur3/murmur_test.go'
2520--- external/murmur3/murmur_test.go 2014-03-24 15:31:42 +0000
2521+++ external/murmur3/murmur_test.go 2014-07-02 13:12:36 +0000
2522@@ -11,11 +11,11 @@
2523 h64_2 uint64
2524 s string
2525 }{
2526- {0x514e28b7, 0x4610abe56eff5cb5, 0x51622daa78f83583, ""},
2527- {0xbb4abcad, 0xa78ddff5adae8d10, 0x128900ef20900135, "hello"},
2528- {0x6f5cb2e9, 0x8b95f808840725c6, 0x1597ed5422bd493b, "hello, world"},
2529- {0xf50e1f30, 0x2a929de9c8f97b2f, 0x56a41d99af43a2db, "19 Jan 2038 at 3:14:07 AM"},
2530- {0x846f6a36, 0xfb3325171f9744da, 0xaaf8b92a5f722952, "The quick brown fox jumps over the lazy dog."},
2531+ {0x00000000, 0x0000000000000000, 0x0000000000000000, ""},
2532+ {0x248bfa47, 0xcbd8a7b341bd9b02, 0x5b1e906a48ae1d19, "hello"},
2533+ {0x149bbb7f, 0x342fac623a5ebc8e, 0x4cdcbc079642414d, "hello, world"},
2534+ {0xe31e8a70, 0xb89e5988b737affc, 0x664fc2950231b2cb, "19 Jan 2038 at 3:14:07 AM"},
2535+ {0xd5c48bfc, 0xcd99481f9ee902c9, 0x695da1a38987b6e7, "The quick brown fox jumps over the lazy dog."},
2536 }
2537
2538 func TestRef(t *testing.T) {
2539@@ -37,6 +37,10 @@
2540 t.Errorf("'%s': 0x%x (want 0x%x)", elem.s, v, elem.h64_1)
2541 }
2542
2543+ if v := Sum64([]byte(elem.s)); v != elem.h64_1 {
2544+ t.Errorf("'%s': 0x%x (want 0x%x)", elem.s, v, elem.h64_1)
2545+ }
2546+
2547 var h128 Hash128 = New128()
2548 h128.Write([]byte(elem.s))
2549 if v1, v2 := h128.Sum128(); v1 != elem.h64_1 || v2 != elem.h64_2 {
2550
2551=== modified file 'http13client/Makefile'
2552--- http13client/Makefile 2014-03-20 12:15:36 +0000
2553+++ http13client/Makefile 2014-07-02 13:12:36 +0000
2554@@ -20,7 +20,8 @@
2555
2556 prune:
2557 rm -rf example_test.go filetransport*.go fs*.go race.go range_test.go \
2558- sniff*.go httptest httputil testdata triv.go jar.go status.go
2559+ sniff*.go httptest httputil testdata triv.go jar.go status.go \
2560+ cookie_test.go
2561 sed -i -e 's+"launchpad.net/ubuntu-push/http13client/+"net/http/+' *.go
2562
2563 fix:
2564
2565=== modified file 'http13client/_patches/empty_server.patch'
2566--- http13client/_patches/empty_server.patch 2014-03-19 23:13:58 +0000
2567+++ http13client/_patches/empty_server.patch 2014-07-02 13:12:36 +0000
2568@@ -1,6 +1,6 @@
2569 === modified file 'http13client/serve_test.go'
2570---- http13client/serve_test.go 2014-03-19 21:38:56 +0000
2571-+++ http13client/serve_test.go 2014-03-19 22:27:37 +0000
2572+--- http13client/serve_test.go 2014-06-20 11:00:47 +0000
2573++++ http13client/serve_test.go 2014-06-20 12:00:22 +0000
2574 @@ -2,60 +2,15 @@
2575 // Use of this source code is governed by a BSD-style
2576 // license that can be found in the LICENSE file.
2577@@ -62,7 +62,7 @@
2578
2579 func (a dummyAddr) Network() string {
2580 return string(a)
2581-@@ -93,1289 +48,6 @@
2582+@@ -93,1325 +48,6 @@
2583 return nil
2584 }
2585
2586@@ -906,31 +906,50 @@
2587 -}
2588 -
2589 -type serverExpectTest struct {
2590-- contentLength int // of request body
2591+- contentLength int // of request body
2592+- chunked bool
2593 - expectation string // e.g. "100-continue"
2594 - readBody bool // whether handler should read the body (if false, sends StatusUnauthorized)
2595 - expectedResponse string // expected substring in first line of http response
2596 -}
2597 -
2598+-func expectTest(contentLength int, expectation string, readBody bool, expectedResponse string) serverExpectTest {
2599+- return serverExpectTest{
2600+- contentLength: contentLength,
2601+- expectation: expectation,
2602+- readBody: readBody,
2603+- expectedResponse: expectedResponse,
2604+- }
2605+-}
2606+-
2607 -var serverExpectTests = []serverExpectTest{
2608 - // Normal 100-continues, case-insensitive.
2609-- {100, "100-continue", true, "100 Continue"},
2610-- {100, "100-cOntInUE", true, "100 Continue"},
2611+- expectTest(100, "100-continue", true, "100 Continue"),
2612+- expectTest(100, "100-cOntInUE", true, "100 Continue"),
2613 -
2614 - // No 100-continue.
2615-- {100, "", true, "200 OK"},
2616+- expectTest(100, "", true, "200 OK"),
2617 -
2618 - // 100-continue but requesting client to deny us,
2619 - // so it never reads the body.
2620-- {100, "100-continue", false, "401 Unauthorized"},
2621+- expectTest(100, "100-continue", false, "401 Unauthorized"),
2622 - // Likewise without 100-continue:
2623-- {100, "", false, "401 Unauthorized"},
2624+- expectTest(100, "", false, "401 Unauthorized"),
2625 -
2626 - // Non-standard expectations are failures
2627-- {0, "a-pony", false, "417 Expectation Failed"},
2628+- expectTest(0, "a-pony", false, "417 Expectation Failed"),
2629 -
2630-- // Expect-100 requested but no body
2631-- {0, "100-continue", true, "400 Bad Request"},
2632+- // Expect-100 requested but no body (is apparently okay: Issue 7625)
2633+- expectTest(0, "100-continue", true, "200 OK"),
2634+- // Expect-100 requested but handler doesn't read the body
2635+- expectTest(0, "100-continue", false, "401 Unauthorized"),
2636+- // Expect-100 continue with no body, but a chunked body.
2637+- {
2638+- expectation: "100-continue",
2639+- readBody: true,
2640+- chunked: true,
2641+- expectedResponse: "100 Continue",
2642+- },
2643 -}
2644 -
2645 -// Tests that the server responds to the "Expect" request header
2646@@ -959,21 +978,38 @@
2647 -
2648 - // Only send the body immediately if we're acting like an HTTP client
2649 - // that doesn't send 100-continue expectations.
2650-- writeBody := test.contentLength > 0 && strings.ToLower(test.expectation) != "100-continue"
2651+- writeBody := test.contentLength != 0 && strings.ToLower(test.expectation) != "100-continue"
2652 -
2653 - go func() {
2654+- contentLen := fmt.Sprintf("Content-Length: %d", test.contentLength)
2655+- if test.chunked {
2656+- contentLen = "Transfer-Encoding: chunked"
2657+- }
2658 - _, err := fmt.Fprintf(conn, "POST /?readbody=%v HTTP/1.1\r\n"+
2659 - "Connection: close\r\n"+
2660-- "Content-Length: %d\r\n"+
2661+- "%s\r\n"+
2662 - "Expect: %s\r\nHost: foo\r\n\r\n",
2663-- test.readBody, test.contentLength, test.expectation)
2664+- test.readBody, contentLen, test.expectation)
2665 - if err != nil {
2666 - t.Errorf("On test %#v, error writing request headers: %v", test, err)
2667 - return
2668 - }
2669 - if writeBody {
2670+- var targ io.WriteCloser = struct {
2671+- io.Writer
2672+- io.Closer
2673+- }{
2674+- conn,
2675+- ioutil.NopCloser(nil),
2676+- }
2677+- if test.chunked {
2678+- targ = httputil.NewChunkedWriter(conn)
2679+- }
2680 - body := strings.Repeat("A", test.contentLength)
2681-- _, err = fmt.Fprint(conn, body)
2682+- _, err = fmt.Fprint(targ, body)
2683+- if err == nil {
2684+- err = targ.Close()
2685+- }
2686 - if err != nil {
2687 - if !test.readBody {
2688 - // Server likely already hung up on us.
2689@@ -1352,7 +1388,7 @@
2690 type neverEnding byte
2691
2692 func (b neverEnding) Read(p []byte) (n int, err error) {
2693-@@ -1384,1344 +56,3 @@
2694+@@ -1420,1392 +56,3 @@
2695 }
2696 return len(p), nil
2697 }
2698@@ -2080,7 +2116,7 @@
2699 - got := ht.rawResponse(req)
2700 - wantStatus := fmt.Sprintf("%d %s", code, StatusText(code))
2701 - if !strings.Contains(got, wantStatus) {
2702-- t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, req, got)
2703+- t.Errorf("Code %d: Wanted %q Modified for %q: %s", code, wantStatus, req, got)
2704 - } else if strings.Contains(got, "Content-Length") {
2705 - t.Errorf("Code %d: Got a Content-Length from %q: %s", code, req, got)
2706 - } else if strings.Contains(got, "stuff") {
2707@@ -2090,6 +2126,21 @@
2708 - }
2709 -}
2710 -
2711+-func TestContentTypeOkayOn204(t *testing.T) {
2712+- ht := newHandlerTest(HandlerFunc(func(w ResponseWriter, r *Request) {
2713+- w.Header().Set("Content-Length", "123") // suppressed
2714+- w.Header().Set("Content-Type", "foo/bar")
2715+- w.WriteHeader(204)
2716+- }))
2717+- got := ht.rawResponse("GET / HTTP/1.1")
2718+- if !strings.Contains(got, "Content-Type: foo/bar") {
2719+- t.Errorf("Response = %q; want Content-Type: foo/bar", got)
2720+- }
2721+- if strings.Contains(got, "Content-Length: 123") {
2722+- t.Errorf("Response = %q; don't want a Content-Length", got)
2723+- }
2724+-}
2725+-
2726 -// Issue 6995
2727 -// A server Handler can receive a Request, and then turn around and
2728 -// give a copy of that Request.Body out to the Transport (e.g. any
2729@@ -2261,7 +2312,7 @@
2730 - ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
2731 - ts.Config.ConnState = func(c net.Conn, state ConnState) {
2732 - if c == nil {
2733-- t.Error("nil conn seen in state %s", state)
2734+- t.Errorf("nil conn seen in state %s", state)
2735 - return
2736 - }
2737 - mu.Lock()
2738@@ -2397,6 +2448,39 @@
2739 - }
2740 -}
2741 -
2742+-// golang.org/issue/7856
2743+-func TestServerEmptyBodyRace(t *testing.T) {
2744+- defer afterTest(t)
2745+- var n int32
2746+- ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
2747+- atomic.AddInt32(&n, 1)
2748+- }))
2749+- defer ts.Close()
2750+- var wg sync.WaitGroup
2751+- const reqs = 20
2752+- for i := 0; i < reqs; i++ {
2753+- wg.Add(1)
2754+- go func() {
2755+- defer wg.Done()
2756+- res, err := Get(ts.URL)
2757+- if err != nil {
2758+- t.Error(err)
2759+- return
2760+- }
2761+- defer res.Body.Close()
2762+- _, err = io.Copy(ioutil.Discard, res.Body)
2763+- if err != nil {
2764+- t.Error(err)
2765+- return
2766+- }
2767+- }()
2768+- }
2769+- wg.Wait()
2770+- if got := atomic.LoadInt32(&n); got != reqs {
2771+- t.Errorf("handler ran %d times; want %d", got, reqs)
2772+- }
2773+-}
2774+-
2775 -func TestServerConnStateNew(t *testing.T) {
2776 - sawNew := false // if the test is buggy, we'll race on this variable.
2777 - srv := &Server{
2778@@ -2699,9 +2783,9 @@
2779 -}
2780
2781 === modified file 'http13client/server.go'
2782---- http13client/server.go 2014-03-19 20:20:19 +0000
2783-+++ http13client/server.go 2014-03-19 22:27:37 +0000
2784-@@ -2,1984 +2,17 @@
2785+--- http13client/server.go 2014-06-20 11:00:47 +0000
2786++++ http13client/server.go 2014-06-20 12:05:53 +0000
2787+@@ -2,1976 +2,16 @@
2788 // Use of this source code is governed by a BSD-style
2789 // license that can be found in the LICENSE file.
2790
2791@@ -2723,7 +2807,7 @@
2792 - "path"
2793 - "runtime"
2794 - "strconv"
2795- "strings"
2796+- "strings"
2797 "sync"
2798 - "sync/atomic"
2799 - "time"
2800@@ -3506,18 +3590,16 @@
2801 - }
2802 -
2803 - code := w.status
2804-- if !bodyAllowedForStatus(code) {
2805-- // Must not have body.
2806-- // RFC 2616 section 10.3.5: "the response MUST NOT include other entity-headers"
2807-- for _, k := range []string{"Content-Type", "Content-Length", "Transfer-Encoding"} {
2808-- delHeader(k)
2809-- }
2810-- } else {
2811+- if bodyAllowedForStatus(code) {
2812 - // If no content type, apply sniffing algorithm to body.
2813 - _, haveType := header["Content-Type"]
2814 - if !haveType {
2815 - setHeader.contentType = DetectContentType(p)
2816 - }
2817+- } else {
2818+- for _, k := range suppressedHeaders(code) {
2819+- delHeader(k)
2820+- }
2821 - }
2822 -
2823 - if _, ok := header["Date"]; !ok {
2824@@ -3865,16 +3947,10 @@
2825 - // Expect 100 Continue support
2826 - req := w.req
2827 - if req.expectsContinue() {
2828-- if req.ProtoAtLeast(1, 1) {
2829+- if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
2830 - // Wrap the Body reader with one that replies on the connection
2831 - req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
2832 - }
2833-- if req.ContentLength == 0 {
2834-- w.Header().Set("Connection", "close")
2835-- w.WriteHeader(StatusBadRequest)
2836-- w.finishRequest()
2837-- break
2838-- }
2839 - req.Header.Del("Expect")
2840 - } else if req.Header.get("Expect") != "" {
2841 - w.sendExpectationFailed()
2842@@ -4685,11 +4761,11 @@
2843 -}
2844 +)
2845
2846- // eofReader is a non-nil io.ReadCloser that always returns EOF.
2847- // It embeds a *strings.Reader so it still has a WriteTo method
2848-@@ -1992,28 +25,6 @@
2849- ioutil.NopCloser(nil),
2850- }
2851+ type eofReaderWithWriteTo struct{}
2852+
2853+@@ -1991,28 +31,6 @@
2854+ // Verify that an io.Copy from an eofReader won't require a buffer.
2855+ var _ io.WriterTo = eofReader
2856
2857 -// initNPNRequest is an HTTP handler that initializes certain
2858 -// uninitialized fields in its *Request. Such partially-initialized
2859
2860=== modified file 'http13client/_patches/fix_code.patch'
2861--- http13client/_patches/fix_code.patch 2014-03-19 23:13:58 +0000
2862+++ http13client/_patches/fix_code.patch 2014-07-02 13:12:36 +0000
2863@@ -1,6 +1,6 @@
2864 === modified file 'http13client/client.go'
2865---- http13client/client.go 2014-03-19 20:20:19 +0000
2866-+++ http13client/client.go 2014-03-19 22:27:37 +0000
2867+--- http13client/client.go 2014-06-20 11:00:47 +0000
2868++++ http13client/client.go 2014-06-20 12:05:53 +0000
2869 @@ -17,6 +17,7 @@
2870 "io/ioutil"
2871 "log"
2872@@ -18,7 +18,7 @@
2873
2874 // Timeout specifies a time limit for requests made by this
2875 // Client. The timeout includes connection time, any
2876-@@ -177,7 +178,7 @@
2877+@@ -184,7 +185,7 @@
2878 // Headers, leaving it uninitialized. We guarantee to the
2879 // Transport that this has been initialized, though.
2880 if req.Header == nil {
2881@@ -27,7 +27,7 @@
2882 }
2883
2884 if u := req.URL.User; u != nil {
2885-@@ -308,7 +309,7 @@
2886+@@ -316,7 +317,7 @@
2887 if ireq.Method == "POST" || ireq.Method == "PUT" {
2888 nreq.Method = "GET"
2889 }
2890@@ -38,8 +38,8 @@
2891 break
2892
2893 === modified file 'http13client/cookie.go'
2894---- http13client/cookie.go 2014-03-19 20:20:19 +0000
2895-+++ http13client/cookie.go 2014-03-19 22:27:37 +0000
2896+--- http13client/cookie.go 2014-06-20 11:00:47 +0000
2897++++ http13client/cookie.go 2014-06-20 12:05:53 +0000
2898 @@ -5,10 +5,9 @@
2899 package http
2900
2901@@ -94,7 +94,7 @@
2902 Name: name,
2903 Value: value,
2904 Raw: line,
2905-@@ -129,59 +108,12 @@
2906+@@ -125,59 +104,12 @@
2907 return cookies
2908 }
2909
2910@@ -156,7 +156,7 @@
2911 lines, ok := h["Cookie"]
2912 if !ok {
2913 return cookies
2914-@@ -213,7 +145,7 @@
2915+@@ -209,7 +141,7 @@
2916 if !success {
2917 continue
2918 }
2919@@ -167,8 +167,8 @@
2920 }
2921
2922 === modified file 'http13client/header.go'
2923---- http13client/header.go 2014-03-19 20:20:19 +0000
2924-+++ http13client/header.go 2014-03-19 22:27:37 +0000
2925+--- http13client/header.go 2014-06-20 11:00:47 +0000
2926++++ http13client/header.go 2014-06-20 12:00:22 +0000
2927 @@ -5,176 +5,9 @@
2928 package http
2929
2930@@ -348,8 +348,8 @@
2931 // token must be all lowercase.
2932
2933 === modified file 'http13client/request.go'
2934---- http13client/request.go 2014-03-19 20:20:19 +0000
2935-+++ http13client/request.go 2014-03-19 22:27:37 +0000
2936+--- http13client/request.go 2014-06-20 11:00:47 +0000
2937++++ http13client/request.go 2014-06-20 12:05:53 +0000
2938 @@ -16,6 +16,7 @@
2939 "io/ioutil"
2940 "mime"
2941@@ -358,25 +358,25 @@
2942 "net/textproto"
2943 "net/url"
2944 "strconv"
2945-@@ -103,7 +104,7 @@
2946- // The request parser implements this by canonicalizing the
2947- // name, making the first character and any characters
2948- // following a hyphen uppercase and the rest lowercase.
2949+@@ -121,7 +122,7 @@
2950+ // added and may override values in Header.
2951+ //
2952+ // See the documentation for the Request.Write method.
2953 - Header Header
2954 + Header http.Header
2955
2956 // Body is the request's body.
2957 //
2958-@@ -164,7 +165,7 @@
2959- // For server requests, Trailer is only populated after Body has been
2960- // closed or fully consumed.
2961- // Trailer support is only partially complete.
2962+@@ -199,7 +200,7 @@
2963+ // not mutate Trailer.
2964+ //
2965+ // Few HTTP clients, servers, or proxies support HTTP trailers.
2966 - Trailer Header
2967 + Trailer http.Header
2968
2969 // RemoteAddr allows HTTP servers and other software to record
2970 // the network address that sent the request, usually for
2971-@@ -204,7 +205,7 @@
2972+@@ -239,7 +240,7 @@
2973 }
2974
2975 // Cookies parses and returns the HTTP cookies sent with the request.
2976@@ -385,7 +385,7 @@
2977 return readCookies(r.Header, "")
2978 }
2979
2980-@@ -212,7 +213,7 @@
2981+@@ -247,7 +248,7 @@
2982
2983 // Cookie returns the named cookie provided in the request or
2984 // ErrNoCookie if not found.
2985@@ -394,7 +394,7 @@
2986 for _, c := range readCookies(r.Header, name) {
2987 return c, nil
2988 }
2989-@@ -223,7 +224,7 @@
2990+@@ -258,7 +259,7 @@
2991 // AddCookie does not attach more than one Cookie header field. That
2992 // means all cookies, if any, are written into the same line,
2993 // separated by semicolon.
2994@@ -403,7 +403,7 @@
2995 s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value))
2996 if c := r.Header.Get("Cookie"); c != "" {
2997 r.Header.Set("Cookie", c+"; "+s)
2998-@@ -326,7 +327,7 @@
2999+@@ -361,7 +362,7 @@
3000 }
3001
3002 // extraHeaders may be nil
3003@@ -412,7 +412,7 @@
3004 host := req.Host
3005 if host == "" {
3006 if req.URL == nil {
3007-@@ -456,7 +457,7 @@
3008+@@ -490,7 +491,7 @@
3009 Proto: "HTTP/1.1",
3010 ProtoMajor: 1,
3011 ProtoMinor: 1,
3012@@ -421,7 +421,7 @@
3013 Body: rc,
3014 Host: u.Host,
3015 }
3016-@@ -571,7 +572,7 @@
3017+@@ -605,7 +606,7 @@
3018 if err != nil {
3019 return nil, err
3020 }
3021@@ -430,7 +430,7 @@
3022
3023 // RFC2616: Must treat
3024 // GET /index.html HTTP/1.1
3025-@@ -582,7 +583,7 @@
3026+@@ -616,7 +617,7 @@
3027 // the same. In the second case, any Host line is ignored.
3028 req.Host = req.URL.Host
3029 if req.Host == "" {
3030@@ -439,7 +439,7 @@
3031 }
3032 delete(req.Header, "Host")
3033
3034-@@ -630,12 +631,12 @@
3035+@@ -638,12 +639,12 @@
3036 //
3037 // MaxBytesReader prevents clients from accidentally or maliciously
3038 // sending a large request and wasting server resources.
3039@@ -454,7 +454,7 @@
3040 r io.ReadCloser // underlying reader
3041 n int64 // max bytes remaining
3042 stopped bool
3043-@@ -645,9 +646,6 @@
3044+@@ -653,9 +654,6 @@
3045 if l.n <= 0 {
3046 if !l.stopped {
3047 l.stopped = true
3048@@ -464,7 +464,7 @@
3049 }
3050 return 0, errors.New("http: request body too large")
3051 }
3052-@@ -852,16 +850,16 @@
3053+@@ -858,18 +856,18 @@
3054 }
3055
3056 func (r *Request) expectsContinue() bool {
3057@@ -484,11 +484,13 @@
3058 - return hasToken(r.Header.get("Connection"), "close")
3059 + return hasToken(r.Header.Get("Connection"), "close")
3060 }
3061+
3062+ func (r *Request) closeBody() {
3063
3064 === modified file 'http13client/response.go'
3065---- http13client/response.go 2014-03-19 20:20:19 +0000
3066-+++ http13client/response.go 2014-03-19 22:27:37 +0000
3067-@@ -11,6 +11,7 @@
3068+--- http13client/response.go 2014-06-20 11:00:47 +0000
3069++++ http13client/response.go 2014-06-20 12:05:53 +0000
3070+@@ -12,6 +12,7 @@
3071 "crypto/tls"
3072 "errors"
3073 "io"
3074@@ -496,7 +498,7 @@
3075 "net/textproto"
3076 "net/url"
3077 "strconv"
3078-@@ -40,7 +41,7 @@
3079+@@ -41,7 +42,7 @@
3080 // omitted from Header.
3081 //
3082 // Keys in the map are canonicalized (see CanonicalHeaderKey).
3083@@ -505,7 +507,7 @@
3084
3085 // Body represents the response body.
3086 //
3087-@@ -69,7 +70,7 @@
3088+@@ -71,7 +72,7 @@
3089
3090 // Trailer maps trailer keys to values, in the same
3091 // format as the header.
3092@@ -514,7 +516,7 @@
3093
3094 // The Request that was sent to obtain this Response.
3095 // Request's Body is nil (having already been consumed).
3096-@@ -84,7 +85,7 @@
3097+@@ -86,7 +87,7 @@
3098 }
3099
3100 // Cookies parses and returns the cookies set in the Set-Cookie headers.
3101@@ -523,7 +525,7 @@
3102 return readSetCookies(r.Header)
3103 }
3104
3105-@@ -153,7 +154,7 @@
3106+@@ -155,7 +156,7 @@
3107 }
3108 return nil, err
3109 }
3110@@ -532,7 +534,7 @@
3111
3112 fixPragmaCacheControl(resp.Header)
3113
3114-@@ -169,7 +170,7 @@
3115+@@ -171,7 +172,7 @@
3116 // Pragma: no-cache
3117 // like
3118 // Cache-Control: no-cache
3119@@ -543,17 +545,17 @@
3120 header["Cache-Control"] = []string{"no-cache"}
3121
3122 === modified file 'http13client/transfer.go'
3123---- http13client/transfer.go 2014-03-19 20:20:19 +0000
3124-+++ http13client/transfer.go 2014-03-19 22:27:37 +0000
3125+--- http13client/transfer.go 2014-06-20 11:00:47 +0000
3126++++ http13client/transfer.go 2014-06-20 12:05:53 +0000
3127 @@ -11,6 +11,7 @@
3128 "fmt"
3129 "io"
3130 "io/ioutil"
3131 + "net/http"
3132 "net/textproto"
3133+ "sort"
3134 "strconv"
3135- "strings"
3136-@@ -36,7 +37,7 @@
3137+@@ -37,7 +38,7 @@
3138 ContentLength int64 // -1 means unknown, 0 means exactly none
3139 Close bool
3140 TransferEncoding []string
3141@@ -562,16 +564,16 @@
3142 }
3143
3144 func newTransferWriter(r interface{}) (t *transferWriter, err error) {
3145-@@ -174,7 +175,7 @@
3146- io.WriteString(w, "Trailer: ")
3147- needComma := false
3148+@@ -171,7 +172,7 @@
3149+ if t.Trailer != nil {
3150+ keys := make([]string, 0, len(t.Trailer))
3151 for k := range t.Trailer {
3152 - k = CanonicalHeaderKey(k)
3153 + k = http.CanonicalHeaderKey(k)
3154 switch k {
3155 case "Transfer-Encoding", "Trailer", "Content-Length":
3156 return &badStringError{"invalid Trailer key", k}
3157-@@ -237,7 +238,7 @@
3158+@@ -243,7 +244,7 @@
3159
3160 type transferReader struct {
3161 // Input
3162@@ -580,7 +582,7 @@
3163 StatusCode int
3164 RequestMethod string
3165 ProtoMajor int
3166-@@ -247,7 +248,7 @@
3167+@@ -253,7 +254,7 @@
3168 ContentLength int64
3169 TransferEncoding []string
3170 Close bool
3171@@ -589,7 +591,7 @@
3172 }
3173
3174 // bodyAllowedForStatus reports whether a given response status code
3175-@@ -308,7 +309,7 @@
3176+@@ -330,7 +331,7 @@
3177 return err
3178 }
3179 if isResponse && t.RequestMethod == "HEAD" {
3180@@ -598,7 +600,7 @@
3181 return err
3182 } else {
3183 t.ContentLength = n
3184-@@ -386,7 +387,7 @@
3185+@@ -408,7 +409,7 @@
3186 func isIdentity(te []string) bool { return len(te) == 1 && te[0] == "identity" }
3187
3188 // Sanitize transfer encoding
3189@@ -607,7 +609,7 @@
3190 raw, present := header["Transfer-Encoding"]
3191 if !present {
3192 return nil, nil
3193-@@ -429,7 +430,7 @@
3194+@@ -451,7 +452,7 @@
3195 // Determine the expected body length, using RFC 2616 Section 4.4. This
3196 // function is not a method, because ultimately it should be shared by
3197 // ReadResponse and ReadRequest.
3198@@ -616,7 +618,7 @@
3199
3200 // Logic based on response type or status
3201 if noBodyExpected(requestMethod) {
3202-@@ -449,7 +450,7 @@
3203+@@ -471,7 +472,7 @@
3204 }
3205
3206 // Logic based on Content-Length
3207@@ -625,7 +627,7 @@
3208 if cl != "" {
3209 n, err := parseContentLength(cl)
3210 if err != nil {
3211-@@ -475,18 +476,18 @@
3212+@@ -497,18 +498,18 @@
3213 // Determine whether to hang up after sending a request and body, or
3214 // receiving a response and body
3215 // 'header' is the request headers
3216@@ -647,7 +649,7 @@
3217 header.Del("Connection")
3218 return true
3219 }
3220-@@ -495,17 +496,17 @@
3221+@@ -517,17 +518,17 @@
3222 }
3223
3224 // Parse the trailer header
3225@@ -669,22 +671,28 @@
3226 switch key {
3227 case "Transfer-Encoding", "Trailer", "Content-Length":
3228 return nil, &badStringError{"bad trailer key", key}
3229-@@ -642,9 +643,9 @@
3230+@@ -664,14 +665,14 @@
3231 }
3232 switch rr := b.hdr.(type) {
3233 case *Request:
3234-- rr.Trailer = Header(hdr)
3235-+ rr.Trailer = http.Header(hdr)
3236+- mergeSetHeader(&rr.Trailer, Header(hdr))
3237++ mergeSetHeader(&rr.Trailer, http.Header(hdr))
3238 case *Response:
3239-- rr.Trailer = Header(hdr)
3240-+ rr.Trailer = http.Header(hdr)
3241+- mergeSetHeader(&rr.Trailer, Header(hdr))
3242++ mergeSetHeader(&rr.Trailer, http.Header(hdr))
3243 }
3244 return nil
3245 }
3246+
3247+-func mergeSetHeader(dst *Header, src Header) {
3248++func mergeSetHeader(dst *http.Header, src http.Header) {
3249+ if *dst == nil {
3250+ *dst = src
3251+ return
3252
3253 === modified file 'http13client/transport.go'
3254---- http13client/transport.go 2014-03-19 20:20:19 +0000
3255-+++ http13client/transport.go 2014-03-19 22:27:37 +0000
3256+--- http13client/transport.go 2014-06-20 11:00:47 +0000
3257++++ http13client/transport.go 2014-06-20 12:05:53 +0000
3258 @@ -18,6 +18,7 @@
3259 "io"
3260 "log"
3261@@ -693,7 +701,7 @@
3262 "net/url"
3263 "os"
3264 "strings"
3265-@@ -144,12 +145,12 @@
3266+@@ -147,12 +148,12 @@
3267 // optional extra headers to write.
3268 type transportRequest struct {
3269 *Request // original request, not to be mutated
3270@@ -709,7 +717,7 @@
3271 }
3272 return tr.extra
3273 }
3274-@@ -512,7 +513,7 @@
3275+@@ -519,7 +520,7 @@
3276 case cm.targetScheme == "http":
3277 pconn.isProxy = true
3278 if pa != "" {
3279@@ -718,7 +726,7 @@
3280 h.Set("Proxy-Authorization", pa)
3281 }
3282 }
3283-@@ -521,7 +522,7 @@
3284+@@ -528,7 +529,7 @@
3285 Method: "CONNECT",
3286 URL: &url.URL{Opaque: cm.targetAddr},
3287 Host: cm.targetAddr,
3288@@ -727,7 +735,7 @@
3289 }
3290 if pa != "" {
3291 connectReq.Header.Set("Proxy-Authorization", pa)
3292-@@ -735,7 +736,7 @@
3293+@@ -748,7 +749,7 @@
3294 // mutateHeaderFunc is an optional func to modify extra
3295 // headers on each outbound request before it's written. (the
3296 // original Request given to RoundTrip is not modified)
3297@@ -735,5 +743,5 @@
3298 + mutateHeaderFunc func(http.Header)
3299 }
3300
3301- func (pc *persistConn) isBroken() bool {
3302+ // isBroken reports whether this connection is in a known broken state.
3303
3304
3305=== modified file 'http13client/_patches/fix_status.patch'
3306--- http13client/_patches/fix_status.patch 2014-03-19 23:43:25 +0000
3307+++ http13client/_patches/fix_status.patch 2014-07-02 13:12:36 +0000
3308@@ -1,7 +1,7 @@
3309 === modified file 'http13client/client.go'
3310---- http13client/client.go 2014-03-19 23:13:58 +0000
3311-+++ http13client/client.go 2014-03-19 23:38:11 +0000
3312-@@ -210,7 +210,7 @@
3313+--- http13client/client.go 2014-06-20 12:46:25 +0000
3314++++ http13client/client.go 2014-06-20 12:46:45 +0000
3315+@@ -217,7 +217,7 @@
3316 // automatically redirect.
3317 func shouldRedirectGet(statusCode int) bool {
3318 switch statusCode {
3319@@ -10,7 +10,7 @@
3320 return true
3321 }
3322 return false
3323-@@ -220,7 +220,7 @@
3324+@@ -227,7 +227,7 @@
3325 // automatically redirect.
3326 func shouldRedirectPost(statusCode int) bool {
3327 switch statusCode {
3328@@ -21,9 +21,9 @@
3329 return false
3330
3331 === modified file 'http13client/client_test.go'
3332---- http13client/client_test.go 2014-03-19 23:13:58 +0000
3333-+++ http13client/client_test.go 2014-03-19 23:39:48 +0000
3334-@@ -202,7 +202,7 @@
3335+--- http13client/client_test.go 2014-06-20 12:46:25 +0000
3336++++ http13client/client_test.go 2014-06-20 12:46:45 +0000
3337+@@ -204,7 +204,7 @@
3338 }
3339 }
3340 if n < 15 {
3341@@ -32,7 +32,7 @@
3342 return
3343 }
3344 fmt.Fprintf(w, "n=%d", n)
3345-@@ -324,7 +324,7 @@
3346+@@ -326,7 +326,7 @@
3347 }
3348 if r.URL.Path == "/" {
3349 http.SetCookie(w, expectedCookies[1])
3350@@ -41,7 +41,7 @@
3351 } else {
3352 http.SetCookie(w, expectedCookies[2])
3353 w.Write([]byte("hello"))
3354-@@ -783,7 +783,7 @@
3355+@@ -785,7 +785,7 @@
3356 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3357 if r.URL.Path == "/" {
3358 sawRoot <- true
3359@@ -50,7 +50,7 @@
3360 return
3361 }
3362 if r.URL.Path == "/slow" {
3363-@@ -844,7 +844,7 @@
3364+@@ -846,7 +846,7 @@
3365 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3366 saw <- r.RemoteAddr
3367 if r.URL.Path == "/" {
3368@@ -61,9 +61,9 @@
3369 defer ts.Close()
3370
3371 === modified file 'http13client/request_test.go'
3372---- http13client/request_test.go 2014-03-19 23:13:58 +0000
3373-+++ http13client/request_test.go 2014-03-19 23:40:12 +0000
3374-@@ -164,11 +164,11 @@
3375+--- http13client/request_test.go 2014-06-20 12:46:25 +0000
3376++++ http13client/request_test.go 2014-06-20 12:46:45 +0000
3377+@@ -182,11 +182,11 @@
3378 switch r.URL.Path {
3379 case "/":
3380 w.Header().Set("Location", "/foo/")
3381@@ -79,9 +79,9 @@
3382 defer ts.Close()
3383
3384 === modified file 'http13client/response.go'
3385---- http13client/response.go 2014-03-19 23:13:58 +0000
3386-+++ http13client/response.go 2014-03-19 23:38:56 +0000
3387-@@ -204,9 +204,8 @@
3388+--- http13client/response.go 2014-06-20 12:46:25 +0000
3389++++ http13client/response.go 2014-06-20 12:46:45 +0000
3390+@@ -205,9 +205,8 @@
3391 // Status line
3392 text := r.Status
3393 if text == "" {
3394@@ -94,10 +94,23 @@
3395 }
3396 }
3397
3398+=== modified file 'http13client/responsewrite_test.go'
3399+--- http13client/responsewrite_test.go 2014-06-20 12:46:25 +0000
3400++++ http13client/responsewrite_test.go 2014-06-20 12:47:05 +0000
3401+@@ -197,7 +197,7 @@
3402+ // there were two.
3403+ {
3404+ Response{
3405+- StatusCode: StatusOK,
3406++ StatusCode: http.StatusOK,
3407+ ProtoMajor: 1,
3408+ ProtoMinor: 1,
3409+ Request: &Request{Method: "POST"},
3410+
3411 === modified file 'http13client/transport_test.go'
3412---- http13client/transport_test.go 2014-03-19 23:13:58 +0000
3413-+++ http13client/transport_test.go 2014-03-19 23:40:39 +0000
3414-@@ -968,7 +968,7 @@
3415+--- http13client/transport_test.go 2014-06-20 12:46:25 +0000
3416++++ http13client/transport_test.go 2014-06-20 12:46:45 +0000
3417+@@ -1004,7 +1004,7 @@
3418 defer afterTest(t)
3419 const deniedMsg = "sorry, denied."
3420 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3421@@ -106,7 +119,7 @@
3422 }))
3423 defer ts.Close()
3424 tr := &Transport{}
3425-@@ -992,7 +992,7 @@
3426+@@ -1028,7 +1028,7 @@
3427 func TestChunkedNoContent(t *testing.T) {
3428 defer afterTest(t)
3429 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3430
3431=== modified file 'http13client/_patches/fix_tests.patch'
3432--- http13client/_patches/fix_tests.patch 2014-03-19 23:13:58 +0000
3433+++ http13client/_patches/fix_tests.patch 2014-07-02 13:12:36 +0000
3434@@ -1,6 +1,6 @@
3435 === modified file 'http13client/client_test.go'
3436---- http13client/client_test.go 2014-03-19 21:38:56 +0000
3437-+++ http13client/client_test.go 2014-03-19 22:27:37 +0000
3438+--- http13client/client_test.go 2014-06-20 11:00:47 +0000
3439++++ http13client/client_test.go 2014-06-20 12:05:53 +0000
3440 @@ -15,8 +15,8 @@
3441 "fmt"
3442 "io"
3443@@ -11,7 +11,7 @@
3444 . "launchpad.net/ubuntu-push/http13client"
3445 "net/http/httptest"
3446 "net/url"
3447-@@ -27,7 +27,7 @@
3448+@@ -29,7 +29,7 @@
3449 "time"
3450 )
3451
3452@@ -20,7 +20,7 @@
3453 w.Header().Set("Last-Modified", "sometime")
3454 fmt.Fprintf(w, "User-agent: go\nDisallow: /something/")
3455 })
3456-@@ -193,7 +193,7 @@
3457+@@ -195,7 +195,7 @@
3458 func TestClientRedirects(t *testing.T) {
3459 defer afterTest(t)
3460 var ts *httptest.Server
3461@@ -29,7 +29,7 @@
3462 n, _ := strconv.Atoi(r.FormValue("n"))
3463 // Test Referer header. (7 is arbitrary position to test at)
3464 if n == 7 {
3465-@@ -202,7 +202,7 @@
3466+@@ -204,7 +204,7 @@
3467 }
3468 }
3469 if n < 15 {
3470@@ -38,7 +38,7 @@
3471 return
3472 }
3473 fmt.Fprintf(w, "n=%d", n)
3474-@@ -271,7 +271,7 @@
3475+@@ -273,7 +273,7 @@
3476 bytes.Buffer
3477 }
3478 var ts *httptest.Server
3479@@ -47,7 +47,7 @@
3480 log.Lock()
3481 fmt.Fprintf(&log.Buffer, "%s %s ", r.Method, r.RequestURI)
3482 log.Unlock()
3483-@@ -312,21 +312,21 @@
3484+@@ -314,21 +314,21 @@
3485 }
3486 }
3487
3488@@ -75,7 +75,7 @@
3489 w.Write([]byte("hello"))
3490 }
3491 })
3492-@@ -334,7 +334,7 @@
3493+@@ -336,7 +336,7 @@
3494 func TestClientSendsCookieFromJar(t *testing.T) {
3495 tr := &recordingTransport{}
3496 client := &Client{Transport: tr}
3497@@ -84,7 +84,7 @@
3498 us := "http://dummy.faketld/"
3499 u, _ := url.Parse(us)
3500 client.Jar.SetCookies(u, expectedCookies)
3501-@@ -364,19 +364,19 @@
3502+@@ -366,19 +366,19 @@
3503 // scope of all cookies.
3504 type TestJar struct {
3505 m sync.Mutex
3506@@ -108,7 +108,7 @@
3507 j.m.Lock()
3508 defer j.m.Unlock()
3509 return j.perURL[u.Host]
3510-@@ -391,7 +391,7 @@
3511+@@ -393,7 +393,7 @@
3512 Jar: new(TestJar),
3513 }
3514 u, _ := url.Parse(ts.URL)
3515@@ -117,7 +117,7 @@
3516 resp, err := c.Get(ts.URL)
3517 if err != nil {
3518 t.Fatalf("Get: %v", err)
3519-@@ -400,7 +400,7 @@
3520+@@ -402,7 +402,7 @@
3521 matchReturnedCookies(t, expectedCookies, resp.Cookies())
3522 }
3523
3524@@ -126,7 +126,7 @@
3525 if len(given) != len(expected) {
3526 t.Logf("Received cookies: %v", given)
3527 t.Errorf("Expected %d cookies, got %d", len(expected), len(given))
3528-@@ -421,14 +421,14 @@
3529+@@ -423,14 +423,14 @@
3530
3531 func TestJarCalls(t *testing.T) {
3532 defer afterTest(t)
3533@@ -144,7 +144,7 @@
3534 }
3535 }))
3536 defer ts.Close()
3537-@@ -468,11 +468,11 @@
3538+@@ -470,11 +470,11 @@
3539 log bytes.Buffer
3540 }
3541
3542@@ -158,7 +158,7 @@
3543 j.logf("Cookies(%q)\n", u)
3544 return nil
3545 }
3546-@@ -486,11 +486,11 @@
3547+@@ -488,11 +488,11 @@
3548 func TestStreamingGet(t *testing.T) {
3549 defer afterTest(t)
3550 say := make(chan string)
3551@@ -173,7 +173,7 @@
3552 }
3553 }))
3554 defer ts.Close()
3555-@@ -536,7 +536,7 @@
3556+@@ -538,7 +538,7 @@
3557 // don't send a TCP packet per line of the http request + body.
3558 func TestClientWrites(t *testing.T) {
3559 defer afterTest(t)
3560@@ -182,7 +182,7 @@
3561 }))
3562 defer ts.Close()
3563
3564-@@ -568,46 +568,6 @@
3565+@@ -570,46 +570,6 @@
3566 }
3567 }
3568
3569@@ -229,7 +229,7 @@
3570 func TestClientErrorWithRequestURI(t *testing.T) {
3571 defer afterTest(t)
3572 req, _ := NewRequest("GET", "http://localhost:1234/", nil)
3573-@@ -639,7 +599,7 @@
3574+@@ -641,7 +601,7 @@
3575
3576 func TestClientWithCorrectTLSServerName(t *testing.T) {
3577 defer afterTest(t)
3578@@ -238,7 +238,7 @@
3579 if r.TLS.ServerName != "127.0.0.1" {
3580 t.Errorf("expected client to set ServerName 127.0.0.1, got: %q", r.TLS.ServerName)
3581 }
3582-@@ -652,33 +612,6 @@
3583+@@ -654,33 +614,6 @@
3584 }
3585 }
3586
3587@@ -272,7 +272,7 @@
3588 // Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName
3589 // when not empty.
3590 //
3591-@@ -690,7 +623,7 @@
3592+@@ -692,7 +625,7 @@
3593 // The httptest.Server has a cert with "example.com" as its name.
3594 func TestTransportUsesTLSConfigServerName(t *testing.T) {
3595 defer afterTest(t)
3596@@ -281,7 +281,7 @@
3597 w.Write([]byte("Hello"))
3598 }))
3599 defer ts.Close()
3600-@@ -711,7 +644,7 @@
3601+@@ -713,7 +646,7 @@
3602
3603 func TestResponseSetsTLSConnectionState(t *testing.T) {
3604 defer afterTest(t)
3605@@ -290,7 +290,7 @@
3606 w.Write([]byte("Hello"))
3607 }))
3608 defer ts.Close()
3609-@@ -739,7 +672,7 @@
3610+@@ -741,7 +674,7 @@
3611 // Verify Response.ContentLength is populated. http://golang.org/issue/4126
3612 func TestClientHeadContentLength(t *testing.T) {
3613 defer afterTest(t)
3614@@ -299,7 +299,7 @@
3615 if v := r.FormValue("cl"); v != "" {
3616 w.Header().Set("Content-Length", v)
3617 }
3618-@@ -775,7 +708,7 @@
3619+@@ -777,7 +710,7 @@
3620 func TestEmptyPasswordAuth(t *testing.T) {
3621 defer afterTest(t)
3622 gopher := "gopher"
3623@@ -308,7 +308,7 @@
3624 auth := r.Header.Get("Authorization")
3625 if strings.HasPrefix(auth, "Basic ") {
3626 encoded := auth[6:]
3627-@@ -847,15 +780,15 @@
3628+@@ -849,15 +782,15 @@
3629 defer afterTest(t)
3630 sawRoot := make(chan bool, 1)
3631 sawSlow := make(chan bool, 1)
3632@@ -327,7 +327,7 @@
3633 sawSlow <- true
3634 time.Sleep(2 * time.Second)
3635 return
3636-@@ -908,10 +841,10 @@
3637+@@ -910,10 +843,10 @@
3638 func TestClientRedirectEatsBody(t *testing.T) {
3639 defer afterTest(t)
3640 saw := make(chan string, 2)
3641@@ -340,233 +340,50 @@
3642 }
3643 }))
3644 defer ts.Close()
3645-
3646-=== modified file 'http13client/cookie_test.go'
3647---- http13client/cookie_test.go 2014-03-19 20:20:19 +0000
3648-+++ http13client/cookie_test.go 2014-03-19 22:27:37 +0000
3649-@@ -9,6 +9,7 @@
3650- "encoding/json"
3651- "fmt"
3652- "log"
3653-+ "net/http"
3654- "os"
3655- "reflect"
3656- "strings"
3657-@@ -17,39 +18,39 @@
3658- )
3659-
3660- var writeSetCookiesTests = []struct {
3661-- Cookie *Cookie
3662-+ Cookie *http.Cookie
3663- Raw string
3664- }{
3665- {
3666-- &Cookie{Name: "cookie-1", Value: "v$1"},
3667-+ &http.Cookie{Name: "cookie-1", Value: "v$1"},
3668- "cookie-1=v$1",
3669- },
3670- {
3671-- &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600},
3672-+ &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600},
3673- "cookie-2=two; Max-Age=3600",
3674- },
3675- {
3676-- &Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"},
3677-+ &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"},
3678- "cookie-3=three; Domain=example.com",
3679- },
3680- {
3681-- &Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},
3682-+ &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},
3683- "cookie-4=four; Path=/restricted/",
3684- },
3685- {
3686-- &Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"},
3687-+ &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"},
3688- "cookie-5=five",
3689- },
3690- {
3691-- &Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"},
3692-+ &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"},
3693- "cookie-6=six",
3694- },
3695- {
3696-- &Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"},
3697-+ &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"},
3698- "cookie-7=seven; Domain=127.0.0.1",
3699- },
3700- {
3701-- &Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
3702-+ &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
3703- "cookie-8=eight",
3704- },
3705- }
3706-@@ -71,10 +72,10 @@
3707- }
3708- }
3709-
3710--type headerOnlyResponseWriter Header
3711-+type headerOnlyResponseWriter http.Header
3712-
3713--func (ho headerOnlyResponseWriter) Header() Header {
3714-- return Header(ho)
3715-+func (ho headerOnlyResponseWriter) Header() http.Header {
3716-+ return http.Header(ho)
3717- }
3718-
3719- func (ho headerOnlyResponseWriter) Write([]byte) (int, error) {
3720-@@ -86,9 +87,9 @@
3721- }
3722-
3723- func TestSetCookie(t *testing.T) {
3724-- m := make(Header)
3725-- SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"})
3726-- SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600})
3727-+ m := make(http.Header)
3728-+ http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"})
3729-+ http.SetCookie(headerOnlyResponseWriter(m), &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600})
3730- if l := len(m["Set-Cookie"]); l != 2 {
3731- t.Fatalf("expected %d cookies, got %d", 2, l)
3732- }
3733-@@ -101,19 +102,19 @@
3734- }
3735-
3736- var addCookieTests = []struct {
3737-- Cookies []*Cookie
3738-+ Cookies []*http.Cookie
3739- Raw string
3740- }{
3741- {
3742-- []*Cookie{},
3743-+ []*http.Cookie{},
3744- "",
3745- },
3746- {
3747-- []*Cookie{{Name: "cookie-1", Value: "v$1"}},
3748-+ []*http.Cookie{{Name: "cookie-1", Value: "v$1"}},
3749- "cookie-1=v$1",
3750- },
3751- {
3752-- []*Cookie{
3753-+ []*http.Cookie{
3754- {Name: "cookie-1", Value: "v$1"},
3755- {Name: "cookie-2", Value: "v$2"},
3756- {Name: "cookie-3", Value: "v$3"},
3757-@@ -136,16 +137,16 @@
3758- }
3759-
3760- var readSetCookiesTests = []struct {
3761-- Header Header
3762-- Cookies []*Cookie
3763-+ Header http.Header
3764-+ Cookies []*http.Cookie
3765- }{
3766- {
3767-- Header{"Set-Cookie": {"Cookie-1=v$1"}},
3768-- []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
3769-+ http.Header{"Set-Cookie": {"Cookie-1=v$1"}},
3770-+ []*http.Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
3771- },
3772- {
3773-- Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
3774-- []*Cookie{{
3775-+ http.Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
3776-+ []*http.Cookie{{
3777- Name: "NID",
3778- Value: "99=YsDT5i3E-CXax-",
3779- Path: "/",
3780-@@ -157,8 +158,8 @@
3781- }},
3782- },
3783- {
3784-- Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
3785-- []*Cookie{{
3786-+ http.Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
3787-+ []*http.Cookie{{
3788- Name: ".ASPXAUTH",
3789- Value: "7E3AA",
3790- Path: "/",
3791-@@ -169,8 +170,8 @@
3792- }},
3793- },
3794- {
3795-- Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
3796-- []*Cookie{{
3797-+ http.Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
3798-+ []*http.Cookie{{
3799- Name: "ASP.NET_SessionId",
3800- Value: "foo",
3801- Path: "/",
3802-@@ -207,37 +208,37 @@
3803- }
3804-
3805- var readCookiesTests = []struct {
3806-- Header Header
3807-+ Header http.Header
3808- Filter string
3809-- Cookies []*Cookie
3810-+ Cookies []*http.Cookie
3811- }{
3812- {
3813-- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
3814-- "",
3815-- []*Cookie{
3816-- {Name: "Cookie-1", Value: "v$1"},
3817-- {Name: "c2", Value: "v2"},
3818-- },
3819-- },
3820-- {
3821-- Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
3822-- "c2",
3823-- []*Cookie{
3824-- {Name: "c2", Value: "v2"},
3825-- },
3826-- },
3827-- {
3828-- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
3829-- "",
3830-- []*Cookie{
3831-- {Name: "Cookie-1", Value: "v$1"},
3832-- {Name: "c2", Value: "v2"},
3833-- },
3834-- },
3835-- {
3836-- Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
3837-- "c2",
3838-- []*Cookie{
3839-+ http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
3840-+ "",
3841-+ []*http.Cookie{
3842-+ {Name: "Cookie-1", Value: "v$1"},
3843-+ {Name: "c2", Value: "v2"},
3844-+ },
3845-+ },
3846-+ {
3847-+ http.Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
3848-+ "c2",
3849-+ []*http.Cookie{
3850-+ {Name: "c2", Value: "v2"},
3851-+ },
3852-+ },
3853-+ {
3854-+ http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
3855-+ "",
3856-+ []*http.Cookie{
3857-+ {Name: "Cookie-1", Value: "v$1"},
3858-+ {Name: "c2", Value: "v2"},
3859-+ },
3860-+ },
3861-+ {
3862-+ http.Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
3863-+ "c2",
3864-+ []*http.Cookie{
3865- {Name: "c2", Value: "v2"},
3866- },
3867- },
3868+@@ -957,7 +890,7 @@
3869+
3870+ func TestClientTrailers(t *testing.T) {
3871+ defer afterTest(t)
3872+- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
3873++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3874+ w.Header().Set("Connection", "close")
3875+ w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B")
3876+ w.Header().Add("Trailer", "Server-Trailer-C")
3877+@@ -992,9 +925,9 @@
3878+ // trailers to be sent, if and only if they were
3879+ // previously declared with w.Header().Set("Trailer",
3880+ // ..keys..)
3881+- w.(Flusher).Flush()
3882+- conn, buf, _ := w.(Hijacker).Hijack()
3883+- t := Header{}
3884++ w.(http.Flusher).Flush()
3885++ conn, buf, _ := w.(http.Hijacker).Hijack()
3886++ t := http.Header{}
3887+ t.Set("Server-Trailer-A", "valuea")
3888+ t.Set("Server-Trailer-C", "valuec") // skipping B
3889+ buf.WriteString("0\r\n") // eof
3890+@@ -1015,7 +948,7 @@
3891+ req.Trailer["Client-Trailer-B"] = []string{"valueb"}
3892+ }),
3893+ ))
3894+- req.Trailer = Header{
3895++ req.Trailer = http.Header{
3896+ "Client-Trailer-A": nil, // to be set later
3897+ "Client-Trailer-B": nil, // to be set later
3898+ }
3899+@@ -1027,7 +960,7 @@
3900+ if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil {
3901+ t.Error(err)
3902+ }
3903+- want := Header{
3904++ want := http.Header{
3905+ "Server-Trailer-A": []string{"valuea"},
3906+ "Server-Trailer-B": nil,
3907+ "Server-Trailer-C": []string{"valuec"},
3908
3909 === modified file 'http13client/export_test.go'
3910---- http13client/export_test.go 2014-03-19 20:20:19 +0000
3911-+++ http13client/export_test.go 2014-03-19 22:27:37 +0000
3912+--- http13client/export_test.go 2014-06-20 11:00:47 +0000
3913++++ http13client/export_test.go 2014-06-20 12:00:22 +0000
3914 @@ -9,15 +9,12 @@
3915
3916 import (
3917@@ -599,8 +416,8 @@
3918 noProxyEnv.reset()
3919
3920 === modified file 'http13client/header_test.go'
3921---- http13client/header_test.go 2014-03-19 20:20:19 +0000
3922-+++ http13client/header_test.go 2014-03-19 22:27:37 +0000
3923+--- http13client/header_test.go 2014-06-20 11:00:47 +0000
3924++++ http13client/header_test.go 2014-06-20 12:00:22 +0000
3925 @@ -6,19 +6,20 @@
3926
3927 import (
3928@@ -731,8 +548,8 @@
3929 }
3930
3931 === modified file 'http13client/npn_test.go'
3932---- http13client/npn_test.go 2014-03-19 21:38:56 +0000
3933-+++ http13client/npn_test.go 2014-03-19 22:27:37 +0000
3934+--- http13client/npn_test.go 2014-06-20 11:00:47 +0000
3935++++ http13client/npn_test.go 2014-06-20 12:05:53 +0000
3936 @@ -11,13 +11,14 @@
3937 "io"
3938 "io/ioutil"
3939@@ -792,8 +609,8 @@
3940 func (w http09Writer) WriteHeader(int) {} // no headers
3941
3942 === modified file 'http13client/readrequest_test.go'
3943---- http13client/readrequest_test.go 2014-03-19 20:20:19 +0000
3944-+++ http13client/readrequest_test.go 2014-03-19 22:27:37 +0000
3945+--- http13client/readrequest_test.go 2014-06-20 11:00:47 +0000
3946++++ http13client/readrequest_test.go 2014-06-20 12:00:22 +0000
3947 @@ -9,6 +9,7 @@
3948 "bytes"
3949 "fmt"
3950@@ -909,8 +726,8 @@
3951 Close: false,
3952
3953 === modified file 'http13client/request_test.go'
3954---- http13client/request_test.go 2014-03-19 21:38:56 +0000
3955-+++ http13client/request_test.go 2014-03-19 22:27:37 +0000
3956+--- http13client/request_test.go 2014-06-20 11:00:47 +0000
3957++++ http13client/request_test.go 2014-06-20 12:05:53 +0000
3958 @@ -12,6 +12,7 @@
3959 "io/ioutil"
3960 "mime/multipart"
3961@@ -945,8 +762,26 @@
3962 + req.Header = http.Header{"Content-Type": {"text/plain"}}
3963 multipart, err = req.MultipartReader()
3964 if multipart != nil {
3965- t.Errorf("unexpected multipart for text/plain")
3966-@@ -159,7 +160,7 @@
3967+ t.Error("unexpected multipart for text/plain")
3968+@@ -161,7 +162,7 @@
3969+ func TestParseMultipartForm(t *testing.T) {
3970+ req := &Request{
3971+ Method: "POST",
3972+- Header: Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}},
3973++ Header: http.Header{"Content-Type": {`multipart/form-data; boundary="foo123"`}},
3974+ Body: ioutil.NopCloser(new(bytes.Buffer)),
3975+ }
3976+ err := req.ParseMultipartForm(25)
3977+@@ -169,7 +170,7 @@
3978+ t.Error("expected multipart EOF, got nil")
3979+ }
3980+
3981+- req.Header = Header{"Content-Type": {"text/plain"}}
3982++ req.Header = http.Header{"Content-Type": {"text/plain"}}
3983+ err = req.ParseMultipartForm(25)
3984+ if err != ErrNotMultipart {
3985+ t.Error("expected ErrNotMultipart for text/plain")
3986+@@ -177,7 +178,7 @@
3987 }
3988
3989 func TestRedirect(t *testing.T) {
3990@@ -957,8 +792,8 @@
3991 w.Header().Set("Location", "/foo/")
3992
3993 === modified file 'http13client/requestwrite_test.go'
3994---- http13client/requestwrite_test.go 2014-03-19 20:20:19 +0000
3995-+++ http13client/requestwrite_test.go 2014-03-19 22:27:37 +0000
3996+--- http13client/requestwrite_test.go 2014-06-20 11:00:47 +0000
3997++++ http13client/requestwrite_test.go 2014-06-20 12:00:22 +0000
3998 @@ -10,6 +10,7 @@
3999 "fmt"
4000 "io"
4001@@ -1068,8 +903,8 @@
4002 var braw bytes.Buffer
4003
4004 === modified file 'http13client/response_test.go'
4005---- http13client/response_test.go 2014-03-19 20:20:19 +0000
4006-+++ http13client/response_test.go 2014-03-19 22:27:37 +0000
4007+--- http13client/response_test.go 2014-06-20 11:00:47 +0000
4008++++ http13client/response_test.go 2014-06-20 12:05:53 +0000
4009 @@ -12,6 +12,7 @@
4010 "fmt"
4011 "io"
4012@@ -1078,7 +913,7 @@
4013 "net/url"
4014 "reflect"
4015 "regexp"
4016-@@ -44,7 +45,7 @@
4017+@@ -48,7 +49,7 @@
4018 ProtoMajor: 1,
4019 ProtoMinor: 0,
4020 Request: dummyReq("GET"),
4021@@ -1087,7 +922,7 @@
4022 "Connection": {"close"}, // TODO(rsc): Delete?
4023 },
4024 Close: true,
4025-@@ -67,7 +68,7 @@
4026+@@ -71,7 +72,7 @@
4027 Proto: "HTTP/1.1",
4028 ProtoMajor: 1,
4029 ProtoMinor: 1,
4030@@ -1096,7 +931,7 @@
4031 Request: dummyReq("GET"),
4032 Close: true,
4033 ContentLength: -1,
4034-@@ -88,7 +89,7 @@
4035+@@ -92,7 +93,7 @@
4036 Proto: "HTTP/1.1",
4037 ProtoMajor: 1,
4038 ProtoMinor: 1,
4039@@ -1105,7 +940,7 @@
4040 Request: dummyReq("GET"),
4041 Close: false,
4042 ContentLength: 0,
4043-@@ -112,7 +113,7 @@
4044+@@ -116,7 +117,7 @@
4045 ProtoMajor: 1,
4046 ProtoMinor: 0,
4047 Request: dummyReq("GET"),
4048@@ -1114,61 +949,61 @@
4049 "Connection": {"close"},
4050 "Content-Length": {"10"},
4051 },
4052-@@ -142,7 +143,7 @@
4053- ProtoMajor: 1,
4054- ProtoMinor: 1,
4055- Request: dummyReq("GET"),
4056-- Header: Header{},
4057-+ Header: http.Header{},
4058- Close: false,
4059- ContentLength: -1,
4060- TransferEncoding: []string{"chunked"},
4061-@@ -169,7 +170,7 @@
4062- ProtoMajor: 1,
4063- ProtoMinor: 1,
4064- Request: dummyReq("GET"),
4065-- Header: Header{},
4066-+ Header: http.Header{},
4067- Close: false,
4068- ContentLength: -1,
4069- TransferEncoding: []string{"chunked"},
4070-@@ -191,7 +192,7 @@
4071- ProtoMajor: 1,
4072- ProtoMinor: 1,
4073- Request: dummyReq("HEAD"),
4074-- Header: Header{},
4075-+ Header: http.Header{},
4076- TransferEncoding: []string{"chunked"},
4077- Close: false,
4078- ContentLength: -1,
4079-@@ -213,7 +214,7 @@
4080- ProtoMajor: 1,
4081- ProtoMinor: 0,
4082- Request: dummyReq("HEAD"),
4083-- Header: Header{"Content-Length": {"256"}},
4084-+ Header: http.Header{"Content-Length": {"256"}},
4085- TransferEncoding: nil,
4086- Close: true,
4087- ContentLength: 256,
4088-@@ -235,7 +236,7 @@
4089- ProtoMajor: 1,
4090- ProtoMinor: 1,
4091- Request: dummyReq("HEAD"),
4092-- Header: Header{"Content-Length": {"256"}},
4093-+ Header: http.Header{"Content-Length": {"256"}},
4094- TransferEncoding: nil,
4095- Close: false,
4096- ContentLength: 256,
4097-@@ -256,7 +257,7 @@
4098- ProtoMajor: 1,
4099- ProtoMinor: 0,
4100- Request: dummyReq("HEAD"),
4101-- Header: Header{},
4102-+ Header: http.Header{},
4103- TransferEncoding: nil,
4104- Close: true,
4105- ContentLength: -1,
4106-@@ -278,7 +279,7 @@
4107+@@ -146,7 +147,7 @@
4108+ ProtoMajor: 1,
4109+ ProtoMinor: 1,
4110+ Request: dummyReq("GET"),
4111+- Header: Header{},
4112++ Header: http.Header{},
4113+ Close: false,
4114+ ContentLength: -1,
4115+ TransferEncoding: []string{"chunked"},
4116+@@ -173,7 +174,7 @@
4117+ ProtoMajor: 1,
4118+ ProtoMinor: 1,
4119+ Request: dummyReq("GET"),
4120+- Header: Header{},
4121++ Header: http.Header{},
4122+ Close: false,
4123+ ContentLength: -1,
4124+ TransferEncoding: []string{"chunked"},
4125+@@ -195,7 +196,7 @@
4126+ ProtoMajor: 1,
4127+ ProtoMinor: 1,
4128+ Request: dummyReq("HEAD"),
4129+- Header: Header{},
4130++ Header: http.Header{},
4131+ TransferEncoding: []string{"chunked"},
4132+ Close: false,
4133+ ContentLength: -1,
4134+@@ -217,7 +218,7 @@
4135+ ProtoMajor: 1,
4136+ ProtoMinor: 0,
4137+ Request: dummyReq("HEAD"),
4138+- Header: Header{"Content-Length": {"256"}},
4139++ Header: http.Header{"Content-Length": {"256"}},
4140+ TransferEncoding: nil,
4141+ Close: true,
4142+ ContentLength: 256,
4143+@@ -239,7 +240,7 @@
4144+ ProtoMajor: 1,
4145+ ProtoMinor: 1,
4146+ Request: dummyReq("HEAD"),
4147+- Header: Header{"Content-Length": {"256"}},
4148++ Header: http.Header{"Content-Length": {"256"}},
4149+ TransferEncoding: nil,
4150+ Close: false,
4151+ ContentLength: 256,
4152+@@ -260,7 +261,7 @@
4153+ ProtoMajor: 1,
4154+ ProtoMinor: 0,
4155+ Request: dummyReq("HEAD"),
4156+- Header: Header{},
4157++ Header: http.Header{},
4158+ TransferEncoding: nil,
4159+ Close: true,
4160+ ContentLength: -1,
4161+@@ -282,7 +283,7 @@
4162 ProtoMajor: 1,
4163 ProtoMinor: 1,
4164 Request: dummyReq("GET"),
4165@@ -1177,25 +1012,25 @@
4166 "Content-Length": {"0"},
4167 },
4168 Close: false,
4169-@@ -299,7 +300,7 @@
4170- ProtoMajor: 1,
4171- ProtoMinor: 0,
4172- Request: dummyReq("GET"),
4173-- Header: Header{},
4174-+ Header: http.Header{},
4175- Close: true,
4176- ContentLength: -1,
4177- },
4178-@@ -318,7 +319,7 @@
4179- ProtoMajor: 1,
4180- ProtoMinor: 0,
4181- Request: dummyReq("GET"),
4182-- Header: Header{},
4183-+ Header: http.Header{},
4184- Close: true,
4185- ContentLength: -1,
4186- },
4187-@@ -340,7 +341,7 @@
4188+@@ -303,7 +304,7 @@
4189+ ProtoMajor: 1,
4190+ ProtoMinor: 0,
4191+ Request: dummyReq("GET"),
4192+- Header: Header{},
4193++ Header: http.Header{},
4194+ Close: true,
4195+ ContentLength: -1,
4196+ },
4197+@@ -322,7 +323,7 @@
4198+ ProtoMajor: 1,
4199+ ProtoMinor: 0,
4200+ Request: dummyReq("GET"),
4201+- Header: Header{},
4202++ Header: http.Header{},
4203+ Close: true,
4204+ ContentLength: -1,
4205+ },
4206+@@ -344,7 +345,7 @@
4207 ProtoMajor: 1,
4208 ProtoMinor: 1,
4209 Request: dummyReq("GET"),
4210@@ -1204,7 +1039,7 @@
4211 "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
4212 },
4213 Close: true,
4214-@@ -363,7 +364,7 @@
4215+@@ -367,7 +368,7 @@
4216 Proto: "HTTP/1.0",
4217 ProtoMajor: 1,
4218 ProtoMinor: 0,
4219@@ -1213,7 +1048,7 @@
4220 "Connection": {"close"}, // TODO(rsc): Delete?
4221 },
4222 Close: true,
4223-@@ -545,7 +546,7 @@
4224+@@ -549,7 +550,7 @@
4225 func TestLocationResponse(t *testing.T) {
4226 for i, tt := range responseLocationTests {
4227 res := new(Response)
4228@@ -1222,7 +1057,7 @@
4229 res.Header.Set("Location", tt.location)
4230 if tt.requrl != "" {
4231 res.Request = &Request{}
4232-@@ -626,16 +627,3 @@
4233+@@ -630,16 +631,3 @@
4234 t.Errorf("ReadResponse = %v; want io.ErrUnexpectedEOF", err)
4235 }
4236 }
4237@@ -1241,8 +1076,8 @@
4238 -}
4239
4240 === modified file 'http13client/responsewrite_test.go'
4241---- http13client/responsewrite_test.go 2014-03-19 20:20:19 +0000
4242-+++ http13client/responsewrite_test.go 2014-03-19 22:27:37 +0000
4243+--- http13client/responsewrite_test.go 2014-06-20 11:00:47 +0000
4244++++ http13client/responsewrite_test.go 2014-06-20 12:05:53 +0000
4245 @@ -7,6 +7,7 @@
4246 import (
4247 "bytes"
4248@@ -1257,7 +1092,7 @@
4249 Request: dummyReq("GET"),
4250 - Header: Header{},
4251 + Header: http.Header{},
4252- Body: ioutil.NopCloser(bytes.NewBufferString("abcdef")),
4253+ Body: ioutil.NopCloser(strings.NewReader("abcdef")),
4254 ContentLength: 6,
4255 },
4256 @@ -41,7 +42,7 @@
4257@@ -1270,6 +1105,60 @@
4258 ContentLength: -1,
4259 },
4260 @@ -56,7 +57,7 @@
4261+ ProtoMajor: 1,
4262+ ProtoMinor: 1,
4263+ Request: dummyReq("GET"),
4264+- Header: Header{},
4265++ Header: http.Header{},
4266+ Body: ioutil.NopCloser(strings.NewReader("abcdef")),
4267+ ContentLength: -1,
4268+ Close: true,
4269+@@ -73,7 +74,7 @@
4270+ ProtoMajor: 1,
4271+ ProtoMinor: 1,
4272+ Request: dummyReq11("GET"),
4273+- Header: Header{},
4274++ Header: http.Header{},
4275+ Body: ioutil.NopCloser(strings.NewReader("abcdef")),
4276+ ContentLength: -1,
4277+ Close: false,
4278+@@ -91,7 +92,7 @@
4279+ ProtoMajor: 1,
4280+ ProtoMinor: 1,
4281+ Request: dummyReq11("GET"),
4282+- Header: Header{},
4283++ Header: http.Header{},
4284+ Body: ioutil.NopCloser(strings.NewReader("abcdef")),
4285+ ContentLength: -1,
4286+ TransferEncoding: []string{"chunked"},
4287+@@ -108,7 +109,7 @@
4288+ ProtoMajor: 1,
4289+ ProtoMinor: 1,
4290+ Request: dummyReq11("GET"),
4291+- Header: Header{},
4292++ Header: http.Header{},
4293+ Body: nil,
4294+ ContentLength: 0,
4295+ Close: false,
4296+@@ -124,7 +125,7 @@
4297+ ProtoMajor: 1,
4298+ ProtoMinor: 1,
4299+ Request: dummyReq11("GET"),
4300+- Header: Header{},
4301++ Header: http.Header{},
4302+ Body: ioutil.NopCloser(strings.NewReader("")),
4303+ ContentLength: 0,
4304+ Close: false,
4305+@@ -140,7 +141,7 @@
4306+ ProtoMajor: 1,
4307+ ProtoMinor: 1,
4308+ Request: dummyReq11("GET"),
4309+- Header: Header{},
4310++ Header: http.Header{},
4311+ Body: ioutil.NopCloser(strings.NewReader("foo")),
4312+ ContentLength: 0,
4313+ Close: false,
4314+@@ -156,7 +157,7 @@
4315 ProtoMajor: 1,
4316 ProtoMinor: 1,
4317 Request: dummyReq("GET"),
4318@@ -1278,7 +1167,7 @@
4319 Body: ioutil.NopCloser(strings.NewReader("abcdef")),
4320 ContentLength: 6,
4321 TransferEncoding: []string{"chunked"},
4322-@@ -77,7 +78,7 @@
4323+@@ -177,7 +178,7 @@
4324 ProtoMajor: 1,
4325 ProtoMinor: 1,
4326 Request: dummyReq("GET"),
4327@@ -1287,11 +1176,20 @@
4328 "Foo": []string{" Bar\nBaz "},
4329 },
4330 Body: nil,
4331+@@ -200,7 +201,7 @@
4332+ ProtoMajor: 1,
4333+ ProtoMinor: 1,
4334+ Request: &Request{Method: "POST"},
4335+- Header: Header{},
4336++ Header: http.Header{},
4337+ ContentLength: 0,
4338+ TransferEncoding: nil,
4339+ Body: nil,
4340
4341 === modified file 'http13client/transport_test.go'
4342---- http13client/transport_test.go 2014-03-19 21:38:56 +0000
4343-+++ http13client/transport_test.go 2014-03-19 22:27:37 +0000
4344-@@ -17,8 +17,8 @@
4345+--- http13client/transport_test.go 2014-06-20 11:00:47 +0000
4346++++ http13client/transport_test.go 2014-06-20 12:05:53 +0000
4347+@@ -18,8 +18,8 @@
4348 "io/ioutil"
4349 "log"
4350 "net"
4351@@ -1301,7 +1199,7 @@
4352 "net/http/httptest"
4353 "net/url"
4354 "os"
4355-@@ -34,7 +34,7 @@
4356+@@ -35,7 +35,7 @@
4357 // and then verify that the final 2 responses get errors back.
4358
4359 // hostPortHandler writes back the client's "host:port".
4360@@ -1310,7 +1208,7 @@
4361 if r.FormValue("close") == "true" {
4362 w.Header().Set("Connection", "close")
4363 }
4364-@@ -280,7 +280,7 @@
4365+@@ -289,7 +289,7 @@
4366 const msg = "foobar"
4367
4368 var addrSeen map[string]int
4369@@ -1319,7 +1217,7 @@
4370 addrSeen[r.RemoteAddr]++
4371 if r.URL.Path == "/chunked/" {
4372 w.WriteHeader(200)
4373-@@ -299,7 +299,7 @@
4374+@@ -308,7 +308,7 @@
4375 wantLen := []int{len(msg), -1}[pi]
4376 addrSeen = make(map[string]int)
4377 for i := 0; i < 3; i++ {
4378@@ -1328,7 +1226,7 @@
4379 if err != nil {
4380 t.Errorf("Get %s: %v", path, err)
4381 continue
4382-@@ -329,7 +329,7 @@
4383+@@ -338,7 +338,7 @@
4384 defer afterTest(t)
4385 resch := make(chan string)
4386 gotReq := make(chan bool)
4387@@ -1337,7 +1235,7 @@
4388 gotReq <- true
4389 msg := <-resch
4390 _, err := w.Write([]byte(msg))
4391-@@ -457,12 +457,12 @@
4392+@@ -466,12 +466,12 @@
4393 if testing.Short() {
4394 t.Skip("skipping test in short mode")
4395 }
4396@@ -1353,7 +1251,7 @@
4397 buf.Flush()
4398 conn.Close()
4399 }))
4400-@@ -510,7 +510,7 @@
4401+@@ -519,7 +519,7 @@
4402 // with no bodies properly
4403 func TestTransportHeadResponses(t *testing.T) {
4404 defer afterTest(t)
4405@@ -1362,7 +1260,7 @@
4406 if r.Method != "HEAD" {
4407 panic("expected HEAD; got " + r.Method)
4408 }
4409-@@ -545,7 +545,7 @@
4410+@@ -554,7 +554,7 @@
4411 // on responses to HEAD requests.
4412 func TestTransportHeadChunkedResponse(t *testing.T) {
4413 defer afterTest(t)
4414@@ -1371,7 +1269,7 @@
4415 if r.Method != "HEAD" {
4416 panic("expected HEAD; got " + r.Method)
4417 }
4418-@@ -588,7 +588,7 @@
4419+@@ -597,7 +597,7 @@
4420 func TestRoundTripGzip(t *testing.T) {
4421 defer afterTest(t)
4422 const responseBody = "test response body"
4423@@ -1380,7 +1278,7 @@
4424 accept := req.Header.Get("Accept-Encoding")
4425 if expect := req.FormValue("expect_accept"); accept != expect {
4426 t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q",
4427-@@ -647,7 +647,7 @@
4428+@@ -656,7 +656,7 @@
4429 defer afterTest(t)
4430 const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
4431 const nRandBytes = 1024 * 1024
4432@@ -1389,7 +1287,7 @@
4433 if req.Method == "HEAD" {
4434 if g := req.Header.Get("Accept-Encoding"); g != "" {
4435 t.Errorf("HEAD request sent with Accept-Encoding of %q; want none", g)
4436-@@ -742,11 +742,11 @@
4437+@@ -751,11 +751,11 @@
4438 func TestTransportProxy(t *testing.T) {
4439 defer afterTest(t)
4440 ch := make(chan string, 1)
4441@@ -1403,7 +1301,7 @@
4442 ch <- "proxy for " + r.URL.String()
4443 }))
4444 defer proxy.Close()
4445-@@ -770,7 +770,7 @@
4446+@@ -779,7 +779,7 @@
4447 // Content-Encoding is removed.
4448 func TestTransportGzipRecursive(t *testing.T) {
4449 defer afterTest(t)
4450@@ -1412,7 +1310,16 @@
4451 w.Header().Set("Content-Encoding", "gzip")
4452 w.Write(rgz)
4453 }))
4454-@@ -802,7 +802,7 @@
4455+@@ -807,7 +807,7 @@
4456+ // a short gzip body
4457+ func TestTransportGzipShort(t *testing.T) {
4458+ defer afterTest(t)
4459+- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
4460++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4461+ w.Header().Set("Content-Encoding", "gzip")
4462+ w.Write([]byte{0x1f, 0x8b})
4463+ }))
4464+@@ -838,7 +838,7 @@
4465 defer afterTest(t)
4466 gotReqCh := make(chan bool)
4467 unblockCh := make(chan bool)
4468@@ -1421,7 +1328,7 @@
4469 gotReqCh <- true
4470 <-unblockCh
4471 w.Header().Set("Content-Length", "0")
4472-@@ -869,7 +869,7 @@
4473+@@ -905,7 +905,7 @@
4474 t.Skip("skipping test; see http://golang.org/issue/7237")
4475 }
4476 defer afterTest(t)
4477@@ -1430,7 +1337,7 @@
4478 }))
4479 defer ts.Close()
4480
4481-@@ -912,7 +912,7 @@
4482+@@ -948,7 +948,7 @@
4483 c := &Client{Transport: tr}
4484
4485 unblockCh := make(chan bool, 1)
4486@@ -1439,7 +1346,7 @@
4487 <-unblockCh
4488 tr.CloseIdleConnections()
4489 }))
4490-@@ -939,7 +939,7 @@
4491+@@ -975,7 +975,7 @@
4492 func TestIssue3644(t *testing.T) {
4493 defer afterTest(t)
4494 const numFoos = 5000
4495@@ -1448,7 +1355,7 @@
4496 w.Header().Set("Connection", "close")
4497 for i := 0; i < numFoos; i++ {
4498 w.Write([]byte("foo "))
4499-@@ -967,8 +967,8 @@
4500+@@ -1003,8 +1003,8 @@
4501 func TestIssue3595(t *testing.T) {
4502 defer afterTest(t)
4503 const deniedMsg = "sorry, denied."
4504@@ -1459,7 +1366,7 @@
4505 }))
4506 defer ts.Close()
4507 tr := &Transport{}
4508-@@ -991,7 +991,7 @@
4509+@@ -1027,7 +1027,7 @@
4510 // "client fails to handle requests with no body and chunked encoding"
4511 func TestChunkedNoContent(t *testing.T) {
4512 defer afterTest(t)
4513@@ -1468,7 +1375,7 @@
4514 w.WriteHeader(StatusNoContent)
4515 }))
4516 defer ts.Close()
4517-@@ -1019,7 +1019,7 @@
4518+@@ -1055,7 +1055,7 @@
4519 maxProcs, numReqs = 4, 50
4520 }
4521 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs))
4522@@ -1477,7 +1384,7 @@
4523 fmt.Fprintf(w, "%v", r.FormValue("echo"))
4524 }))
4525 defer ts.Close()
4526-@@ -1080,8 +1080,8 @@
4527+@@ -1116,8 +1116,8 @@
4528 }
4529 defer afterTest(t)
4530 const debug = false
4531@@ -1488,7 +1395,7 @@
4532 io.Copy(w, neverEnding('a'))
4533 })
4534 ts := httptest.NewServer(mux)
4535-@@ -1144,11 +1144,11 @@
4536+@@ -1180,11 +1180,11 @@
4537 }
4538 defer afterTest(t)
4539 const debug = false
4540@@ -1503,20 +1410,22 @@
4541 defer r.Body.Close()
4542 io.Copy(ioutil.Discard, r.Body)
4543 })
4544-@@ -1214,9 +1214,9 @@
4545- if testing.Short() {
4546+@@ -1251,11 +1251,11 @@
4547 t.Skip("skipping timeout test in -short mode")
4548 }
4549+ inHandler := make(chan bool, 1)
4550 - mux := NewServeMux()
4551-- mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {})
4552+- mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {
4553++ mux := http.NewServeMux()
4554++ mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {
4555+ inHandler <- true
4556+ })
4557 - mux.HandleFunc("/slow", func(w ResponseWriter, r *Request) {
4558-+ mux := http.NewServeMux()
4559-+ mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {})
4560 + mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
4561+ inHandler <- true
4562 time.Sleep(2 * time.Second)
4563 })
4564- ts := httptest.NewServer(mux)
4565-@@ -1276,9 +1276,9 @@
4566+@@ -1322,9 +1322,9 @@
4567 t.Skip("skipping test in -short mode")
4568 }
4569 unblockc := make(chan bool)
4570@@ -1528,7 +1437,7 @@
4571 <-unblockc
4572 }))
4573 defer ts.Close()
4574-@@ -1386,14 +1386,14 @@
4575+@@ -1431,14 +1431,14 @@
4576 defer afterTest(t)
4577 writeErr := make(chan error, 1)
4578 msg := []byte("young\n")
4579@@ -1545,7 +1454,7 @@
4580 }
4581 }))
4582 defer ts.Close()
4583-@@ -1449,7 +1449,7 @@
4584+@@ -1494,7 +1494,7 @@
4585 res := &Response{
4586 Status: "200 OK",
4587 StatusCode: 200,
4588@@ -1554,7 +1463,7 @@
4589 Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())),
4590 }
4591 return res, nil
4592-@@ -1478,7 +1478,7 @@
4593+@@ -1523,7 +1523,7 @@
4594 defer afterTest(t)
4595 tr := &Transport{}
4596 _, err := tr.RoundTrip(&Request{
4597@@ -1563,7 +1472,7 @@
4598 URL: &url.URL{
4599 Scheme: "http",
4600 },
4601-@@ -1492,14 +1492,14 @@
4602+@@ -1537,14 +1537,14 @@
4603 func TestTransportSocketLateBinding(t *testing.T) {
4604 defer afterTest(t)
4605
4606@@ -1582,7 +1491,7 @@
4607 w.Header().Set("bar-ipport", r.RemoteAddr)
4608 })
4609 ts := httptest.NewServer(mux)
4610-@@ -1720,7 +1720,7 @@
4611+@@ -1767,7 +1767,7 @@
4612 var mu sync.Mutex
4613 var n int
4614
4615@@ -1591,7 +1500,7 @@
4616 mu.Lock()
4617 n++
4618 mu.Unlock()
4619-@@ -1756,7 +1756,7 @@
4620+@@ -1803,7 +1803,7 @@
4621 // then closes it.
4622 func TestTransportClosesRequestBody(t *testing.T) {
4623 defer afterTest(t)
4624@@ -1600,4 +1509,49 @@
4625 io.Copy(ioutil.Discard, r.Body)
4626 }))
4627 defer ts.Close()
4628+@@ -1890,9 +1890,9 @@
4629+ t.Skip("skipping flaky test on Windows; golang.org/issue/7634")
4630+ }
4631+ closedc := make(chan bool, 1)
4632+- ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
4633++ ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4634+ if strings.Contains(r.URL.Path, "/keep-alive-then-die") {
4635+- conn, _, _ := w.(Hijacker).Hijack()
4636++ conn, _, _ := w.(http.Hijacker).Hijack()
4637+ conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nfoo"))
4638+ conn.Close()
4639+ closedc <- true
4640+@@ -1994,12 +1994,12 @@
4641+ }
4642+ defer closeConn()
4643+
4644+- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
4645++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4646+ if r.Method == "GET" {
4647+ io.WriteString(w, "bar")
4648+ return
4649+ }
4650+- conn, _, _ := w.(Hijacker).Hijack()
4651++ conn, _, _ := w.(http.Hijacker).Hijack()
4652+ sconn.Lock()
4653+ sconn.c = conn
4654+ sconn.Unlock()
4655+@@ -2056,7 +2056,7 @@
4656+ }
4657+ defer afterTest(t)
4658+ readBody := make(chan error, 1)
4659+- ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
4660++ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4661+ _, err := ioutil.ReadAll(r.Body)
4662+ readBody <- err
4663+ }))
4664+@@ -2098,7 +2098,7 @@
4665+ }
4666+ }
4667+
4668+-func wantBody(res *http.Response, err error, want string) error {
4669++func wantBody(res *Response, err error, want string) error {
4670+ if err != nil {
4671+ return err
4672+ }
4673
4674
4675=== modified file 'http13client/_using.txt'
4676--- http13client/_using.txt 2014-03-20 12:20:01 +0000
4677+++ http13client/_using.txt 2014-07-02 13:12:36 +0000
4678@@ -1,5 +1,5 @@
4679-parent: 19512:32c32aef2a41 tip
4680- test: enable bug385_32 test on amd64p32.
4681-branch: default
4682+parent: 20169:9895f9e36435 go1.3 release
4683+ go1.3
4684+branch: release-branch.go1.3
4685 commit: (clean)
4686 update: (current)
4687
4688=== modified file 'http13client/client.go'
4689--- http13client/client.go 2014-03-20 09:26:28 +0000
4690+++ http13client/client.go 2014-07-02 13:12:36 +0000
4691@@ -92,8 +92,9 @@
4692 // authentication, or cookies.
4693 //
4694 // RoundTrip should not modify the request, except for
4695- // consuming and closing the Body. The request's URL and
4696- // Header fields are guaranteed to be initialized.
4697+ // consuming and closing the Body, including on errors. The
4698+ // request's URL and Header fields are guaranteed to be
4699+ // initialized.
4700 RoundTrip(*Request) (*Response, error)
4701 }
4702
4703@@ -141,6 +142,9 @@
4704 // (typically Transport) may not be able to re-use a persistent TCP
4705 // connection to the server for a subsequent "keep-alive" request.
4706 //
4707+// The request Body, if non-nil, will be closed by the underlying
4708+// Transport, even on errors.
4709+//
4710 // Generally Get, Post, or PostForm will be used instead of Do.
4711 func (c *Client) Do(req *Request) (resp *Response, err error) {
4712 if req.Method == "GET" || req.Method == "HEAD" {
4713@@ -163,14 +167,17 @@
4714 // Caller should close resp.Body when done reading from it.
4715 func send(req *Request, t RoundTripper) (resp *Response, err error) {
4716 if t == nil {
4717+ req.closeBody()
4718 return nil, errors.New("http: no Client.Transport or DefaultTransport")
4719 }
4720
4721 if req.URL == nil {
4722+ req.closeBody()
4723 return nil, errors.New("http: nil Request.URL")
4724 }
4725
4726 if req.RequestURI != "" {
4727+ req.closeBody()
4728 return nil, errors.New("http: Request.RequestURI can't be set in client requests.")
4729 }
4730
4731@@ -278,6 +285,7 @@
4732 var via []*Request
4733
4734 if ireq.URL == nil {
4735+ ireq.closeBody()
4736 return nil, errors.New("http: nil Request.URL")
4737 }
4738
4739@@ -400,7 +408,7 @@
4740 // Caller should close resp.Body when done reading from it.
4741 //
4742 // If the provided body is also an io.Closer, it is closed after the
4743-// body is successfully written to the server.
4744+// request.
4745 func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error) {
4746 req, err := NewRequest("POST", url, body)
4747 if err != nil {
4748
4749=== modified file 'http13client/client_test.go'
4750--- http13client/client_test.go 2014-03-20 09:26:28 +0000
4751+++ http13client/client_test.go 2014-07-02 13:12:36 +0000
4752@@ -20,6 +20,8 @@
4753 "net/http"
4754 "net/http/httptest"
4755 "net/url"
4756+ "reflect"
4757+ "sort"
4758 "strconv"
4759 "strings"
4760 "sync"
4761@@ -877,3 +879,93 @@
4762 t.Fatal("server saw different client ports before & after the redirect")
4763 }
4764 }
4765+
4766+// eofReaderFunc is an io.Reader that runs itself, and then returns io.EOF.
4767+type eofReaderFunc func()
4768+
4769+func (f eofReaderFunc) Read(p []byte) (n int, err error) {
4770+ f()
4771+ return 0, io.EOF
4772+}
4773+
4774+func TestClientTrailers(t *testing.T) {
4775+ defer afterTest(t)
4776+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
4777+ w.Header().Set("Connection", "close")
4778+ w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B")
4779+ w.Header().Add("Trailer", "Server-Trailer-C")
4780+
4781+ var decl []string
4782+ for k := range r.Trailer {
4783+ decl = append(decl, k)
4784+ }
4785+ sort.Strings(decl)
4786+
4787+ slurp, err := ioutil.ReadAll(r.Body)
4788+ if err != nil {
4789+ t.Errorf("Server reading request body: %v", err)
4790+ }
4791+ if string(slurp) != "foo" {
4792+ t.Errorf("Server read request body %q; want foo", slurp)
4793+ }
4794+ if r.Trailer == nil {
4795+ io.WriteString(w, "nil Trailer")
4796+ } else {
4797+ fmt.Fprintf(w, "decl: %v, vals: %s, %s",
4798+ decl,
4799+ r.Trailer.Get("Client-Trailer-A"),
4800+ r.Trailer.Get("Client-Trailer-B"))
4801+ }
4802+
4803+ // TODO: golang.org/issue/7759: there's no way yet for
4804+ // the server to set trailers without hijacking, so do
4805+ // that for now, just to test the client. Later, in
4806+ // Go 1.4, it should be implicit that any mutations
4807+ // to w.Header() after the initial write are the
4808+ // trailers to be sent, if and only if they were
4809+ // previously declared with w.Header().Set("Trailer",
4810+ // ..keys..)
4811+ w.(http.Flusher).Flush()
4812+ conn, buf, _ := w.(http.Hijacker).Hijack()
4813+ t := http.Header{}
4814+ t.Set("Server-Trailer-A", "valuea")
4815+ t.Set("Server-Trailer-C", "valuec") // skipping B
4816+ buf.WriteString("0\r\n") // eof
4817+ t.Write(buf)
4818+ buf.WriteString("\r\n") // end of trailers
4819+ buf.Flush()
4820+ conn.Close()
4821+ }))
4822+ defer ts.Close()
4823+
4824+ var req *Request
4825+ req, _ = NewRequest("POST", ts.URL, io.MultiReader(
4826+ eofReaderFunc(func() {
4827+ req.Trailer["Client-Trailer-A"] = []string{"valuea"}
4828+ }),
4829+ strings.NewReader("foo"),
4830+ eofReaderFunc(func() {
4831+ req.Trailer["Client-Trailer-B"] = []string{"valueb"}
4832+ }),
4833+ ))
4834+ req.Trailer = http.Header{
4835+ "Client-Trailer-A": nil, // to be set later
4836+ "Client-Trailer-B": nil, // to be set later
4837+ }
4838+ req.ContentLength = -1
4839+ res, err := DefaultClient.Do(req)
4840+ if err != nil {
4841+ t.Fatal(err)
4842+ }
4843+ if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil {
4844+ t.Error(err)
4845+ }
4846+ want := http.Header{
4847+ "Server-Trailer-A": []string{"valuea"},
4848+ "Server-Trailer-B": nil,
4849+ "Server-Trailer-C": []string{"valuec"},
4850+ }
4851+ if !reflect.DeepEqual(res.Trailer, want) {
4852+ t.Errorf("Response trailers = %#v; want %#v", res.Trailer, want)
4853+ }
4854+}
4855
4856=== modified file 'http13client/cookie.go'
4857--- http13client/cookie.go 2014-03-19 23:13:58 +0000
4858+++ http13client/cookie.go 2014-07-02 13:12:36 +0000
4859@@ -55,11 +55,7 @@
4860 attr, val = attr[:j], attr[j+1:]
4861 }
4862 lowerAttr := strings.ToLower(attr)
4863- parseCookieValueFn := parseCookieValue
4864- if lowerAttr == "expires" {
4865- parseCookieValueFn = parseCookieExpiresValue
4866- }
4867- val, success = parseCookieValueFn(val)
4868+ val, success = parseCookieValue(val)
4869 if !success {
4870 c.Unparsed = append(c.Unparsed, parts[i])
4871 continue
4872@@ -230,12 +226,23 @@
4873 // ; US-ASCII characters excluding CTLs,
4874 // ; whitespace DQUOTE, comma, semicolon,
4875 // ; and backslash
4876+// We loosen this as spaces and commas are common in cookie values
4877+// but we produce a quoted cookie-value in when value starts or ends
4878+// with a comma or space.
4879+// See http://golang.org/issue/7243 for the discussion.
4880 func sanitizeCookieValue(v string) string {
4881- return sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
4882+ v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v)
4883+ if len(v) == 0 {
4884+ return v
4885+ }
4886+ if v[0] == ' ' || v[0] == ',' || v[len(v)-1] == ' ' || v[len(v)-1] == ',' {
4887+ return `"` + v + `"`
4888+ }
4889+ return v
4890 }
4891
4892 func validCookieValueByte(b byte) bool {
4893- return 0x20 < b && b < 0x7f && b != '"' && b != ',' && b != ';' && b != '\\'
4894+ return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\'
4895 }
4896
4897 // path-av = "Path=" path-value
4898@@ -270,38 +277,13 @@
4899 return string(buf)
4900 }
4901
4902-func unquoteCookieValue(v string) string {
4903- if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {
4904- return v[1 : len(v)-1]
4905- }
4906- return v
4907-}
4908-
4909-func isCookieByte(c byte) bool {
4910- switch {
4911- case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,
4912- 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:
4913- return true
4914- }
4915- return false
4916-}
4917-
4918-func isCookieExpiresByte(c byte) (ok bool) {
4919- return isCookieByte(c) || c == ',' || c == ' '
4920-}
4921-
4922 func parseCookieValue(raw string) (string, bool) {
4923- return parseCookieValueUsing(raw, isCookieByte)
4924-}
4925-
4926-func parseCookieExpiresValue(raw string) (string, bool) {
4927- return parseCookieValueUsing(raw, isCookieExpiresByte)
4928-}
4929-
4930-func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) {
4931- raw = unquoteCookieValue(raw)
4932+ // Strip the quotes, if present.
4933+ if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' {
4934+ raw = raw[1 : len(raw)-1]
4935+ }
4936 for i := 0; i < len(raw); i++ {
4937- if !validByte(raw[i]) {
4938+ if !validCookieValueByte(raw[i]) {
4939 return "", false
4940 }
4941 }
4942
4943=== removed file 'http13client/cookie_test.go'
4944--- http13client/cookie_test.go 2014-03-19 23:13:58 +0000
4945+++ http13client/cookie_test.go 1970-01-01 00:00:00 +0000
4946@@ -1,304 +0,0 @@
4947-// Copyright 2010 The Go Authors. All rights reserved.
4948-// Use of this source code is governed by a BSD-style
4949-// license that can be found in the LICENSE file.
4950-
4951-package http
4952-
4953-import (
4954- "bytes"
4955- "encoding/json"
4956- "fmt"
4957- "log"
4958- "net/http"
4959- "os"
4960- "reflect"
4961- "strings"
4962- "testing"
4963- "time"
4964-)
4965-
4966-var writeSetCookiesTests = []struct {
4967- Cookie *http.Cookie
4968- Raw string
4969-}{
4970- {
4971- &http.Cookie{Name: "cookie-1", Value: "v$1"},
4972- "cookie-1=v$1",
4973- },
4974- {
4975- &http.Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600},
4976- "cookie-2=two; Max-Age=3600",
4977- },
4978- {
4979- &http.Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"},
4980- "cookie-3=three; Domain=example.com",
4981- },
4982- {
4983- &http.Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},
4984- "cookie-4=four; Path=/restricted/",
4985- },
4986- {
4987- &http.Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"},
4988- "cookie-5=five",
4989- },
4990- {
4991- &http.Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"},
4992- "cookie-6=six",
4993- },
4994- {
4995- &http.Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"},
4996- "cookie-7=seven; Domain=127.0.0.1",
4997- },
4998- {
4999- &http.Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
5000- "cookie-8=eight",
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches