View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2002-2017, University of Amsterdam
    7                              VU University Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(http_header,
   37          [ http_read_request/2,        % +Stream, -Request
   38            http_read_reply_header/2,   % +Stream, -Reply
   39            http_reply/2,               % +What, +Stream
   40            http_reply/3,               % +What, +Stream, +HdrExtra
   41            http_reply/4,               % +What, +Stream, +HdrExtra, -Code
   42            http_reply/5,               % +What, +Stream, +HdrExtra, +Context,
   43                                        % -Code
   44            http_reply/6,               % +What, +Stream, +HdrExtra, +Context,
   45                                        % +Request, -Code
   46            http_reply_header/3,        % +Stream, +What, +HdrExtra
   47            http_status_reply/4,        % +Status, +Out, +HdrExtra, -Code
   48            http_status_reply/5,        % +Status, +Out, +HdrExtra,
   49                                        % +Context, -Code
   50
   51            http_timestamp/2,           % +Time, -HTTP string
   52
   53            http_post_data/3,           % +Stream, +Data, +HdrExtra
   54
   55            http_read_header/2,         % +Fd, -Header
   56            http_parse_header/2,        % +Codes, -Header
   57            http_parse_header_value/3,  % +Header, +HeaderValue, -MediaTypes
   58            http_join_headers/3,        % +Default, +InHdr, -OutHdr
   59            http_update_encoding/3,     % +HeaderIn, -Encoding, -HeaderOut
   60            http_update_connection/4,   % +HeaderIn, +Request, -Connection, -HeaderOut
   61            http_update_transfer/4      % +HeaderIn, +Request, -Transfer, -HeaderOut
   62          ]).   63:- use_module(library(readutil)).   64:- use_module(library(debug)).   65:- use_module(library(error)).   66:- use_module(library(option)).   67:- use_module(library(lists)).   68:- use_module(library(url)).   69:- use_module(library(uri)).   70:- use_module(library(memfile)).   71:- use_module(library(settings)).   72:- use_module(library(error)).   73:- use_module(library(pairs)).   74:- use_module(library(socket)).   75:- use_module(library(dcg/basics)).   76:- use_module(html_write).   77:- use_module(http_exception).   78:- use_module(mimetype).   79:- use_module(mimepack).   80
   81:- multifile
   82    http:status_page/3,             % +Status, +Context, -HTML
   83    http:post_data_hook/3,          % +Data, +Out, +HdrExtra
   84    http:mime_type_encoding/2.       % +MimeType, -Encoding
   85
   86% see http_update_transfer/4.
   87
   88:- setting(http:chunked_transfer, oneof([never,on_request,if_possible]),
   89           on_request, 'When to use Transfer-Encoding: Chunked').

Handling HTTP headers

The library library(http/http_header) provides primitives for parsing and composing HTTP headers. Its functionality is normally hidden by the other parts of the HTTP server and client libraries. */

  100                 /*******************************
  101                 *          READ REQUEST        *
  102                 *******************************/
 http_read_request(+FdIn:stream, -Request) is det
Read an HTTP request-header from FdIn and return the broken-down request fields as +Name(+Value) pairs in a list. Request is unified to end_of_file if FdIn is at the end of input.
  110http_read_request(In, Request) :-
  111    catch(read_line_to_codes(In, Codes), E, true),
  112    (   var(E)
  113    ->  (   Codes == end_of_file
  114        ->  debug(http(header), 'end-of-file', []),
  115            Request = end_of_file
  116        ;   debug(http(header), 'First line: ~s', [Codes]),
  117            Request =  [input(In)|Request1],
  118            phrase(request(In, Request1), Codes),
  119            (   Request1 = [unknown(Text)|_]
  120            ->  string_codes(S, Text),
  121                syntax_error(http_request(S))
  122            ;   true
  123            )
  124        )
  125    ;   (   debugging(http(request))
  126        ->  message_to_string(E, Msg),
  127            debug(http(request), "Exception reading 1st line: ~s", [Msg])
  128        ;   true
  129        ),
  130        Request = end_of_file
  131    ).
 http_read_reply_header(+FdIn, -Reply)
Read the HTTP reply header. Throws an exception if the current input does not contain a valid reply header.
  139http_read_reply_header(In, [input(In)|Reply]) :-
  140    read_line_to_codes(In, Codes),
  141    (   Codes == end_of_file
  142    ->  debug(http(header), 'end-of-file', []),
  143        throw(error(syntax(http_reply_header, end_of_file), _))
  144    ;   debug(http(header), 'First line: ~s~n', [Codes]),
  145        (   phrase(reply(In, Reply), Codes)
  146        ->  true
  147        ;   atom_codes(Header, Codes),
  148            syntax_error(http_reply_header(Header))
  149        )
  150    ).
  151
  152
  153                 /*******************************
  154                 *        FORMULATE REPLY       *
  155                 *******************************/
 http_reply(+Data, +Out:stream) is det
 http_reply(+Data, +Out:stream, +HdrExtra) is det
 http_reply(+Data, +Out:stream, +HdrExtra, -Code) is det
 http_reply(+Data, +Out:stream, +HdrExtra, +Context, -Code) is det
 http_reply(+Data, +Out:stream, +HdrExtra, +Context, +Request, -Code) is det
Compose a complete HTTP reply from the term Data using additional headers from HdrExtra to the output stream Out. ExtraHeader is a list of Field(Value). Data is one of:
html(HTML)
HTML tokens as produced by html//1 from html_write.pl
file(+MimeType, +FileName)
Reply content of FileName using MimeType
file(+MimeType, +FileName, +Range)
Reply partial content of FileName with given MimeType
tmp_file(+MimeType, +FileName)
Same as file, but do not include modification time
bytes(+MimeType, +Bytes)
Send a sequence of Bytes with the indicated MimeType. Bytes is either a string of character codes 0..255 or list of integers in the range 0..255. Out-of-bound codes result in a representation error exception.
stream(+In, +Len)
Reply content of stream.
cgi_stream(+In, +Len)
Reply content of stream, which should start with an HTTP header, followed by a blank line. This is the typical output from a CGI script.
Status
HTTP status report as defined by http_status_reply/4.
Arguments:
HdrExtra- provides additional reply-header fields, encoded as Name(Value). It can also contain a field content_length(-Len) to retrieve the value of the Content-length header that is replied.
Code- is the numeric HTTP status code sent
To be done
- Complete documentation
  204http_reply(What, Out) :-
  205    http_reply(What, Out, [connection(close)], _).
  206
  207http_reply(Data, Out, HdrExtra) :-
  208    http_reply(Data, Out, HdrExtra, _Code).
  209
  210http_reply(Data, Out, HdrExtra, Code) :-
  211    http_reply(Data, Out, HdrExtra, [], Code).
  212
  213http_reply(Data, Out, HdrExtra, Context, Code) :-
  214    http_reply(Data, Out, HdrExtra, Context, [method(get)], Code).
  215
  216http_reply(Data, Out, HdrExtra, _Context, Request, Code) :-
  217    byte_count(Out, C0),
  218    memberchk(method(Method), Request),
  219    catch(http_reply_data(Data, Out, HdrExtra, Method, Code), E, true),
  220    !,
  221    (   var(E)
  222    ->  true
  223    ;   E = error(io_error(write, _), _)
  224    ->  byte_count(Out, C1),
  225        Sent is C1 - C0,
  226        throw(error(http_write_short(Data, Sent), _))
  227    ;   E = error(timeout_error(write, _), _)
  228    ->  throw(E)
  229    ;   map_exception_to_http_status(E, Status, NewHdr, NewContext),
  230        http_status_reply(Status, Out, NewHdr, NewContext, Request, Code)
  231    ).
  232http_reply(Status, Out, HdrExtra, Context, Request, Code) :-
  233    http_status_reply(Status, Out, HdrExtra, Context, Request, Code).
  234
  235:- meta_predicate
  236    if_no_head(+, 0).
 http_reply_data(+Data, +Out, +HdrExtra, +Method, -Code) is semidet
Fails if Data is not a defined reply-data format, but a status term. See http_reply/3 and http_status_reply/6.
Errors
- Various I/O errors.
  245http_reply_data(Data, Out, HdrExtra, Method, Code) :-
  246    http_reply_data_(Data, Out, HdrExtra, Method, Code),
  247    flush_output(Out).
  248
  249http_reply_data_(html(HTML), Out, HdrExtra, Method, Code) :-
  250    !,
  251    phrase(reply_header(html(HTML), HdrExtra, Code), Header),
  252    format(Out, '~s', [Header]),
  253    if_no_head(Method, print_html(Out, HTML)).
  254http_reply_data_(file(Type, File), Out, HdrExtra, Method, Code) :-
  255    !,
  256    phrase(reply_header(file(Type, File), HdrExtra, Code), Header),
  257    reply_file(Out, File, Header, Method).
  258http_reply_data_(gzip_file(Type, File), Out, HdrExtra, Method, Code) :-
  259    !,
  260    phrase(reply_header(gzip_file(Type, File), HdrExtra, Code), Header),
  261    reply_file(Out, File, Header, Method).
  262http_reply_data_(file(Type, File, Range), Out, HdrExtra, Method, Code) :-
  263    !,
  264    phrase(reply_header(file(Type, File, Range), HdrExtra, Code), Header),
  265    reply_file_range(Out, File, Header, Range, Method).
  266http_reply_data_(tmp_file(Type, File), Out, HdrExtra, Method, Code) :-
  267    !,
  268    phrase(reply_header(tmp_file(Type, File), HdrExtra, Code), Header),
  269    reply_file(Out, File, Header, Method).
  270http_reply_data_(bytes(Type, Bytes), Out, HdrExtra, Method, Code) :-
  271    !,
  272    phrase(reply_header(bytes(Type, Bytes), HdrExtra, Code), Header),
  273    format(Out, '~s', [Header]),
  274    if_no_head(Method, format(Out, '~s', [Bytes])).
  275http_reply_data_(stream(In, Len), Out, HdrExtra, Method, Code) :-
  276    !,
  277    phrase(reply_header(cgi_data(Len), HdrExtra, Code), Header),
  278    copy_stream(Out, In, Header, Method, 0, end).
  279http_reply_data_(cgi_stream(In, Len), Out, HdrExtra, Method, Code) :-
  280    !,
  281    http_read_header(In, CgiHeader),
  282    seek(In, 0, current, Pos),
  283    Size is Len - Pos,
  284    http_join_headers(HdrExtra, CgiHeader, Hdr2),
  285    phrase(reply_header(cgi_data(Size), Hdr2, Code), Header),
  286    copy_stream(Out, In, Header, Method, 0, end).
  287
  288if_no_head(head, _) :- !.
  289if_no_head(_, Goal) :-
  290    call(Goal).
  291
  292reply_file(Out, _File, Header, head) :-
  293    !,
  294    format(Out, '~s', [Header]).
  295reply_file(Out, File, Header, _) :-
  296    setup_call_cleanup(
  297        open(File, read, In, [type(binary)]),
  298        copy_stream(Out, In, Header, 0, end),
  299        close(In)).
  300
  301reply_file_range(Out, _File, Header, _Range, head) :-
  302    !,
  303    format(Out, '~s', [Header]).
  304reply_file_range(Out, File, Header, bytes(From, To), _) :-
  305    setup_call_cleanup(
  306        open(File, read, In, [type(binary)]),
  307        copy_stream(Out, In, Header, From, To),
  308        close(In)).
  309
  310copy_stream(Out, _, Header, head, _, _) :-
  311    !,
  312    format(Out, '~s', [Header]).
  313copy_stream(Out, In, Header, _, From, To) :-
  314    copy_stream(Out, In, Header, From, To).
  315
  316copy_stream(Out, In, Header, From, To) :-
  317    (   From == 0
  318    ->  true
  319    ;   seek(In, From, bof, _)
  320    ),
  321    peek_byte(In, _),
  322    format(Out, '~s', [Header]),
  323    (   To == end
  324    ->  copy_stream_data(In, Out)
  325    ;   Len is To - From,
  326        copy_stream_data(In, Out, Len)
  327    ).
 http_status_reply(+Status, +Out, +HdrExtra, -Code) is det
 http_status_reply(+Status, +Out, +HdrExtra, +Context, -Code) is det
 http_status_reply(+Status, +Out, +HdrExtra, +Context, +Request, -Code) is det
Emit HTML non-200 status reports. Such requests are always sent as UTF-8 documents.

Status can be one of the following:

authorise(Method)
Challenge authorization. Method is one of
  • basic(Realm)
  • digest(Digest)
authorise(basic, Realm)
Same as authorise(basic(Realm)). Deprecated.
bad_request(ErrorTerm)
busy
created(Location)
forbidden(Url)
moved(To)
moved_temporary(To)
no_content
not_acceptable(WhyHtml)
not_found(Path)
method_not_allowed(Method, Path)
not_modified
resource_error(ErrorTerm)
see_other(To)
switching_protocols(Goal, Options)
server_error(ErrorTerm)
unavailable(WhyHtml)
  361http_status_reply(Status, Out, HdrExtra, Code) :-
  362    http_status_reply(Status, Out, HdrExtra, [], Code).
  363
  364http_status_reply(Status, Out, HdrExtra, Context, Code) :-
  365    http_status_reply(Status, Out, HdrExtra, Context, [method(get)], Code).
  366
  367http_status_reply(Status, Out, HdrExtra, Context, Request, Code) :-
  368    option(method(Method), Request, get),
  369    setup_call_cleanup(
  370        set_stream(Out, encoding(utf8)),
  371        status_reply_flush(Status, Out, HdrExtra, Context, Method, Code),
  372        set_stream(Out, encoding(octet))),
  373    !.
  374
  375status_reply_flush(Status, Out, HdrExtra, Context, Method, Code) :-
  376    status_reply(Status, Out, HdrExtra, Context, Method, Code),
  377    flush_output(Out).
  378
  379status_reply(no_content, Out, HdrExtra, _Context, _Method, Code) :-
  380    !,
  381    phrase(reply_header(status(no_content), HdrExtra, Code), Header),
  382    format(Out, '~s', [Header]).
  383status_reply(switching_protocols(_Goal,Options), Out,
  384             HdrExtra0, _Context, _Method, Code) :-
  385    !,
  386    (   option(headers(Extra1), Options)
  387    ->  true
  388    ;   option(header(Extra1), Options, [])
  389    ),
  390    http_join_headers(HdrExtra0, Extra1, HdrExtra),
  391    phrase(reply_header(status(switching_protocols), HdrExtra, Code), Header),
  392    format(Out, '~s', [Header]).
  393status_reply(created(Location), Out, HdrExtra, _Context, Method, Code) :-
  394    !,
  395    phrase(page([ title('201 Created')
  396                ],
  397                [ h1('Created'),
  398                  p(['The document was created ',
  399                     a(href(Location), ' Here')
  400                    ]),
  401                  \address
  402                ]),
  403           HTML),
  404    phrase(reply_header(created(Location, HTML), HdrExtra, Code), Header),
  405    format(Out, '~s', [Header]),
  406    print_html_if_no_head(Method, Out, HTML).
  407status_reply(moved(To), Out, HdrExtra, _Context, Method, Code) :-
  408    !,
  409    phrase(page([ title('301 Moved Permanently')
  410                ],
  411                [ h1('Moved Permanently'),
  412                  p(['The document has moved ',
  413                     a(href(To), ' Here')
  414                    ]),
  415                  \address
  416                ]),
  417           HTML),
  418    phrase(reply_header(moved(To, HTML), HdrExtra, Code), Header),
  419    format(Out, '~s', [Header]),
  420    print_html_if_no_head(Method, Out, HTML).
  421status_reply(moved_temporary(To), Out, HdrExtra, _Context, Method, Code) :-
  422    !,
  423    phrase(page([ title('302 Moved Temporary')
  424                ],
  425                [ h1('Moved Temporary'),
  426                  p(['The document is currently ',
  427                     a(href(To), ' Here')
  428                    ]),
  429                  \address
  430                ]),
  431           HTML),
  432    phrase(reply_header(moved_temporary(To, HTML),
  433                        HdrExtra, Code), Header),
  434    format(Out, '~s', [Header]),
  435    print_html_if_no_head(Method, Out, HTML).
  436status_reply(see_other(To),Out,HdrExtra, _Context, Method, Code) :-
  437    !,
  438    phrase(page([ title('303 See Other')
  439                 ],
  440                 [ h1('See Other'),
  441                   p(['See other document ',
  442                      a(href(To), ' Here')
  443                     ]),
  444                   \address
  445                 ]),
  446            HTML),
  447     phrase(reply_header(see_other(To, HTML), HdrExtra, Code), Header),
  448     format(Out, '~s', [Header]),
  449     print_html_if_no_head(Method, Out, HTML).
  450status_reply(bad_request(ErrorTerm), Out, HdrExtra, _Context, Method, Code) :-
  451    !,
  452    '$messages':translate_message(ErrorTerm, Lines, []),
  453    phrase(page([ title('400 Bad Request')
  454                ],
  455                [ h1('Bad Request'),
  456                  p(\html_message_lines(Lines)),
  457                  \address
  458                ]),
  459           HTML),
  460    phrase(reply_header(status(bad_request, HTML),
  461                        HdrExtra, Code), Header),
  462    format(Out, '~s', [Header]),
  463    print_html_if_no_head(Method, Out, HTML).
  464status_reply(not_found(URL), Out, HdrExtra, Context, Method, Code) :-
  465    !,
  466    status_page_hook(not_found(URL), 404, Context, HTML),
  467    phrase(reply_header(status(not_found, HTML), HdrExtra, Code), Header),
  468    format(Out, '~s', [Header]),
  469    print_html_if_no_head(Method, Out, HTML).
  470status_reply(method_not_allowed(Method, URL), Out, HdrExtra, Context, QMethod, Code) :-
  471    !,
  472    upcase_atom(Method, UMethod),
  473    status_page_hook(method_not_allowed(UMethod,URL), 405, Context, HTML),
  474    phrase(reply_header(status(method_not_allowed, HTML),
  475                        HdrExtra, Code), Header),
  476    format(Out, '~s', [Header]),
  477    if_no_head(QMethod, print_html(Out, HTML)).
  478status_reply(forbidden(URL), Out, HdrExtra, Context, Method, Code) :-
  479    !,
  480    status_page_hook(forbidden(URL), 403, Context, HTML),
  481    phrase(reply_header(status(forbidden, HTML), HdrExtra, Code), Header),
  482    format(Out, '~s', [Header]),
  483    print_html_if_no_head(Method, Out, HTML).
  484status_reply(authorise(basic, ''), Out, HdrExtra, Context, Method, Code) :-
  485    !,
  486    status_reply(authorise(basic), Out, HdrExtra, Context, Method, Code).
  487status_reply(authorise(basic, Realm), Out, HdrExtra, Context, Method, Code) :-
  488    !,
  489    status_reply(authorise(basic(Realm)), Out, HdrExtra, Context,
  490                 Method, Code).
  491status_reply(authorise(Method), Out, HdrExtra, Context, QMethod, Code) :-
  492    !,
  493    status_page_hook(authorise(Method), 401, Context, HTML),
  494    phrase(reply_header(authorise(Method, HTML),
  495                        HdrExtra, Code), Header),
  496    format(Out, '~s', [Header]),
  497    print_html_if_no_head(QMethod, Out, HTML).
  498status_reply(not_modified, Out, HdrExtra, _Context, _Method, Code) :-
  499    !,
  500    phrase(reply_header(status(not_modified), HdrExtra, Code), Header),
  501    format(Out, '~s', [Header]).
  502status_reply(server_error(ErrorTerm), Out, HdrExtra, _Context, Method, Code) :-
  503    in_or_exclude_backtrace(ErrorTerm, ErrorTerm1),
  504    '$messages':translate_message(ErrorTerm1, Lines, []),
  505    phrase(page([ title('500 Internal server error')
  506                ],
  507                [ h1('Internal server error'),
  508                  p(\html_message_lines(Lines)),
  509                  \address
  510                ]),
  511           HTML),
  512    phrase(reply_header(status(server_error, HTML),
  513                        HdrExtra, Code), Header),
  514    format(Out, '~s', [Header]),
  515    print_html_if_no_head(Method, Out, HTML).
  516status_reply(not_acceptable(WhyHTML), Out, HdrExtra, _Context,
  517             Method, Code) :-
  518    !,
  519    phrase(page([ title('406 Not Acceptable')
  520                ],
  521                [ h1('Not Acceptable'),
  522                  WhyHTML,
  523                  \address
  524                ]),
  525           HTML),
  526    phrase(reply_header(status(not_acceptable, HTML), HdrExtra, Code), Header),
  527    format(Out, '~s', [Header]),
  528    print_html_if_no_head(Method, Out, HTML).
  529status_reply(unavailable(WhyHTML), Out, HdrExtra, _Context, Method, Code) :-
  530    !,
  531    phrase(page([ title('503 Service Unavailable')
  532                ],
  533                [ h1('Service Unavailable'),
  534                  WhyHTML,
  535                  \address
  536                ]),
  537           HTML),
  538    phrase(reply_header(status(service_unavailable, HTML), HdrExtra, Code),
  539           Header),
  540    format(Out, '~s', [Header]),
  541    print_html_if_no_head(Method, Out, HTML).
  542status_reply(resource_error(ErrorTerm), Out, HdrExtra, Context, Method, Code) :-
  543    !,
  544    '$messages':translate_message(ErrorTerm, Lines, []),
  545    status_reply(unavailable(p(\html_message_lines(Lines))),
  546                 Out, HdrExtra, Context, Method, Code).
  547status_reply(busy, Out, HdrExtra, Context, Method, Code) :-
  548    !,
  549    HTML = p(['The server is temporarily out of resources, ',
  550              'please try again later']),
  551    http_status_reply(unavailable(HTML), Out, HdrExtra, Context,
  552                      Method, Code).
  553
  554print_html_if_no_head(head, _, _) :- !.
  555print_html_if_no_head(_, Out, HTML) :-
  556    print_html(Out, HTML).
 status_page_hook(+Term, +Code, +Context, -HTMLTokens) is det
Calls the following two hooks to generate an HTML page from a status reply.
  566status_page_hook(Term, Status, Context, HTML) :-
  567    (   http:status_page(Term, Context, HTML)
  568    ;   http:status_page(Status, Context, HTML) % deprecated
  569    ),
  570    !.
  571
  572status_page_hook(authorise(_Method), 401, _Context, HTML):-
  573    phrase(page([ title('401 Authorization Required')
  574                ],
  575                [ h1('Authorization Required'),
  576                  p(['This server could not verify that you ',
  577                     'are authorized to access the document ',
  578                     'requested.  Either you supplied the wrong ',
  579                     'credentials (e.g., bad password), or your ',
  580                     'browser doesn\'t understand how to supply ',
  581                     'the credentials required.'
  582                    ]),
  583                  \address
  584                ]),
  585           HTML).
  586status_page_hook(forbidden(URL), 403, _Context, HTML) :-
  587    phrase(page([ title('403 Forbidden')
  588                ],
  589                [ h1('Forbidden'),
  590                  p(['You don\'t have permission to access ', URL,
  591                     ' on this server'
  592                    ]),
  593                  \address
  594                ]),
  595           HTML).
  596status_page_hook(not_found(URL), 404, _Context, HTML) :-
  597    phrase(page([ title('404 Not Found')
  598                ],
  599                [ h1('Not Found'),
  600                  p(['The requested URL ', tt(URL),
  601                     ' was not found on this server'
  602                    ]),
  603                  \address
  604                ]),
  605           HTML).
  606status_page_hook(method_not_allowed(UMethod,URL), 405, _Context, HTML) :-
  607    phrase(page([ title('405 Method not allowed')
  608                ],
  609                [ h1('Method not allowed'),
  610                  p(['The requested URL ', tt(URL),
  611                     ' does not support method ', tt(UMethod), '.'
  612                    ]),
  613                  \address
  614                ]),
  615           HTML).
  616
  617
  618html_message_lines([]) -->
  619    [].
  620html_message_lines([nl|T]) -->
  621    !,
  622    html([br([])]),
  623    html_message_lines(T).
  624html_message_lines([flush]) -->
  625    [].
  626html_message_lines([Fmt-Args|T]) -->
  627    !,
  628    { format(string(S), Fmt, Args)
  629    },
  630    html([S]),
  631    html_message_lines(T).
  632html_message_lines([Fmt|T]) -->
  633    !,
  634    { format(string(S), Fmt, [])
  635    },
  636    html([S]),
  637    html_message_lines(T).
 http_join_headers(+Default, +Header, -Out)
Append headers from Default to Header if they are not already part of it.
  644http_join_headers([], H, H).
  645http_join_headers([H|T], Hdr0, Hdr) :-
  646    functor(H, N, A),
  647    functor(H2, N, A),
  648    member(H2, Hdr0),
  649    !,
  650    http_join_headers(T, Hdr0, Hdr).
  651http_join_headers([H|T], Hdr0, [H|Hdr]) :-
  652    http_join_headers(T, Hdr0, Hdr).
 http_update_encoding(+HeaderIn, -Encoding, -HeaderOut)
Allow for rewrite of the header, adjusting the encoding. We distinguish three options. If the user announces `text', we always use UTF-8 encoding. If the user announces charset=utf-8 we use UTF-8 and otherwise we use octet (raw) encoding. Alternatively we could dynamically choose for ASCII, ISO-Latin-1 or UTF-8.
  664http_update_encoding(Header0, utf8, [content_type(Type)|Header]) :-
  665    select(content_type(Type0), Header0, Header),
  666    sub_atom(Type0, 0, _, _, 'text/'),
  667    !,
  668    (   sub_atom(Type0, S, _, _, ';')
  669    ->  sub_atom(Type0, 0, S, _, B)
  670    ;   B = Type0
  671    ),
  672    atom_concat(B, '; charset=UTF-8', Type).
  673http_update_encoding(Header, Encoding, Header) :-
  674    memberchk(content_type(Type), Header),
  675    (   (   sub_atom(Type, _, _, _, 'UTF-8')
  676        ;   sub_atom(Type, _, _, _, 'utf-8')
  677        )
  678    ->  Encoding = utf8
  679    ;   http:mime_type_encoding(Type, Encoding)
  680    ->  true
  681    ;   mime_type_encoding(Type, Encoding)
  682    ).
  683http_update_encoding(Header, octet, Header).
 mime_type_encoding(+MimeType, -Encoding) is semidet
Encoding is the (default) character encoding for MimeType. Hooked by mime_type_encoding/2.
  690mime_type_encoding('application/json',         utf8).
  691mime_type_encoding('application/jsonrequest',  utf8).
  692mime_type_encoding('application/x-prolog',     utf8).
  693mime_type_encoding('application/n-quads',      utf8).
  694mime_type_encoding('application/n-triples',    utf8).
  695mime_type_encoding('application/sparql-query', utf8).
  696mime_type_encoding('application/trig',         utf8).
 http:mime_type_encoding(+MimeType, -Encoding) is semidet
Encoding is the (default) character encoding for MimeType. This is used for setting the encoding for HTTP replies after the user calls format('Content-type: <MIME type>~n'). This hook is called before mime_type_encoding/2. This default defines utf8 for JSON and Turtle derived application/ MIME types.
 http_update_connection(+CGIHeader, +Request, -Connection, -Header)
Merge keep-alive information from Request and CGIHeader into Header.
  712http_update_connection(CgiHeader, Request, Connect,
  713                       [connection(Connect)|Rest]) :-
  714    select(connection(CgiConn), CgiHeader, Rest),
  715    !,
  716    connection(Request, ReqConnection),
  717    join_connection(ReqConnection, CgiConn, Connect).
  718http_update_connection(CgiHeader, Request, Connect,
  719                       [connection(Connect)|CgiHeader]) :-
  720    connection(Request, Connect).
  721
  722join_connection(Keep1, Keep2, Connection) :-
  723    (   downcase_atom(Keep1, 'keep-alive'),
  724        downcase_atom(Keep2, 'keep-alive')
  725    ->  Connection = 'Keep-Alive'
  726    ;   Connection = close
  727    ).
 connection(+Header, -Connection)
Extract the desired connection from a header.
  734connection(Header, Close) :-
  735    (   memberchk(connection(Connection), Header)
  736    ->  Close = Connection
  737    ;   memberchk(http_version(1-X), Header),
  738        X >= 1
  739    ->  Close = 'Keep-Alive'
  740    ;   Close = close
  741    ).
 http_update_transfer(+Request, +CGIHeader, -Transfer, -Header)
Decide on the transfer encoding from the Request and the CGI header. The behaviour depends on the setting http:chunked_transfer. If never, even explitic requests are ignored. If on_request, chunked encoding is used if requested through the CGI header and allowed by the client. If if_possible, chunked encoding is used whenever the client allows for it, which is interpreted as the client supporting HTTP 1.1 or higher.

Chunked encoding is more space efficient and allows the client to start processing partial results. The drawback is that errors lead to incomplete pages instead of a nicely formatted complete page.

  760http_update_transfer(Request, CgiHeader, Transfer, Header) :-
  761    setting(http:chunked_transfer, When),
  762    http_update_transfer(When, Request, CgiHeader, Transfer, Header).
  763
  764http_update_transfer(never, _, CgiHeader, none, Header) :-
  765    !,
  766    delete(CgiHeader, transfer_encoding(_), Header).
  767http_update_transfer(_, _, CgiHeader, none, Header) :-
  768    memberchk(location(_), CgiHeader),
  769    !,
  770    delete(CgiHeader, transfer_encoding(_), Header).
  771http_update_transfer(_, Request, CgiHeader, Transfer, Header) :-
  772    select(transfer_encoding(CgiTransfer), CgiHeader, Rest),
  773    !,
  774    transfer(Request, ReqConnection),
  775    join_transfer(ReqConnection, CgiTransfer, Transfer),
  776    (   Transfer == none
  777    ->  Header = Rest
  778    ;   Header = [transfer_encoding(Transfer)|Rest]
  779    ).
  780http_update_transfer(if_possible, Request, CgiHeader, Transfer, Header) :-
  781    transfer(Request, Transfer),
  782    Transfer \== none,
  783    !,
  784    Header = [transfer_encoding(Transfer)|CgiHeader].
  785http_update_transfer(_, _, CgiHeader, none, CgiHeader).
  786
  787join_transfer(chunked, chunked, chunked) :- !.
  788join_transfer(_, _, none).
 transfer(+Header, -Connection)
Extract the desired connection from a header.
  795transfer(Header, Transfer) :-
  796    (   memberchk(transfer_encoding(Transfer0), Header)
  797    ->  Transfer = Transfer0
  798    ;   memberchk(http_version(1-X), Header),
  799        X >= 1
  800    ->  Transfer = chunked
  801    ;   Transfer = none
  802    ).
 content_length_in_encoding(+Encoding, +In, -Bytes)
Determine hom many bytes are required to represent the data from stream In using the given encoding. Fails if the data cannot be represented with the given encoding.
  811content_length_in_encoding(Enc, Stream, Bytes) :-
  812    stream_property(Stream, position(Here)),
  813    setup_call_cleanup(
  814        open_null_stream(Out),
  815        ( set_stream(Out, encoding(Enc)),
  816          catch(copy_stream_data(Stream, Out), _, fail),
  817          flush_output(Out),
  818          byte_count(Out, Bytes)
  819        ),
  820        ( close(Out, [force(true)]),
  821          set_stream_position(Stream, Here)
  822        )).
  823
  824
  825                 /*******************************
  826                 *          POST SUPPORT        *
  827                 *******************************/
 http_post_data(+Data, +Out:stream, +HdrExtra) is det
Send data on behalf on an HTTP POST request. This predicate is normally called by http_post/4 from http_client.pl to send the POST data to the server. Data is one of:
  920http_post_data(Data, Out, HdrExtra) :-
  921    http:post_data_hook(Data, Out, HdrExtra),
  922    !.
  923http_post_data(html(HTML), Out, HdrExtra) :-
  924    !,
  925    phrase(post_header(html(HTML), HdrExtra), Header),
  926    format(Out, '~s', [Header]),
  927    print_html(Out, HTML).
  928http_post_data(xml(XML), Out, HdrExtra) :-
  929    !,
  930    http_post_data(xml(text/xml, XML, []), Out, HdrExtra).
  931http_post_data(xml(Type, XML), Out, HdrExtra) :-
  932    !,
  933    http_post_data(xml(Type, XML, []), Out, HdrExtra).
  934http_post_data(xml(Type, XML, Options), Out, HdrExtra) :-
  935    !,
  936    setup_call_cleanup(
  937        new_memory_file(MemFile),
  938        (   setup_call_cleanup(
  939                open_memory_file(MemFile, write, MemOut),
  940                xml_write(MemOut, XML, Options),
  941                close(MemOut)),
  942            http_post_data(memory_file(Type, MemFile), Out, HdrExtra)
  943        ),
  944        free_memory_file(MemFile)).
  945http_post_data(file(File), Out, HdrExtra) :-
  946    !,
  947    (   file_mime_type(File, Type)
  948    ->  true
  949    ;   Type = text/plain
  950    ),
  951    http_post_data(file(Type, File), Out, HdrExtra).
  952http_post_data(file(Type, File), Out, HdrExtra) :-
  953    !,
  954    phrase(post_header(file(Type, File), HdrExtra), Header),
  955    format(Out, '~s', [Header]),
  956    setup_call_cleanup(
  957        open(File, read, In, [type(binary)]),
  958        copy_stream_data(In, Out),
  959        close(In)).
  960http_post_data(memory_file(Type, Handle), Out, HdrExtra) :-
  961    !,
  962    phrase(post_header(memory_file(Type, Handle), HdrExtra), Header),
  963    format(Out, '~s', [Header]),
  964    setup_call_cleanup(
  965        open_memory_file(Handle, read, In, [encoding(octet)]),
  966        copy_stream_data(In, Out),
  967        close(In)).
  968http_post_data(codes(Codes), Out, HdrExtra) :-
  969    !,
  970    http_post_data(codes(text/plain, Codes), Out, HdrExtra).
  971http_post_data(codes(Type, Codes), Out, HdrExtra) :-
  972    !,
  973    phrase(post_header(codes(Type, Codes), HdrExtra), Header),
  974    format(Out, '~s', [Header]),
  975    setup_call_cleanup(
  976        set_stream(Out, encoding(utf8)),
  977        format(Out, '~s', [Codes]),
  978        set_stream(Out, encoding(octet))).
  979http_post_data(bytes(Type, Bytes), Out, HdrExtra) :-
  980    !,
  981    phrase(post_header(bytes(Type, Bytes), HdrExtra), Header),
  982    format(Out, '~s~s', [Header, Bytes]).
  983http_post_data(atom(Atom), Out, HdrExtra) :-
  984    !,
  985    http_post_data(atom(text/plain, Atom), Out, HdrExtra).
  986http_post_data(atom(Type, Atom), Out, HdrExtra) :-
  987    !,
  988    phrase(post_header(atom(Type, Atom), HdrExtra), Header),
  989    format(Out, '~s', [Header]),
  990    setup_call_cleanup(
  991        set_stream(Out, encoding(utf8)),
  992        write(Out, Atom),
  993        set_stream(Out, encoding(octet))).
  994http_post_data(cgi_stream(In, _Len), Out, HdrExtra) :-
  995    !,
  996    debug(obsolete, 'Obsolete 2nd argument in cgi_stream(In,Len)', []),
  997    http_post_data(cgi_stream(In), Out, HdrExtra).
  998http_post_data(cgi_stream(In), Out, HdrExtra) :-
  999    !,
 1000    http_read_header(In, Header0),
 1001    http_update_encoding(Header0, Encoding, Header),
 1002    content_length_in_encoding(Encoding, In, Size),
 1003    http_join_headers(HdrExtra, Header, Hdr2),
 1004    phrase(post_header(cgi_data(Size), Hdr2), HeaderText),
 1005    format(Out, '~s', [HeaderText]),
 1006    setup_call_cleanup(
 1007        set_stream(Out, encoding(Encoding)),
 1008        copy_stream_data(In, Out),
 1009        set_stream(Out, encoding(octet))).
 1010http_post_data(form(Fields), Out, HdrExtra) :-
 1011    !,
 1012    parse_url_search(Codes, Fields),
 1013    length(Codes, Size),
 1014    http_join_headers(HdrExtra,
 1015                      [ content_type('application/x-www-form-urlencoded')
 1016                      ], Header),
 1017    phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1018    format(Out, '~s', [HeaderChars]),
 1019    format(Out, '~s', [Codes]).
 1020http_post_data(form_data(Data), Out, HdrExtra) :-
 1021    !,
 1022    setup_call_cleanup(
 1023        new_memory_file(MemFile),
 1024        ( setup_call_cleanup(
 1025              open_memory_file(MemFile, write, MimeOut),
 1026              mime_pack(Data, MimeOut, Boundary),
 1027              close(MimeOut)),
 1028          size_memory_file(MemFile, Size, octet),
 1029          format(string(ContentType),
 1030                 'multipart/form-data; boundary=~w', [Boundary]),
 1031          http_join_headers(HdrExtra,
 1032                            [ mime_version('1.0'),
 1033                              content_type(ContentType)
 1034                            ], Header),
 1035          phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1036          format(Out, '~s', [HeaderChars]),
 1037          setup_call_cleanup(
 1038              open_memory_file(MemFile, read, In, [encoding(octet)]),
 1039              copy_stream_data(In, Out),
 1040              close(In))
 1041        ),
 1042        free_memory_file(MemFile)).
 1043http_post_data(List, Out, HdrExtra) :-          % multipart-mixed
 1044    is_list(List),
 1045    !,
 1046    setup_call_cleanup(
 1047        new_memory_file(MemFile),
 1048        ( setup_call_cleanup(
 1049              open_memory_file(MemFile, write, MimeOut),
 1050              mime_pack(List, MimeOut, Boundary),
 1051              close(MimeOut)),
 1052          size_memory_file(MemFile, Size, octet),
 1053          format(string(ContentType),
 1054                 'multipart/mixed; boundary=~w', [Boundary]),
 1055          http_join_headers(HdrExtra,
 1056                            [ mime_version('1.0'),
 1057                              content_type(ContentType)
 1058                            ], Header),
 1059          phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1060          format(Out, '~s', [HeaderChars]),
 1061          setup_call_cleanup(
 1062              open_memory_file(MemFile, read, In, [encoding(octet)]),
 1063              copy_stream_data(In, Out),
 1064              close(In))
 1065        ),
 1066        free_memory_file(MemFile)).
 post_header(+Data, +HeaderExtra)//
Generate the POST header, emitting HeaderExtra, followed by the HTTP Content-length and Content-type fields.
 1073post_header(html(Tokens), HdrExtra) -->
 1074    header_fields(HdrExtra, Len),
 1075    content_length(html(Tokens), Len),
 1076    content_type(text/html),
 1077    "\r\n".
 1078post_header(file(Type, File), HdrExtra) -->
 1079    header_fields(HdrExtra, Len),
 1080    content_length(file(File), Len),
 1081    content_type(Type),
 1082    "\r\n".
 1083post_header(memory_file(Type, File), HdrExtra) -->
 1084    header_fields(HdrExtra, Len),
 1085    content_length(memory_file(File), Len),
 1086    content_type(Type),
 1087    "\r\n".
 1088post_header(cgi_data(Size), HdrExtra) -->
 1089    header_fields(HdrExtra, Len),
 1090    content_length(Size, Len),
 1091    "\r\n".
 1092post_header(codes(Type, Codes), HdrExtra) -->
 1093    header_fields(HdrExtra, Len),
 1094    content_length(codes(Codes, utf8), Len),
 1095    content_type(Type, utf8),
 1096    "\r\n".
 1097post_header(bytes(Type, Bytes), HdrExtra) -->
 1098    header_fields(HdrExtra, Len),
 1099    content_length(bytes(Bytes), Len),
 1100    content_type(Type),
 1101    "\r\n".
 1102post_header(atom(Type, Atom), HdrExtra) -->
 1103    header_fields(HdrExtra, Len),
 1104    content_length(atom(Atom, utf8), Len),
 1105    content_type(Type, utf8),
 1106    "\r\n".
 1107
 1108
 1109                 /*******************************
 1110                 *       OUTPUT HEADER DCG      *
 1111                 *******************************/
 http_reply_header(+Out:stream, +What, +HdrExtra) is det
Create a reply header using reply_header//3 and send it to Stream.
 1118http_reply_header(Out, What, HdrExtra) :-
 1119    phrase(reply_header(What, HdrExtra, _Code), String),
 1120    !,
 1121    format(Out, '~s', [String]).
 reply_header(+Data, +HdrExtra, -Code)// is det
Grammar that realises the HTTP handler for sending Data. Data is a real data object as described with http_reply/2 or a not-200-ok HTTP status reply. The following status replies are defined.
See also
- http_status_reply/4 formulates the not-200-ok HTTP replies.
 1141reply_header(string(String), HdrExtra, Code) -->
 1142    reply_header(string(text/plain, String), HdrExtra, Code).
 1143reply_header(string(Type, String), HdrExtra, Code) -->
 1144    vstatus(ok, Code, HdrExtra),
 1145    date(now),
 1146    header_fields(HdrExtra, CLen),
 1147    content_length(codes(String, utf8), CLen),
 1148    content_type(Type, utf8),
 1149    "\r\n".
 1150reply_header(bytes(Type, Bytes), HdrExtra, Code) -->
 1151    vstatus(ok, Code, HdrExtra),
 1152    date(now),
 1153    header_fields(HdrExtra, CLen),
 1154    content_length(bytes(Bytes), CLen),
 1155    content_type(Type),
 1156    "\r\n".
 1157reply_header(html(Tokens), HdrExtra, Code) -->
 1158    vstatus(ok, Code, HdrExtra),
 1159    date(now),
 1160    header_fields(HdrExtra, CLen),
 1161    content_length(html(Tokens), CLen),
 1162    content_type(text/html),
 1163    "\r\n".
 1164reply_header(file(Type, File), HdrExtra, Code) -->
 1165    vstatus(ok, Code, HdrExtra),
 1166    date(now),
 1167    modified(file(File)),
 1168    header_fields(HdrExtra, CLen),
 1169    content_length(file(File), CLen),
 1170    content_type(Type),
 1171    "\r\n".
 1172reply_header(gzip_file(Type, File), HdrExtra, Code) -->
 1173    vstatus(ok, Code, HdrExtra),
 1174    date(now),
 1175    modified(file(File)),
 1176    header_fields(HdrExtra, CLen),
 1177    content_length(file(File), CLen),
 1178    content_type(Type),
 1179    content_encoding(gzip),
 1180    "\r\n".
 1181reply_header(file(Type, File, Range), HdrExtra, Code) -->
 1182    vstatus(partial_content, Code, HdrExtra),
 1183    date(now),
 1184    modified(file(File)),
 1185    header_fields(HdrExtra, CLen),
 1186    content_length(file(File, Range), CLen),
 1187    content_type(Type),
 1188    "\r\n".
 1189reply_header(tmp_file(Type, File), HdrExtra, Code) -->
 1190    vstatus(ok, Code, HdrExtra),
 1191    date(now),
 1192    header_fields(HdrExtra, CLen),
 1193    content_length(file(File), CLen),
 1194    content_type(Type),
 1195    "\r\n".
 1196reply_header(cgi_data(Size), HdrExtra, Code) -->
 1197    vstatus(ok, Code, HdrExtra),
 1198    date(now),
 1199    header_fields(HdrExtra, CLen),
 1200    content_length(Size, CLen),
 1201    "\r\n".
 1202reply_header(chunked_data, HdrExtra, Code) -->
 1203    vstatus(ok, Code, HdrExtra),
 1204    date(now),
 1205    header_fields(HdrExtra, _),
 1206    (   {memberchk(transfer_encoding(_), HdrExtra)}
 1207    ->  ""
 1208    ;   transfer_encoding(chunked)
 1209    ),
 1210    "\r\n".
 1211reply_header(moved(To, Tokens), HdrExtra, Code) -->
 1212    vstatus(moved, Code, HdrExtra),
 1213    date(now),
 1214    header_field('Location', To),
 1215    header_fields(HdrExtra, CLen),
 1216    content_length(html(Tokens), CLen),
 1217    content_type(text/html, utf8),
 1218    "\r\n".
 1219reply_header(created(Location, Tokens), HdrExtra, Code) -->
 1220    vstatus(created, Code, HdrExtra),
 1221    date(now),
 1222    header_field('Location', Location),
 1223    header_fields(HdrExtra, CLen),
 1224    content_length(html(Tokens), CLen),
 1225    content_type(text/html, utf8),
 1226    "\r\n".
 1227reply_header(moved_temporary(To, Tokens), HdrExtra, Code) -->
 1228    vstatus(moved_temporary, Code, HdrExtra),
 1229    date(now),
 1230    header_field('Location', To),
 1231    header_fields(HdrExtra, CLen),
 1232    content_length(html(Tokens), CLen),
 1233    content_type(text/html, utf8),
 1234    "\r\n".
 1235reply_header(see_other(To,Tokens),HdrExtra, Code) -->
 1236    vstatus(see_other, Code, HdrExtra),
 1237    date(now),
 1238    header_field('Location',To),
 1239    header_fields(HdrExtra, CLen),
 1240    content_length(html(Tokens), CLen),
 1241    content_type(text/html, utf8),
 1242    "\r\n".
 1243reply_header(status(Status), HdrExtra, Code) --> % Empty messages: 1xx, 204 and 304
 1244    vstatus(Status, Code),
 1245    header_fields(HdrExtra, Clen),
 1246    { Clen = 0 },
 1247    "\r\n".
 1248reply_header(status(Status, Tokens), HdrExtra, Code) -->
 1249    vstatus(Status, Code),
 1250    date(now),
 1251    header_fields(HdrExtra, CLen),
 1252    content_length(html(Tokens), CLen),
 1253    content_type(text/html, utf8),
 1254    "\r\n".
 1255reply_header(authorise(Method, Tokens), HdrExtra, Code) -->
 1256    vstatus(authorise, Code),
 1257    date(now),
 1258    authenticate(Method),
 1259    header_fields(HdrExtra, CLen),
 1260    content_length(html(Tokens), CLen),
 1261    content_type(text/html, utf8),
 1262    "\r\n".
 vstatus(+Status, -Code)// is det
 vstatus(+Status, -Code, +HdrExtra)// is det
Emit the HTTP header for Status
 1269vstatus(_Status, Code, HdrExtra) -->
 1270    {memberchk(status(Code), HdrExtra)},
 1271    !,
 1272    vstatus(_NewStatus, Code).
 1273vstatus(Status, Code, _) -->
 1274    vstatus(Status, Code).
 1275
 1276vstatus(Status, Code) -->
 1277    "HTTP/1.1 ",
 1278    status_number(Status, Code),
 1279    " ",
 1280    status_comment(Status),
 1281    "\r\n".
 status_number(?Status, ?Code)// is semidet
Parse/generate the HTTP status numbers and map them to the proper name.
See also
- See the source code for supported status names and codes.
 1290status_number(Status, Code) -->
 1291    { var(Status) },
 1292    !,
 1293    integer(Code),
 1294    { status_number(Status, Code) },
 1295    !.
 1296status_number(Status, Code) -->
 1297    { status_number(Status, Code) },
 1298    integer(Code).
 status_number(+Status:atom, -Code:nonneg) is det
status_number(-Status:atom, +Code:nonneg) is det
Relates a symbolic HTTP status names to their integer Code. Each code also needs a rule for status_comment//1.
throws
- type_error If Code is instantiated with something other than an integer.
- domain_error If Code is instantiated with an integer outside of the range [100-599] of defined HTTP status codes.
 1312% Unrecognized status codes that are within a defined code class.
 1313% RFC 7231 states:
 1314%   "[...] a client MUST understand the class of any status code,
 1315%    as indicated by the first digit, and treat an unrecognized status code
 1316%    as being equivalent to the `x00` status code of that class [...]
 1317%   "
 1318% @see http://tools.ietf.org/html/rfc7231#section-6
 1319
 1320status_number(Status, Code):-
 1321    nonvar(Status),
 1322    !,
 1323    status_number_fact(Status, Code).
 1324status_number(Status, Code):-
 1325    nonvar(Code),
 1326    !,
 1327    (   between(100, 599, Code)
 1328    ->  (   status_number_fact(Status, Code)
 1329        ->  true
 1330        ;   ClassCode is Code // 100 * 100,
 1331            status_number_fact(Status, ClassCode)
 1332        )
 1333    ;   domain_error(http_code, Code)
 1334    ).
 1335
 1336status_number_fact(continue,                   100).
 1337status_number_fact(switching_protocols,        101).
 1338status_number_fact(ok,                         200).
 1339status_number_fact(created,                    201).
 1340status_number_fact(accepted,                   202).
 1341status_number_fact(non_authoritative_info,     203).
 1342status_number_fact(no_content,                 204).
 1343status_number_fact(reset_content,              205).
 1344status_number_fact(partial_content,            206).
 1345status_number_fact(multiple_choices,           300).
 1346status_number_fact(moved,                      301).
 1347status_number_fact(moved_temporary,            302).
 1348status_number_fact(see_other,                  303).
 1349status_number_fact(not_modified,               304).
 1350status_number_fact(use_proxy,                  305).
 1351status_number_fact(unused,                     306).
 1352status_number_fact(temporary_redirect,         307).
 1353status_number_fact(bad_request,                400).
 1354status_number_fact(authorise,                  401).
 1355status_number_fact(payment_required,           402).
 1356status_number_fact(forbidden,                  403).
 1357status_number_fact(not_found,                  404).
 1358status_number_fact(method_not_allowed,         405).
 1359status_number_fact(not_acceptable,             406).
 1360status_number_fact(request_timeout,            408).
 1361status_number_fact(conflict,                   409).
 1362status_number_fact(gone,                       410).
 1363status_number_fact(length_required,            411).
 1364status_number_fact(payload_too_large,          413).
 1365status_number_fact(uri_too_long,               414).
 1366status_number_fact(unsupported_media_type,     415).
 1367status_number_fact(expectation_failed,         417).
 1368status_number_fact(upgrade_required,           426).
 1369status_number_fact(server_error,               500).
 1370status_number_fact(not_implemented,            501).
 1371status_number_fact(bad_gateway,                502).
 1372status_number_fact(service_unavailable,        503).
 1373status_number_fact(gateway_timeout,            504).
 1374status_number_fact(http_version_not_supported, 505).
 status_comment(+Code:atom)// is det
Emit standard HTTP human-readable comment on the reply-status.
 1381status_comment(continue) -->
 1382    "Continue".
 1383status_comment(switching_protocols) -->
 1384    "Switching Protocols".
 1385status_comment(ok) -->
 1386    "OK".
 1387status_comment(created) -->
 1388    "Created".
 1389status_comment(accepted) -->
 1390    "Accepted".
 1391status_comment(non_authoritative_info) -->
 1392    "Non-Authoritative Information".
 1393status_comment(no_content) -->
 1394    "No Content".
 1395status_comment(reset_content) -->
 1396    "Reset Content".
 1397status_comment(created) -->
 1398    "Created".
 1399status_comment(partial_content) -->
 1400    "Partial content".
 1401status_comment(multiple_choices) -->
 1402    "Multiple Choices".
 1403status_comment(moved) -->
 1404    "Moved Permanently".
 1405status_comment(moved_temporary) -->
 1406    "Moved Temporary".
 1407status_comment(see_other) -->
 1408    "See Other".
 1409status_comment(not_modified) -->
 1410    "Not Modified".
 1411status_comment(use_proxy) -->
 1412    "Use Proxy".
 1413status_comment(unused) -->
 1414    "Unused".
 1415status_comment(temporary_redirect) -->
 1416    "Temporary Redirect".
 1417status_comment(bad_request) -->
 1418    "Bad Request".
 1419status_comment(authorise) -->
 1420    "Authorization Required".
 1421status_comment(payment_required) -->
 1422    "Payment Required".
 1423status_comment(forbidden) -->
 1424    "Forbidden".
 1425status_comment(not_found) -->
 1426    "Not Found".
 1427status_comment(method_not_allowed) -->
 1428    "Method Not Allowed".
 1429status_comment(not_acceptable) -->
 1430    "Not Acceptable".
 1431status_comment(request_timeout) -->
 1432    "Request Timeout".
 1433status_comment(conflict) -->
 1434    "Conflict".
 1435status_comment(gone) -->
 1436    "Gone".
 1437status_comment(length_required) -->
 1438    "Length Required".
 1439status_comment(payload_too_large) -->
 1440    "Payload Too Large".
 1441status_comment(uri_too_long) -->
 1442    "URI Too Long".
 1443status_comment(unsupported_media_type) -->
 1444    "Unsupported Media Type".
 1445status_comment(expectation_failed) -->
 1446    "Expectation Failed".
 1447status_comment(upgrade_required) -->
 1448    "Upgrade Required".
 1449status_comment(server_error) -->
 1450    "Internal Server Error".
 1451status_comment(not_implemented) -->
 1452    "Not Implemented".
 1453status_comment(bad_gateway) -->
 1454    "Bad Gateway".
 1455status_comment(service_unavailable) -->
 1456    "Service Unavailable".
 1457status_comment(gateway_timeout) -->
 1458    "Gateway Timeout".
 1459status_comment(http_version_not_supported) -->
 1460    "HTTP Version Not Supported".
 1461
 1462authenticate(negotiate(Data)) -->
 1463    "WWW-Authenticate: Negotiate ",
 1464    { base64(Data, DataBase64),
 1465      atom_codes(DataBase64, Codes)
 1466    },
 1467    string(Codes), "\r\n".
 1468authenticate(negotiate) -->
 1469    "WWW-Authenticate: Negotiate\r\n".
 1470
 1471authenticate(basic) -->
 1472    !,
 1473    "WWW-Authenticate: Basic\r\n".
 1474authenticate(basic(Realm)) -->
 1475    "WWW-Authenticate: Basic Realm=\"", atom(Realm), "\"\r\n".
 1476
 1477authenticate(digest) -->
 1478    !,
 1479    "WWW-Authenticate: Digest\r\n".
 1480authenticate(digest(Details)) -->
 1481    "WWW-Authenticate: Digest ", atom(Details), "\r\n".
 1482
 1483
 1484date(Time) -->
 1485    "Date: ",
 1486    (   { Time == now }
 1487    ->  now
 1488    ;   rfc_date(Time)
 1489    ),
 1490    "\r\n".
 1491
 1492modified(file(File)) -->
 1493    !,
 1494    { time_file(File, Time)
 1495    },
 1496    modified(Time).
 1497modified(Time) -->
 1498    "Last-modified: ",
 1499    (   { Time == now }
 1500    ->  now
 1501    ;   rfc_date(Time)
 1502    ),
 1503    "\r\n".
 content_length(+Object, ?Len)// is det
Emit the content-length field and (optionally) the content-range field.
Arguments:
Len- Number of bytes specified
 1513content_length(file(File, bytes(From, To)), Len) -->
 1514    !,
 1515    { size_file(File, Size),
 1516      (   To == end
 1517      ->  Len is Size - From,
 1518          RangeEnd is Size - 1
 1519      ;   Len is To+1 - From,       % To is index of last byte
 1520          RangeEnd = To
 1521      )
 1522    },
 1523    content_range(bytes, From, RangeEnd, Size),
 1524    content_length(Len, Len).
 1525content_length(Reply, Len) -->
 1526    { length_of(Reply, Len)
 1527    },
 1528    "Content-Length: ", integer(Len),
 1529    "\r\n".
 1530
 1531
 1532length_of(_, Len) :-
 1533    nonvar(Len),
 1534    !.
 1535length_of(codes(String, Encoding), Len) :-
 1536    !,
 1537    setup_call_cleanup(
 1538        open_null_stream(Out),
 1539        ( set_stream(Out, encoding(Encoding)),
 1540          format(Out, '~s', [String]),
 1541          byte_count(Out, Len)
 1542        ),
 1543        close(Out)).
 1544length_of(atom(Atom, Encoding), Len) :-
 1545    !,
 1546    setup_call_cleanup(
 1547        open_null_stream(Out),
 1548        ( set_stream(Out, encoding(Encoding)),
 1549          format(Out, '~a', [Atom]),
 1550          byte_count(Out, Len)
 1551        ),
 1552        close(Out)).
 1553length_of(file(File), Len) :-
 1554    !,
 1555    size_file(File, Len).
 1556length_of(memory_file(Handle), Len) :-
 1557    !,
 1558    size_memory_file(Handle, Len, octet).
 1559length_of(html(Tokens), Len) :-
 1560    !,
 1561    html_print_length(Tokens, Len).
 1562length_of(bytes(Bytes), Len) :-
 1563    !,
 1564    (   string(Bytes)
 1565    ->  string_length(Bytes, Len)
 1566    ;   length(Bytes, Len)          % assuming a list of 0..255
 1567    ).
 1568length_of(Len, Len).
 content_range(+Unit:atom, +From:int, +RangeEnd:int, +Size:int)// is det
Emit the Content-Range header for partial content (206) replies.
 1576content_range(Unit, From, RangeEnd, Size) -->
 1577    "Content-Range: ", atom(Unit), " ",
 1578    integer(From), "-", integer(RangeEnd), "/", integer(Size),
 1579    "\r\n".
 1580
 1581content_encoding(Encoding) -->
 1582    "Content-Encoding: ", atom(Encoding), "\r\n".
 1583
 1584transfer_encoding(Encoding) -->
 1585    "Transfer-Encoding: ", atom(Encoding), "\r\n".
 1586
 1587content_type(Type) -->
 1588    content_type(Type, _).
 1589
 1590content_type(Type, Charset) -->
 1591    ctype(Type),
 1592    charset(Charset),
 1593    "\r\n".
 1594
 1595ctype(Main/Sub) -->
 1596    !,
 1597    "Content-Type: ",
 1598    atom(Main),
 1599    "/",
 1600    atom(Sub).
 1601ctype(Type) -->
 1602    !,
 1603    "Content-Type: ",
 1604    atom(Type).
 1605
 1606charset(Var) -->
 1607    { var(Var) },
 1608    !.
 1609charset(utf8) -->
 1610    !,
 1611    "; charset=UTF-8".
 1612charset(CharSet) -->
 1613    "; charset=",
 1614    atom(CharSet).
 header_field(-Name, -Value)// is det
 header_field(+Name, +Value) is det
Process an HTTP request property. Request properties appear as a single line in an HTTP header.
 1622header_field(Name, Value) -->
 1623    { var(Name) },                 % parsing
 1624    !,
 1625    field_name(Name),
 1626    ":",
 1627    whites,
 1628    read_field_value(ValueChars),
 1629    blanks_to_nl,
 1630    !,
 1631    {   field_to_prolog(Name, ValueChars, Value)
 1632    ->  true
 1633    ;   atom_codes(Value, ValueChars),
 1634        domain_error(Name, Value)
 1635    }.
 1636header_field(Name, Value) -->
 1637    field_name(Name),
 1638    ": ",
 1639    field_value(Value),
 1640    "\r\n".
 read_field_value(-Codes)//
Read a field eagerly upto the next whitespace
 1646read_field_value([H|T]) -->
 1647    [H],
 1648    { \+ code_type(H, space) },
 1649    !,
 1650    read_field_value(T).
 1651read_field_value([]) -->
 1652    "".
 1653read_field_value([H|T]) -->
 1654    [H],
 1655    read_field_value(T).
 http_parse_header_value(+Field, +Value, -Prolog) is semidet
Translate Value in a meaningful Prolog term. Field denotes the HTTP request field for which we do the translation. Supported fields are:
content_length
Converted into an integer
cookie
Converted into a list with Name=Value by cookies//1.
set_cookie
Converted into a term set_cookie(Name, Value, Options). Options is a list consisting of Name=Value or a single atom (e.g., secure)
host
Converted to HostName:Port if applicable.
range
Converted into bytes(From, To), where From is an integer and To is either an integer or the atom end.
accept
Parsed to a list of media descriptions. Each media is a term media(Type, TypeParams, Quality, AcceptExts). The list is sorted according to preference.
content_disposition
Parsed into disposition(Name, Attributes), where Attributes is a list of Name=Value pairs.
content_type
Parsed into media(Type/SubType, Attributes), where Attributes is a list of Name=Value pairs.
 1688http_parse_header_value(Field, Value, Prolog) :-
 1689    known_field(Field, _),
 1690    to_codes(Value, Codes),
 1691    parse_header_value(Field, Codes, Prolog).
 known_field(?FieldName, ?AutoConvert)
True if the value of FieldName is by default translated into a Prolog data structure.
 1698known_field(content_length,      true).
 1699known_field(status,              true).
 1700known_field(cookie,              true).
 1701known_field(set_cookie,          true).
 1702known_field(host,                true).
 1703known_field(range,               maybe).
 1704known_field(accept,              maybe).
 1705known_field(content_disposition, maybe).
 1706known_field(content_type,        false).
 1707
 1708to_codes(In, Codes) :-
 1709    (   is_list(In)
 1710    ->  Codes = In
 1711    ;   atom_codes(In, Codes)
 1712    ).
 field_to_prolog(+Field, +ValueCodes, -Prolog) is semidet
Translate the value string into a sensible Prolog term. For known_fields(_,true), this must succeed. For maybe, we just return the atom if the translation fails.
 1720field_to_prolog(Field, Codes, Prolog) :-
 1721    known_field(Field, true),
 1722    !,
 1723    (   parse_header_value(Field, Codes, Prolog0)
 1724    ->  Prolog = Prolog0
 1725    ).
 1726field_to_prolog(Field, Codes, Prolog) :-
 1727    known_field(Field, maybe),
 1728    parse_header_value(Field, Codes, Prolog0),
 1729    !,
 1730    Prolog = Prolog0.
 1731field_to_prolog(_, Codes, Atom) :-
 1732    atom_codes(Atom, Codes).
 parse_header_value(+Field, +ValueCodes, -Value) is semidet
Parse the value text of an HTTP field into a meaningful Prolog representation.
 1739parse_header_value(content_length, ValueChars, ContentLength) :-
 1740    number_codes(ContentLength, ValueChars).
 1741parse_header_value(status, ValueChars, Code) :-
 1742    (   phrase(" ", L, _),
 1743        append(Pre, L, ValueChars)
 1744    ->  number_codes(Code, Pre)
 1745    ;   number_codes(Code, ValueChars)
 1746    ).
 1747parse_header_value(cookie, ValueChars, Cookies) :-
 1748    debug(cookie, 'Cookie: ~s', [ValueChars]),
 1749    phrase(cookies(Cookies), ValueChars).
 1750parse_header_value(set_cookie, ValueChars, SetCookie) :-
 1751    debug(cookie, 'SetCookie: ~s', [ValueChars]),
 1752    phrase(set_cookie(SetCookie), ValueChars).
 1753parse_header_value(host, ValueChars, Host) :-
 1754    (   append(HostChars, [0':|PortChars], ValueChars),
 1755        catch(number_codes(Port, PortChars), _, fail)
 1756    ->  atom_codes(HostName, HostChars),
 1757        Host = HostName:Port
 1758    ;   atom_codes(Host, ValueChars)
 1759    ).
 1760parse_header_value(range, ValueChars, Range) :-
 1761    phrase(range(Range), ValueChars).
 1762parse_header_value(accept, ValueChars, Media) :-
 1763    parse_accept(ValueChars, Media).
 1764parse_header_value(content_disposition, ValueChars, Disposition) :-
 1765    phrase(content_disposition(Disposition), ValueChars).
 1766parse_header_value(content_type, ValueChars, Type) :-
 1767    phrase(parse_content_type(Type), ValueChars).
 1768
 1769field_value(set_cookie(Name, Value, Options)) -->
 1770    !,
 1771    atom(Name), "=", atom(Value),
 1772    value_options(Options, cookie).
 1773field_value(disposition(Disposition, Options)) -->
 1774    !,
 1775    atom(Disposition), value_options(Options, disposition).
 1776field_value(Atomic) -->
 1777    atom(Atomic).
 value_options(+List, +Field)//
Emit field parameters such as ; charset=UTF-8. There are three versions: a plain key (secure), token values and quoted string values. Seems we cannot deduce that from the actual value.
 1786value_options([], _) --> [].
 1787value_options([H|T], Field) -->
 1788    "; ", value_option(H, Field),
 1789    value_options(T, Field).
 1790
 1791value_option(secure=true, cookie) -->
 1792    !,
 1793    "secure".
 1794value_option(Name=Value, Type) -->
 1795    { string_option(Name, Type) },
 1796    !,
 1797    atom(Name), "=",
 1798    qstring(Value).
 1799value_option(Name=Value, Type) -->
 1800    { token_option(Name, Type) },
 1801    !,
 1802    atom(Name), "=", atom(Value).
 1803value_option(Name=Value, _Type) -->
 1804    atom(Name), "=",
 1805    option_value(Value).
 1806
 1807string_option(filename, disposition).
 1808
 1809token_option(path, cookie).
 1810
 1811option_value(Value) -->
 1812    { number(Value) },
 1813    !,
 1814    number(Value).
 1815option_value(Value) -->
 1816    { (   atom(Value)
 1817      ->  true
 1818      ;   string(Value)
 1819      ),
 1820      forall(string_code(_, Value, C),
 1821             token_char(C))
 1822    },
 1823    !,
 1824    atom(Value).
 1825option_value(Atomic) -->
 1826    qstring(Atomic).
 1827
 1828qstring(Atomic) -->
 1829    { string_codes(Atomic, Codes) },
 1830    "\"",
 1831    qstring_codes(Codes),
 1832    "\"".
 1833
 1834qstring_codes([]) --> [].
 1835qstring_codes([H|T]) --> qstring_code(H), qstring_codes(T).
 1836
 1837qstring_code(C) --> {qstring_esc(C)}, !, "\\", [C].
 1838qstring_code(C) --> [C].
 1839
 1840qstring_esc(0'").
 1841qstring_esc(C) :- ctl(C).
 1842
 1843
 1844                 /*******************************
 1845                 *        ACCEPT HEADERS        *
 1846                 *******************************/
 1847
 1848:- dynamic accept_cache/2. 1849:- volatile accept_cache/2. 1850
 1851parse_accept(Codes, Media) :-
 1852    atom_codes(Atom, Codes),
 1853    (   accept_cache(Atom, Media0)
 1854    ->  Media = Media0
 1855    ;   phrase(accept(Media0), Codes),
 1856        keysort(Media0, Media1),
 1857        pairs_values(Media1, Media2),
 1858        assertz(accept_cache(Atom, Media2)),
 1859        Media = Media2
 1860    ).
 accept(-Media)// is semidet
Parse an HTTP Accept: header
 1866accept([H|T]) -->
 1867    blanks,
 1868    media_range(H),
 1869    blanks,
 1870    (   ","
 1871    ->  accept(T)
 1872    ;   {T=[]}
 1873    ).
 1874
 1875media_range(s(SortQuality,Spec)-media(Type, TypeParams, Quality, AcceptExts)) -->
 1876    media_type(Type),
 1877    blanks,
 1878    (   ";"
 1879    ->  blanks,
 1880        parameters_and_quality(TypeParams, Quality, AcceptExts)
 1881    ;   { TypeParams = [],
 1882          Quality = 1.0,
 1883          AcceptExts = []
 1884        }
 1885    ),
 1886    { SortQuality is float(-Quality),
 1887      rank_specialised(Type, TypeParams, Spec)
 1888    }.
 content_disposition(-Disposition)//
Parse Content-Disposition value
 1895content_disposition(disposition(Disposition, Options)) -->
 1896    token(Disposition), blanks,
 1897    value_parameters(Options).
 parse_content_type(-Type)//
Parse Content-Type value into a term media(Type/SubType, Parameters).
 1904parse_content_type(media(Type, Parameters)) -->
 1905    media_type(Type), blanks,
 1906    value_parameters(Parameters).
 rank_specialised(+Type, +TypeParam, -Key) is det
Although the specification linked above is unclear, it seems that more specialised types must be preferred over less specialized ones.
To be done
- Is there an official specification of this?
 1917rank_specialised(Type/SubType, TypeParams, v(VT, VS, SortVP)) :-
 1918    var_or_given(Type, VT),
 1919    var_or_given(SubType, VS),
 1920    length(TypeParams, VP),
 1921    SortVP is -VP.
 1922
 1923var_or_given(V, Val) :-
 1924    (   var(V)
 1925    ->  Val = 0
 1926    ;   Val = -1
 1927    ).
 1928
 1929media_type(Type/SubType) -->
 1930    type(Type), "/", type(SubType).
 1931
 1932type(_) -->
 1933    "*",
 1934    !.
 1935type(Type) -->
 1936    token(Type).
 1937
 1938parameters_and_quality(Params, Quality, AcceptExts) -->
 1939    token(Name),
 1940    blanks, "=", blanks,
 1941    (   { Name == q }
 1942    ->  float(Quality), blanks,
 1943        value_parameters(AcceptExts),
 1944        { Params = [] }
 1945    ;   { Params = [Name=Value|T] },
 1946        parameter_value(Value),
 1947        blanks,
 1948        (   ";"
 1949        ->  blanks,
 1950            parameters_and_quality(T, Quality, AcceptExts)
 1951        ;   { T = [],
 1952              Quality = 1.0,
 1953              AcceptExts = []
 1954            }
 1955        )
 1956    ).
 value_parameters(-Params:list) is det
Accept (";" <parameter>)*, returning a list of Name=Value, where both Name and Value are atoms.
 1963value_parameters([H|T]) -->
 1964    ";",
 1965    !,
 1966    blanks, token(Name), blanks,
 1967    (   "="
 1968    ->  blanks,
 1969        (   token(Value)
 1970        ->  []
 1971        ;   quoted_string(Value)
 1972        ),
 1973        { H = (Name=Value) }
 1974    ;   { H = Name }
 1975    ),
 1976    blanks,
 1977    value_parameters(T).
 1978value_parameters([]) -->
 1979    [].
 1980
 1981parameter_value(Value) --> token(Value), !.
 1982parameter_value(Value) --> quoted_string(Value).
 token(-Name)// is semidet
Process an HTTP header token from the input.
 1989token(Name) -->
 1990    token_char(C1),
 1991    token_chars(Cs),
 1992    { atom_codes(Name, [C1|Cs]) }.
 1993
 1994token_chars([H|T]) -->
 1995    token_char(H),
 1996    !,
 1997    token_chars(T).
 1998token_chars([]) --> [].
 1999
 2000token_char(C) --> [C], { token_char(C) }.
 2001
 2002token_char(C) :-
 2003    \+ ctl(C),
 2004    \+ separator_code(C).
 2005
 2006ctl(C) :- between(0,31,C), !.
 2007ctl(127).
 2008
 2009separator_code(0'().
 2010separator_code(0')).
 2011separator_code(0'<).
 2012separator_code(0'>).
 2013separator_code(0'@).
 2014separator_code(0',).
 2015separator_code(0';).
 2016separator_code(0':).
 2017separator_code(0'\\).
 2018separator_code(0'").
 2019separator_code(0'/).
 2020separator_code(0'[).
 2021separator_code(0']).
 2022separator_code(0'?).
 2023separator_code(0'=).
 2024separator_code(0'{).
 2025separator_code(0'}).
 2026separator_code(0'\s).
 2027separator_code(0'\t).
 quoted_string(-Text)// is semidet
True if input starts with a quoted string representing Text.
 2034quoted_string(Text) -->
 2035    "\"",
 2036    quoted_text(Codes),
 2037    { atom_codes(Text, Codes) }.
 2038
 2039quoted_text([]) -->
 2040    "\"",
 2041    !.
 2042quoted_text([H|T]) -->
 2043    "\\", !, [H],
 2044    quoted_text(T).
 2045quoted_text([H|T]) -->
 2046    [H],
 2047    !,
 2048    quoted_text(T).
 header_fields(+Fields, ?ContentLength)// is det
Process a sequence of [Name(Value), ...] attributes for the header. A term content_length(Len) is special. If instantiated it emits the header. If not it just unifies ContentLength with the argument of the content_length(Len) term. This allows for both sending and retrieving the content-length.
 2059header_fields([], _) --> [].
 2060header_fields([content_length(CLen)|T], CLen) -->
 2061    !,
 2062    (   { var(CLen) }
 2063    ->  ""
 2064    ;   header_field(content_length, CLen)
 2065    ),
 2066    header_fields(T, CLen).           % Continue or return first only?
 2067header_fields([status(_)|T], CLen) -->   % handled by vstatus//3.
 2068    !,
 2069    header_fields(T, CLen).
 2070header_fields([H|T], CLen) -->
 2071    { H =.. [Name, Value] },
 2072    header_field(Name, Value),
 2073    header_fields(T, CLen).
 field_name(?PrologName)
Convert between prolog_name and HttpName. Field names are, according to RFC 2616, considered tokens and covered by the following definition:
token          = 1*<any CHAR except CTLs or separators>
separators     = "(" | ")" | "<" | ">" | "@"
               | "," | ";" | ":" | "\" | <">
               | "/" | "[" | "]" | "?" | "="
               | "{" | "}" | SP | HT
 2090:- public
 2091    field_name//1. 2092
 2093field_name(Name) -->
 2094    { var(Name) },
 2095    !,
 2096    rd_field_chars(Chars),
 2097    { atom_codes(Name, Chars) }.
 2098field_name(mime_version) -->
 2099    !,
 2100    "MIME-Version".
 2101field_name(Name) -->
 2102    { atom_codes(Name, Chars) },
 2103    wr_field_chars(Chars).
 2104
 2105rd_field_chars_no_fold([C|T]) -->
 2106    [C],
 2107    { rd_field_char(C, _) },
 2108    !,
 2109    rd_field_chars_no_fold(T).
 2110rd_field_chars_no_fold([]) -->
 2111    [].
 2112
 2113rd_field_chars([C0|T]) -->
 2114    [C],
 2115    { rd_field_char(C, C0) },
 2116    !,
 2117    rd_field_chars(T).
 2118rd_field_chars([]) -->
 2119    [].
 separators(-CharCodes) is det
CharCodes is a list of separators according to RFC2616
 2125separators("()<>@,;:\\\"/[]?={} \t").
 2126
 2127term_expansion(rd_field_char('expand me',_), Clauses) :-
 2128
 2129    Clauses = [ rd_field_char(0'-, 0'_)
 2130              | Cls
 2131              ],
 2132    separators(SepString),
 2133    string_codes(SepString, Seps),
 2134    findall(rd_field_char(In, Out),
 2135            (   between(32, 127, In),
 2136                \+ memberchk(In, Seps),
 2137                In \== 0'-,         % 0'
 2138                code_type(Out, to_lower(In))),
 2139            Cls).
 2140
 2141rd_field_char('expand me', _).                  % avoid recursion
 2142
 2143wr_field_chars([C|T]) -->
 2144    !,
 2145    { code_type(C, to_lower(U)) },
 2146    [U],
 2147    wr_field_chars2(T).
 2148wr_field_chars([]) -->
 2149    [].
 2150
 2151wr_field_chars2([]) --> [].
 2152wr_field_chars2([C|T]) -->              % 0'
 2153    (   { C == 0'_ }
 2154    ->  "-",
 2155        wr_field_chars(T)
 2156    ;   [C],
 2157        wr_field_chars2(T)
 2158    ).
 now//
Current time using rfc_date//1.
 2164now -->
 2165    { get_time(Time)
 2166    },
 2167    rfc_date(Time).
 rfc_date(+Time)// is det
Write time according to RFC1123 specification as required by the RFC2616 HTTP protocol specs.
 2174rfc_date(Time, String, Tail) :-
 2175    stamp_date_time(Time, Date, 'UTC'),
 2176    format_time(codes(String, Tail),
 2177                '%a, %d %b %Y %T GMT',
 2178                Date, posix).
 http_timestamp(+Time:timestamp, -Text:atom) is det
Generate a description of a Time in HTTP format (RFC1123)
 2184http_timestamp(Time, Atom) :-
 2185    stamp_date_time(Time, Date, 'UTC'),
 2186    format_time(atom(Atom),
 2187                '%a, %d %b %Y %T GMT',
 2188                Date, posix).
 2189
 2190
 2191                 /*******************************
 2192                 *         REQUEST DCG          *
 2193                 *******************************/
 2194
 2195request(Fd, [method(Method),request_uri(ReqURI)|Header]) -->
 2196    method(Method),
 2197    blanks,
 2198    nonblanks(Query),
 2199    { atom_codes(ReqURI, Query),
 2200      request_uri_parts(ReqURI, Header, Rest)
 2201    },
 2202    request_header(Fd, Rest),
 2203    !.
 2204request(Fd, [unknown(What)|Header]) -->
 2205    string(What),
 2206    eos,
 2207    !,
 2208    {   http_read_header(Fd, Header)
 2209    ->  true
 2210    ;   Header = []
 2211    }.
 2212
 2213method(get)     --> "GET", !.
 2214method(put)     --> "PUT", !.
 2215method(head)    --> "HEAD", !.
 2216method(post)    --> "POST", !.
 2217method(delete)  --> "DELETE", !.
 2218method(patch)   --> "PATCH", !.
 2219method(options) --> "OPTIONS", !.
 2220method(trace)   --> "TRACE", !.
 request_uri_parts(+RequestURI, -Parts, ?Tail) is det
Process the request-uri, producing the following parts:
path(-Path)
Decode path information (always present)
search(-QueryParams)
Present if there is a ?name=value&... part of the request uri. QueryParams is a Name=Value list.
fragment(-Fragment)
Present if there is a #Fragment.
 2234request_uri_parts(ReqURI, [path(Path)|Parts], Rest) :-
 2235    uri_components(ReqURI, Components),
 2236    uri_data(path, Components, PathText),
 2237    uri_encoded(path, Path, PathText),
 2238    phrase(uri_parts(Components), Parts, Rest).
 2239
 2240uri_parts(Components) -->
 2241    uri_search(Components),
 2242    uri_fragment(Components).
 2243
 2244uri_search(Components) -->
 2245    { uri_data(search, Components, Search),
 2246      nonvar(Search),
 2247      catch(uri_query_components(Search, Query),
 2248            error(syntax_error(_),_),
 2249            fail)
 2250    },
 2251    !,
 2252    [ search(Query) ].
 2253uri_search(_) --> [].
 2254
 2255uri_fragment(Components) -->
 2256    { uri_data(fragment, Components, String),
 2257      nonvar(String),
 2258      !,
 2259      uri_encoded(fragment, Fragment, String)
 2260    },
 2261    [ fragment(Fragment) ].
 2262uri_fragment(_) --> [].
 request_header(+In:stream, -Header:list) is det
Read the remainder (after the request-uri) of the HTTP header and return it as a Name(Value) list.
 2269request_header(_, []) -->               % Old-style non-version header
 2270    blanks,
 2271    eos,
 2272    !.
 2273request_header(Fd, [http_version(Version)|Header]) -->
 2274    http_version(Version),
 2275    blanks,
 2276    eos,
 2277    !,
 2278    {   Version = 1-_
 2279    ->  http_read_header(Fd, Header)
 2280    ;   Header = []
 2281    }.
 2282
 2283http_version(Version) -->
 2284    blanks,
 2285    "HTTP/",
 2286    http_version_number(Version).
 2287
 2288http_version_number(Major-Minor) -->
 2289    integer(Major),
 2290    ".",
 2291    integer(Minor).
 2292
 2293
 2294                 /*******************************
 2295                 *            COOKIES           *
 2296                 *******************************/
 cookies(-List)// is semidet
Translate a cookie description into a list Name=Value.
 2302cookies([Name=Value|T]) -->
 2303    blanks,
 2304    cookie(Name, Value),
 2305    !,
 2306    blanks,
 2307    (   ";"
 2308    ->  cookies(T)
 2309    ;   { T = [] }
 2310    ).
 2311cookies(List) -->
 2312    string(Skipped),
 2313    ";",
 2314    !,
 2315    { print_message(warning, http(skipped_cookie(Skipped))) },
 2316    cookies(List).
 2317cookies([]) -->
 2318    blanks.
 2319
 2320cookie(Name, Value) -->
 2321    cookie_name(Name),
 2322    blanks, "=", blanks,
 2323    cookie_value(Value).
 2324
 2325cookie_name(Name) -->
 2326    { var(Name) },
 2327    !,
 2328    rd_field_chars_no_fold(Chars),
 2329    { atom_codes(Name, Chars) }.
 2330
 2331cookie_value(Value) -->
 2332    quoted_string(Value),
 2333    !.
 2334cookie_value(Value) -->
 2335    chars_to_semicolon_or_blank(Chars),
 2336    { atom_codes(Value, Chars)
 2337    }.
 2338
 2339chars_to_semicolon_or_blank([H|T]) -->
 2340    [H],
 2341    { H \== 32, H \== 0'; },
 2342    !,
 2343    chars_to_semicolon_or_blank(T).
 2344chars_to_semicolon_or_blank([]) -->
 2345    [].
 2346
 2347set_cookie(set_cookie(Name, Value, Options)) -->
 2348    ws,
 2349    cookie(Name, Value),
 2350    cookie_options(Options).
 2351
 2352cookie_options([H|T]) -->
 2353    ws,
 2354    ";",
 2355    ws,
 2356    cookie_option(H),
 2357    !,
 2358    cookie_options(T).
 2359cookie_options([]) -->
 2360    ws.
 2361
 2362ws --> " ", !, ws.
 2363ws --> [].
 cookie_option(-Option)// is semidet
True if input represents a valid Cookie option. Officially, all cookie options use the syntax <name>=<value>, except for secure. M$ decided to extend this to include at least httponly (only the Gods know what it means).
Arguments:
Option- Term of the form Name=Value
bug
- Incorrectly accepts options without = for M$ compatibility.
 2376cookie_option(Name=Value) -->
 2377    rd_field_chars(NameChars), ws,
 2378    { atom_codes(Name, NameChars) },
 2379    (   "="
 2380    ->  ws,
 2381        chars_to_semicolon(ValueChars),
 2382        { atom_codes(Value, ValueChars)
 2383        }
 2384    ;   { Value = true }
 2385    ).
 2386
 2387chars_to_semicolon([H|T]) -->
 2388    [H],
 2389    { H \== 32, H \== 0'; },
 2390    !,
 2391    chars_to_semicolon(T).
 2392chars_to_semicolon([]), ";" -->
 2393    ws, ";",
 2394    !.
 2395chars_to_semicolon([H|T]) -->
 2396    [H],
 2397    chars_to_semicolon(T).
 2398chars_to_semicolon([]) -->
 2399    [].
 range(-Range)// is semidet
Process the range header value. Range is currently defined as:
bytes(From, To)
Where From is an integer and To is either an integer or the atom end.
 2409range(bytes(From, To)) -->
 2410    "bytes", whites, "=", whites, integer(From), "-",
 2411    (   integer(To)
 2412    ->  ""
 2413    ;   { To = end }
 2414    ).
 2415
 2416
 2417                 /*******************************
 2418                 *           REPLY DCG          *
 2419                 *******************************/
 reply(+In, -Reply:list)// is semidet
Process the first line of an HTTP reply. After that, read the remainder of the header and parse it. After successful completion, Reply contains the following fields, followed by the fields produced by http_read_header/2.
http_version(Major-Minor)
status(Code, Status, Comment)
Code is an integer between 100 and 599. Status is a Prolog internal name. Comment is the comment following the code as it appears in the reply's HTTP status line. @see status_number//2.
 2436reply(Fd, [http_version(HttpVersion), status(Code, Status, Comment)|Header]) -->
 2437    http_version(HttpVersion),
 2438    blanks,
 2439    (   status_number(Status, Code)
 2440    ->  []
 2441    ;   integer(Status)
 2442    ),
 2443    blanks,
 2444    string(CommentCodes),
 2445    blanks_to_nl,
 2446    !,
 2447    blanks,
 2448    { atom_codes(Comment, CommentCodes),
 2449      http_read_header(Fd, Header)
 2450    }.
 2451
 2452
 2453                 /*******************************
 2454                 *            READ HEADER       *
 2455                 *******************************/
 http_read_header(+Fd, -Header) is det
Read Name: Value lines from FD until an empty line is encountered. Field-name are converted to Prolog conventions (all lower, _ instead of -): Content-Type: text/html --> content_type(text/html)
 2463http_read_header(Fd, Header) :-
 2464    read_header_data(Fd, Text),
 2465    http_parse_header(Text, Header).
 2466
 2467read_header_data(Fd, Header) :-
 2468    read_line_to_codes(Fd, Header, Tail),
 2469    read_header_data(Header, Fd, Tail),
 2470    debug(http(header), 'Header = ~n~s~n', [Header]).
 2471
 2472read_header_data([0'\r,0'\n], _, _) :- !.
 2473read_header_data([0'\n], _, _) :- !.
 2474read_header_data([], _, _) :- !.
 2475read_header_data(_, Fd, Tail) :-
 2476    read_line_to_codes(Fd, Tail, NewTail),
 2477    read_header_data(Tail, Fd, NewTail).
 http_parse_header(+Text:codes, -Header:list) is det
Header is a list of Name(Value)-terms representing the structure of the HTTP header in Text.
Errors
- domain_error(http_request_line, Line)
 2486http_parse_header(Text, Header) :-
 2487    phrase(header(Header), Text),
 2488    debug(http(header), 'Field: ~p', [Header]).
 2489
 2490header(List) -->
 2491    header_field(Name, Value),
 2492    !,
 2493    { mkfield(Name, Value, List, Tail)
 2494    },
 2495    blanks,
 2496    header(Tail).
 2497header([]) -->
 2498    blanks,
 2499    eos,
 2500    !.
 2501header(_) -->
 2502    string(S), blanks_to_nl,
 2503    !,
 2504    { string_codes(Line, S),
 2505      syntax_error(http_parameter(Line))
 2506    }.
 address//
Emit the HTML for the server address on behalve of error and status messages (non-200 replies). Default is
SWI-Prolog httpd at <hostname>

The address can be modified by providing a definition for the multifile predicate http_address//0.

 2520:- multifile
 2521    http:http_address//0. 2522
 2523address -->
 2524    http:http_address,
 2525    !.
 2526address -->
 2527    { gethostname(Host) },
 2528    html(address([ a(href('http://www.swi-prolog.org'), 'SWI-Prolog'),
 2529                   ' httpd at ', Host
 2530                 ])).
 2531
 2532mkfield(host, Host:Port, [host(Host),port(Port)|Tail], Tail) :- !.
 2533mkfield(Name, Value, [Att|Tail], Tail) :-
 2534    Att =.. [Name, Value].
 http:http_address// is det
HTML-rule that emits the location of the HTTP server. This hook is called from address//0 to customise the server address. The server address is emitted on non-200-ok replies.
 http:status_page(+Status, +Context, -HTMLTokens) is semidet
Hook called by http_status_reply/4 and http_status_reply/5 that allows for emitting custom error pages for the following HTTP page types:

The hook is tried twice, first using the status term, e.g., not_found(URL) and than with the code, e.g. 404. The second call is deprecated and only exists for compatibility.

Arguments:
Context- is the 4th argument of http_status_reply/5, which is invoked after raising an exception of the format http_reply(Status, HeaderExtra, Context). The default context is [] (the empty list).
HTMLTokens- is a list of tokens as produced by html//1. It is passed to print_html/2.
 2565                 /*******************************
 2566                 *            MESSAGES          *
 2567                 *******************************/
 2568
 2569:- multifile
 2570    prolog:message//1,
 2571    prolog:error_message//1. 2572
 2573prolog:error_message(http_write_short(Data, Sent)) -->
 2574    data(Data),
 2575    [ ': remote hangup after ~D bytes'-[Sent] ].
 2576prolog:error_message(syntax_error(http_request(Request))) -->
 2577    [ 'Illegal HTTP request: ~s'-[Request] ].
 2578prolog:error_message(syntax_error(http_parameter(Line))) -->
 2579    [ 'Illegal HTTP parameter: ~s'-[Line] ].
 2580
 2581prolog:message(http(skipped_cookie(S))) -->
 2582    [ 'Skipped illegal cookie: ~s'-[S] ].
 2583
 2584data(bytes(MimeType, _Bytes)) -->
 2585    !,
 2586    [ 'bytes(~p, ...)'-[MimeType] ].
 2587data(Data) -->
 2588    [ '~p'-[Data] ]