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

    В статье я хочу рассказать не столько об ошибке в 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: Господа минусующие, пожалуйста высказывайте свое мнение. Если я не прав, то мне бы хотелось знать в чем.
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 10

      0
      если сообщение содержит невалидный стандартный заголовок, он не будет допущен правилом его описывающим, но extension-header заголовок допустит, что не правильно.

      Почему не правильно? Согласно схемы это будет правильный extension-header заголовок.

      Что значит «невалидный» стандартный заголовок?
        0
        Согласно схемы это будет правильный extension-header заголовок.

        Да, я с этим согласен.

        Может это конечно мое восприятие смещено, но мне кажется что допустим такое сообщение:
        HTTP/1.0 200 OK
        Date: Fri, 31 Dec 1999 23:59:59 GMT
        Content-Type: text/html
        Content-Length: здесь фигня какая-то вместо цифр
        

        должно считаться не валидным.

        Сейчас получиться что оно валидно, но вместо Content-Length получим extension-header. У меня в реальности, в моей задачи такое поведение очень досаждает, выходит так что невозможно проверить синтаксическую корректность сообщения, и очевидные синтаксические ошибки проваливаются на другой уровень.
          +1
          Выходит, что грамматически сообщение валидно. Не валидно значение/формат интересующего вас конкретного заголовка, поэтому, думаю, ошибку должен выдавать тот, кто обрабатывает заголовки.
            0
            В даном случае никакой вообще ошибки — ведь поле Content-Length необязательно.
              0
              Может быть искусственно ввести в грамматику правила, описывающие формат (некоторых) стандартных заголовков?
                0
                Эти правила есть:

                Content-Length = "Content-Length" ":" 1*DIGIT

                просто получается, что extension-header нивелирует их своей жадностью
                  0
                  Задача кажется нерешаемой одним лишь парсером, сгенерированным по грамматике. Мне кажется, что без заточек на имена заголовков или модификаций самой грамматики тут не обойтись
              0
              кто обрабатывает заголовки

              Так ведь extension-header с именем Content-Length никто обрабатывать не будет, а ошибка есть.
            0
            Не факт что это ошибка.
            Насколько я просмотрел, отдельного HTTP-статуса валидации — нет.
            Есть, к примеру, 411, который посылается если не найдено корректное поле Content-length.
            Ну а вообще, странно, да, ибо к примеру, в ЯП постоянно исключают keyword'ы из ident'ов.
              0
              Хорошая аналогия.
              По поводу ошибка или нет, тут нужно учесть, что парсеры для таких сообщений (знаю, что SIP имеет схожий формат точно) делают следующим образом (я других вариантов не встречал): режут исходное сообщение на заголовки и каждый заголовок уже парсят отдельно каким либо способом, причем здесь один вариант — выбирать парсер по имени заголовка, т.е. Content-Length будет парсится отдельно своим парсером. И вот как раз в этом случае, естественным образом получится, что если мы не можем распарсить заголовок предназначеным для него парсером, то возникает ошибка (если этот заголовок важен для нас). Но только это не совсем то поведение которое прописано в rfc.

            Only users with full accounts can post comments. Log in, please.