Pull to refresh

Ошибка в HTTP протоколе

Reading time 3 min
Views 10K
В статье я хочу рассказать не столько об ошибке в RFC 2616, сколько о своем подходе к созданию парсера HTTP сообщений, показать его преимущества и недостатки. В основу моего подхода положено два принципа «лучше час потерять, потом за пять минут долететь» и «пусть компьютер работает, а я отдохну».

И так задача в целом: реализовать HTTP сервер, и HTTP парсер, в частности. Протокол версии 1.1 описан в RFC 2616. В этой спецификации можно выделить две сущности: описательная часть и BNF правила, которые определяют синтаксис соообщений. BNF правила хорошо формализованая вещь, для которой даже существует RFC 5234, где грамматика BNF описана с помощью опяь же BNF правил. Правда выпущена RFC 5234 была позже чем RFC 2616 (HTTP), и имеет несколько непринципиальных отличий.

BNF, краткий экскурс

BNF грамматика, довольно проста, поэтому я приведу пример с пояснениями, думаю этого будет достаточно, для того чтобы составить представление (для тех кто не знаком).
start-line      = Request-Line | Status-Line

generic-message = start-line
                 *(message-header CRLF)
                 CRLF
                 [ message-body ]

Если перевести на русский язык, получится:
1) start-line — это Request-Line или Status-Line (это тоже правила, которые где-то описаны)
2) generic-message — это последовательность из start-line, *(message-header CRLF), CRLF и возможно message-body ([...] скобки определяют не обязательность). Где *(message-header CRLF) допускает, 0 или больше повторений конкатенации двух правил message-header и CRLF.
Чем-то похоже на регулярные выражения, что не удивительно.

Про ошибку

Для того чтобы проследить ошибку, ниже я привел ряд правил. Бегло пробегаясь по ним, можно увидить следующее: Запрос состоит из Request-Line и повторения заголовков (header) разделенных CRLF. В числе групп заголовков присутствует entity-header, который в отличии от general-header и request-header, содержит extension-header. Правило extension-header разрешает нестандартные заголовки, иначе говоря, именно оно разрашает добавить в запрос заголовок
My-Header: I am server
при этом запрос останется валидным. Кроме того, это правило открывает возмножность писать расширения протокола. Так как extension-header допускает любые заголовки, в том числе и стандартные (From, Accept, Host, Referer и т.д.), возникает такая ситуация: если сообщение содержит невалидный стандартный заголовок, он не будет допущен правилом его описывающим, но extension-header заголовок допустит, что не правильно.
Request       = Request-Line              ; Section 5.1
                *(( general-header        ; Section 4.5
                 | request-header         ; Section 5.3
                 | entity-header ) CRLF)  ; Section 7.1
                 CRLF
                [ message-body ]          ; Section 4.3

entity-header  = Allow                    ; Section 14.7
               | Content-Encoding         ; Section 14.11
               | Content-Language         ; Section 14.12
               | Content-Length           ; Section 14.13
               | Content-Location         ; Section 14.14
               | Content-MD5              ; Section 14.15
               | Content-Range            ; Section 14.16
               | Content-Type             ; Section 14.17
               | Expires                  ; Section 14.21
               | Last-Modified            ; Section 14.29
               | extension-header

extension-header = message-header

message-header = field-name ":" [ field-value ]
field-name     = token
field-value    = *( field-content | LWS )
field-content  = <the OCTETs making up the field-value
                 and consisting of either *TEXT or combinations
                 of token, separators, and quoted-string>

К сожалению, в BNF нет возможности описать правило вида «что-то не включая что-то другое». В спецификации такие правила описываются неформально:
ctext          = <any TEXT excluding "(" and ")">
qdtext         = <any TEXT except <">>

Корректное правило field-name должно выглядеть примерно следующим образом:
field-name     = <any token excluding "Accept", "Allow", ... all header names from rfc 2616, 2617 ...>


О главном

Я хотел рассказать об утилитах, которые строят ДКА на основе BNF. То есть, в идеале, генерируют парсер автоматически. Но как-то так получилось, что заголовок для привлечения внимания занял слишком много времени, поэтому про инструменты в следующий раз.

UPD: Господа минусующие, пожалуйста высказывайте свое мнение. Если я не прав, то мне бы хотелось знать в чем.
Tags:
Hubs:
+8
Comments 10
Comments Comments 10

Articles