Merge lp:~pedronis/ubuntu-push/api-handling-dry into lp:ubuntu-push

Proposed by Samuele Pedroni
Status: Superseded
Proposed branch: lp:~pedronis/ubuntu-push/api-handling-dry
Merge into: lp:ubuntu-push
Diff against target: 8208 lines (+3856/-1643)
65 files modified
Makefile (+2/-2)
PACKAGE_DEPS (+2/-0)
bus/endpoint.go (+4/-4)
bus/testing/testing_endpoint_test.go (+1/-1)
client/client.go (+66/-28)
client/client_test.go (+91/-22)
client/gethosts/gethost_test.go (+1/-1)
client/service/common.go (+99/-0)
client/service/postal.go (+122/-0)
client/service/postal_test.go (+191/-0)
client/service/service.go (+156/-168)
client/service/service_test.go (+166/-136)
client/session/session.go (+6/-17)
client/session/session_test.go (+15/-28)
debian/changelog (+32/-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 (+198/-0)
launch_helper/helper_c.go (+127/-0)
launch_helper/helper_test.go (+60/-0)
messaging/messaging.go (+348/-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/suite.go (+2/-2)
server/acceptance/suites/unicast.go (+11/-3)
server/api/handlers.go (+155/-93)
server/api/handlers_test.go (+179/-54)
server/session/session.go (+79/-29)
server/session/session_test.go (+95/-4)
server/store/inmemory.go (+26/-0)
server/store/inmemory_test.go (+44/-0)
server/store/store.go (+8/-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:~pedronis/ubuntu-push/api-handling-dry
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+224341@code.launchpad.net

Commit message

refactor api handling to avoid repetition and have just one ServeHTTP

Description of the change

refactor api handling to avoid repetition and have just one ServeHTTP

To post a comment you must log in.
199. By Samuele Pedroni

Merged better-ok-matching into api-handling-dry.

Unmerged revisions

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

Subscribers

People subscribed via source and target branches