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-2018, 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:status_reply/3,            % +Status, -Reply, +Options
   84    http:serialize_reply/2,         % +Reply, -Body
   85    http:post_data_hook/3,          % +Data, +Out, +HdrExtra
   86    http:mime_type_encoding/2.      % +MimeType, -Encoding
   87
   88% see http_update_transfer/4.
   89
   90:- setting(http:chunked_transfer, oneof([never,on_request,if_possible]),
   91           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. */

  101:- discontiguous
  102    term_expansion/2.  103
  104
  105                 /*******************************
  106                 *          READ REQUEST        *
  107                 *******************************/
 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.
  115http_read_request(In, Request) :-
  116    catch(read_line_to_codes(In, Codes), E, true),
  117    (   var(E)
  118    ->  (   Codes == end_of_file
  119        ->  debug(http(header), 'end-of-file', []),
  120            Request = end_of_file
  121        ;   debug(http(header), 'First line: ~s', [Codes]),
  122            Request =  [input(In)|Request1],
  123            phrase(request(In, Request1), Codes),
  124            (   Request1 = [unknown(Text)|_]
  125            ->  string_codes(S, Text),
  126                syntax_error(http_request(S))
  127            ;   true
  128            )
  129        )
  130    ;   (   debugging(http(request))
  131        ->  message_to_string(E, Msg),
  132            debug(http(request), "Exception reading 1st line: ~s", [Msg])
  133        ;   true
  134        ),
  135        Request = end_of_file
  136    ).
 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.
  144http_read_reply_header(In, [input(In)|Reply]) :-
  145    read_line_to_codes(In, Codes),
  146    (   Codes == end_of_file
  147    ->  debug(http(header), 'end-of-file', []),
  148        throw(error(syntax(http_reply_header, end_of_file), _))
  149    ;   debug(http(header), 'First line: ~s~n', [Codes]),
  150        (   phrase(reply(In, Reply), Codes)
  151        ->  true
  152        ;   atom_codes(Header, Codes),
  153            syntax_error(http_reply_header(Header))
  154        )
  155    ).
  156
  157
  158                 /*******************************
  159                 *        FORMULATE REPLY       *
  160                 *******************************/
 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
  209http_reply(What, Out) :-
  210    http_reply(What, Out, [connection(close)], _).
  211
  212http_reply(Data, Out, HdrExtra) :-
  213    http_reply(Data, Out, HdrExtra, _Code).
  214
  215http_reply(Data, Out, HdrExtra, Code) :-
  216    http_reply(Data, Out, HdrExtra, [], Code).
  217
  218http_reply(Data, Out, HdrExtra, Context, Code) :-
  219    http_reply(Data, Out, HdrExtra, Context, [method(get)], Code).
  220
  221http_reply(Data, Out, HdrExtra, _Context, Request, Code) :-
  222    byte_count(Out, C0),
  223    memberchk(method(Method), Request),
  224    catch(http_reply_data(Data, Out, HdrExtra, Method, Code), E, true),
  225    !,
  226    (   var(E)
  227    ->  true
  228    ;   (   E = error(io_error(write,_), _)
  229        ;   E = error(socket_error(_,_), _)
  230        )
  231    ->  byte_count(Out, C1),
  232        Sent is C1 - C0,
  233        throw(error(http_write_short(Data, Sent), _))
  234    ;   E = error(timeout_error(write, _), _)
  235    ->  throw(E)
  236    ;   map_exception_to_http_status(E, Status, NewHdr, NewContext),
  237        http_status_reply(Status, Out, NewHdr, NewContext, Request, Code)
  238    ).
  239http_reply(Status, Out, HdrExtra, Context, Request, Code) :-
  240    http_status_reply(Status, Out, HdrExtra, Context, Request, Code).
  241
  242:- meta_predicate
  243    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.
  252http_reply_data(Data, Out, HdrExtra, Method, Code) :-
  253    http_reply_data_(Data, Out, HdrExtra, Method, Code),
  254    flush_output(Out).
  255
  256http_reply_data_(html(HTML), Out, HdrExtra, Method, Code) :-
  257    !,
  258    phrase(reply_header(html(HTML), HdrExtra, Code), Header),
  259    format(Out, '~s', [Header]),
  260    if_no_head(print_html(Out, HTML), Method).
  261http_reply_data_(file(Type, File), Out, HdrExtra, Method, Code) :-
  262    !,
  263    phrase(reply_header(file(Type, File), HdrExtra, Code), Header),
  264    reply_file(Out, File, Header, Method).
  265http_reply_data_(gzip_file(Type, File), Out, HdrExtra, Method, Code) :-
  266    !,
  267    phrase(reply_header(gzip_file(Type, File), HdrExtra, Code), Header),
  268    reply_file(Out, File, Header, Method).
  269http_reply_data_(file(Type, File, Range), Out, HdrExtra, Method, Code) :-
  270    !,
  271    phrase(reply_header(file(Type, File, Range), HdrExtra, Code), Header),
  272    reply_file_range(Out, File, Header, Range, Method).
  273http_reply_data_(tmp_file(Type, File), Out, HdrExtra, Method, Code) :-
  274    !,
  275    phrase(reply_header(tmp_file(Type, File), HdrExtra, Code), Header),
  276    reply_file(Out, File, Header, Method).
  277http_reply_data_(bytes(Type, Bytes), Out, HdrExtra, Method, Code) :-
  278    !,
  279    phrase(reply_header(bytes(Type, Bytes), HdrExtra, Code), Header),
  280    format(Out, '~s', [Header]),
  281    if_no_head(format(Out, '~s', [Bytes]), Method).
  282http_reply_data_(stream(In, Len), Out, HdrExtra, Method, Code) :-
  283    !,
  284    phrase(reply_header(cgi_data(Len), HdrExtra, Code), Header),
  285    copy_stream(Out, In, Header, Method, 0, end).
  286http_reply_data_(cgi_stream(In, Len), Out, HdrExtra, Method, Code) :-
  287    !,
  288    http_read_header(In, CgiHeader),
  289    seek(In, 0, current, Pos),
  290    Size is Len - Pos,
  291    http_join_headers(HdrExtra, CgiHeader, Hdr2),
  292    phrase(reply_header(cgi_data(Size), Hdr2, Code), Header),
  293    copy_stream(Out, In, Header, Method, 0, end).
  294
  295if_no_head(_, head) :-
  296    !.
  297if_no_head(Goal, _) :-
  298    call(Goal).
  299
  300reply_file(Out, _File, Header, head) :-
  301    !,
  302    format(Out, '~s', [Header]).
  303reply_file(Out, File, Header, _) :-
  304    setup_call_cleanup(
  305        open(File, read, In, [type(binary)]),
  306        copy_stream(Out, In, Header, 0, end),
  307        close(In)).
  308
  309reply_file_range(Out, _File, Header, _Range, head) :-
  310    !,
  311    format(Out, '~s', [Header]).
  312reply_file_range(Out, File, Header, bytes(From, To), _) :-
  313    setup_call_cleanup(
  314        open(File, read, In, [type(binary)]),
  315        copy_stream(Out, In, Header, From, To),
  316        close(In)).
  317
  318copy_stream(Out, _, Header, head, _, _) :-
  319    !,
  320    format(Out, '~s', [Header]).
  321copy_stream(Out, In, Header, _, From, To) :-
  322    copy_stream(Out, In, Header, From, To).
  323
  324copy_stream(Out, In, Header, From, To) :-
  325    (   From == 0
  326    ->  true
  327    ;   seek(In, From, bof, _)
  328    ),
  329    peek_byte(In, _),
  330    format(Out, '~s', [Header]),
  331    (   To == end
  332    ->  copy_stream_data(In, Out)
  333    ;   Len is To - From,
  334        copy_stream_data(In, Out, Len)
  335    ).
 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)
  369http_status_reply(Status, Out, Options) :-
  370    _{header:HdrExtra, context:Context, code:Code, method:Method} :< Options,
  371    http_status_reply(Status, Out, HdrExtra, Context, [method(Method)], Code).
  372
  373http_status_reply(Status, Out, HdrExtra, Code) :-
  374    http_status_reply(Status, Out, HdrExtra, [], Code).
  375
  376http_status_reply(Status, Out, HdrExtra, Context, Code) :-
  377    http_status_reply(Status, Out, HdrExtra, Context, [method(get)], Code).
  378
  379http_status_reply(Status, Out, HdrExtra, Context, Request, Code) :-
  380    option(method(Method), Request, get),
  381    parsed_accept(Request, Accept),
  382    status_reply_flush(Status, Out,
  383                       _{ context: Context,
  384                          method:  Method,
  385                          code:    Code,
  386                          accept:  Accept,
  387                          header:  HdrExtra
  388                        }).
  389
  390parsed_accept(Request, Accept) :-
  391    memberchk(accept(Accept0), Request),
  392    http_parse_header_value(accept, Accept0, Accept1),
  393    !,
  394    Accept = Accept1.
  395parsed_accept(_, [ media(text/html, [], 0.1,  []),
  396                   media(_,         [], 0.01, [])
  397                 ]).
  398
  399status_reply_flush(Status, Out, Options) :-
  400    status_reply(Status, Out, Options),
  401    !,
  402    flush_output(Out).
 status_reply(+Status, +Out, +Options:Dict)
Formulate a non-200 reply and send it to the stream Out. Options is a dict containing:
  415% Replies without content
  416status_reply(no_content, Out, Options) :-
  417    !,
  418    phrase(reply_header(status(no_content), Options), Header),
  419    format(Out, '~s', [Header]).
  420status_reply(switching_protocols(_Goal,SwitchOptions), Out, Options) :-
  421    !,
  422    (   option(headers(Extra1), SwitchOptions)
  423    ->  true
  424    ;   option(header(Extra1), SwitchOptions, [])
  425    ),
  426    http_join_headers(Options.header, Extra1, HdrExtra),
  427    phrase(reply_header(status(switching_protocols),
  428                        Options.put(header,HdrExtra)), Header),
  429    format(Out, '~s', [Header]).
  430status_reply(authorise(basic, ''), Out, Options) :-
  431    !,
  432    status_reply(authorise(basic), Out, Options).
  433status_reply(authorise(basic, Realm), Out, Options) :-
  434    !,
  435    status_reply(authorise(basic(Realm)), Out, Options).
  436status_reply(not_modified, Out, Options) :-
  437    !,
  438    phrase(reply_header(status(not_modified), Options), Header),
  439    format(Out, '~s', [Header]).
  440% aliases (compatibility)
  441status_reply(busy, Out, Options) :-
  442    status_reply(service_unavailable(busy), Out, Options).
  443status_reply(unavailable(Why), Out, Options) :-
  444    status_reply(service_unavailable(Why), Out, Options).
  445status_reply(resource_error(Why), Out, Options) :-
  446    status_reply(service_unavailable(Why), Out, Options).
  447% replies with content
  448status_reply(Status, Out, Options) :-
  449    status_has_content(Status),
  450    status_page_hook(Status, Reply, Options),
  451    serialize_body(Reply, Body),
  452    Status =.. List,
  453    append(List, [Body], ExList),
  454    ExStatus =.. ExList,
  455    phrase(reply_header(ExStatus, Options), Header),
  456    format(Out, '~s', [Header]),
  457    reply_status_body(Out, Body, Options).
 status_has_content(+StatusTerm, -HTTPCode)
True when StatusTerm is a status that usually comes with an expanatory content message.
  464status_has_content(created(_Location)).
  465status_has_content(moved(_To)).
  466status_has_content(moved_temporary(_To)).
  467status_has_content(see_other(_To)).
  468status_has_content(bad_request(_ErrorTerm)).
  469status_has_content(authorise(_Method)).
  470status_has_content(forbidden(_URL)).
  471status_has_content(not_found(_URL)).
  472status_has_content(method_not_allowed(_Method, _URL)).
  473status_has_content(not_acceptable(_Why)).
  474status_has_content(server_error(_ErrorTerm)).
  475status_has_content(service_unavailable(_Why)).
 serialize_body(+Reply, -Body) is det
Serialize the reply as returned by status_page_hook/3 into a term:
body(Type, Encoding, Content)
In this term, Type is the media type, Encoding is the required wire encoding and Content a string representing the content.
  486serialize_body(Reply, Body) :-
  487    http:serialize_reply(Reply, Body),
  488    !.
  489serialize_body(html_tokens(Tokens), body(text/html, utf8, Content)) :-
  490    !,
  491    with_output_to(string(Content), print_html(Tokens)).
  492serialize_body(Reply, Reply) :-
  493    Reply = body(_,_,_),
  494    !.
  495serialize_body(Reply, _) :-
  496    domain_error(http_reply_body, Reply).
  497
  498reply_status_body(_, _, Options) :-
  499    Options.method == head,
  500    !.
  501reply_status_body(Out, body(_Type, Encoding, Content), _Options) :-
  502    (   Encoding == octet
  503    ->  format(Out, '~s', [Content])
  504    ;   setup_call_cleanup(
  505            set_stream(Out, encoding(Encoding)),
  506            format(Out, '~s', [Content]),
  507            set_stream(Out, encoding(octet)))
  508    ).
 http:serialize_reply(+Reply, -Body) is semidet
Multifile hook to serialize the result of status_reply/3 into a term
body(Type, Encoding, Content)
In this term, Type is the media type, Encoding is the required wire encoding and Content a string representing the content.
 status_page_hook(+Term, -Reply, +Options) is det
Calls the following two hooks to generate an HTML page from a status reply.
http:status_reply(+Term, -Reply, +Options)
Provide non-HTML description of the (non-200) reply. The term Reply is handed to serialize_body/2, calling the hook http:serialize_reply/2.
http:status_page(+Term, +Context, -HTML)
http:status_page(+Code, +Context, -HTML)
Arguments:
Term- is the status term, e.g., not_found(URL)
See also
- http:status_page/3
  535status_page_hook(Term, Reply, Options) :-
  536    Context = Options.context,
  537    functor(Term, Name, _),
  538    status_number_fact(Name, Code),
  539    (   Options.code = Code,
  540        http:status_reply(Term, Reply, Options)
  541    ;   http:status_page(Term, Context, HTML),
  542        Reply = html_tokens(HTML)
  543    ;   http:status_page(Code, Context, HTML), % deprecated
  544        Reply = html_tokens(HTML)
  545    ),
  546    !.
  547status_page_hook(created(Location), html_tokens(HTML), _Options) :-
  548    phrase(page([ title('201 Created')
  549                ],
  550                [ h1('Created'),
  551                  p(['The document was created ',
  552                     a(href(Location), ' Here')
  553                    ]),
  554                  \address
  555                ]),
  556           HTML).
  557status_page_hook(moved(To), html_tokens(HTML), _Options) :-
  558    phrase(page([ title('301 Moved Permanently')
  559                ],
  560                [ h1('Moved Permanently'),
  561                  p(['The document has moved ',
  562                     a(href(To), ' Here')
  563                    ]),
  564                  \address
  565                ]),
  566           HTML).
  567status_page_hook(moved_temporary(To), html_tokens(HTML), _Options) :-
  568    phrase(page([ title('302 Moved Temporary')
  569                ],
  570                [ h1('Moved Temporary'),
  571                  p(['The document is currently ',
  572                     a(href(To), ' Here')
  573                    ]),
  574                  \address
  575                ]),
  576           HTML).
  577status_page_hook(see_other(To), html_tokens(HTML), _Options) :-
  578    phrase(page([ title('303 See Other')
  579                 ],
  580                 [ h1('See Other'),
  581                   p(['See other document ',
  582                      a(href(To), ' Here')
  583                     ]),
  584                   \address
  585                 ]),
  586            HTML).
  587status_page_hook(bad_request(ErrorTerm), html_tokens(HTML), _Options) :-
  588    '$messages':translate_message(ErrorTerm, Lines, []),
  589    phrase(page([ title('400 Bad Request')
  590                ],
  591                [ h1('Bad Request'),
  592                  p(\html_message_lines(Lines)),
  593                  \address
  594                ]),
  595           HTML).
  596status_page_hook(authorise(_Method), html_tokens(HTML), _Options):-
  597    phrase(page([ title('401 Authorization Required')
  598                ],
  599                [ h1('Authorization Required'),
  600                  p(['This server could not verify that you ',
  601                     'are authorized to access the document ',
  602                     'requested.  Either you supplied the wrong ',
  603                     'credentials (e.g., bad password), or your ',
  604                     'browser doesn\'t understand how to supply ',
  605                     'the credentials required.'
  606                    ]),
  607                  \address
  608                ]),
  609           HTML).
  610status_page_hook(forbidden(URL), html_tokens(HTML), _Options) :-
  611    phrase(page([ title('403 Forbidden')
  612                ],
  613                [ h1('Forbidden'),
  614                  p(['You don\'t have permission to access ', URL,
  615                     ' on this server'
  616                    ]),
  617                  \address
  618                ]),
  619           HTML).
  620status_page_hook(not_found(URL), html_tokens(HTML), _Options) :-
  621    phrase(page([ title('404 Not Found')
  622                ],
  623                [ h1('Not Found'),
  624                  p(['The requested URL ', tt(URL),
  625                     ' was not found on this server'
  626                    ]),
  627                  \address
  628                ]),
  629           HTML).
  630status_page_hook(method_not_allowed(Method,URL), html_tokens(HTML), _Options) :-
  631    upcase_atom(Method, UMethod),
  632    phrase(page([ title('405 Method not allowed')
  633                ],
  634                [ h1('Method not allowed'),
  635                  p(['The requested URL ', tt(URL),
  636                     ' does not support method ', tt(UMethod), '.'
  637                    ]),
  638                  \address
  639                ]),
  640           HTML).
  641status_page_hook(not_acceptable(WhyHTML), html_tokens(HTML), _Options) :-
  642    phrase(page([ title('406 Not Acceptable')
  643                ],
  644                [ h1('Not Acceptable'),
  645                  WhyHTML,
  646                  \address
  647                ]),
  648           HTML).
  649status_page_hook(server_error(ErrorTerm), html_tokens(HTML), _Options) :-
  650    '$messages':translate_message(ErrorTerm, Lines, []),
  651    phrase(page([ title('500 Internal server error')
  652                ],
  653                [ h1('Internal server error'),
  654                  p(\html_message_lines(Lines)),
  655                  \address
  656                ]),
  657           HTML).
  658status_page_hook(service_unavailable(Why), html_tokens(HTML), _Options) :-
  659    phrase(page([ title('503 Service Unavailable')
  660                ],
  661                [ h1('Service Unavailable'),
  662                  \unavailable(Why),
  663                  \address
  664                ]),
  665           HTML).
  666
  667unavailable(busy) -->
  668    html(p(['The server is temporarily out of resources, ',
  669            'please try again later'])).
  670unavailable(error(Formal,Context)) -->
  671    { '$messages':translate_message(error(Formal,Context), Lines, []) },
  672    html_message_lines(Lines).
  673unavailable(HTML) -->
  674    html(HTML).
  675
  676html_message_lines([]) -->
  677    [].
  678html_message_lines([nl|T]) -->
  679    !,
  680    html([br([])]),
  681    html_message_lines(T).
  682html_message_lines([flush]) -->
  683    [].
  684html_message_lines([Fmt-Args|T]) -->
  685    !,
  686    { format(string(S), Fmt, Args)
  687    },
  688    html([S]),
  689    html_message_lines(T).
  690html_message_lines([Fmt|T]) -->
  691    !,
  692    { format(string(S), Fmt, [])
  693    },
  694    html([S]),
  695    html_message_lines(T).
 http_join_headers(+Default, +Header, -Out)
Append headers from Default to Header if they are not already part of it.
  702http_join_headers([], H, H).
  703http_join_headers([H|T], Hdr0, Hdr) :-
  704    functor(H, N, A),
  705    functor(H2, N, A),
  706    member(H2, Hdr0),
  707    !,
  708    http_join_headers(T, Hdr0, Hdr).
  709http_join_headers([H|T], Hdr0, [H|Hdr]) :-
  710    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.
  722http_update_encoding(Header0, utf8, [content_type(Type)|Header]) :-
  723    select(content_type(Type0), Header0, Header),
  724    sub_atom(Type0, 0, _, _, 'text/'),
  725    !,
  726    (   sub_atom(Type0, S, _, _, ';')
  727    ->  sub_atom(Type0, 0, S, _, B)
  728    ;   B = Type0
  729    ),
  730    atom_concat(B, '; charset=UTF-8', Type).
  731http_update_encoding(Header, Encoding, Header) :-
  732    memberchk(content_type(Type), Header),
  733    (   (   sub_atom(Type, _, _, _, 'UTF-8')
  734        ;   sub_atom(Type, _, _, _, 'utf-8')
  735        )
  736    ->  Encoding = utf8
  737    ;   http:mime_type_encoding(Type, Encoding)
  738    ->  true
  739    ;   mime_type_encoding(Type, Encoding)
  740    ).
  741http_update_encoding(Header, octet, Header).
 mime_type_encoding(+MimeType, -Encoding) is semidet
Encoding is the (default) character encoding for MimeType. Hooked by http:mime_type_encoding/2.
  748mime_type_encoding('application/json',         utf8).
  749mime_type_encoding('application/jsonrequest',  utf8).
  750mime_type_encoding('application/x-prolog',     utf8).
  751mime_type_encoding('application/n-quads',      utf8).
  752mime_type_encoding('application/n-triples',    utf8).
  753mime_type_encoding('application/sparql-query', utf8).
  754mime_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.
  770http_update_connection(CgiHeader, Request, Connect,
  771                       [connection(Connect)|Rest]) :-
  772    select(connection(CgiConn), CgiHeader, Rest),
  773    !,
  774    connection(Request, ReqConnection),
  775    join_connection(ReqConnection, CgiConn, Connect).
  776http_update_connection(CgiHeader, Request, Connect,
  777                       [connection(Connect)|CgiHeader]) :-
  778    connection(Request, Connect).
  779
  780join_connection(Keep1, Keep2, Connection) :-
  781    (   downcase_atom(Keep1, 'keep-alive'),
  782        downcase_atom(Keep2, 'keep-alive')
  783    ->  Connection = 'Keep-Alive'
  784    ;   Connection = close
  785    ).
 connection(+Header, -Connection)
Extract the desired connection from a header.
  792connection(Header, Close) :-
  793    (   memberchk(connection(Connection), Header)
  794    ->  Close = Connection
  795    ;   memberchk(http_version(1-X), Header),
  796        X >= 1
  797    ->  Close = 'Keep-Alive'
  798    ;   Close = close
  799    ).
 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.

  818http_update_transfer(Request, CgiHeader, Transfer, Header) :-
  819    setting(http:chunked_transfer, When),
  820    http_update_transfer(When, Request, CgiHeader, Transfer, Header).
  821
  822http_update_transfer(never, _, CgiHeader, none, Header) :-
  823    !,
  824    delete(CgiHeader, transfer_encoding(_), Header).
  825http_update_transfer(_, _, CgiHeader, none, Header) :-
  826    memberchk(location(_), CgiHeader),
  827    !,
  828    delete(CgiHeader, transfer_encoding(_), Header).
  829http_update_transfer(_, Request, CgiHeader, Transfer, Header) :-
  830    select(transfer_encoding(CgiTransfer), CgiHeader, Rest),
  831    !,
  832    transfer(Request, ReqConnection),
  833    join_transfer(ReqConnection, CgiTransfer, Transfer),
  834    (   Transfer == none
  835    ->  Header = Rest
  836    ;   Header = [transfer_encoding(Transfer)|Rest]
  837    ).
  838http_update_transfer(if_possible, Request, CgiHeader, Transfer, Header) :-
  839    transfer(Request, Transfer),
  840    Transfer \== none,
  841    !,
  842    Header = [transfer_encoding(Transfer)|CgiHeader].
  843http_update_transfer(_, _, CgiHeader, none, CgiHeader).
  844
  845join_transfer(chunked, chunked, chunked) :- !.
  846join_transfer(_, _, none).
 transfer(+Header, -Connection)
Extract the desired connection from a header.
  853transfer(Header, Transfer) :-
  854    (   memberchk(transfer_encoding(Transfer0), Header)
  855    ->  Transfer = Transfer0
  856    ;   memberchk(http_version(1-X), Header),
  857        X >= 1
  858    ->  Transfer = chunked
  859    ;   Transfer = none
  860    ).
 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.
  869content_length_in_encoding(Enc, Stream, Bytes) :-
  870    stream_property(Stream, position(Here)),
  871    setup_call_cleanup(
  872        open_null_stream(Out),
  873        ( set_stream(Out, encoding(Enc)),
  874          catch(copy_stream_data(Stream, Out), _, fail),
  875          flush_output(Out),
  876          byte_count(Out, Bytes)
  877        ),
  878        ( close(Out, [force(true)]),
  879          set_stream_position(Stream, Here)
  880        )).
  881
  882
  883                 /*******************************
  884                 *          POST SUPPORT        *
  885                 *******************************/
 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:
  978http_post_data(Data, Out, HdrExtra) :-
  979    http:post_data_hook(Data, Out, HdrExtra),
  980    !.
  981http_post_data(html(HTML), Out, HdrExtra) :-
  982    !,
  983    phrase(post_header(html(HTML), HdrExtra), Header),
  984    format(Out, '~s', [Header]),
  985    print_html(Out, HTML).
  986http_post_data(xml(XML), Out, HdrExtra) :-
  987    !,
  988    http_post_data(xml(text/xml, XML, []), Out, HdrExtra).
  989http_post_data(xml(Type, XML), Out, HdrExtra) :-
  990    !,
  991    http_post_data(xml(Type, XML, []), Out, HdrExtra).
  992http_post_data(xml(Type, XML, Options), Out, HdrExtra) :-
  993    !,
  994    setup_call_cleanup(
  995        new_memory_file(MemFile),
  996        (   setup_call_cleanup(
  997                open_memory_file(MemFile, write, MemOut),
  998                xml_write(MemOut, XML, Options),
  999                close(MemOut)),
 1000            http_post_data(memory_file(Type, MemFile), Out, HdrExtra)
 1001        ),
 1002        free_memory_file(MemFile)).
 1003http_post_data(file(File), Out, HdrExtra) :-
 1004    !,
 1005    (   file_mime_type(File, Type)
 1006    ->  true
 1007    ;   Type = text/plain
 1008    ),
 1009    http_post_data(file(Type, File), Out, HdrExtra).
 1010http_post_data(file(Type, File), Out, HdrExtra) :-
 1011    !,
 1012    phrase(post_header(file(Type, File), HdrExtra), Header),
 1013    format(Out, '~s', [Header]),
 1014    setup_call_cleanup(
 1015        open(File, read, In, [type(binary)]),
 1016        copy_stream_data(In, Out),
 1017        close(In)).
 1018http_post_data(memory_file(Type, Handle), Out, HdrExtra) :-
 1019    !,
 1020    phrase(post_header(memory_file(Type, Handle), HdrExtra), Header),
 1021    format(Out, '~s', [Header]),
 1022    setup_call_cleanup(
 1023        open_memory_file(Handle, read, In, [encoding(octet)]),
 1024        copy_stream_data(In, Out),
 1025        close(In)).
 1026http_post_data(codes(Codes), Out, HdrExtra) :-
 1027    !,
 1028    http_post_data(codes(text/plain, Codes), Out, HdrExtra).
 1029http_post_data(codes(Type, Codes), Out, HdrExtra) :-
 1030    !,
 1031    phrase(post_header(codes(Type, Codes), HdrExtra), Header),
 1032    format(Out, '~s', [Header]),
 1033    setup_call_cleanup(
 1034        set_stream(Out, encoding(utf8)),
 1035        format(Out, '~s', [Codes]),
 1036        set_stream(Out, encoding(octet))).
 1037http_post_data(bytes(Type, Bytes), Out, HdrExtra) :-
 1038    !,
 1039    phrase(post_header(bytes(Type, Bytes), HdrExtra), Header),
 1040    format(Out, '~s~s', [Header, Bytes]).
 1041http_post_data(atom(Atom), Out, HdrExtra) :-
 1042    !,
 1043    http_post_data(atom(text/plain, Atom), Out, HdrExtra).
 1044http_post_data(atom(Type, Atom), Out, HdrExtra) :-
 1045    !,
 1046    phrase(post_header(atom(Type, Atom), HdrExtra), Header),
 1047    format(Out, '~s', [Header]),
 1048    setup_call_cleanup(
 1049        set_stream(Out, encoding(utf8)),
 1050        write(Out, Atom),
 1051        set_stream(Out, encoding(octet))).
 1052http_post_data(cgi_stream(In, _Len), Out, HdrExtra) :-
 1053    !,
 1054    debug(obsolete, 'Obsolete 2nd argument in cgi_stream(In,Len)', []),
 1055    http_post_data(cgi_stream(In), Out, HdrExtra).
 1056http_post_data(cgi_stream(In), Out, HdrExtra) :-
 1057    !,
 1058    http_read_header(In, Header0),
 1059    http_update_encoding(Header0, Encoding, Header),
 1060    content_length_in_encoding(Encoding, In, Size),
 1061    http_join_headers(HdrExtra, Header, Hdr2),
 1062    phrase(post_header(cgi_data(Size), Hdr2), HeaderText),
 1063    format(Out, '~s', [HeaderText]),
 1064    setup_call_cleanup(
 1065        set_stream(Out, encoding(Encoding)),
 1066        copy_stream_data(In, Out),
 1067        set_stream(Out, encoding(octet))).
 1068http_post_data(form(Fields), Out, HdrExtra) :-
 1069    !,
 1070    parse_url_search(Codes, Fields),
 1071    length(Codes, Size),
 1072    http_join_headers(HdrExtra,
 1073                      [ content_type('application/x-www-form-urlencoded')
 1074                      ], Header),
 1075    phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1076    format(Out, '~s', [HeaderChars]),
 1077    format(Out, '~s', [Codes]).
 1078http_post_data(form_data(Data), Out, HdrExtra) :-
 1079    !,
 1080    setup_call_cleanup(
 1081        new_memory_file(MemFile),
 1082        ( setup_call_cleanup(
 1083              open_memory_file(MemFile, write, MimeOut),
 1084              mime_pack(Data, MimeOut, Boundary),
 1085              close(MimeOut)),
 1086          size_memory_file(MemFile, Size, octet),
 1087          format(string(ContentType),
 1088                 'multipart/form-data; boundary=~w', [Boundary]),
 1089          http_join_headers(HdrExtra,
 1090                            [ mime_version('1.0'),
 1091                              content_type(ContentType)
 1092                            ], Header),
 1093          phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1094          format(Out, '~s', [HeaderChars]),
 1095          setup_call_cleanup(
 1096              open_memory_file(MemFile, read, In, [encoding(octet)]),
 1097              copy_stream_data(In, Out),
 1098              close(In))
 1099        ),
 1100        free_memory_file(MemFile)).
 1101http_post_data(List, Out, HdrExtra) :-          % multipart-mixed
 1102    is_list(List),
 1103    !,
 1104    setup_call_cleanup(
 1105        new_memory_file(MemFile),
 1106        ( setup_call_cleanup(
 1107              open_memory_file(MemFile, write, MimeOut),
 1108              mime_pack(List, MimeOut, Boundary),
 1109              close(MimeOut)),
 1110          size_memory_file(MemFile, Size, octet),
 1111          format(string(ContentType),
 1112                 'multipart/mixed; boundary=~w', [Boundary]),
 1113          http_join_headers(HdrExtra,
 1114                            [ mime_version('1.0'),
 1115                              content_type(ContentType)
 1116                            ], Header),
 1117          phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1118          format(Out, '~s', [HeaderChars]),
 1119          setup_call_cleanup(
 1120              open_memory_file(MemFile, read, In, [encoding(octet)]),
 1121              copy_stream_data(In, Out),
 1122              close(In))
 1123        ),
 1124        free_memory_file(MemFile)).
 post_header(+Data, +HeaderExtra)//
Generate the POST header, emitting HeaderExtra, followed by the HTTP Content-length and Content-type fields.
 1131post_header(html(Tokens), HdrExtra) -->
 1132    header_fields(HdrExtra, Len),
 1133    content_length(html(Tokens), Len),
 1134    content_type(text/html),
 1135    "\r\n".
 1136post_header(file(Type, File), HdrExtra) -->
 1137    header_fields(HdrExtra, Len),
 1138    content_length(file(File), Len),
 1139    content_type(Type),
 1140    "\r\n".
 1141post_header(memory_file(Type, File), HdrExtra) -->
 1142    header_fields(HdrExtra, Len),
 1143    content_length(memory_file(File), Len),
 1144    content_type(Type),
 1145    "\r\n".
 1146post_header(cgi_data(Size), HdrExtra) -->
 1147    header_fields(HdrExtra, Len),
 1148    content_length(Size, Len),
 1149    "\r\n".
 1150post_header(codes(Type, Codes), HdrExtra) -->
 1151    header_fields(HdrExtra, Len),
 1152    content_length(codes(Codes, utf8), Len),
 1153    content_type(Type, utf8),
 1154    "\r\n".
 1155post_header(bytes(Type, Bytes), HdrExtra) -->
 1156    header_fields(HdrExtra, Len),
 1157    content_length(bytes(Bytes), Len),
 1158    content_type(Type),
 1159    "\r\n".
 1160post_header(atom(Type, Atom), HdrExtra) -->
 1161    header_fields(HdrExtra, Len),
 1162    content_length(atom(Atom, utf8), Len),
 1163    content_type(Type, utf8),
 1164    "\r\n".
 1165
 1166
 1167                 /*******************************
 1168                 *       OUTPUT HEADER DCG      *
 1169                 *******************************/
 http_reply_header(+Out:stream, +What, +HdrExtra) is det
Create a reply header using reply_header//3 and send it to Stream.
 1176http_reply_header(Out, What, HdrExtra) :-
 1177    phrase(reply_header(What, HdrExtra, _Code), String),
 1178    !,
 1179    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.
 1203reply_header(Data, Dict) -->
 1204    { _{header:HdrExtra, code:Code} :< Dict },
 1205    reply_header(Data, HdrExtra, Code).
 1206
 1207reply_header(string(String), HdrExtra, Code) -->
 1208    reply_header(string(text/plain, String), HdrExtra, Code).
 1209reply_header(string(Type, String), HdrExtra, Code) -->
 1210    vstatus(ok, Code, HdrExtra),
 1211    date(now),
 1212    header_fields(HdrExtra, CLen),
 1213    content_length(codes(String, utf8), CLen),
 1214    content_type(Type, utf8),
 1215    "\r\n".
 1216reply_header(bytes(Type, Bytes), HdrExtra, Code) -->
 1217    vstatus(ok, Code, HdrExtra),
 1218    date(now),
 1219    header_fields(HdrExtra, CLen),
 1220    content_length(bytes(Bytes), CLen),
 1221    content_type(Type),
 1222    "\r\n".
 1223reply_header(html(Tokens), HdrExtra, Code) -->
 1224    vstatus(ok, Code, HdrExtra),
 1225    date(now),
 1226    header_fields(HdrExtra, CLen),
 1227    content_length(html(Tokens), CLen),
 1228    content_type(text/html),
 1229    "\r\n".
 1230reply_header(file(Type, File), HdrExtra, Code) -->
 1231    vstatus(ok, Code, HdrExtra),
 1232    date(now),
 1233    modified(file(File)),
 1234    header_fields(HdrExtra, CLen),
 1235    content_length(file(File), CLen),
 1236    content_type(Type),
 1237    "\r\n".
 1238reply_header(gzip_file(Type, File), HdrExtra, Code) -->
 1239    vstatus(ok, Code, HdrExtra),
 1240    date(now),
 1241    modified(file(File)),
 1242    header_fields(HdrExtra, CLen),
 1243    content_length(file(File), CLen),
 1244    content_type(Type),
 1245    content_encoding(gzip),
 1246    "\r\n".
 1247reply_header(file(Type, File, Range), HdrExtra, Code) -->
 1248    vstatus(partial_content, Code, HdrExtra),
 1249    date(now),
 1250    modified(file(File)),
 1251    header_fields(HdrExtra, CLen),
 1252    content_length(file(File, Range), CLen),
 1253    content_type(Type),
 1254    "\r\n".
 1255reply_header(tmp_file(Type, File), HdrExtra, Code) -->
 1256    vstatus(ok, Code, HdrExtra),
 1257    date(now),
 1258    header_fields(HdrExtra, CLen),
 1259    content_length(file(File), CLen),
 1260    content_type(Type),
 1261    "\r\n".
 1262reply_header(cgi_data(Size), HdrExtra, Code) -->
 1263    vstatus(ok, Code, HdrExtra),
 1264    date(now),
 1265    header_fields(HdrExtra, CLen),
 1266    content_length(Size, CLen),
 1267    "\r\n".
 1268reply_header(chunked_data, HdrExtra, Code) -->
 1269    vstatus(ok, Code, HdrExtra),
 1270    date(now),
 1271    header_fields(HdrExtra, _),
 1272    (   {memberchk(transfer_encoding(_), HdrExtra)}
 1273    ->  ""
 1274    ;   transfer_encoding(chunked)
 1275    ),
 1276    "\r\n".
 1277% non-200 replies without a body (e.g., 1xx, 204, 304)
 1278reply_header(status(Status), HdrExtra, Code) -->
 1279    vstatus(Status, Code),
 1280    header_fields(HdrExtra, Clen),
 1281    { Clen = 0 },
 1282    "\r\n".
 1283% non-200 replies with a body
 1284reply_header(Data, HdrExtra, Code) -->
 1285    { status_reply_headers(Data,
 1286                           body(Type, Encoding, Content),
 1287                           ReplyHeaders),
 1288      http_join_headers(ReplyHeaders, HdrExtra, Headers),
 1289      functor(Data, CodeName, _)
 1290    },
 1291    vstatus(CodeName, Code, Headers),
 1292    date(now),
 1293    header_fields(Headers, CLen),
 1294    content_length(codes(Content, Encoding), CLen),
 1295    content_type(Type, Encoding),
 1296    "\r\n".
 1297
 1298status_reply_headers(created(Location, Body), Body,
 1299                     [ location(Location) ]).
 1300status_reply_headers(moved(To, Body), Body,
 1301                     [ location(To) ]).
 1302status_reply_headers(moved_temporary(To, Body), Body,
 1303                     [ location(To) ]).
 1304status_reply_headers(see_other(To, Body), Body,
 1305                     [ location(To) ]).
 1306status_reply_headers(authorise(Method, Body), Body,
 1307                     [ www_authenticate(Method) ]).
 1308status_reply_headers(not_found(_URL, Body), Body, []).
 1309status_reply_headers(forbidden(_URL, Body), Body, []).
 1310status_reply_headers(method_not_allowed(_Method, _URL, Body), Body, []).
 1311status_reply_headers(server_error(_Error, Body), Body, []).
 1312status_reply_headers(service_unavailable(_Why, Body), Body, []).
 1313status_reply_headers(not_acceptable(_Why, Body), Body, []).
 1314status_reply_headers(bad_request(_Error, Body), Body, []).
 vstatus(+Status, -Code)// is det
 vstatus(+Status, -Code, +HdrExtra)// is det
Emit the HTTP header for Status
 1322vstatus(_Status, Code, HdrExtra) -->
 1323    {memberchk(status(Code), HdrExtra)},
 1324    !,
 1325    vstatus(_NewStatus, Code).
 1326vstatus(Status, Code, _) -->
 1327    vstatus(Status, Code).
 1328
 1329vstatus(Status, Code) -->
 1330    "HTTP/1.1 ",
 1331    status_number(Status, Code),
 1332    " ",
 1333    status_comment(Status),
 1334    "\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.
 1343status_number(Status, Code) -->
 1344    { var(Status) },
 1345    !,
 1346    integer(Code),
 1347    { status_number(Status, Code) },
 1348    !.
 1349status_number(Status, Code) -->
 1350    { status_number(Status, Code) },
 1351    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.
 1365% Unrecognized status codes that are within a defined code class.
 1366% RFC 7231 states:
 1367%   "[...] a client MUST understand the class of any status code,
 1368%    as indicated by the first digit, and treat an unrecognized status code
 1369%    as being equivalent to the `x00` status code of that class [...]
 1370%   "
 1371% @see http://tools.ietf.org/html/rfc7231#section-6
 1372
 1373status_number(Status, Code) :-
 1374    nonvar(Status),
 1375    !,
 1376    status_number_fact(Status, Code).
 1377status_number(Status, Code) :-
 1378    nonvar(Code),
 1379    !,
 1380    (   between(100, 599, Code)
 1381    ->  (   status_number_fact(Status, Code)
 1382        ->  true
 1383        ;   ClassCode is Code // 100 * 100,
 1384            status_number_fact(Status, ClassCode)
 1385        )
 1386    ;   domain_error(http_code, Code)
 1387    ).
 1388
 1389status_number_fact(continue,                   100).
 1390status_number_fact(switching_protocols,        101).
 1391status_number_fact(ok,                         200).
 1392status_number_fact(created,                    201).
 1393status_number_fact(accepted,                   202).
 1394status_number_fact(non_authoritative_info,     203).
 1395status_number_fact(no_content,                 204).
 1396status_number_fact(reset_content,              205).
 1397status_number_fact(partial_content,            206).
 1398status_number_fact(multiple_choices,           300).
 1399status_number_fact(moved,                      301).
 1400status_number_fact(moved_temporary,            302).
 1401status_number_fact(see_other,                  303).
 1402status_number_fact(not_modified,               304).
 1403status_number_fact(use_proxy,                  305).
 1404status_number_fact(unused,                     306).
 1405status_number_fact(temporary_redirect,         307).
 1406status_number_fact(bad_request,                400).
 1407status_number_fact(authorise,                  401).
 1408status_number_fact(payment_required,           402).
 1409status_number_fact(forbidden,                  403).
 1410status_number_fact(not_found,                  404).
 1411status_number_fact(method_not_allowed,         405).
 1412status_number_fact(not_acceptable,             406).
 1413status_number_fact(request_timeout,            408).
 1414status_number_fact(conflict,                   409).
 1415status_number_fact(gone,                       410).
 1416status_number_fact(length_required,            411).
 1417status_number_fact(payload_too_large,          413).
 1418status_number_fact(uri_too_long,               414).
 1419status_number_fact(unsupported_media_type,     415).
 1420status_number_fact(expectation_failed,         417).
 1421status_number_fact(upgrade_required,           426).
 1422status_number_fact(server_error,               500).
 1423status_number_fact(not_implemented,            501).
 1424status_number_fact(bad_gateway,                502).
 1425status_number_fact(service_unavailable,        503).
 1426status_number_fact(gateway_timeout,            504).
 1427status_number_fact(http_version_not_supported, 505).
 status_comment(+Code:atom)// is det
Emit standard HTTP human-readable comment on the reply-status.
 1434status_comment(continue) -->
 1435    "Continue".
 1436status_comment(switching_protocols) -->
 1437    "Switching Protocols".
 1438status_comment(ok) -->
 1439    "OK".
 1440status_comment(created) -->
 1441    "Created".
 1442status_comment(accepted) -->
 1443    "Accepted".
 1444status_comment(non_authoritative_info) -->
 1445    "Non-Authoritative Information".
 1446status_comment(no_content) -->
 1447    "No Content".
 1448status_comment(reset_content) -->
 1449    "Reset Content".
 1450status_comment(created) -->
 1451    "Created".
 1452status_comment(partial_content) -->
 1453    "Partial content".
 1454status_comment(multiple_choices) -->
 1455    "Multiple Choices".
 1456status_comment(moved) -->
 1457    "Moved Permanently".
 1458status_comment(moved_temporary) -->
 1459    "Moved Temporary".
 1460status_comment(see_other) -->
 1461    "See Other".
 1462status_comment(not_modified) -->
 1463    "Not Modified".
 1464status_comment(use_proxy) -->
 1465    "Use Proxy".
 1466status_comment(unused) -->
 1467    "Unused".
 1468status_comment(temporary_redirect) -->
 1469    "Temporary Redirect".
 1470status_comment(bad_request) -->
 1471    "Bad Request".
 1472status_comment(authorise) -->
 1473    "Authorization Required".
 1474status_comment(payment_required) -->
 1475    "Payment Required".
 1476status_comment(forbidden) -->
 1477    "Forbidden".
 1478status_comment(not_found) -->
 1479    "Not Found".
 1480status_comment(method_not_allowed) -->
 1481    "Method Not Allowed".
 1482status_comment(not_acceptable) -->
 1483    "Not Acceptable".
 1484status_comment(request_timeout) -->
 1485    "Request Timeout".
 1486status_comment(conflict) -->
 1487    "Conflict".
 1488status_comment(gone) -->
 1489    "Gone".
 1490status_comment(length_required) -->
 1491    "Length Required".
 1492status_comment(payload_too_large) -->
 1493    "Payload Too Large".
 1494status_comment(uri_too_long) -->
 1495    "URI Too Long".
 1496status_comment(unsupported_media_type) -->
 1497    "Unsupported Media Type".
 1498status_comment(expectation_failed) -->
 1499    "Expectation Failed".
 1500status_comment(upgrade_required) -->
 1501    "Upgrade Required".
 1502status_comment(server_error) -->
 1503    "Internal Server Error".
 1504status_comment(not_implemented) -->
 1505    "Not Implemented".
 1506status_comment(bad_gateway) -->
 1507    "Bad Gateway".
 1508status_comment(service_unavailable) -->
 1509    "Service Unavailable".
 1510status_comment(gateway_timeout) -->
 1511    "Gateway Timeout".
 1512status_comment(http_version_not_supported) -->
 1513    "HTTP Version Not Supported".
 1514
 1515date(Time) -->
 1516    "Date: ",
 1517    (   { Time == now }
 1518    ->  now
 1519    ;   rfc_date(Time)
 1520    ),
 1521    "\r\n".
 1522
 1523modified(file(File)) -->
 1524    !,
 1525    { time_file(File, Time)
 1526    },
 1527    modified(Time).
 1528modified(Time) -->
 1529    "Last-modified: ",
 1530    (   { Time == now }
 1531    ->  now
 1532    ;   rfc_date(Time)
 1533    ),
 1534    "\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
 1544content_length(file(File, bytes(From, To)), Len) -->
 1545    !,
 1546    { size_file(File, Size),
 1547      (   To == end
 1548      ->  Len is Size - From,
 1549          RangeEnd is Size - 1
 1550      ;   Len is To+1 - From,       % To is index of last byte
 1551          RangeEnd = To
 1552      )
 1553    },
 1554    content_range(bytes, From, RangeEnd, Size),
 1555    content_length(Len, Len).
 1556content_length(Reply, Len) -->
 1557    { length_of(Reply, Len)
 1558    },
 1559    "Content-Length: ", integer(Len),
 1560    "\r\n".
 1561
 1562
 1563length_of(_, Len) :-
 1564    nonvar(Len),
 1565    !.
 1566length_of(codes(String, Encoding), Len) :-
 1567    !,
 1568    setup_call_cleanup(
 1569        open_null_stream(Out),
 1570        ( set_stream(Out, encoding(Encoding)),
 1571          format(Out, '~s', [String]),
 1572          byte_count(Out, Len)
 1573        ),
 1574        close(Out)).
 1575length_of(atom(Atom, Encoding), Len) :-
 1576    !,
 1577    setup_call_cleanup(
 1578        open_null_stream(Out),
 1579        ( set_stream(Out, encoding(Encoding)),
 1580          format(Out, '~a', [Atom]),
 1581          byte_count(Out, Len)
 1582        ),
 1583        close(Out)).
 1584length_of(file(File), Len) :-
 1585    !,
 1586    size_file(File, Len).
 1587length_of(memory_file(Handle), Len) :-
 1588    !,
 1589    size_memory_file(Handle, Len, octet).
 1590length_of(html_tokens(Tokens), Len) :-
 1591    !,
 1592    html_print_length(Tokens, Len).
 1593length_of(html(Tokens), Len) :-     % deprecated
 1594    !,
 1595    html_print_length(Tokens, Len).
 1596length_of(bytes(Bytes), Len) :-
 1597    !,
 1598    (   string(Bytes)
 1599    ->  string_length(Bytes, Len)
 1600    ;   length(Bytes, Len)          % assuming a list of 0..255
 1601    ).
 1602length_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.
 1610content_range(Unit, From, RangeEnd, Size) -->
 1611    "Content-Range: ", atom(Unit), " ",
 1612    integer(From), "-", integer(RangeEnd), "/", integer(Size),
 1613    "\r\n".
 1614
 1615content_encoding(Encoding) -->
 1616    "Content-Encoding: ", atom(Encoding), "\r\n".
 1617
 1618transfer_encoding(Encoding) -->
 1619    "Transfer-Encoding: ", atom(Encoding), "\r\n".
 1620
 1621content_type(Type) -->
 1622    content_type(Type, _).
 1623
 1624content_type(Type, Charset) -->
 1625    ctype(Type),
 1626    charset(Charset),
 1627    "\r\n".
 1628
 1629ctype(Main/Sub) -->
 1630    !,
 1631    "Content-Type: ",
 1632    atom(Main),
 1633    "/",
 1634    atom(Sub).
 1635ctype(Type) -->
 1636    !,
 1637    "Content-Type: ",
 1638    atom(Type).
 1639
 1640charset(Var) -->
 1641    { var(Var) },
 1642    !.
 1643charset(utf8) -->
 1644    !,
 1645    "; charset=UTF-8".
 1646charset(CharSet) -->
 1647    "; charset=",
 1648    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.
 1656header_field(Name, Value) -->
 1657    { var(Name) },                 % parsing
 1658    !,
 1659    field_name(Name),
 1660    ":",
 1661    whites,
 1662    read_field_value(ValueChars),
 1663    blanks_to_nl,
 1664    !,
 1665    {   field_to_prolog(Name, ValueChars, Value)
 1666    ->  true
 1667    ;   atom_codes(Value, ValueChars),
 1668        domain_error(Name, Value)
 1669    }.
 1670header_field(Name, Value) -->
 1671    field_name(Name),
 1672    ": ",
 1673    field_value(Name, Value),
 1674    "\r\n".
 read_field_value(-Codes)//
Read a field eagerly upto the next whitespace
 1680read_field_value([H|T]) -->
 1681    [H],
 1682    { \+ code_type(H, space) },
 1683    !,
 1684    read_field_value(T).
 1685read_field_value([]) -->
 1686    "".
 1687read_field_value([H|T]) -->
 1688    [H],
 1689    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
status
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.

As some fields are already parsed in the Request, this predicate is a no-op when called on an already parsed field.

Arguments:
Value- is either an atom, a list of codes or an already parsed header value.
 1730http_parse_header_value(Field, Value, Prolog) :-
 1731    known_field(Field, _, Type),
 1732    (   already_parsed(Type, Value)
 1733    ->  Prolog = Value
 1734    ;   to_codes(Value, Codes),
 1735        parse_header_value(Field, Codes, Prolog)
 1736    ).
 1737
 1738already_parsed(integer, V)    :- !, integer(V).
 1739already_parsed(list(Type), L) :- !, is_list(L), maplist(already_parsed(Type), L).
 1740already_parsed(Term, V)       :- subsumes_term(Term, V).
 known_field(?FieldName, ?AutoConvert, -Type)
True if the value of FieldName is by default translated into a Prolog data structure.
 1748known_field(content_length,      true,  integer).
 1749known_field(status,              true,  integer).
 1750known_field(cookie,              true,  list(_=_)).
 1751known_field(set_cookie,          true,  list(set_cookie(_Name,_Value,_Options))).
 1752known_field(host,                true,  _Host:_Port).
 1753known_field(range,               maybe, bytes(_,_)).
 1754known_field(accept,              maybe, list(media(_Type, _Parms, _Q, _Exts))).
 1755known_field(content_disposition, maybe, disposition(_Name, _Attributes)).
 1756known_field(content_type,        false, media(_Type/_Sub, _Attributes)).
 1757
 1758to_codes(In, Codes) :-
 1759    (   is_list(In)
 1760    ->  Codes = In
 1761    ;   atom_codes(In, Codes)
 1762    ).
 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.
 1770field_to_prolog(Field, Codes, Prolog) :-
 1771    known_field(Field, true, _Type),
 1772    !,
 1773    (   parse_header_value(Field, Codes, Prolog0)
 1774    ->  Prolog = Prolog0
 1775    ).
 1776field_to_prolog(Field, Codes, Prolog) :-
 1777    known_field(Field, maybe, _Type),
 1778    parse_header_value(Field, Codes, Prolog0),
 1779    !,
 1780    Prolog = Prolog0.
 1781field_to_prolog(_, Codes, Atom) :-
 1782    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.
 1789parse_header_value(content_length, ValueChars, ContentLength) :-
 1790    number_codes(ContentLength, ValueChars).
 1791parse_header_value(status, ValueChars, Code) :-
 1792    (   phrase(" ", L, _),
 1793        append(Pre, L, ValueChars)
 1794    ->  number_codes(Code, Pre)
 1795    ;   number_codes(Code, ValueChars)
 1796    ).
 1797parse_header_value(cookie, ValueChars, Cookies) :-
 1798    debug(cookie, 'Cookie: ~s', [ValueChars]),
 1799    phrase(cookies(Cookies), ValueChars).
 1800parse_header_value(set_cookie, ValueChars, SetCookie) :-
 1801    debug(cookie, 'SetCookie: ~s', [ValueChars]),
 1802    phrase(set_cookie(SetCookie), ValueChars).
 1803parse_header_value(host, ValueChars, Host) :-
 1804    (   append(HostChars, [0':|PortChars], ValueChars),
 1805        catch(number_codes(Port, PortChars), _, fail)
 1806    ->  atom_codes(HostName, HostChars),
 1807        Host = HostName:Port
 1808    ;   atom_codes(Host, ValueChars)
 1809    ).
 1810parse_header_value(range, ValueChars, Range) :-
 1811    phrase(range(Range), ValueChars).
 1812parse_header_value(accept, ValueChars, Media) :-
 1813    parse_accept(ValueChars, Media).
 1814parse_header_value(content_disposition, ValueChars, Disposition) :-
 1815    phrase(content_disposition(Disposition), ValueChars).
 1816parse_header_value(content_type, ValueChars, Type) :-
 1817    phrase(parse_content_type(Type), ValueChars).
 field_value(+Name, +Value)//
 1821field_value(_, set_cookie(Name, Value, Options)) -->
 1822    !,
 1823    atom(Name), "=", atom(Value),
 1824    value_options(Options, cookie).
 1825field_value(_, disposition(Disposition, Options)) -->
 1826    !,
 1827    atom(Disposition), value_options(Options, disposition).
 1828field_value(www_authenticate, Auth) -->
 1829    auth_field_value(Auth).
 1830field_value(_, Atomic) -->
 1831    atom(Atomic).
 auth_field_value(+AuthValue)//
Emit the authentication requirements (WWW-Authenticate field).
 1837auth_field_value(negotiate(Data)) -->
 1838    "Negotiate ",
 1839    { base64(Data, DataBase64),
 1840      atom_codes(DataBase64, Codes)
 1841    },
 1842    string(Codes), "\r\n".
 1843auth_field_value(negotiate) -->
 1844    "Negotiate\r\n".
 1845auth_field_value(basic) -->
 1846    !,
 1847    "Basic\r\n".
 1848auth_field_value(basic(Realm)) -->
 1849    "Basic Realm=\"", atom(Realm), "\"\r\n".
 1850auth_field_value(digest) -->
 1851    !,
 1852    "Digest\r\n".
 1853auth_field_value(digest(Details)) -->
 1854    "Digest ", atom(Details), "\r\n".
 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.
 1863value_options([], _) --> [].
 1864value_options([H|T], Field) -->
 1865    "; ", value_option(H, Field),
 1866    value_options(T, Field).
 1867
 1868value_option(secure=true, cookie) -->
 1869    !,
 1870    "secure".
 1871value_option(Name=Value, Type) -->
 1872    { string_option(Name, Type) },
 1873    !,
 1874    atom(Name), "=",
 1875    qstring(Value).
 1876value_option(Name=Value, Type) -->
 1877    { token_option(Name, Type) },
 1878    !,
 1879    atom(Name), "=", atom(Value).
 1880value_option(Name=Value, _Type) -->
 1881    atom(Name), "=",
 1882    option_value(Value).
 1883
 1884string_option(filename, disposition).
 1885
 1886token_option(path, cookie).
 1887
 1888option_value(Value) -->
 1889    { number(Value) },
 1890    !,
 1891    number(Value).
 1892option_value(Value) -->
 1893    { (   atom(Value)
 1894      ->  true
 1895      ;   string(Value)
 1896      ),
 1897      forall(string_code(_, Value, C),
 1898             token_char(C))
 1899    },
 1900    !,
 1901    atom(Value).
 1902option_value(Atomic) -->
 1903    qstring(Atomic).
 1904
 1905qstring(Atomic) -->
 1906    { string_codes(Atomic, Codes) },
 1907    "\"",
 1908    qstring_codes(Codes),
 1909    "\"".
 1910
 1911qstring_codes([]) --> [].
 1912qstring_codes([H|T]) --> qstring_code(H), qstring_codes(T).
 1913
 1914qstring_code(C) --> {qstring_esc(C)}, !, "\\", [C].
 1915qstring_code(C) --> [C].
 1916
 1917qstring_esc(0'").
 1918qstring_esc(C) :- ctl(C).
 1919
 1920
 1921                 /*******************************
 1922                 *        ACCEPT HEADERS        *
 1923                 *******************************/
 1924
 1925:- dynamic accept_cache/2. 1926:- volatile accept_cache/2. 1927
 1928parse_accept(Codes, Media) :-
 1929    atom_codes(Atom, Codes),
 1930    (   accept_cache(Atom, Media0)
 1931    ->  Media = Media0
 1932    ;   phrase(accept(Media0), Codes),
 1933        keysort(Media0, Media1),
 1934        pairs_values(Media1, Media2),
 1935        assertz(accept_cache(Atom, Media2)),
 1936        Media = Media2
 1937    ).
 accept(-Media)// is semidet
Parse an HTTP Accept: header
 1943accept([H|T]) -->
 1944    blanks,
 1945    media_range(H),
 1946    blanks,
 1947    (   ","
 1948    ->  accept(T)
 1949    ;   {T=[]}
 1950    ).
 1951
 1952media_range(s(SortQuality,Spec)-media(Type, TypeParams, Quality, AcceptExts)) -->
 1953    media_type(Type),
 1954    blanks,
 1955    (   ";"
 1956    ->  blanks,
 1957        parameters_and_quality(TypeParams, Quality, AcceptExts)
 1958    ;   { TypeParams = [],
 1959          Quality = 1.0,
 1960          AcceptExts = []
 1961        }
 1962    ),
 1963    { SortQuality is float(-Quality),
 1964      rank_specialised(Type, TypeParams, Spec)
 1965    }.
 content_disposition(-Disposition)//
Parse Content-Disposition value
 1972content_disposition(disposition(Disposition, Options)) -->
 1973    token(Disposition), blanks,
 1974    value_parameters(Options).
 parse_content_type(-Type)//
Parse Content-Type value into a term media(Type/SubType, Parameters).
 1981parse_content_type(media(Type, Parameters)) -->
 1982    media_type(Type), blanks,
 1983    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?
 1994rank_specialised(Type/SubType, TypeParams, v(VT, VS, SortVP)) :-
 1995    var_or_given(Type, VT),
 1996    var_or_given(SubType, VS),
 1997    length(TypeParams, VP),
 1998    SortVP is -VP.
 1999
 2000var_or_given(V, Val) :-
 2001    (   var(V)
 2002    ->  Val = 0
 2003    ;   Val = -1
 2004    ).
 2005
 2006media_type(Type/SubType) -->
 2007    type(Type), "/", type(SubType).
 2008
 2009type(_) -->
 2010    "*",
 2011    !.
 2012type(Type) -->
 2013    token(Type).
 2014
 2015parameters_and_quality(Params, Quality, AcceptExts) -->
 2016    token(Name),
 2017    blanks, "=", blanks,
 2018    (   { Name == q }
 2019    ->  float(Quality), blanks,
 2020        value_parameters(AcceptExts),
 2021        { Params = [] }
 2022    ;   { Params = [Name=Value|T] },
 2023        parameter_value(Value),
 2024        blanks,
 2025        (   ";"
 2026        ->  blanks,
 2027            parameters_and_quality(T, Quality, AcceptExts)
 2028        ;   { T = [],
 2029              Quality = 1.0,
 2030              AcceptExts = []
 2031            }
 2032        )
 2033    ).
 value_parameters(-Params:list) is det
Accept (";" <parameter>)*, returning a list of Name=Value, where both Name and Value are atoms.
 2040value_parameters([H|T]) -->
 2041    ";",
 2042    !,
 2043    blanks, token(Name), blanks,
 2044    (   "="
 2045    ->  blanks,
 2046        (   token(Value)
 2047        ->  []
 2048        ;   quoted_string(Value)
 2049        ),
 2050        { H = (Name=Value) }
 2051    ;   { H = Name }
 2052    ),
 2053    blanks,
 2054    value_parameters(T).
 2055value_parameters([]) -->
 2056    [].
 2057
 2058parameter_value(Value) --> token(Value), !.
 2059parameter_value(Value) --> quoted_string(Value).
 token(-Name)// is semidet
Process an HTTP header token from the input.
 2066token(Name) -->
 2067    token_char(C1),
 2068    token_chars(Cs),
 2069    { atom_codes(Name, [C1|Cs]) }.
 2070
 2071token_chars([H|T]) -->
 2072    token_char(H),
 2073    !,
 2074    token_chars(T).
 2075token_chars([]) --> [].
 2076
 2077token_char(C) :-
 2078    \+ ctl(C),
 2079    \+ separator_code(C).
 2080
 2081ctl(C) :- between(0,31,C), !.
 2082ctl(127).
 2083
 2084separator_code(0'().
 2085separator_code(0')).
 2086separator_code(0'<).
 2087separator_code(0'>).
 2088separator_code(0'@).
 2089separator_code(0',).
 2090separator_code(0';).
 2091separator_code(0':).
 2092separator_code(0'\\).
 2093separator_code(0'").
 2094separator_code(0'/).
 2095separator_code(0'[).
 2096separator_code(0']).
 2097separator_code(0'?).
 2098separator_code(0'=).
 2099separator_code(0'{).
 2100separator_code(0'}).
 2101separator_code(0'\s).
 2102separator_code(0'\t).
 2103
 2104term_expansion(token_char(x) --> [x], Clauses) :-
 2105    findall((token_char(C)-->[C]),
 2106            (   between(0, 255, C),
 2107                token_char(C)
 2108            ),
 2109            Clauses).
 2110
 2111token_char(x) --> [x].
 quoted_string(-Text)// is semidet
True if input starts with a quoted string representing Text.
 2117quoted_string(Text) -->
 2118    "\"",
 2119    quoted_text(Codes),
 2120    { atom_codes(Text, Codes) }.
 2121
 2122quoted_text([]) -->
 2123    "\"",
 2124    !.
 2125quoted_text([H|T]) -->
 2126    "\\", !, [H],
 2127    quoted_text(T).
 2128quoted_text([H|T]) -->
 2129    [H],
 2130    !,
 2131    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.
 2142header_fields([], _) --> [].
 2143header_fields([content_length(CLen)|T], CLen) -->
 2144    !,
 2145    (   { var(CLen) }
 2146    ->  ""
 2147    ;   header_field(content_length, CLen)
 2148    ),
 2149    header_fields(T, CLen).           % Continue or return first only?
 2150header_fields([status(_)|T], CLen) -->   % handled by vstatus//3.
 2151    !,
 2152    header_fields(T, CLen).
 2153header_fields([H|T], CLen) -->
 2154    { H =.. [Name, Value] },
 2155    header_field(Name, Value),
 2156    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
 2173:- public
 2174    field_name//1. 2175
 2176field_name(Name) -->
 2177    { var(Name) },
 2178    !,
 2179    rd_field_chars(Chars),
 2180    { atom_codes(Name, Chars) }.
 2181field_name(mime_version) -->
 2182    !,
 2183    "MIME-Version".
 2184field_name(www_authenticate) -->
 2185    !,
 2186    "WWW-Authenticate".
 2187field_name(Name) -->
 2188    { atom_codes(Name, Chars) },
 2189    wr_field_chars(Chars).
 2190
 2191rd_field_chars_no_fold([C|T]) -->
 2192    [C],
 2193    { rd_field_char(C, _) },
 2194    !,
 2195    rd_field_chars_no_fold(T).
 2196rd_field_chars_no_fold([]) -->
 2197    [].
 2198
 2199rd_field_chars([C0|T]) -->
 2200    [C],
 2201    { rd_field_char(C, C0) },
 2202    !,
 2203    rd_field_chars(T).
 2204rd_field_chars([]) -->
 2205    [].
 separators(-CharCodes) is det
CharCodes is a list of separators according to RFC2616
 2211separators("()<>@,;:\\\"/[]?={} \t").
 2212
 2213term_expansion(rd_field_char('expand me',_), Clauses) :-
 2214
 2215    Clauses = [ rd_field_char(0'-, 0'_)
 2216              | Cls
 2217              ],
 2218    separators(SepString),
 2219    string_codes(SepString, Seps),
 2220    findall(rd_field_char(In, Out),
 2221            (   between(32, 127, In),
 2222                \+ memberchk(In, Seps),
 2223                In \== 0'-,         % 0'
 2224                code_type(Out, to_lower(In))),
 2225            Cls).
 2226
 2227rd_field_char('expand me', _).                  % avoid recursion
 2228
 2229wr_field_chars([C|T]) -->
 2230    !,
 2231    { code_type(C, to_lower(U)) },
 2232    [U],
 2233    wr_field_chars2(T).
 2234wr_field_chars([]) -->
 2235    [].
 2236
 2237wr_field_chars2([]) --> [].
 2238wr_field_chars2([C|T]) -->              % 0'
 2239    (   { C == 0'_ }
 2240    ->  "-",
 2241        wr_field_chars(T)
 2242    ;   [C],
 2243        wr_field_chars2(T)
 2244    ).
 now//
Current time using rfc_date//1.
 2250now -->
 2251    { get_time(Time)
 2252    },
 2253    rfc_date(Time).
 rfc_date(+Time)// is det
Write time according to RFC1123 specification as required by the RFC2616 HTTP protocol specs.
 2260rfc_date(Time, String, Tail) :-
 2261    stamp_date_time(Time, Date, 'UTC'),
 2262    format_time(codes(String, Tail),
 2263                '%a, %d %b %Y %T GMT',
 2264                Date, posix).
 http_timestamp(+Time:timestamp, -Text:atom) is det
Generate a description of a Time in HTTP format (RFC1123)
 2270http_timestamp(Time, Atom) :-
 2271    stamp_date_time(Time, Date, 'UTC'),
 2272    format_time(atom(Atom),
 2273                '%a, %d %b %Y %T GMT',
 2274                Date, posix).
 2275
 2276
 2277                 /*******************************
 2278                 *         REQUEST DCG          *
 2279                 *******************************/
 2280
 2281request(Fd, [method(Method),request_uri(ReqURI)|Header]) -->
 2282    method(Method),
 2283    blanks,
 2284    nonblanks(Query),
 2285    { atom_codes(ReqURI, Query),
 2286      request_uri_parts(ReqURI, Header, Rest)
 2287    },
 2288    request_header(Fd, Rest),
 2289    !.
 2290request(Fd, [unknown(What)|Header]) -->
 2291    string(What),
 2292    eos,
 2293    !,
 2294    {   http_read_header(Fd, Header)
 2295    ->  true
 2296    ;   Header = []
 2297    }.
 2298
 2299method(get)     --> "GET", !.
 2300method(put)     --> "PUT", !.
 2301method(head)    --> "HEAD", !.
 2302method(post)    --> "POST", !.
 2303method(delete)  --> "DELETE", !.
 2304method(patch)   --> "PATCH", !.
 2305method(options) --> "OPTIONS", !.
 2306method(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.
 2320request_uri_parts(ReqURI, [path(Path)|Parts], Rest) :-
 2321    uri_components(ReqURI, Components),
 2322    uri_data(path, Components, PathText),
 2323    uri_encoded(path, Path, PathText),
 2324    phrase(uri_parts(Components), Parts, Rest).
 2325
 2326uri_parts(Components) -->
 2327    uri_search(Components),
 2328    uri_fragment(Components).
 2329
 2330uri_search(Components) -->
 2331    { uri_data(search, Components, Search),
 2332      nonvar(Search),
 2333      catch(uri_query_components(Search, Query),
 2334            error(syntax_error(_),_),
 2335            fail)
 2336    },
 2337    !,
 2338    [ search(Query) ].
 2339uri_search(_) --> [].
 2340
 2341uri_fragment(Components) -->
 2342    { uri_data(fragment, Components, String),
 2343      nonvar(String),
 2344      !,
 2345      uri_encoded(fragment, Fragment, String)
 2346    },
 2347    [ fragment(Fragment) ].
 2348uri_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.
 2355request_header(_, []) -->               % Old-style non-version header
 2356    blanks,
 2357    eos,
 2358    !.
 2359request_header(Fd, [http_version(Version)|Header]) -->
 2360    http_version(Version),
 2361    blanks,
 2362    eos,
 2363    !,
 2364    {   Version = 1-_
 2365    ->  http_read_header(Fd, Header)
 2366    ;   Header = []
 2367    }.
 2368
 2369http_version(Version) -->
 2370    blanks,
 2371    "HTTP/",
 2372    http_version_number(Version).
 2373
 2374http_version_number(Major-Minor) -->
 2375    integer(Major),
 2376    ".",
 2377    integer(Minor).
 2378
 2379
 2380                 /*******************************
 2381                 *            COOKIES           *
 2382                 *******************************/
 cookies(-List)// is semidet
Translate a cookie description into a list Name=Value.
 2388cookies([Name=Value|T]) -->
 2389    blanks,
 2390    cookie(Name, Value),
 2391    !,
 2392    blanks,
 2393    (   ";"
 2394    ->  cookies(T)
 2395    ;   { T = [] }
 2396    ).
 2397cookies(List) -->
 2398    string(Skipped),
 2399    ";",
 2400    !,
 2401    { print_message(warning, http(skipped_cookie(Skipped))) },
 2402    cookies(List).
 2403cookies([]) -->
 2404    blanks.
 2405
 2406cookie(Name, Value) -->
 2407    cookie_name(Name),
 2408    blanks, "=", blanks,
 2409    cookie_value(Value).
 2410
 2411cookie_name(Name) -->
 2412    { var(Name) },
 2413    !,
 2414    rd_field_chars_no_fold(Chars),
 2415    { atom_codes(Name, Chars) }.
 2416
 2417cookie_value(Value) -->
 2418    quoted_string(Value),
 2419    !.
 2420cookie_value(Value) -->
 2421    chars_to_semicolon_or_blank(Chars),
 2422    { atom_codes(Value, Chars)
 2423    }.
 2424
 2425chars_to_semicolon_or_blank([H|T]) -->
 2426    [H],
 2427    { H \== 32, H \== 0'; },
 2428    !,
 2429    chars_to_semicolon_or_blank(T).
 2430chars_to_semicolon_or_blank([]) -->
 2431    [].
 2432
 2433set_cookie(set_cookie(Name, Value, Options)) -->
 2434    ws,
 2435    cookie(Name, Value),
 2436    cookie_options(Options).
 2437
 2438cookie_options([H|T]) -->
 2439    ws,
 2440    ";",
 2441    ws,
 2442    cookie_option(H),
 2443    !,
 2444    cookie_options(T).
 2445cookie_options([]) -->
 2446    ws.
 2447
 2448ws --> " ", !, ws.
 2449ws --> [].
 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 and HttpOnly.
Arguments:
Option- Term of the form Name=Value
bug
- Incorrectly accepts options without = for M$ compatibility.
 2461cookie_option(Name=Value) -->
 2462    rd_field_chars(NameChars), ws,
 2463    { atom_codes(Name, NameChars) },
 2464    (   "="
 2465    ->  ws,
 2466        chars_to_semicolon(ValueChars),
 2467        { atom_codes(Value, ValueChars)
 2468        }
 2469    ;   { Value = true }
 2470    ).
 2471
 2472chars_to_semicolon([H|T]) -->
 2473    [H],
 2474    { H \== 32, H \== 0'; },
 2475    !,
 2476    chars_to_semicolon(T).
 2477chars_to_semicolon([]), ";" -->
 2478    ws, ";",
 2479    !.
 2480chars_to_semicolon([H|T]) -->
 2481    [H],
 2482    chars_to_semicolon(T).
 2483chars_to_semicolon([]) -->
 2484    [].
 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.
 2494range(bytes(From, To)) -->
 2495    "bytes", whites, "=", whites, integer(From), "-",
 2496    (   integer(To)
 2497    ->  ""
 2498    ;   { To = end }
 2499    ).
 2500
 2501
 2502                 /*******************************
 2503                 *           REPLY DCG          *
 2504                 *******************************/
 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.
 2521reply(Fd, [http_version(HttpVersion), status(Code, Status, Comment)|Header]) -->
 2522    http_version(HttpVersion),
 2523    blanks,
 2524    (   status_number(Status, Code)
 2525    ->  []
 2526    ;   integer(Status)
 2527    ),
 2528    blanks,
 2529    string(CommentCodes),
 2530    blanks_to_nl,
 2531    !,
 2532    blanks,
 2533    { atom_codes(Comment, CommentCodes),
 2534      http_read_header(Fd, Header)
 2535    }.
 2536
 2537
 2538                 /*******************************
 2539                 *            READ HEADER       *
 2540                 *******************************/
 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)
 2548http_read_header(Fd, Header) :-
 2549    read_header_data(Fd, Text),
 2550    http_parse_header(Text, Header).
 2551
 2552read_header_data(Fd, Header) :-
 2553    read_line_to_codes(Fd, Header, Tail),
 2554    read_header_data(Header, Fd, Tail),
 2555    debug(http(header), 'Header = ~n~s~n', [Header]).
 2556
 2557read_header_data([0'\r,0'\n], _, _) :- !.
 2558read_header_data([0'\n], _, _) :- !.
 2559read_header_data([], _, _) :- !.
 2560read_header_data(_, Fd, Tail) :-
 2561    read_line_to_codes(Fd, Tail, NewTail),
 2562    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)
 2571http_parse_header(Text, Header) :-
 2572    phrase(header(Header), Text),
 2573    debug(http(header), 'Field: ~p', [Header]).
 2574
 2575header(List) -->
 2576    header_field(Name, Value),
 2577    !,
 2578    { mkfield(Name, Value, List, Tail)
 2579    },
 2580    blanks,
 2581    header(Tail).
 2582header([]) -->
 2583    blanks,
 2584    eos,
 2585    !.
 2586header(_) -->
 2587    string(S), blanks_to_nl,
 2588    !,
 2589    { string_codes(Line, S),
 2590      syntax_error(http_parameter(Line))
 2591    }.
 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:http_address//0.

 2605:- multifile
 2606    http:http_address//0. 2607
 2608address -->
 2609    http:http_address,
 2610    !.
 2611address -->
 2612    { gethostname(Host) },
 2613    html(address([ a(href('http://www.swi-prolog.org'), 'SWI-Prolog'),
 2614                   ' httpd at ', Host
 2615                 ])).
 2616
 2617mkfield(host, Host:Port, [host(Host),port(Port)|Tail], Tail) :- !.
 2618mkfield(Name, Value, [Att|Tail], Tail) :-
 2619    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.
 2658                 /*******************************
 2659                 *            MESSAGES          *
 2660                 *******************************/
 2661
 2662:- multifile
 2663    prolog:message//1,
 2664    prolog:error_message//1. 2665
 2666prolog:error_message(http_write_short(Data, Sent)) -->
 2667    data(Data),
 2668    [ ': remote hangup after ~D bytes'-[Sent] ].
 2669prolog:error_message(syntax_error(http_request(Request))) -->
 2670    [ 'Illegal HTTP request: ~s'-[Request] ].
 2671prolog:error_message(syntax_error(http_parameter(Line))) -->
 2672    [ 'Illegal HTTP parameter: ~s'-[Line] ].
 2673
 2674prolog:message(http(skipped_cookie(S))) -->
 2675    [ 'Skipped illegal cookie: ~s'-[S] ].
 2676
 2677data(bytes(MimeType, _Bytes)) -->
 2678    !,
 2679    [ 'bytes(~p, ...)'-[MimeType] ].
 2680data(Data) -->
 2681    [ '~p'-[Data] ]