1 // Copyright 2010 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package http 6 7 import ( 8 "bytes" 9 "io/ioutil" 10 "strings" 11 "testing" 12 ) 13 14 type respWriteTest struct { 15 Resp Response 16 Raw string 17 } 18 19 func TestResponseWrite(t *testing.T) { 20 respWriteTests := []respWriteTest{ 21 // HTTP/1.0, identity coding; no trailer 22 { 23 Response{ 24 StatusCode: 503, 25 ProtoMajor: 1, 26 ProtoMinor: 0, 27 Request: dummyReq("GET"), 28 Header: Header{}, 29 Body: ioutil.NopCloser(strings.NewReader("abcdef")), 30 ContentLength: 6, 31 }, 32 33 "HTTP/1.0 503 Service Unavailable\r\n" + 34 "Content-Length: 6\r\n\r\n" + 35 "abcdef", 36 }, 37 // Unchunked response without Content-Length. 38 { 39 Response{ 40 StatusCode: 200, 41 ProtoMajor: 1, 42 ProtoMinor: 0, 43 Request: dummyReq("GET"), 44 Header: Header{}, 45 Body: ioutil.NopCloser(strings.NewReader("abcdef")), 46 ContentLength: -1, 47 }, 48 "HTTP/1.0 200 OK\r\n" + 49 "\r\n" + 50 "abcdef", 51 }, 52 // HTTP/1.1 response with unknown length and Connection: close 53 { 54 Response{ 55 StatusCode: 200, 56 ProtoMajor: 1, 57 ProtoMinor: 1, 58 Request: dummyReq("GET"), 59 Header: Header{}, 60 Body: ioutil.NopCloser(strings.NewReader("abcdef")), 61 ContentLength: -1, 62 Close: true, 63 }, 64 "HTTP/1.1 200 OK\r\n" + 65 "Connection: close\r\n" + 66 "\r\n" + 67 "abcdef", 68 }, 69 // HTTP/1.1 response with unknown length and not setting connection: close 70 { 71 Response{ 72 StatusCode: 200, 73 ProtoMajor: 1, 74 ProtoMinor: 1, 75 Request: dummyReq11("GET"), 76 Header: Header{}, 77 Body: ioutil.NopCloser(strings.NewReader("abcdef")), 78 ContentLength: -1, 79 Close: false, 80 }, 81 "HTTP/1.1 200 OK\r\n" + 82 "Connection: close\r\n" + 83 "\r\n" + 84 "abcdef", 85 }, 86 // HTTP/1.1 response with unknown length and not setting connection: close, but 87 // setting chunked. 88 { 89 Response{ 90 StatusCode: 200, 91 ProtoMajor: 1, 92 ProtoMinor: 1, 93 Request: dummyReq11("GET"), 94 Header: Header{}, 95 Body: ioutil.NopCloser(strings.NewReader("abcdef")), 96 ContentLength: -1, 97 TransferEncoding: []string{"chunked"}, 98 Close: false, 99 }, 100 "HTTP/1.1 200 OK\r\n" + 101 "Transfer-Encoding: chunked\r\n\r\n" + 102 "6\r\nabcdef\r\n0\r\n\r\n", 103 }, 104 // HTTP/1.1 response 0 content-length, and nil body 105 { 106 Response{ 107 StatusCode: 200, 108 ProtoMajor: 1, 109 ProtoMinor: 1, 110 Request: dummyReq11("GET"), 111 Header: Header{}, 112 Body: nil, 113 ContentLength: 0, 114 Close: false, 115 }, 116 "HTTP/1.1 200 OK\r\n" + 117 "Content-Length: 0\r\n" + 118 "\r\n", 119 }, 120 // HTTP/1.1 response 0 content-length, and non-nil empty body 121 { 122 Response{ 123 StatusCode: 200, 124 ProtoMajor: 1, 125 ProtoMinor: 1, 126 Request: dummyReq11("GET"), 127 Header: Header{}, 128 Body: ioutil.NopCloser(strings.NewReader("")), 129 ContentLength: 0, 130 Close: false, 131 }, 132 "HTTP/1.1 200 OK\r\n" + 133 "Content-Length: 0\r\n" + 134 "\r\n", 135 }, 136 // HTTP/1.1 response 0 content-length, and non-nil non-empty body 137 { 138 Response{ 139 StatusCode: 200, 140 ProtoMajor: 1, 141 ProtoMinor: 1, 142 Request: dummyReq11("GET"), 143 Header: Header{}, 144 Body: ioutil.NopCloser(strings.NewReader("foo")), 145 ContentLength: 0, 146 Close: false, 147 }, 148 "HTTP/1.1 200 OK\r\n" + 149 "Connection: close\r\n" + 150 "\r\nfoo", 151 }, 152 // HTTP/1.1, chunked coding; empty trailer; close 153 { 154 Response{ 155 StatusCode: 200, 156 ProtoMajor: 1, 157 ProtoMinor: 1, 158 Request: dummyReq("GET"), 159 Header: Header{}, 160 Body: ioutil.NopCloser(strings.NewReader("abcdef")), 161 ContentLength: 6, 162 TransferEncoding: []string{"chunked"}, 163 Close: true, 164 }, 165 166 "HTTP/1.1 200 OK\r\n" + 167 "Connection: close\r\n" + 168 "Transfer-Encoding: chunked\r\n\r\n" + 169 "6\r\nabcdef\r\n0\r\n\r\n", 170 }, 171 172 // Header value with a newline character (Issue 914). 173 // Also tests removal of leading and trailing whitespace. 174 { 175 Response{ 176 StatusCode: 204, 177 ProtoMajor: 1, 178 ProtoMinor: 1, 179 Request: dummyReq("GET"), 180 Header: Header{ 181 "Foo": []string{" Bar\nBaz "}, 182 }, 183 Body: nil, 184 ContentLength: 0, 185 TransferEncoding: []string{"chunked"}, 186 Close: true, 187 }, 188 189 "HTTP/1.1 204 No Content\r\n" + 190 "Connection: close\r\n" + 191 "Foo: Bar Baz\r\n" + 192 "\r\n", 193 }, 194 195 // Want a single Content-Length header. Fixing issue 8180 where 196 // there were two. 197 { 198 Response{ 199 StatusCode: StatusOK, 200 ProtoMajor: 1, 201 ProtoMinor: 1, 202 Request: &Request{Method: "POST"}, 203 Header: Header{}, 204 ContentLength: 0, 205 TransferEncoding: nil, 206 Body: nil, 207 }, 208 "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", 209 }, 210 211 // When a response to a POST has Content-Length: -1, make sure we don't 212 // write the Content-Length as -1. 213 { 214 Response{ 215 StatusCode: StatusOK, 216 ProtoMajor: 1, 217 ProtoMinor: 1, 218 Request: &Request{Method: "POST"}, 219 Header: Header{}, 220 ContentLength: -1, 221 Body: ioutil.NopCloser(strings.NewReader("abcdef")), 222 }, 223 "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nabcdef", 224 }, 225 226 // Status code under 100 should be zero-padded to 227 // three digits. Still bogus, but less bogus. (be 228 // consistent with generating three digits, since the 229 // Transport requires it) 230 { 231 Response{ 232 StatusCode: 7, 233 Status: "license to violate specs", 234 ProtoMajor: 1, 235 ProtoMinor: 0, 236 Request: dummyReq("GET"), 237 Header: Header{}, 238 Body: nil, 239 }, 240 241 "HTTP/1.0 007 license to violate specs\r\nContent-Length: 0\r\n\r\n", 242 }, 243 244 // No stutter. Status code in 1xx range response should 245 // not include a Content-Length header. See issue #16942. 246 { 247 Response{ 248 StatusCode: 123, 249 Status: "123 Sesame Street", 250 ProtoMajor: 1, 251 ProtoMinor: 0, 252 Request: dummyReq("GET"), 253 Header: Header{}, 254 Body: nil, 255 }, 256 257 "HTTP/1.0 123 Sesame Street\r\n\r\n", 258 }, 259 260 // Status code 204 (No content) response should not include a 261 // Content-Length header. See issue #16942. 262 { 263 Response{ 264 StatusCode: 204, 265 Status: "No Content", 266 ProtoMajor: 1, 267 ProtoMinor: 0, 268 Request: dummyReq("GET"), 269 Header: Header{}, 270 Body: nil, 271 }, 272 273 "HTTP/1.0 204 No Content\r\n\r\n", 274 }, 275 } 276 277 for i := range respWriteTests { 278 tt := &respWriteTests[i] 279 var braw bytes.Buffer 280 err := tt.Resp.Write(&braw) 281 if err != nil { 282 t.Errorf("error writing #%d: %s", i, err) 283 continue 284 } 285 sraw := braw.String() 286 if sraw != tt.Raw { 287 t.Errorf("Test %d, expecting:\n%q\nGot:\n%q\n", i, tt.Raw, sraw) 288 continue 289 } 290 } 291 } 292