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-2020, 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:- autoload(html_write,
   64	    [ print_html/2, print_html/1, page/4, html/3,
   65	      html_print_length/2
   66	    ]).   67:- autoload(http_exception,[map_exception_to_http_status/4]).   68:- autoload(mimepack,[mime_pack/3]).   69:- autoload(mimetype,[file_mime_type/2]).   70:- autoload(library(apply),[maplist/2]).   71:- autoload(library(base64),[base64/2]).   72:- autoload(library(debug),[debug/3,debugging/1]).   73:- autoload(library(error),[syntax_error/1,domain_error/2]).   74:- autoload(library(lists),[append/3,member/2,select/3,delete/3]).   75:- autoload(library(memfile),
   76	    [ new_memory_file/1, open_memory_file/3,
   77	      free_memory_file/1, open_memory_file/4,
   78	      size_memory_file/3
   79	    ]).   80:- autoload(library(option),[option/3,option/2]).   81:- autoload(library(pairs),[pairs_values/2]).   82:- autoload(library(readutil),
   83	    [read_line_to_codes/2,read_line_to_codes/3]).   84:- autoload(library(sgml_write),[xml_write/3]).   85:- autoload(library(socket),[gethostname/1]).   86:- autoload(library(uri),
   87	    [ uri_components/2, uri_data/3, uri_encoded/3, uri_query_components/2
   88	    ]).   89:- autoload(library(url),[parse_url_search/2]).   90:- autoload(library(dcg/basics),
   91	    [ integer/3, atom/3, whites/2, blanks_to_nl/2, string/3,
   92	      number/3, blanks/2, float/3, nonblanks/3, eos/2
   93	    ]).   94:- use_module(library(settings),[setting/4,setting/2]).   95
   96:- multifile
   97    http:status_page/3,             % +Status, +Context, -HTML
   98    http:status_reply/3,            % +Status, -Reply, +Options
   99    http:serialize_reply/2,         % +Reply, -Body
  100    http:post_data_hook/3,          % +Data, +Out, +HdrExtra
  101    http:mime_type_encoding/2.      % +MimeType, -Encoding
  102
  103% see http_update_transfer/4.
  104
  105:- setting(http:chunked_transfer, oneof([never,on_request,if_possible]),
  106           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. */

  116:- discontiguous
  117    term_expansion/2.  118
  119
  120                 /*******************************
  121                 *          READ REQUEST        *
  122                 *******************************/
 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.
  130http_read_request(In, Request) :-
  131    catch(read_line_to_codes(In, Codes), E, true),
  132    (   var(E)
  133    ->  (   Codes == end_of_file
  134        ->  debug(http(header), 'end-of-file', []),
  135            Request = end_of_file
  136        ;   debug(http(header), 'First line: ~s', [Codes]),
  137            Request =  [input(In)|Request1],
  138            phrase(request(In, Request1), Codes),
  139            (   Request1 = [unknown(Text)|_]
  140            ->  string_codes(S, Text),
  141                syntax_error(http_request(S))
  142            ;   true
  143            )
  144        )
  145    ;   (   debugging(http(request))
  146        ->  message_to_string(E, Msg),
  147            debug(http(request), "Exception reading 1st line: ~s", [Msg])
  148        ;   true
  149        ),
  150        Request = end_of_file
  151    ).
 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.
  159http_read_reply_header(In, [input(In)|Reply]) :-
  160    read_line_to_codes(In, Codes),
  161    (   Codes == end_of_file
  162    ->  debug(http(header), 'end-of-file', []),
  163        throw(error(syntax(http_reply_header, end_of_file), _))
  164    ;   debug(http(header), 'First line: ~s~n', [Codes]),
  165        (   phrase(reply(In, Reply), Codes)
  166        ->  true
  167        ;   atom_codes(Header, Codes),
  168            syntax_error(http_reply_header(Header))
  169        )
  170    ).
  171
  172
  173                 /*******************************
  174                 *        FORMULATE REPLY       *
  175                 *******************************/
 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
  224http_reply(What, Out) :-
  225    http_reply(What, Out, [connection(close)], _).
  226
  227http_reply(Data, Out, HdrExtra) :-
  228    http_reply(Data, Out, HdrExtra, _Code).
  229
  230http_reply(Data, Out, HdrExtra, Code) :-
  231    http_reply(Data, Out, HdrExtra, [], Code).
  232
  233http_reply(Data, Out, HdrExtra, Context, Code) :-
  234    http_reply(Data, Out, HdrExtra, Context, [method(get)], Code).
  235
  236http_reply(Data, Out, HdrExtra, _Context, Request, Code) :-
  237    byte_count(Out, C0),
  238    memberchk(method(Method), Request),
  239    catch(http_reply_data(Data, Out, HdrExtra, Method, Code), E, true),
  240    !,
  241    (   var(E)
  242    ->  true
  243    ;   (   E = error(io_error(write,_), _)
  244        ;   E = error(socket_error(_,_), _)
  245        )
  246    ->  byte_count(Out, C1),
  247        Sent is C1 - C0,
  248        throw(error(http_write_short(Data, Sent), _))
  249    ;   E = error(timeout_error(write, _), _)
  250    ->  throw(E)
  251    ;   map_exception_to_http_status(E, Status, NewHdr, NewContext),
  252        http_status_reply(Status, Out, NewHdr, NewContext, Request, Code)
  253    ).
  254http_reply(Status, Out, HdrExtra, Context, Request, Code) :-
  255    http_status_reply(Status, Out, HdrExtra, Context, Request, Code).
  256
  257:- meta_predicate
  258    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.
  267http_reply_data(Data, Out, HdrExtra, Method, Code) :-
  268    http_reply_data_(Data, Out, HdrExtra, Method, Code),
  269    flush_output(Out).
  270
  271http_reply_data_(html(HTML), Out, HdrExtra, Method, Code) :-
  272    !,
  273    phrase(reply_header(html(HTML), HdrExtra, Code), Header),
  274    send_reply_header(Out, Header),
  275    if_no_head(print_html(Out, HTML), Method).
  276http_reply_data_(file(Type, File), Out, HdrExtra, Method, Code) :-
  277    !,
  278    phrase(reply_header(file(Type, File), HdrExtra, Code), Header),
  279    reply_file(Out, File, Header, Method).
  280http_reply_data_(gzip_file(Type, File), Out, HdrExtra, Method, Code) :-
  281    !,
  282    phrase(reply_header(gzip_file(Type, File), HdrExtra, Code), Header),
  283    reply_file(Out, File, Header, Method).
  284http_reply_data_(file(Type, File, Range), Out, HdrExtra, Method, Code) :-
  285    !,
  286    phrase(reply_header(file(Type, File, Range), HdrExtra, Code), Header),
  287    reply_file_range(Out, File, Header, Range, Method).
  288http_reply_data_(tmp_file(Type, File), Out, HdrExtra, Method, Code) :-
  289    !,
  290    phrase(reply_header(tmp_file(Type, File), HdrExtra, Code), Header),
  291    reply_file(Out, File, Header, Method).
  292http_reply_data_(bytes(Type, Bytes), Out, HdrExtra, Method, Code) :-
  293    !,
  294    phrase(reply_header(bytes(Type, Bytes), HdrExtra, Code), Header),
  295    send_reply_header(Out, Header),
  296    if_no_head(format(Out, '~s', [Bytes]), Method).
  297http_reply_data_(stream(In, Len), Out, HdrExtra, Method, Code) :-
  298    !,
  299    phrase(reply_header(cgi_data(Len), HdrExtra, Code), Header),
  300    copy_stream(Out, In, Header, Method, 0, end).
  301http_reply_data_(cgi_stream(In, Len), Out, HdrExtra, Method, Code) :-
  302    !,
  303    http_read_header(In, CgiHeader),
  304    seek(In, 0, current, Pos),
  305    Size is Len - Pos,
  306    http_join_headers(HdrExtra, CgiHeader, Hdr2),
  307    phrase(reply_header(cgi_data(Size), Hdr2, Code), Header),
  308    copy_stream(Out, In, Header, Method, 0, end).
  309
  310if_no_head(_, head) :-
  311    !.
  312if_no_head(Goal, _) :-
  313    call(Goal).
  314
  315reply_file(Out, _File, Header, head) :-
  316    !,
  317    send_reply_header(Out, Header).
  318reply_file(Out, File, Header, _) :-
  319    setup_call_cleanup(
  320        open(File, read, In, [type(binary)]),
  321        copy_stream(Out, In, Header, 0, end),
  322        close(In)).
  323
  324reply_file_range(Out, _File, Header, _Range, head) :-
  325    !,
  326    send_reply_header(Out, Header).
  327reply_file_range(Out, File, Header, bytes(From, To), _) :-
  328    setup_call_cleanup(
  329        open(File, read, In, [type(binary)]),
  330        copy_stream(Out, In, Header, From, To),
  331        close(In)).
  332
  333copy_stream(Out, _, Header, head, _, _) :-
  334    !,
  335    send_reply_header(Out, Header).
  336copy_stream(Out, In, Header, _, From, To) :-
  337    copy_stream(Out, In, Header, From, To).
  338
  339copy_stream(Out, In, Header, From, To) :-
  340    (   From == 0
  341    ->  true
  342    ;   seek(In, From, bof, _)
  343    ),
  344    peek_byte(In, _),
  345    send_reply_header(Out, Header),
  346    (   To == end
  347    ->  copy_stream_data(In, Out)
  348    ;   Len is To - From,
  349        copy_stream_data(In, Out, Len)
  350    ).
 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)
  384http_status_reply(Status, Out, Options) :-
  385    _{header:HdrExtra, context:Context, code:Code, method:Method} :< Options,
  386    http_status_reply(Status, Out, HdrExtra, Context, [method(Method)], Code).
  387
  388http_status_reply(Status, Out, HdrExtra, Code) :-
  389    http_status_reply(Status, Out, HdrExtra, [], Code).
  390
  391http_status_reply(Status, Out, HdrExtra, Context, Code) :-
  392    http_status_reply(Status, Out, HdrExtra, Context, [method(get)], Code).
  393
  394http_status_reply(Status, Out, HdrExtra, Context, Request, Code) :-
  395    option(method(Method), Request, get),
  396    parsed_accept(Request, Accept),
  397    status_reply_flush(Status, Out,
  398                       _{ context: Context,
  399                          method:  Method,
  400                          code:    Code,
  401                          accept:  Accept,
  402                          header:  HdrExtra
  403                        }).
  404
  405parsed_accept(Request, Accept) :-
  406    memberchk(accept(Accept0), Request),
  407    http_parse_header_value(accept, Accept0, Accept1),
  408    !,
  409    Accept = Accept1.
  410parsed_accept(_, [ media(text/html, [], 0.1,  []),
  411                   media(_,         [], 0.01, [])
  412                 ]).
  413
  414status_reply_flush(Status, Out, Options) :-
  415    status_reply(Status, Out, Options),
  416    !,
  417    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:
  430% Replies without content
  431status_reply(no_content, Out, Options) :-
  432    !,
  433    phrase(reply_header(status(no_content), Options), Header),
  434    send_reply_header(Out, Header).
  435status_reply(switching_protocols(_Goal,SwitchOptions), Out, Options) :-
  436    !,
  437    (   option(headers(Extra1), SwitchOptions)
  438    ->  true
  439    ;   option(header(Extra1), SwitchOptions, [])
  440    ),
  441    http_join_headers(Options.header, Extra1, HdrExtra),
  442    phrase(reply_header(status(switching_protocols),
  443                        Options.put(header,HdrExtra)), Header),
  444    send_reply_header(Out, Header).
  445status_reply(authorise(basic, ''), Out, Options) :-
  446    !,
  447    status_reply(authorise(basic), Out, Options).
  448status_reply(authorise(basic, Realm), Out, Options) :-
  449    !,
  450    status_reply(authorise(basic(Realm)), Out, Options).
  451status_reply(not_modified, Out, Options) :-
  452    !,
  453    phrase(reply_header(status(not_modified), Options), Header),
  454    send_reply_header(Out, Header).
  455% aliases (compatibility)
  456status_reply(busy, Out, Options) :-
  457    status_reply(service_unavailable(busy), Out, Options).
  458status_reply(unavailable(Why), Out, Options) :-
  459    status_reply(service_unavailable(Why), Out, Options).
  460status_reply(resource_error(Why), Out, Options) :-
  461    status_reply(service_unavailable(Why), Out, Options).
  462% replies with content
  463status_reply(Status, Out, Options) :-
  464    status_has_content(Status),
  465    status_page_hook(Status, Reply, Options),
  466    serialize_body(Reply, Body),
  467    Status =.. List,
  468    append(List, [Body], ExList),
  469    ExStatus =.. ExList,
  470    phrase(reply_header(ExStatus, Options), Header),
  471    send_reply_header(Out, Header),
  472    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.
  479status_has_content(created(_Location)).
  480status_has_content(moved(_To)).
  481status_has_content(moved_temporary(_To)).
  482status_has_content(gone(_URL)).
  483status_has_content(see_other(_To)).
  484status_has_content(bad_request(_ErrorTerm)).
  485status_has_content(authorise(_Method)).
  486status_has_content(forbidden(_URL)).
  487status_has_content(not_found(_URL)).
  488status_has_content(method_not_allowed(_Method, _URL)).
  489status_has_content(not_acceptable(_Why)).
  490status_has_content(server_error(_ErrorTerm)).
  491status_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.
  502serialize_body(Reply, Body) :-
  503    http:serialize_reply(Reply, Body),
  504    !.
  505serialize_body(html_tokens(Tokens), body(text/html, utf8, Content)) :-
  506    !,
  507    with_output_to(string(Content), print_html(Tokens)).
  508serialize_body(Reply, Reply) :-
  509    Reply = body(_,_,_),
  510    !.
  511serialize_body(Reply, _) :-
  512    domain_error(http_reply_body, Reply).
  513
  514reply_status_body(_, _, Options) :-
  515    Options.method == head,
  516    !.
  517reply_status_body(Out, body(_Type, Encoding, Content), _Options) :-
  518    (   Encoding == octet
  519    ->  format(Out, '~s', [Content])
  520    ;   setup_call_cleanup(
  521            set_stream(Out, encoding(Encoding)),
  522            format(Out, '~s', [Content]),
  523            set_stream(Out, encoding(octet)))
  524    ).
 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 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
- status_page/3
  551status_page_hook(Term, Reply, Options) :-
  552    Context = Options.context,
  553    functor(Term, Name, _),
  554    status_number_fact(Name, Code),
  555    (   Options.code = Code,
  556        http:status_reply(Term, Reply, Options)
  557    ;   http:status_page(Term, Context, HTML),
  558        Reply = html_tokens(HTML)
  559    ;   http:status_page(Code, Context, HTML), % deprecated
  560        Reply = html_tokens(HTML)
  561    ),
  562    !.
  563status_page_hook(created(Location), html_tokens(HTML), _Options) :-
  564    phrase(page([ title('201 Created')
  565                ],
  566                [ h1('Created'),
  567                  p(['The document was created ',
  568                     a(href(Location), ' Here')
  569                    ]),
  570                  \address
  571                ]),
  572           HTML).
  573status_page_hook(moved(To), html_tokens(HTML), _Options) :-
  574    phrase(page([ title('301 Moved Permanently')
  575                ],
  576                [ h1('Moved Permanently'),
  577                  p(['The document has moved ',
  578                     a(href(To), ' Here')
  579                    ]),
  580                  \address
  581                ]),
  582           HTML).
  583status_page_hook(moved_temporary(To), html_tokens(HTML), _Options) :-
  584    phrase(page([ title('302 Moved Temporary')
  585                ],
  586                [ h1('Moved Temporary'),
  587                  p(['The document is currently ',
  588                     a(href(To), ' Here')
  589                    ]),
  590                  \address
  591                ]),
  592           HTML).
  593status_page_hook(gone(URL), html_tokens(HTML), _Options) :-
  594    phrase(page([ title('410 Resource Gone')
  595                ],
  596                [ h1('Resource Gone'),
  597                  p(['The document has been removed ',
  598                     a(href(URL), ' from here')
  599                    ]),
  600                  \address
  601                ]),
  602           HTML).
  603status_page_hook(see_other(To), html_tokens(HTML), _Options) :-
  604    phrase(page([ title('303 See Other')
  605                 ],
  606                 [ h1('See Other'),
  607                   p(['See other document ',
  608                      a(href(To), ' Here')
  609                     ]),
  610                   \address
  611                 ]),
  612            HTML).
  613status_page_hook(bad_request(ErrorTerm), html_tokens(HTML), _Options) :-
  614    '$messages':translate_message(ErrorTerm, Lines, []),
  615    phrase(page([ title('400 Bad Request')
  616                ],
  617                [ h1('Bad Request'),
  618                  p(\html_message_lines(Lines)),
  619                  \address
  620                ]),
  621           HTML).
  622status_page_hook(authorise(_Method), html_tokens(HTML), _Options):-
  623    phrase(page([ title('401 Authorization Required')
  624                ],
  625                [ h1('Authorization Required'),
  626                  p(['This server could not verify that you ',
  627                     'are authorized to access the document ',
  628                     'requested.  Either you supplied the wrong ',
  629                     'credentials (e.g., bad password), or your ',
  630                     'browser doesn\'t understand how to supply ',
  631                     'the credentials required.'
  632                    ]),
  633                  \address
  634                ]),
  635           HTML).
  636status_page_hook(forbidden(URL), html_tokens(HTML), _Options) :-
  637    phrase(page([ title('403 Forbidden')
  638                ],
  639                [ h1('Forbidden'),
  640                  p(['You don\'t have permission to access ', URL,
  641                     ' on this server'
  642                    ]),
  643                  \address
  644                ]),
  645           HTML).
  646status_page_hook(not_found(URL), html_tokens(HTML), _Options) :-
  647    phrase(page([ title('404 Not Found')
  648                ],
  649                [ h1('Not Found'),
  650                  p(['The requested URL ', tt(URL),
  651                     ' was not found on this server'
  652                    ]),
  653                  \address
  654                ]),
  655           HTML).
  656status_page_hook(method_not_allowed(Method,URL), html_tokens(HTML), _Options) :-
  657    upcase_atom(Method, UMethod),
  658    phrase(page([ title('405 Method not allowed')
  659                ],
  660                [ h1('Method not allowed'),
  661                  p(['The requested URL ', tt(URL),
  662                     ' does not support method ', tt(UMethod), '.'
  663                    ]),
  664                  \address
  665                ]),
  666           HTML).
  667status_page_hook(not_acceptable(WhyHTML), html_tokens(HTML), _Options) :-
  668    phrase(page([ title('406 Not Acceptable')
  669                ],
  670                [ h1('Not Acceptable'),
  671                  WhyHTML,
  672                  \address
  673                ]),
  674           HTML).
  675status_page_hook(server_error(ErrorTerm), html_tokens(HTML), _Options) :-
  676    '$messages':translate_message(ErrorTerm, Lines, []),
  677    phrase(page([ title('500 Internal server error')
  678                ],
  679                [ h1('Internal server error'),
  680                  p(\html_message_lines(Lines)),
  681                  \address
  682                ]),
  683           HTML).
  684status_page_hook(service_unavailable(Why), html_tokens(HTML), _Options) :-
  685    phrase(page([ title('503 Service Unavailable')
  686                ],
  687                [ h1('Service Unavailable'),
  688                  \unavailable(Why),
  689                  \address
  690                ]),
  691           HTML).
  692
  693unavailable(busy) -->
  694    html(p(['The server is temporarily out of resources, ',
  695            'please try again later'])).
  696unavailable(error(Formal,Context)) -->
  697    { '$messages':translate_message(error(Formal,Context), Lines, []) },
  698    html_message_lines(Lines).
  699unavailable(HTML) -->
  700    html(HTML).
  701
  702html_message_lines([]) -->
  703    [].
  704html_message_lines([nl|T]) -->
  705    !,
  706    html([br([])]),
  707    html_message_lines(T).
  708html_message_lines([flush]) -->
  709    [].
  710html_message_lines([ansi(_Style,Fmt,Args)|T]) -->
  711    !,
  712    { format(string(S), Fmt, Args)
  713    },
  714    html([S]),
  715    html_message_lines(T).
  716html_message_lines([url(Pos)|T]) -->
  717    !,
  718    msg_url(Pos),
  719    html_message_lines(T).
  720html_message_lines([url(URL, Label)|T]) -->
  721    !,
  722    html(a(href(URL), Label)),
  723    html_message_lines(T).
  724html_message_lines([Fmt-Args|T]) -->
  725    !,
  726    { format(string(S), Fmt, Args)
  727    },
  728    html([S]),
  729    html_message_lines(T).
  730html_message_lines([Fmt|T]) -->
  731    !,
  732    { format(string(S), Fmt, [])
  733    },
  734    html([S]),
  735    html_message_lines(T).
  736
  737msg_url(File:Line:Pos) -->
  738    !,
  739    html([File, :, Line, :, Pos]).
  740msg_url(File:Line) -->
  741    !,
  742    html([File, :, Line]).
  743msg_url(File) -->
  744    html([File]).
 http_join_headers(+Default, +Header, -Out)
Append headers from Default to Header if they are not already part of it.
  751http_join_headers([], H, H).
  752http_join_headers([H|T], Hdr0, Hdr) :-
  753    functor(H, N, A),
  754    functor(H2, N, A),
  755    member(H2, Hdr0),
  756    !,
  757    http_join_headers(T, Hdr0, Hdr).
  758http_join_headers([H|T], Hdr0, [H|Hdr]) :-
  759    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.
  771http_update_encoding(Header0, utf8, [content_type(Type)|Header]) :-
  772    select(content_type(Type0), Header0, Header),
  773    sub_atom(Type0, 0, _, _, 'text/'),
  774    !,
  775    (   sub_atom(Type0, S, _, _, ';')
  776    ->  sub_atom(Type0, 0, S, _, B)
  777    ;   B = Type0
  778    ),
  779    atom_concat(B, '; charset=UTF-8', Type).
  780http_update_encoding(Header, Encoding, Header) :-
  781    memberchk(content_type(Type), Header),
  782    (   sub_atom_icasechk(Type, _, 'utf-8')
  783    ->  Encoding = utf8
  784    ;   http:mime_type_encoding(Type, Encoding)
  785    ->  true
  786    ;   mime_type_encoding(Type, Encoding)
  787    ).
  788http_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.
  795mime_type_encoding('application/json',         utf8).
  796mime_type_encoding('application/jsonrequest',  utf8).
  797mime_type_encoding('application/x-prolog',     utf8).
  798mime_type_encoding('application/n-quads',      utf8).
  799mime_type_encoding('application/n-triples',    utf8).
  800mime_type_encoding('application/sparql-query', utf8).
  801mime_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.
  817http_update_connection(CgiHeader, Request, Connect,
  818                       [connection(Connect)|Rest]) :-
  819    select(connection(CgiConn), CgiHeader, Rest),
  820    !,
  821    connection(Request, ReqConnection),
  822    join_connection(ReqConnection, CgiConn, Connect).
  823http_update_connection(CgiHeader, Request, Connect,
  824                       [connection(Connect)|CgiHeader]) :-
  825    connection(Request, Connect).
  826
  827join_connection(Keep1, Keep2, Connection) :-
  828    (   downcase_atom(Keep1, 'keep-alive'),
  829        downcase_atom(Keep2, 'keep-alive')
  830    ->  Connection = 'Keep-Alive'
  831    ;   Connection = close
  832    ).
 connection(+Header, -Connection)
Extract the desired connection from a header.
  839connection(Header, Close) :-
  840    (   memberchk(connection(Connection), Header)
  841    ->  Close = Connection
  842    ;   memberchk(http_version(1-X), Header),
  843        X >= 1
  844    ->  Close = 'Keep-Alive'
  845    ;   Close = close
  846    ).
 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.

  865http_update_transfer(Request, CgiHeader, Transfer, Header) :-
  866    setting(http:chunked_transfer, When),
  867    http_update_transfer(When, Request, CgiHeader, Transfer, Header).
  868
  869http_update_transfer(never, _, CgiHeader, none, Header) :-
  870    !,
  871    delete(CgiHeader, transfer_encoding(_), Header).
  872http_update_transfer(_, _, CgiHeader, none, Header) :-
  873    memberchk(location(_), CgiHeader),
  874    !,
  875    delete(CgiHeader, transfer_encoding(_), Header).
  876http_update_transfer(_, Request, CgiHeader, Transfer, Header) :-
  877    select(transfer_encoding(CgiTransfer), CgiHeader, Rest),
  878    !,
  879    transfer(Request, ReqConnection),
  880    join_transfer(ReqConnection, CgiTransfer, Transfer),
  881    (   Transfer == none
  882    ->  Header = Rest
  883    ;   Header = [transfer_encoding(Transfer)|Rest]
  884    ).
  885http_update_transfer(if_possible, Request, CgiHeader, Transfer, Header) :-
  886    transfer(Request, Transfer),
  887    Transfer \== none,
  888    !,
  889    Header = [transfer_encoding(Transfer)|CgiHeader].
  890http_update_transfer(_, _, CgiHeader, none, CgiHeader).
  891
  892join_transfer(chunked, chunked, chunked) :- !.
  893join_transfer(_, _, none).
 transfer(+Header, -Connection)
Extract the desired connection from a header.
  900transfer(Header, Transfer) :-
  901    (   memberchk(transfer_encoding(Transfer0), Header)
  902    ->  Transfer = Transfer0
  903    ;   memberchk(http_version(1-X), Header),
  904        X >= 1
  905    ->  Transfer = chunked
  906    ;   Transfer = none
  907    ).
 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.
  916content_length_in_encoding(Enc, Stream, Bytes) :-
  917    stream_property(Stream, position(Here)),
  918    setup_call_cleanup(
  919        open_null_stream(Out),
  920        ( set_stream(Out, encoding(Enc)),
  921          catch(copy_stream_data(Stream, Out), _, fail),
  922          flush_output(Out),
  923          byte_count(Out, Bytes)
  924        ),
  925        ( close(Out, [force(true)]),
  926          set_stream_position(Stream, Here)
  927        )).
  928
  929
  930                 /*******************************
  931                 *          POST SUPPORT        *
  932                 *******************************/
 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:
 1025http_post_data(Data, Out, HdrExtra) :-
 1026    http:post_data_hook(Data, Out, HdrExtra),
 1027    !.
 1028http_post_data(html(HTML), Out, HdrExtra) :-
 1029    !,
 1030    phrase(post_header(html(HTML), HdrExtra), Header),
 1031    send_request_header(Out, Header),
 1032    print_html(Out, HTML).
 1033http_post_data(xml(XML), Out, HdrExtra) :-
 1034    !,
 1035    http_post_data(xml(text/xml, XML, []), Out, HdrExtra).
 1036http_post_data(xml(Type, XML), Out, HdrExtra) :-
 1037    !,
 1038    http_post_data(xml(Type, XML, []), Out, HdrExtra).
 1039http_post_data(xml(Type, XML, Options), Out, HdrExtra) :-
 1040    !,
 1041    setup_call_cleanup(
 1042        new_memory_file(MemFile),
 1043        (   setup_call_cleanup(
 1044                open_memory_file(MemFile, write, MemOut),
 1045                xml_write(MemOut, XML, Options),
 1046                close(MemOut)),
 1047            http_post_data(memory_file(Type, MemFile), Out, HdrExtra)
 1048        ),
 1049        free_memory_file(MemFile)).
 1050http_post_data(file(File), Out, HdrExtra) :-
 1051    !,
 1052    (   file_mime_type(File, Type)
 1053    ->  true
 1054    ;   Type = text/plain
 1055    ),
 1056    http_post_data(file(Type, File), Out, HdrExtra).
 1057http_post_data(file(Type, File), Out, HdrExtra) :-
 1058    !,
 1059    phrase(post_header(file(Type, File), HdrExtra), Header),
 1060    send_request_header(Out, Header),
 1061    setup_call_cleanup(
 1062        open(File, read, In, [type(binary)]),
 1063        copy_stream_data(In, Out),
 1064        close(In)).
 1065http_post_data(memory_file(Type, Handle), Out, HdrExtra) :-
 1066    !,
 1067    phrase(post_header(memory_file(Type, Handle), HdrExtra), Header),
 1068    send_request_header(Out, Header),
 1069    setup_call_cleanup(
 1070        open_memory_file(Handle, read, In, [encoding(octet)]),
 1071        copy_stream_data(In, Out),
 1072        close(In)).
 1073http_post_data(codes(Codes), Out, HdrExtra) :-
 1074    !,
 1075    http_post_data(codes(text/plain, Codes), Out, HdrExtra).
 1076http_post_data(codes(Type, Codes), Out, HdrExtra) :-
 1077    !,
 1078    phrase(post_header(codes(Type, Codes), HdrExtra), Header),
 1079    send_request_header(Out, Header),
 1080    setup_call_cleanup(
 1081        set_stream(Out, encoding(utf8)),
 1082        format(Out, '~s', [Codes]),
 1083        set_stream(Out, encoding(octet))).
 1084http_post_data(bytes(Type, Bytes), Out, HdrExtra) :-
 1085    !,
 1086    phrase(post_header(bytes(Type, Bytes), HdrExtra), Header),
 1087    send_request_header(Out, Header),
 1088    format(Out, '~s', [Bytes]).
 1089http_post_data(atom(Atom), Out, HdrExtra) :-
 1090    !,
 1091    http_post_data(atom(text/plain, Atom), Out, HdrExtra).
 1092http_post_data(atom(Type, Atom), Out, HdrExtra) :-
 1093    !,
 1094    phrase(post_header(atom(Type, Atom), HdrExtra), Header),
 1095    send_request_header(Out, Header),
 1096    setup_call_cleanup(
 1097        set_stream(Out, encoding(utf8)),
 1098        write(Out, Atom),
 1099        set_stream(Out, encoding(octet))).
 1100http_post_data(string(String), Out, HdrExtra) :-
 1101    !,
 1102    http_post_data(atom(text/plain, String), Out, HdrExtra).
 1103http_post_data(string(Type, String), Out, HdrExtra) :-
 1104    !,
 1105    phrase(post_header(string(Type, String), HdrExtra), Header),
 1106    send_request_header(Out, Header),
 1107    setup_call_cleanup(
 1108        set_stream(Out, encoding(utf8)),
 1109        write(Out, String),
 1110        set_stream(Out, encoding(octet))).
 1111http_post_data(cgi_stream(In, _Len), Out, HdrExtra) :-
 1112    !,
 1113    debug(obsolete, 'Obsolete 2nd argument in cgi_stream(In,Len)', []),
 1114    http_post_data(cgi_stream(In), Out, HdrExtra).
 1115http_post_data(cgi_stream(In), Out, HdrExtra) :-
 1116    !,
 1117    http_read_header(In, Header0),
 1118    http_update_encoding(Header0, Encoding, Header),
 1119    content_length_in_encoding(Encoding, In, Size),
 1120    http_join_headers(HdrExtra, Header, Hdr2),
 1121    phrase(post_header(cgi_data(Size), Hdr2), HeaderText),
 1122    send_request_header(Out, HeaderText),
 1123    setup_call_cleanup(
 1124        set_stream(Out, encoding(Encoding)),
 1125        copy_stream_data(In, Out),
 1126        set_stream(Out, encoding(octet))).
 1127http_post_data(form(Fields), Out, HdrExtra) :-
 1128    !,
 1129    parse_url_search(Codes, Fields),
 1130    length(Codes, Size),
 1131    http_join_headers(HdrExtra,
 1132                      [ content_type('application/x-www-form-urlencoded')
 1133                      ], Header),
 1134    phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1135    send_request_header(Out, HeaderChars),
 1136    format(Out, '~s', [Codes]).
 1137http_post_data(form_data(Data), Out, HdrExtra) :-
 1138    !,
 1139    setup_call_cleanup(
 1140        new_memory_file(MemFile),
 1141        ( setup_call_cleanup(
 1142              open_memory_file(MemFile, write, MimeOut),
 1143              mime_pack(Data, MimeOut, Boundary),
 1144              close(MimeOut)),
 1145          size_memory_file(MemFile, Size, octet),
 1146          format(string(ContentType),
 1147                 'multipart/form-data; boundary=~w', [Boundary]),
 1148          http_join_headers(HdrExtra,
 1149                            [ mime_version('1.0'),
 1150                              content_type(ContentType)
 1151                            ], Header),
 1152          phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1153          send_request_header(Out, HeaderChars),
 1154          setup_call_cleanup(
 1155              open_memory_file(MemFile, read, In, [encoding(octet)]),
 1156              copy_stream_data(In, Out),
 1157              close(In))
 1158        ),
 1159        free_memory_file(MemFile)).
 1160http_post_data(List, Out, HdrExtra) :-          % multipart-mixed
 1161    is_list(List),
 1162    !,
 1163    setup_call_cleanup(
 1164        new_memory_file(MemFile),
 1165        ( setup_call_cleanup(
 1166              open_memory_file(MemFile, write, MimeOut),
 1167              mime_pack(List, MimeOut, Boundary),
 1168              close(MimeOut)),
 1169          size_memory_file(MemFile, Size, octet),
 1170          format(string(ContentType),
 1171                 'multipart/mixed; boundary=~w', [Boundary]),
 1172          http_join_headers(HdrExtra,
 1173                            [ mime_version('1.0'),
 1174                              content_type(ContentType)
 1175                            ], Header),
 1176          phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1177          send_request_header(Out, HeaderChars),
 1178          setup_call_cleanup(
 1179              open_memory_file(MemFile, read, In, [encoding(octet)]),
 1180              copy_stream_data(In, Out),
 1181              close(In))
 1182        ),
 1183        free_memory_file(MemFile)).
 post_header(+Data, +HeaderExtra)//
Generate the POST header, emitting HeaderExtra, followed by the HTTP Content-length and Content-type fields.
 1190post_header(html(Tokens), HdrExtra) -->
 1191    header_fields(HdrExtra, Len),
 1192    content_length(html(Tokens), Len),
 1193    content_type(text/html),
 1194    "\r\n".
 1195post_header(file(Type, File), HdrExtra) -->
 1196    header_fields(HdrExtra, Len),
 1197    content_length(file(File), Len),
 1198    content_type(Type),
 1199    "\r\n".
 1200post_header(memory_file(Type, File), HdrExtra) -->
 1201    header_fields(HdrExtra, Len),
 1202    content_length(memory_file(File), Len),
 1203    content_type(Type),
 1204    "\r\n".
 1205post_header(cgi_data(Size), HdrExtra) -->
 1206    header_fields(HdrExtra, Len),
 1207    content_length(Size, Len),
 1208    "\r\n".
 1209post_header(codes(Type, Codes), HdrExtra) -->
 1210    header_fields(HdrExtra, Len),
 1211    content_length(codes(Codes, utf8), Len),
 1212    content_type(Type, utf8),
 1213    "\r\n".
 1214post_header(bytes(Type, Bytes), HdrExtra) -->
 1215    header_fields(HdrExtra, Len),
 1216    content_length(bytes(Bytes), Len),
 1217    content_type(Type),
 1218    "\r\n".
 1219post_header(atom(Type, Atom), HdrExtra) -->
 1220    header_fields(HdrExtra, Len),
 1221    content_length(atom(Atom, utf8), Len),
 1222    content_type(Type, utf8),
 1223    "\r\n".
 1224post_header(string(Type, String), HdrExtra) -->
 1225    header_fields(HdrExtra, Len),
 1226    content_length(string(String, utf8), Len),
 1227    content_type(Type, utf8),
 1228    "\r\n".
 1229
 1230
 1231                 /*******************************
 1232                 *       OUTPUT HEADER DCG      *
 1233                 *******************************/
 http_reply_header(+Out:stream, +What, +HdrExtra) is det
Create a reply header using reply_header//3 and send it to Stream.
 1240http_reply_header(Out, What, HdrExtra) :-
 1241    phrase(reply_header(What, HdrExtra, _Code), String),
 1242    !,
 1243    send_reply_header(Out, 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.
 1267reply_header(Data, Dict) -->
 1268    { _{header:HdrExtra, code:Code} :< Dict },
 1269    reply_header(Data, HdrExtra, Code).
 1270
 1271reply_header(string(String), HdrExtra, Code) -->
 1272    reply_header(string(text/plain, String), HdrExtra, Code).
 1273reply_header(string(Type, String), HdrExtra, Code) -->
 1274    vstatus(ok, Code, HdrExtra),
 1275    date(now),
 1276    header_fields(HdrExtra, CLen),
 1277    content_length(codes(String, utf8), CLen),
 1278    content_type(Type, utf8),
 1279    "\r\n".
 1280reply_header(bytes(Type, Bytes), HdrExtra, Code) -->
 1281    vstatus(ok, Code, HdrExtra),
 1282    date(now),
 1283    header_fields(HdrExtra, CLen),
 1284    content_length(bytes(Bytes), CLen),
 1285    content_type(Type),
 1286    "\r\n".
 1287reply_header(html(Tokens), HdrExtra, Code) -->
 1288    vstatus(ok, Code, HdrExtra),
 1289    date(now),
 1290    header_fields(HdrExtra, CLen),
 1291    content_length(html(Tokens), CLen),
 1292    content_type(text/html),
 1293    "\r\n".
 1294reply_header(file(Type, File), HdrExtra, Code) -->
 1295    vstatus(ok, Code, HdrExtra),
 1296    date(now),
 1297    modified(file(File)),
 1298    header_fields(HdrExtra, CLen),
 1299    content_length(file(File), CLen),
 1300    content_type(Type),
 1301    "\r\n".
 1302reply_header(gzip_file(Type, File), HdrExtra, Code) -->
 1303    vstatus(ok, Code, HdrExtra),
 1304    date(now),
 1305    modified(file(File)),
 1306    header_fields(HdrExtra, CLen),
 1307    content_length(file(File), CLen),
 1308    content_type(Type),
 1309    content_encoding(gzip),
 1310    "\r\n".
 1311reply_header(file(Type, File, Range), HdrExtra, Code) -->
 1312    vstatus(partial_content, Code, HdrExtra),
 1313    date(now),
 1314    modified(file(File)),
 1315    header_fields(HdrExtra, CLen),
 1316    content_length(file(File, Range), CLen),
 1317    content_type(Type),
 1318    "\r\n".
 1319reply_header(tmp_file(Type, File), HdrExtra, Code) -->
 1320    vstatus(ok, Code, HdrExtra),
 1321    date(now),
 1322    header_fields(HdrExtra, CLen),
 1323    content_length(file(File), CLen),
 1324    content_type(Type),
 1325    "\r\n".
 1326reply_header(cgi_data(Size), HdrExtra, Code) -->
 1327    vstatus(ok, Code, HdrExtra),
 1328    date(now),
 1329    header_fields(HdrExtra, CLen),
 1330    content_length(Size, CLen),
 1331    "\r\n".
 1332reply_header(chunked_data, HdrExtra, Code) -->
 1333    vstatus(ok, Code, HdrExtra),
 1334    date(now),
 1335    header_fields(HdrExtra, _),
 1336    (   {memberchk(transfer_encoding(_), HdrExtra)}
 1337    ->  ""
 1338    ;   transfer_encoding(chunked)
 1339    ),
 1340    "\r\n".
 1341% non-200 replies without a body (e.g., 1xx, 204, 304)
 1342reply_header(status(Status), HdrExtra, Code) -->
 1343    vstatus(Status, Code),
 1344    header_fields(HdrExtra, Clen),
 1345    { Clen = 0 },
 1346    "\r\n".
 1347% non-200 replies with a body
 1348reply_header(Data, HdrExtra, Code) -->
 1349    { status_reply_headers(Data,
 1350                           body(Type, Encoding, Content),
 1351                           ReplyHeaders),
 1352      http_join_headers(ReplyHeaders, HdrExtra, Headers),
 1353      functor(Data, CodeName, _)
 1354    },
 1355    vstatus(CodeName, Code, Headers),
 1356    date(now),
 1357    header_fields(Headers, CLen),
 1358    content_length(codes(Content, Encoding), CLen),
 1359    content_type(Type, Encoding),
 1360    "\r\n".
 1361
 1362status_reply_headers(created(Location, Body), Body,
 1363                     [ location(Location) ]).
 1364status_reply_headers(moved(To, Body), Body,
 1365                     [ location(To) ]).
 1366status_reply_headers(moved_temporary(To, Body), Body,
 1367                     [ location(To) ]).
 1368status_reply_headers(gone(_URL, Body), Body, []).
 1369status_reply_headers(see_other(To, Body), Body,
 1370                     [ location(To) ]).
 1371status_reply_headers(authorise(Method, Body), Body,
 1372                     [ www_authenticate(Method) ]).
 1373status_reply_headers(not_found(_URL, Body), Body, []).
 1374status_reply_headers(forbidden(_URL, Body), Body, []).
 1375status_reply_headers(method_not_allowed(_Method, _URL, Body), Body, []).
 1376status_reply_headers(server_error(_Error, Body), Body, []).
 1377status_reply_headers(service_unavailable(_Why, Body), Body, []).
 1378status_reply_headers(not_acceptable(_Why, Body), Body, []).
 1379status_reply_headers(bad_request(_Error, Body), Body, []).
 vstatus(+Status, -Code)// is det
 vstatus(+Status, -Code, +HdrExtra)// is det
Emit the HTTP header for Status
 1387vstatus(_Status, Code, HdrExtra) -->
 1388    {memberchk(status(Code), HdrExtra)},
 1389    !,
 1390    vstatus(_NewStatus, Code).
 1391vstatus(Status, Code, _) -->
 1392    vstatus(Status, Code).
 1393
 1394vstatus(Status, Code) -->
 1395    "HTTP/1.1 ",
 1396    status_number(Status, Code),
 1397    " ",
 1398    status_comment(Status),
 1399    "\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.
 1408status_number(Status, Code) -->
 1409    { var(Status) },
 1410    !,
 1411    integer(Code),
 1412    { status_number(Status, Code) },
 1413    !.
 1414status_number(Status, Code) -->
 1415    { status_number(Status, Code) },
 1416    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.
 1430% Unrecognized status codes that are within a defined code class.
 1431% RFC 7231 states:
 1432%   "[...] a client MUST understand the class of any status code,
 1433%    as indicated by the first digit, and treat an unrecognized status code
 1434%    as being equivalent to the `x00` status code of that class [...]
 1435%   "
 1436% @see http://tools.ietf.org/html/rfc7231#section-6
 1437
 1438status_number(Status, Code) :-
 1439    nonvar(Status),
 1440    !,
 1441    status_number_fact(Status, Code).
 1442status_number(Status, Code) :-
 1443    nonvar(Code),
 1444    !,
 1445    (   between(100, 599, Code)
 1446    ->  (   status_number_fact(Status, Code)
 1447        ->  true
 1448        ;   ClassCode is Code // 100 * 100,
 1449            status_number_fact(Status, ClassCode)
 1450        )
 1451    ;   domain_error(http_code, Code)
 1452    ).
 1453
 1454status_number_fact(continue,                   100).
 1455status_number_fact(switching_protocols,        101).
 1456status_number_fact(ok,                         200).
 1457status_number_fact(created,                    201).
 1458status_number_fact(accepted,                   202).
 1459status_number_fact(non_authoritative_info,     203).
 1460status_number_fact(no_content,                 204).
 1461status_number_fact(reset_content,              205).
 1462status_number_fact(partial_content,            206).
 1463status_number_fact(multiple_choices,           300).
 1464status_number_fact(moved,                      301).
 1465status_number_fact(moved_temporary,            302).
 1466status_number_fact(see_other,                  303).
 1467status_number_fact(not_modified,               304).
 1468status_number_fact(use_proxy,                  305).
 1469status_number_fact(unused,                     306).
 1470status_number_fact(temporary_redirect,         307).
 1471status_number_fact(bad_request,                400).
 1472status_number_fact(authorise,                  401).
 1473status_number_fact(payment_required,           402).
 1474status_number_fact(forbidden,                  403).
 1475status_number_fact(not_found,                  404).
 1476status_number_fact(method_not_allowed,         405).
 1477status_number_fact(not_acceptable,             406).
 1478status_number_fact(request_timeout,            408).
 1479status_number_fact(conflict,                   409).
 1480status_number_fact(gone,                       410).
 1481status_number_fact(length_required,            411).
 1482status_number_fact(payload_too_large,          413).
 1483status_number_fact(uri_too_long,               414).
 1484status_number_fact(unsupported_media_type,     415).
 1485status_number_fact(expectation_failed,         417).
 1486status_number_fact(upgrade_required,           426).
 1487status_number_fact(server_error,               500).
 1488status_number_fact(not_implemented,            501).
 1489status_number_fact(bad_gateway,                502).
 1490status_number_fact(service_unavailable,        503).
 1491status_number_fact(gateway_timeout,            504).
 1492status_number_fact(http_version_not_supported, 505).
 status_comment(+Code:atom)// is det
Emit standard HTTP human-readable comment on the reply-status.
 1499status_comment(continue) -->
 1500    "Continue".
 1501status_comment(switching_protocols) -->
 1502    "Switching Protocols".
 1503status_comment(ok) -->
 1504    "OK".
 1505status_comment(created) -->
 1506    "Created".
 1507status_comment(accepted) -->
 1508    "Accepted".
 1509status_comment(non_authoritative_info) -->
 1510    "Non-Authoritative Information".
 1511status_comment(no_content) -->
 1512    "No Content".
 1513status_comment(reset_content) -->
 1514    "Reset Content".
 1515status_comment(created) -->
 1516    "Created".
 1517status_comment(partial_content) -->
 1518    "Partial content".
 1519status_comment(multiple_choices) -->
 1520    "Multiple Choices".
 1521status_comment(moved) -->
 1522    "Moved Permanently".
 1523status_comment(moved_temporary) -->
 1524    "Moved Temporary".
 1525status_comment(see_other) -->
 1526    "See Other".
 1527status_comment(not_modified) -->
 1528    "Not Modified".
 1529status_comment(use_proxy) -->
 1530    "Use Proxy".
 1531status_comment(unused) -->
 1532    "Unused".
 1533status_comment(temporary_redirect) -->
 1534    "Temporary Redirect".
 1535status_comment(bad_request) -->
 1536    "Bad Request".
 1537status_comment(authorise) -->
 1538    "Authorization Required".
 1539status_comment(payment_required) -->
 1540    "Payment Required".
 1541status_comment(forbidden) -->
 1542    "Forbidden".
 1543status_comment(not_found) -->
 1544    "Not Found".
 1545status_comment(method_not_allowed) -->
 1546    "Method Not Allowed".
 1547status_comment(not_acceptable) -->
 1548    "Not Acceptable".
 1549status_comment(request_timeout) -->
 1550    "Request Timeout".
 1551status_comment(conflict) -->
 1552    "Conflict".
 1553status_comment(gone) -->
 1554    "Gone".
 1555status_comment(length_required) -->
 1556    "Length Required".
 1557status_comment(payload_too_large) -->
 1558    "Payload Too Large".
 1559status_comment(uri_too_long) -->
 1560    "URI Too Long".
 1561status_comment(unsupported_media_type) -->
 1562    "Unsupported Media Type".
 1563status_comment(expectation_failed) -->
 1564    "Expectation Failed".
 1565status_comment(upgrade_required) -->
 1566    "Upgrade Required".
 1567status_comment(server_error) -->
 1568    "Internal Server Error".
 1569status_comment(not_implemented) -->
 1570    "Not Implemented".
 1571status_comment(bad_gateway) -->
 1572    "Bad Gateway".
 1573status_comment(service_unavailable) -->
 1574    "Service Unavailable".
 1575status_comment(gateway_timeout) -->
 1576    "Gateway Timeout".
 1577status_comment(http_version_not_supported) -->
 1578    "HTTP Version Not Supported".
 1579
 1580date(Time) -->
 1581    "Date: ",
 1582    (   { Time == now }
 1583    ->  now
 1584    ;   rfc_date(Time)
 1585    ),
 1586    "\r\n".
 1587
 1588modified(file(File)) -->
 1589    !,
 1590    { time_file(File, Time)
 1591    },
 1592    modified(Time).
 1593modified(Time) -->
 1594    "Last-modified: ",
 1595    (   { Time == now }
 1596    ->  now
 1597    ;   rfc_date(Time)
 1598    ),
 1599    "\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
 1609content_length(file(File, bytes(From, To)), Len) -->
 1610    !,
 1611    { size_file(File, Size),
 1612      (   To == end
 1613      ->  Len is Size - From,
 1614          RangeEnd is Size - 1
 1615      ;   Len is To+1 - From,       % To is index of last byte
 1616          RangeEnd = To
 1617      )
 1618    },
 1619    content_range(bytes, From, RangeEnd, Size),
 1620    content_length(Len, Len).
 1621content_length(Reply, Len) -->
 1622    { length_of(Reply, Len)
 1623    },
 1624    "Content-Length: ", integer(Len),
 1625    "\r\n".
 1626
 1627
 1628length_of(_, Len) :-
 1629    nonvar(Len),
 1630    !.
 1631length_of(string(String, Encoding), Len) :-
 1632    length_of(codes(String, Encoding), Len).
 1633length_of(codes(String, Encoding), Len) :-
 1634    !,
 1635    setup_call_cleanup(
 1636        open_null_stream(Out),
 1637        ( set_stream(Out, encoding(Encoding)),
 1638          format(Out, '~s', [String]),
 1639          byte_count(Out, Len)
 1640        ),
 1641        close(Out)).
 1642length_of(atom(Atom, Encoding), Len) :-
 1643    !,
 1644    setup_call_cleanup(
 1645        open_null_stream(Out),
 1646        ( set_stream(Out, encoding(Encoding)),
 1647          format(Out, '~a', [Atom]),
 1648          byte_count(Out, Len)
 1649        ),
 1650        close(Out)).
 1651length_of(file(File), Len) :-
 1652    !,
 1653    size_file(File, Len).
 1654length_of(memory_file(Handle), Len) :-
 1655    !,
 1656    size_memory_file(Handle, Len, octet).
 1657length_of(html_tokens(Tokens), Len) :-
 1658    !,
 1659    html_print_length(Tokens, Len).
 1660length_of(html(Tokens), Len) :-     % deprecated
 1661    !,
 1662    html_print_length(Tokens, Len).
 1663length_of(bytes(Bytes), Len) :-
 1664    !,
 1665    (   string(Bytes)
 1666    ->  string_length(Bytes, Len)
 1667    ;   length(Bytes, Len)          % assuming a list of 0..255
 1668    ).
 1669length_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.
 1677content_range(Unit, From, RangeEnd, Size) -->
 1678    "Content-Range: ", atom(Unit), " ",
 1679    integer(From), "-", integer(RangeEnd), "/", integer(Size),
 1680    "\r\n".
 1681
 1682content_encoding(Encoding) -->
 1683    "Content-Encoding: ", atom(Encoding), "\r\n".
 1684
 1685transfer_encoding(Encoding) -->
 1686    "Transfer-Encoding: ", atom(Encoding), "\r\n".
 1687
 1688content_type(Type) -->
 1689    content_type(Type, _).
 1690
 1691content_type(Type, Charset) -->
 1692    ctype(Type),
 1693    charset(Charset),
 1694    "\r\n".
 1695
 1696ctype(Main/Sub) -->
 1697    !,
 1698    "Content-Type: ",
 1699    atom(Main),
 1700    "/",
 1701    atom(Sub).
 1702ctype(Type) -->
 1703    !,
 1704    "Content-Type: ",
 1705    atom(Type).
 1706
 1707charset(Var) -->
 1708    { var(Var) },
 1709    !.
 1710charset(utf8) -->
 1711    !,
 1712    "; charset=UTF-8".
 1713charset(CharSet) -->
 1714    "; charset=",
 1715    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.
 1723header_field(Name, Value) -->
 1724    { var(Name) },                 % parsing
 1725    !,
 1726    field_name(Name),
 1727    ":",
 1728    whites,
 1729    read_field_value(ValueChars),
 1730    blanks_to_nl,
 1731    !,
 1732    {   field_to_prolog(Name, ValueChars, Value)
 1733    ->  true
 1734    ;   atom_codes(Value, ValueChars),
 1735        domain_error(Name, Value)
 1736    }.
 1737header_field(Name, Value) -->
 1738    field_name(Name),
 1739    ": ",
 1740    field_value(Name, Value),
 1741    "\r\n".
 read_field_value(-Codes)//
Read a field eagerly upto the next whitespace
 1747read_field_value([H|T]) -->
 1748    [H],
 1749    { \+ code_type(H, space) },
 1750    !,
 1751    read_field_value(T).
 1752read_field_value([]) -->
 1753    "".
 1754read_field_value([H|T]) -->
 1755    [H],
 1756    read_field_value(T).
 send_reply_header(+Out, +String) is det
 send_request_header(+Out, +String) is det
Low level routines to send a single HTTP request or reply line.
 1763send_reply_header(Out, String) :-
 1764    debug(http(send_reply), "< ~s", [String]),
 1765    format(Out, '~s', [String]).
 1766
 1767send_request_header(Out, String) :-
 1768    debug(http(send_request), "> ~s", [String]),
 1769    format(Out, '~s', [String]).
 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.
 1809http_parse_header_value(Field, Value, Prolog) :-
 1810    known_field(Field, _, Type),
 1811    (   already_parsed(Type, Value)
 1812    ->  Prolog = Value
 1813    ;   to_codes(Value, Codes),
 1814        parse_header_value(Field, Codes, Prolog)
 1815    ).
 1816
 1817already_parsed(integer, V)    :- !, integer(V).
 1818already_parsed(list(Type), L) :- !, is_list(L), maplist(already_parsed(Type), L).
 1819already_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.
 1827known_field(content_length,      true,  integer).
 1828known_field(status,              true,  integer).
 1829known_field(cookie,              true,  list(_=_)).
 1830known_field(set_cookie,          true,  list(set_cookie(_Name,_Value,_Options))).
 1831known_field(host,                true,  _Host:_Port).
 1832known_field(range,               maybe, bytes(_,_)).
 1833known_field(accept,              maybe, list(media(_Type, _Parms, _Q, _Exts))).
 1834known_field(content_disposition, maybe, disposition(_Name, _Attributes)).
 1835known_field(content_type,        false, media(_Type/_Sub, _Attributes)).
 1836
 1837to_codes(In, Codes) :-
 1838    (   is_list(In)
 1839    ->  Codes = In
 1840    ;   atom_codes(In, Codes)
 1841    ).
 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.
 1849field_to_prolog(Field, Codes, Prolog) :-
 1850    known_field(Field, true, _Type),
 1851    !,
 1852    (   parse_header_value(Field, Codes, Prolog0)
 1853    ->  Prolog = Prolog0
 1854    ).
 1855field_to_prolog(Field, Codes, Prolog) :-
 1856    known_field(Field, maybe, _Type),
 1857    parse_header_value(Field, Codes, Prolog0),
 1858    !,
 1859    Prolog = Prolog0.
 1860field_to_prolog(_, Codes, Atom) :-
 1861    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.
 1868parse_header_value(content_length, ValueChars, ContentLength) :-
 1869    number_codes(ContentLength, ValueChars).
 1870parse_header_value(status, ValueChars, Code) :-
 1871    (   phrase(" ", L, _),
 1872        append(Pre, L, ValueChars)
 1873    ->  number_codes(Code, Pre)
 1874    ;   number_codes(Code, ValueChars)
 1875    ).
 1876parse_header_value(cookie, ValueChars, Cookies) :-
 1877    debug(cookie, 'Cookie: ~s', [ValueChars]),
 1878    phrase(cookies(Cookies), ValueChars).
 1879parse_header_value(set_cookie, ValueChars, SetCookie) :-
 1880    debug(cookie, 'SetCookie: ~s', [ValueChars]),
 1881    phrase(set_cookie(SetCookie), ValueChars).
 1882parse_header_value(host, ValueChars, Host) :-
 1883    (   append(HostChars, [0':|PortChars], ValueChars),
 1884        catch(number_codes(Port, PortChars), _, fail)
 1885    ->  atom_codes(HostName, HostChars),
 1886        Host = HostName:Port
 1887    ;   atom_codes(Host, ValueChars)
 1888    ).
 1889parse_header_value(range, ValueChars, Range) :-
 1890    phrase(range(Range), ValueChars).
 1891parse_header_value(accept, ValueChars, Media) :-
 1892    parse_accept(ValueChars, Media).
 1893parse_header_value(content_disposition, ValueChars, Disposition) :-
 1894    phrase(content_disposition(Disposition), ValueChars).
 1895parse_header_value(content_type, ValueChars, Type) :-
 1896    phrase(parse_content_type(Type), ValueChars).
 field_value(+Name, +Value)//
 1900field_value(_, set_cookie(Name, Value, Options)) -->
 1901    !,
 1902    atom(Name), "=", atom(Value),
 1903    value_options(Options, cookie).
 1904field_value(_, disposition(Disposition, Options)) -->
 1905    !,
 1906    atom(Disposition), value_options(Options, disposition).
 1907field_value(www_authenticate, Auth) -->
 1908    auth_field_value(Auth).
 1909field_value(_, Atomic) -->
 1910    atom(Atomic).
 auth_field_value(+AuthValue)//
Emit the authentication requirements (WWW-Authenticate field).
 1916auth_field_value(negotiate(Data)) -->
 1917    "Negotiate ",
 1918    { base64(Data, DataBase64),
 1919      atom_codes(DataBase64, Codes)
 1920    },
 1921    string(Codes).
 1922auth_field_value(negotiate) -->
 1923    "Negotiate".
 1924auth_field_value(basic) -->
 1925    !,
 1926    "Basic".
 1927auth_field_value(basic(Realm)) -->
 1928    "Basic Realm=\"", atom(Realm), "\"".
 1929auth_field_value(digest) -->
 1930    !,
 1931    "Digest".
 1932auth_field_value(digest(Details)) -->
 1933    "Digest ", atom(Details).
 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.
 1942value_options([], _) --> [].
 1943value_options([H|T], Field) -->
 1944    "; ", value_option(H, Field),
 1945    value_options(T, Field).
 1946
 1947value_option(secure=true, cookie) -->
 1948    !,
 1949    "secure".
 1950value_option(Name=Value, Type) -->
 1951    { string_option(Name, Type) },
 1952    !,
 1953    atom(Name), "=",
 1954    qstring(Value).
 1955value_option(Name=Value, Type) -->
 1956    { token_option(Name, Type) },
 1957    !,
 1958    atom(Name), "=", atom(Value).
 1959value_option(Name=Value, _Type) -->
 1960    atom(Name), "=",
 1961    option_value(Value).
 1962
 1963string_option(filename, disposition).
 1964
 1965token_option(path, cookie).
 1966
 1967option_value(Value) -->
 1968    { number(Value) },
 1969    !,
 1970    number(Value).
 1971option_value(Value) -->
 1972    { (   atom(Value)
 1973      ->  true
 1974      ;   string(Value)
 1975      ),
 1976      forall(string_code(_, Value, C),
 1977             token_char(C))
 1978    },
 1979    !,
 1980    atom(Value).
 1981option_value(Atomic) -->
 1982    qstring(Atomic).
 1983
 1984qstring(Atomic) -->
 1985    { string_codes(Atomic, Codes) },
 1986    "\"",
 1987    qstring_codes(Codes),
 1988    "\"".
 1989
 1990qstring_codes([]) --> [].
 1991qstring_codes([H|T]) --> qstring_code(H), qstring_codes(T).
 1992
 1993qstring_code(C) --> {qstring_esc(C)}, !, "\\", [C].
 1994qstring_code(C) --> [C].
 1995
 1996qstring_esc(0'").
 1997qstring_esc(C) :- ctl(C).
 1998
 1999
 2000                 /*******************************
 2001                 *        ACCEPT HEADERS        *
 2002                 *******************************/
 2003
 2004:- dynamic accept_cache/2. 2005:- volatile accept_cache/2. 2006
 2007parse_accept(Codes, Media) :-
 2008    atom_codes(Atom, Codes),
 2009    (   accept_cache(Atom, Media0)
 2010    ->  Media = Media0
 2011    ;   phrase(accept(Media0), Codes),
 2012        keysort(Media0, Media1),
 2013        pairs_values(Media1, Media2),
 2014        assertz(accept_cache(Atom, Media2)),
 2015        Media = Media2
 2016    ).
 accept(-Media)// is semidet
Parse an HTTP Accept: header
 2022accept([H|T]) -->
 2023    blanks,
 2024    media_range(H),
 2025    blanks,
 2026    (   ","
 2027    ->  accept(T)
 2028    ;   {T=[]}
 2029    ).
 2030
 2031media_range(s(SortQuality,Spec)-media(Type, TypeParams, Quality, AcceptExts)) -->
 2032    media_type(Type),
 2033    blanks,
 2034    (   ";"
 2035    ->  blanks,
 2036        parameters_and_quality(TypeParams, Quality, AcceptExts)
 2037    ;   { TypeParams = [],
 2038          Quality = 1.0,
 2039          AcceptExts = []
 2040        }
 2041    ),
 2042    { SortQuality is float(-Quality),
 2043      rank_specialised(Type, TypeParams, Spec)
 2044    }.
 content_disposition(-Disposition)//
Parse Content-Disposition value
 2051content_disposition(disposition(Disposition, Options)) -->
 2052    token(Disposition), blanks,
 2053    value_parameters(Options).
 parse_content_type(-Type)//
Parse Content-Type value into a term media(Type/SubType, Parameters).
 2060parse_content_type(media(Type, Parameters)) -->
 2061    media_type(Type), blanks,
 2062    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?
 2073rank_specialised(Type/SubType, TypeParams, v(VT, VS, SortVP)) :-
 2074    var_or_given(Type, VT),
 2075    var_or_given(SubType, VS),
 2076    length(TypeParams, VP),
 2077    SortVP is -VP.
 2078
 2079var_or_given(V, Val) :-
 2080    (   var(V)
 2081    ->  Val = 0
 2082    ;   Val = -1
 2083    ).
 2084
 2085media_type(Type/SubType) -->
 2086    type(Type), "/", type(SubType).
 2087
 2088type(_) -->
 2089    "*",
 2090    !.
 2091type(Type) -->
 2092    token(Type).
 2093
 2094parameters_and_quality(Params, Quality, AcceptExts) -->
 2095    token(Name),
 2096    blanks, "=", blanks,
 2097    (   { Name == q }
 2098    ->  float(Quality), blanks,
 2099        value_parameters(AcceptExts),
 2100        { Params = [] }
 2101    ;   { Params = [Name=Value|T] },
 2102        parameter_value(Value),
 2103        blanks,
 2104        (   ";"
 2105        ->  blanks,
 2106            parameters_and_quality(T, Quality, AcceptExts)
 2107        ;   { T = [],
 2108              Quality = 1.0,
 2109              AcceptExts = []
 2110            }
 2111        )
 2112    ).
 value_parameters(-Params:list) is det
Accept (";" <parameter>)*, returning a list of Name=Value, where both Name and Value are atoms.
 2119value_parameters([H|T]) -->
 2120    ";",
 2121    !,
 2122    blanks, token(Name), blanks,
 2123    (   "="
 2124    ->  blanks,
 2125        (   token(Value)
 2126        ->  []
 2127        ;   quoted_string(Value)
 2128        ),
 2129        { H = (Name=Value) }
 2130    ;   { H = Name }
 2131    ),
 2132    blanks,
 2133    value_parameters(T).
 2134value_parameters([]) -->
 2135    [].
 2136
 2137parameter_value(Value) --> token(Value), !.
 2138parameter_value(Value) --> quoted_string(Value).
 token(-Name)// is semidet
Process an HTTP header token from the input.
 2145token(Name) -->
 2146    token_char(C1),
 2147    token_chars(Cs),
 2148    { atom_codes(Name, [C1|Cs]) }.
 2149
 2150token_chars([H|T]) -->
 2151    token_char(H),
 2152    !,
 2153    token_chars(T).
 2154token_chars([]) --> [].
 2155
 2156token_char(C) :-
 2157    \+ ctl(C),
 2158    \+ separator_code(C).
 2159
 2160ctl(C) :- between(0,31,C), !.
 2161ctl(127).
 2162
 2163separator_code(0'().
 2164separator_code(0')).
 2165separator_code(0'<).
 2166separator_code(0'>).
 2167separator_code(0'@).
 2168separator_code(0',).
 2169separator_code(0';).
 2170separator_code(0':).
 2171separator_code(0'\\).
 2172separator_code(0'").
 2173separator_code(0'/).
 2174separator_code(0'[).
 2175separator_code(0']).
 2176separator_code(0'?).
 2177separator_code(0'=).
 2178separator_code(0'{).
 2179separator_code(0'}).
 2180separator_code(0'\s).
 2181separator_code(0'\t).
 2182
 2183term_expansion(token_char(x) --> [x], Clauses) :-
 2184    findall((token_char(C)-->[C]),
 2185            (   between(0, 255, C),
 2186                token_char(C)
 2187            ),
 2188            Clauses).
 2189
 2190token_char(x) --> [x].
 quoted_string(-Text)// is semidet
True if input starts with a quoted string representing Text.
 2196quoted_string(Text) -->
 2197    "\"",
 2198    quoted_text(Codes),
 2199    { atom_codes(Text, Codes) }.
 2200
 2201quoted_text([]) -->
 2202    "\"",
 2203    !.
 2204quoted_text([H|T]) -->
 2205    "\\", !, [H],
 2206    quoted_text(T).
 2207quoted_text([H|T]) -->
 2208    [H],
 2209    !,
 2210    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.
 2221header_fields([], _) --> [].
 2222header_fields([content_length(CLen)|T], CLen) -->
 2223    !,
 2224    (   { var(CLen) }
 2225    ->  ""
 2226    ;   header_field(content_length, CLen)
 2227    ),
 2228    header_fields(T, CLen).           % Continue or return first only?
 2229header_fields([status(_)|T], CLen) -->   % handled by vstatus//3.
 2230    !,
 2231    header_fields(T, CLen).
 2232header_fields([H|T], CLen) -->
 2233    { H =.. [Name, Value] },
 2234    header_field(Name, Value),
 2235    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
 2252:- public
 2253    field_name//1. 2254
 2255field_name(Name) -->
 2256    { var(Name) },
 2257    !,
 2258    rd_field_chars(Chars),
 2259    { atom_codes(Name, Chars) }.
 2260field_name(mime_version) -->
 2261    !,
 2262    "MIME-Version".
 2263field_name(www_authenticate) -->
 2264    !,
 2265    "WWW-Authenticate".
 2266field_name(Name) -->
 2267    { atom_codes(Name, Chars) },
 2268    wr_field_chars(Chars).
 2269
 2270rd_field_chars_no_fold([C|T]) -->
 2271    [C],
 2272    { rd_field_char(C, _) },
 2273    !,
 2274    rd_field_chars_no_fold(T).
 2275rd_field_chars_no_fold([]) -->
 2276    [].
 2277
 2278rd_field_chars([C0|T]) -->
 2279    [C],
 2280    { rd_field_char(C, C0) },
 2281    !,
 2282    rd_field_chars(T).
 2283rd_field_chars([]) -->
 2284    [].
 separators(-CharCodes) is det
CharCodes is a list of separators according to RFC2616
 2290separators("()<>@,;:\\\"/[]?={} \t").
 2291
 2292term_expansion(rd_field_char('expand me',_), Clauses) :-
 2293
 2294    Clauses = [ rd_field_char(0'-, 0'_)
 2295              | Cls
 2296              ],
 2297    separators(SepString),
 2298    string_codes(SepString, Seps),
 2299    findall(rd_field_char(In, Out),
 2300            (   between(32, 127, In),
 2301                \+ memberchk(In, Seps),
 2302                In \== 0'-,         % 0'
 2303                code_type(Out, to_lower(In))),
 2304            Cls).
 2305
 2306rd_field_char('expand me', _).                  % avoid recursion
 2307
 2308wr_field_chars([C|T]) -->
 2309    !,
 2310    { code_type(C, to_lower(U)) },
 2311    [U],
 2312    wr_field_chars2(T).
 2313wr_field_chars([]) -->
 2314    [].
 2315
 2316wr_field_chars2([]) --> [].
 2317wr_field_chars2([C|T]) -->              % 0'
 2318    (   { C == 0'_ }
 2319    ->  "-",
 2320        wr_field_chars(T)
 2321    ;   [C],
 2322        wr_field_chars2(T)
 2323    ).
 now//
Current time using rfc_date//1.
 2329now -->
 2330    { get_time(Time)
 2331    },
 2332    rfc_date(Time).
 rfc_date(+Time)// is det
Write time according to RFC1123 specification as required by the RFC2616 HTTP protocol specs.
 2339rfc_date(Time, String, Tail) :-
 2340    stamp_date_time(Time, Date, 'UTC'),
 2341    format_time(codes(String, Tail),
 2342                '%a, %d %b %Y %T GMT',
 2343                Date, posix).
 http_timestamp(+Time:timestamp, -Text:atom) is det
Generate a description of a Time in HTTP format (RFC1123)
 2349http_timestamp(Time, Atom) :-
 2350    stamp_date_time(Time, Date, 'UTC'),
 2351    format_time(atom(Atom),
 2352                '%a, %d %b %Y %T GMT',
 2353                Date, posix).
 2354
 2355
 2356                 /*******************************
 2357                 *         REQUEST DCG          *
 2358                 *******************************/
 2359
 2360request(Fd, [method(Method),request_uri(ReqURI)|Header]) -->
 2361    method(Method),
 2362    blanks,
 2363    nonblanks(Query),
 2364    { atom_codes(ReqURI, Query),
 2365      request_uri_parts(ReqURI, Header, Rest)
 2366    },
 2367    request_header(Fd, Rest),
 2368    !.
 2369request(Fd, [unknown(What)|Header]) -->
 2370    string(What),
 2371    eos,
 2372    !,
 2373    {   http_read_header(Fd, Header)
 2374    ->  true
 2375    ;   Header = []
 2376    }.
 2377
 2378method(get)     --> "GET", !.
 2379method(put)     --> "PUT", !.
 2380method(head)    --> "HEAD", !.
 2381method(post)    --> "POST", !.
 2382method(delete)  --> "DELETE", !.
 2383method(patch)   --> "PATCH", !.
 2384method(options) --> "OPTIONS", !.
 2385method(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.
 2399request_uri_parts(ReqURI, [path(Path)|Parts], Rest) :-
 2400    uri_components(ReqURI, Components),
 2401    uri_data(path, Components, PathText),
 2402    uri_encoded(path, Path, PathText),
 2403    phrase(uri_parts(Components), Parts, Rest).
 2404
 2405uri_parts(Components) -->
 2406    uri_search(Components),
 2407    uri_fragment(Components).
 2408
 2409uri_search(Components) -->
 2410    { uri_data(search, Components, Search),
 2411      nonvar(Search),
 2412      catch(uri_query_components(Search, Query),
 2413            error(syntax_error(_),_),
 2414            fail)
 2415    },
 2416    !,
 2417    [ search(Query) ].
 2418uri_search(_) --> [].
 2419
 2420uri_fragment(Components) -->
 2421    { uri_data(fragment, Components, String),
 2422      nonvar(String),
 2423      !,
 2424      uri_encoded(fragment, Fragment, String)
 2425    },
 2426    [ fragment(Fragment) ].
 2427uri_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.
 2434request_header(_, []) -->               % Old-style non-version header
 2435    blanks,
 2436    eos,
 2437    !.
 2438request_header(Fd, [http_version(Version)|Header]) -->
 2439    http_version(Version),
 2440    blanks,
 2441    eos,
 2442    !,
 2443    {   Version = 1-_
 2444    ->  http_read_header(Fd, Header)
 2445    ;   Header = []
 2446    }.
 2447
 2448http_version(Version) -->
 2449    blanks,
 2450    "HTTP/",
 2451    http_version_number(Version).
 2452
 2453http_version_number(Major-Minor) -->
 2454    integer(Major),
 2455    ".",
 2456    integer(Minor).
 2457
 2458
 2459                 /*******************************
 2460                 *            COOKIES           *
 2461                 *******************************/
 cookies(-List)// is semidet
Translate a cookie description into a list Name=Value.
 2467cookies([Name=Value|T]) -->
 2468    blanks,
 2469    cookie(Name, Value),
 2470    !,
 2471    blanks,
 2472    (   ";"
 2473    ->  cookies(T)
 2474    ;   { T = [] }
 2475    ).
 2476cookies(List) -->
 2477    string(Skipped),
 2478    ";",
 2479    !,
 2480    { print_message(warning, http(skipped_cookie(Skipped))) },
 2481    cookies(List).
 2482cookies([]) -->
 2483    blanks.
 2484
 2485cookie(Name, Value) -->
 2486    cookie_name(Name),
 2487    blanks, "=", blanks,
 2488    cookie_value(Value).
 2489
 2490cookie_name(Name) -->
 2491    { var(Name) },
 2492    !,
 2493    rd_field_chars_no_fold(Chars),
 2494    { atom_codes(Name, Chars) }.
 2495
 2496cookie_value(Value) -->
 2497    quoted_string(Value),
 2498    !.
 2499cookie_value(Value) -->
 2500    chars_to_semicolon_or_blank(Chars),
 2501    { atom_codes(Value, Chars)
 2502    }.
 2503
 2504chars_to_semicolon_or_blank([]), ";" -->
 2505    ";",
 2506    !.
 2507chars_to_semicolon_or_blank([]) -->
 2508    " ",
 2509    blanks,
 2510    eos,
 2511    !.
 2512chars_to_semicolon_or_blank([H|T]) -->
 2513    [H],
 2514    !,
 2515    chars_to_semicolon_or_blank(T).
 2516chars_to_semicolon_or_blank([]) -->
 2517    [].
 2518
 2519set_cookie(set_cookie(Name, Value, Options)) -->
 2520    ws,
 2521    cookie(Name, Value),
 2522    cookie_options(Options).
 2523
 2524cookie_options([H|T]) -->
 2525    ws,
 2526    ";",
 2527    ws,
 2528    cookie_option(H),
 2529    !,
 2530    cookie_options(T).
 2531cookie_options([]) -->
 2532    ws.
 2533
 2534ws --> " ", !, ws.
 2535ws --> [].
 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.
 2547cookie_option(Name=Value) -->
 2548    rd_field_chars(NameChars), ws,
 2549    { atom_codes(Name, NameChars) },
 2550    (   "="
 2551    ->  ws,
 2552        chars_to_semicolon(ValueChars),
 2553        { atom_codes(Value, ValueChars)
 2554        }
 2555    ;   { Value = true }
 2556    ).
 2557
 2558chars_to_semicolon([H|T]) -->
 2559    [H],
 2560    { H \== 32, H \== 0'; },
 2561    !,
 2562    chars_to_semicolon(T).
 2563chars_to_semicolon([]), ";" -->
 2564    ws, ";",
 2565    !.
 2566chars_to_semicolon([H|T]) -->
 2567    [H],
 2568    chars_to_semicolon(T).
 2569chars_to_semicolon([]) -->
 2570    [].
 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.
 2580range(bytes(From, To)) -->
 2581    "bytes", whites, "=", whites, integer(From), "-",
 2582    (   integer(To)
 2583    ->  ""
 2584    ;   { To = end }
 2585    ).
 2586
 2587
 2588                 /*******************************
 2589                 *           REPLY DCG          *
 2590                 *******************************/
 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.
 2607reply(Fd, [http_version(HttpVersion), status(Code, Status, Comment)|Header]) -->
 2608    http_version(HttpVersion),
 2609    blanks,
 2610    (   status_number(Status, Code)
 2611    ->  []
 2612    ;   integer(Status)
 2613    ),
 2614    blanks,
 2615    string(CommentCodes),
 2616    blanks_to_nl,
 2617    !,
 2618    blanks,
 2619    { atom_codes(Comment, CommentCodes),
 2620      http_read_header(Fd, Header)
 2621    }.
 2622
 2623
 2624                 /*******************************
 2625                 *            READ HEADER       *
 2626                 *******************************/
 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)
 2634http_read_header(Fd, Header) :-
 2635    read_header_data(Fd, Text),
 2636    http_parse_header(Text, Header).
 2637
 2638read_header_data(Fd, Header) :-
 2639    read_line_to_codes(Fd, Header, Tail),
 2640    read_header_data(Header, Fd, Tail),
 2641    debug(http(header), 'Header = ~n~s~n', [Header]).
 2642
 2643read_header_data([0'\r,0'\n], _, _) :- !.
 2644read_header_data([0'\n], _, _) :- !.
 2645read_header_data([], _, _) :- !.
 2646read_header_data(_, Fd, Tail) :-
 2647    read_line_to_codes(Fd, Tail, NewTail),
 2648    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)
 2657http_parse_header(Text, Header) :-
 2658    phrase(header(Header), Text),
 2659    debug(http(header), 'Field: ~p', [Header]).
 2660
 2661header(List) -->
 2662    header_field(Name, Value),
 2663    !,
 2664    { mkfield(Name, Value, List, Tail)
 2665    },
 2666    blanks,
 2667    header(Tail).
 2668header([]) -->
 2669    blanks,
 2670    eos,
 2671    !.
 2672header(_) -->
 2673    string(S), blanks_to_nl,
 2674    !,
 2675    { string_codes(Line, S),
 2676      syntax_error(http_parameter(Line))
 2677    }.
 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.

 2691:- multifile
 2692    http:http_address//0. 2693
 2694address -->
 2695    http:http_address,
 2696    !.
 2697address -->
 2698    { gethostname(Host) },
 2699    html(address([ a(href('http://www.swi-prolog.org'), 'SWI-Prolog'),
 2700                   ' httpd at ', Host
 2701                 ])).
 2702
 2703mkfield(host, Host:Port, [host(Host),port(Port)|Tail], Tail) :- !.
 2704mkfield(Name, Value, [Att|Tail], Tail) :-
 2705    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.
 2744                 /*******************************
 2745                 *            MESSAGES          *
 2746                 *******************************/
 2747
 2748:- multifile
 2749    prolog:message//1,
 2750    prolog:error_message//1. 2751
 2752prolog:error_message(http_write_short(Data, Sent)) -->
 2753    data(Data),
 2754    [ ': remote hangup after ~D bytes'-[Sent] ].
 2755prolog:error_message(syntax_error(http_request(Request))) -->
 2756    [ 'Illegal HTTP request: ~s'-[Request] ].
 2757prolog:error_message(syntax_error(http_parameter(Line))) -->
 2758    [ 'Illegal HTTP parameter: ~s'-[Line] ].
 2759
 2760prolog:message(http(skipped_cookie(S))) -->
 2761    [ 'Skipped illegal cookie: ~s'-[S] ].
 2762
 2763data(bytes(MimeType, _Bytes)) -->
 2764    !,
 2765    [ 'bytes(~p, ...)'-[MimeType] ].
 2766data(Data) -->
 2767    [ '~p'-[Data] ]