The data \fBvalue\fP field points to, comes exactly as delivered over the
network but with leading and trailing whitespace and newlines stripped
-off. The `value` data is nul-terminated.
+off. The `value` data is nul-terminated. For legacy HTTP/1 "folded headers",
+this API provides the full single value in an unfolded manner with a single
+whitespace between the lines.
\fBamount\fP is how many headers using this name that exist, within the origin
and request scope asked for.
return CURLE_OK;
}
+static CURLcode append_value(struct Curl_easy *data, const char *value,
+ size_t vlen) /* length of the incoming header */
+{
+ struct Curl_header_store *hs;
+ struct Curl_header_store *newhs;
+ size_t olen; /* length of the old value */
+ size_t offset;
+ DEBUGASSERT(data->state.prevhead);
+ hs = data->state.prevhead;
+ olen = strlen(hs->value);
+ offset = hs->value - hs->buffer;
+
+ /* skip all trailing space letters */
+ while(vlen && ISSPACE(value[vlen - 1]))
+ vlen--;
+
+ /* save only one leading space */
+ while((vlen > 1) && ISSPACE(value[0]) && ISSPACE(value[1])) {
+ vlen--;
+ value++;
+ }
+
+ /* since this header block might move in the realloc below, it needs to
+ first be unlinked from the list and then re-added again after the
+ realloc */
+ Curl_llist_remove(&data->state.httphdrs, &hs->node, NULL);
+
+ newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + olen + 1);
+ if(!newhs)
+ return CURLE_OUT_OF_MEMORY;
+ /* ->name' and ->value point into ->buffer (to keep the header allocation
+ in a single memory block), which now potentially have moved. Adjust
+ them. */
+ newhs->name = newhs->buffer;
+ newhs->value = &newhs->buffer[offset];
+
+ /* put the data at the end of the previous data, not the newline */
+ memcpy(&newhs->value[olen], value, vlen);
+ newhs->value[olen + vlen] = 0; /* zero terminate at newline */
+
+ /* insert this node into the list of headers */
+ Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail,
+ newhs, &newhs->node);
+ data->state.prevhead = newhs;
+ return CURLE_OK;
+}
+
+
/*
* Curl_headers_push() gets passed a full HTTP header to store. It gets called
* immediately before the header callback. The header is CRLF terminated.
}
hlen = end - header + 1;
+ if((header[0] == ' ') || (header[0] == '\t'))
+ /* line folding, append value to the previous header's value */
+ return append_value(data, header, hlen);
+
hs = calloc(1, sizeof(*hs) + hlen);
if(!hs)
return CURLE_OUT_OF_MEMORY;
/* insert this node into the list of headers */
Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail,
hs, &hs->node);
-
+ data->state.prevhead = hs;
return CURLE_OK;
fail:
free(hs);
if(k->headerline < 2)
/* the first "header" is the status-line and it has no colon */
return CURLE_OK;
- ptr = memchr(header, ':', hlen);
- if(!ptr) {
- /* this is bad, bail out */
- failf(data, "Header without colon");
- return CURLE_WEIRD_SERVER_REPLY;
+ if(((header[0] == ' ') || (header[0] == '\t')) && k->headerline > 2)
+ /* line folding, can't happen on line 2 */
+ ;
+ else {
+ ptr = memchr(header, ':', hlen);
+ if(!ptr) {
+ /* this is bad, bail out */
+ failf(data, "Header without colon");
+ return CURLE_WEIRD_SERVER_REPLY;
+ }
}
return CURLE_OK;
}
headers */
struct Curl_llist httphdrs; /* received headers */
struct curl_header headerout; /* for external purposes */
+ struct Curl_header_store *prevhead; /* the latest added header */
#endif
trailers_state trailers_state; /* whether we are sending trailers
and what stage are we at */
test1248 test1249 test1250 test1251 test1252 test1253 test1254 test1255 \
test1256 test1257 test1258 test1259 test1260 test1261 test1262 test1263 \
test1264 test1265 test1266 test1267 test1268 test1269 test1270 test1271 \
-test1272 test1273 \
+test1272 test1273 test1274 \
\
test1280 test1281 test1282 test1283 test1284 test1285 test1286 test1287 \
test1288 test1289 test1290 test1291 test1292 test1293 test1294 test1295 \
--- /dev/null
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+header line folding
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK\r
+Date: Tue, 09 Nov 2010 14:49:00 GMT\r
+Server: test-server/\r
+ fake\r
+ folded\r
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT\r
+ETag: "21025-dc7-39462498"\r
+Content-Length: 6\r
+Connection: close\r
+\r
+-foo-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+ <name>
+HTTP header line folding
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -D log/out%TESTNUMBER
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+\r
+</protocol>
+<file name="log/out%TESTNUMBER">
+HTTP/1.1 200 OK\r
+Date: Tue, 09 Nov 2010 14:49:00 GMT\r
+Server: test-server/\r
+ fake\r
+ folded\r
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT\r
+ETag: "21025-dc7-39462498"\r
+Content-Length: 6\r
+Connection: close\r
+\r
+</file>
+</verify>
+</testcase>
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test with trailing space
Content-Type: text/html
+Fold: is
+ folding a
+ line
Content-Length: 0
Set-Cookie: onecookie=data;
Set-Cookie: secondcookie=2data;
# Verify data after the test has been "shot"
<verify>
-<stdout>
+<stdout mode="text">
Date == Thu, 09 Nov 2010 14:49:00 GMT
Server == test with trailing space
Content-Type == text/html
- Set-Cookie == onecookie=data; (0/3)
- Set-Cookie == secondcookie=2data; (1/3)
- Set-Cookie == cookie3=data3; (2/3)
+ Fold == is folding a line
</stdout>
</verify>
</testcase>
"location",
"set-cookie",
"silly-thing",
+ "fold",
NULL
};