3 ms·
Much of the advantage of using a DFA generator like Ragel is lost because the HTTP header grammar is actually ambiguous in several places and can't be streamed.
by nly 12y ago
Much of the advantage of using a DFA generator like Ragel is lost because the HTTP header grammar is actually ambiguous in several places and can't be streamed. You can use it as a component, but it isn't entirely sufficient on its own.
The HTTP 1.1 RFC requires whitespace be stripped at the end of header values, yet also permits (although deprecates) header folding, giving rise to the following ambiguity (using _'s in place of leading spaces):
Foo: Hello\r\n
______\r\n
________\r\n
________ world!\r\n
Bar: smeg
This requires that the parser buffer all the whitespace between 'Hello' and 'world!' (and the RFC doesn't put a standard limit on header value length) just in case 'world!' never comes and the value of the Foo header has to be stripped back to just "Hello"
Here's a related observation from a commit[0] by the Joyent guys, who wrote the streaming parser used by NodeJS: "For http-parser itself to confirm[sic] exactly would involve significant changes in order to synthesize replacement SP octets. Such changes are unlikely to be worth it to support what is an obscure and deprecated feature"
Another example is parsing the Request and Status lines:
GET <uri> HTTP/1.1
Technically <uri> can't contain spaces, but the RFC says you MAY accept them [RFC7230: 3.5. Message Parsing Robustness] ... which then gives rise to the possibility of <uri> containing the literal string " HTTP/1.1", and ultimately opens up bad user agents that send spaces to header injection.
Resolving these ambiguities require implementing your own buffering, and dropping down to Ragels 'state charts' feature to avoid your semantic actions being munged... which leaves you to design the top level state machine yourself.
[0] https://github.com/joyent/http-parser/commit/5d9c3821729b194eef60f41fcc5f8b4657c3d8ff https://github.com/joyent/http-parser/commit/5d9c3821729b194...
- oever 12y agohttps://www.ietf.org/rfc/rfc2616.txt https://www.ietf.org/rfc/rfc2616.txt : "All linear white space, including folding, has the same semantics as SP." So only one space need be counted and any subsequent space or horizontal tab can be ignored. Your example simplifies to: Foo: Hello World\r\n Bar: smeg
- nly 12y agoIt doesn't matter that the obs-fold can be treated as a single space because you don't know that until you've actually reached the fold. Consider: Foo: Hello_______world\r\n Here the whitespace has to be preserved, as per the field-content production. So RFC2616 allowed: Foo: Hello_______\r\n ____world\r\n to be reduced to: Foo: Hello world\r\n Incidentally RFC7230 (it's successor) says something subtly different: "A server that receives an obs-fold in a request message that is not within a message/http container MUST either reject the message by sending a 400 (Bad Request), preferably with a representation explaining that obsolete line folding is unacceptable, or replace each received obs-fold with one or more SP octets prior to interpreting the field value or forwarding the message downstream." So now it's been loosened to one or more SP octets... useful, except there's no mention of TAB octets, which can follow a CRLF as part of an obs-fold... so you still can't just remove the CRLF and preserve all the whitespace... preserving tabs would be illegal. So the new rules don't help streaming either. Joyents parser did this regardless (not sure if it still does). You'll also notice it says the obs-fold can be replaced with a sequence of SP characters... according to the grammar that's the CRLF and the following whitespace, not any whitespace preceding the CRLF. You'd think that would be helpful because it means any whitespace before the CRLF can always be streamed as part of the field value, right? Except... Foo: Hello_______\r\n ____\r\n would still simplify to: Foo: Hello_______[one or more SP octets]\r\n and then what? Well, presumably you then have to trim off the trailing whitespace to produce a value of just "Hello"... so all the whitespace you buffered before you reached the CRLF (in case you reached another field-vchar like the 'w' in 'world') has to be discarded.
- Retric 12y agoAssuming your reading this from a buffer and most of the time you don't need to count then store an index to the o in hello followed by ignoring space. Then to back and count it only if you need to. Or better yet if whitespace is not need just copy a space but if it's needed then move those bytes to the next stage and don't bother to count them that go around.
- nly 12y ago... and what happens if your header happens to run off the end of your buffer amongst the spaces that follow 'Hello'? Cookies can be huge, I can construct a header like 'Cookie: x[8000 spaces]y'