Merge lp:~pedronis/ubuntu-push/update-http13 into lp:ubuntu-push

Proposed by Samuele Pedroni
Status: Superseded
Proposed branch: lp:~pedronis/ubuntu-push/update-http13
Merge into: lp:ubuntu-push
Diff against target: 6384 lines (+2600/-1451)
48 files modified
bus/endpoint.go (+4/-4)
bus/testing/testing_endpoint_test.go (+1/-1)
client/client.go (+64/-27)
client/client_test.go (+69/-19)
client/service/common.go (+99/-0)
client/service/postal.go (+122/-0)
client/service/postal_test.go (+191/-0)
client/service/service.go (+53/-165)
client/service/service_test.go (+44/-136)
client/session/session.go (+6/-17)
client/session/session_test.go (+15/-28)
debian/changelog (+25/-0)
debian/config.json (+3/-1)
debian/rules (+0/-1)
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/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/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/update-http13
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+223915@code.launchpad.net

Commit message

update http13client from the actual go1.3 release

Description of the change

update http13client from the actual go1.3 release

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

changelog

Unmerged revisions

Preview Diff

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

Subscribers

People subscribed via source and target branches