Merge lp:~pedronis/ubuntu-push/murmur3-official-seed into lp:ubuntu-push

Proposed by Samuele Pedroni
Status: Superseded
Proposed branch: lp:~pedronis/ubuntu-push/murmur3-official-seed
Merge into: lp:ubuntu-push
Diff against target: 7042 lines (+3026/-1503)
58 files modified
Makefile (+2/-2)
bus/endpoint.go (+4/-4)
bus/testing/testing_endpoint_test.go (+1/-1)
client/client.go (+66/-28)
client/client_test.go (+71/-19)
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 (+28/-0)
debian/config.json (+3/-1)
debian/rules (+0/-1)
external/README (+2/-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)
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 (+104/-8)
server/api/handlers_test.go (+193/-2)
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)
whoopsie/identifier/identifier.go (+5/-10)
To merge this branch: bzr merge lp:~pedronis/ubuntu-push/murmur3-official-seed
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+223957@code.launchpad.net

Commit message

merge upstream switching of murmur3 to use 0 as seed as done by other impls

Description of the change

merge upstream switching of murmur3 to use 0 as seed as done by other impls, at least that was the intention upstream, needed extra fix

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

fix & changelog

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

Subscribers

People subscribed via source and target branches