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

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

  875http_update_transfer(Request, CgiHeader, Transfer, Header) :-
  876    setting(http:chunked_transfer, When),
  877    http_update_transfer(When, Request, CgiHeader, Transfer, Header).
  878
  879http_update_transfer(never, _, CgiHeader, none, Header) :-
  880    !,
  881    delete(CgiHeader, transfer_encoding(_), Header).
  882http_update_transfer(_, _, CgiHeader, none, Header) :-
  883    memberchk(location(_), CgiHeader),
  884    !,
  885    delete(CgiHeader, transfer_encoding(_), Header).
  886http_update_transfer(_, Request, CgiHeader, Transfer, Header) :-
  887    select(transfer_encoding(CgiTransfer), CgiHeader, Rest),
  888    !,
  889    transfer(Request, ReqConnection),
  890    join_transfer(ReqConnection, CgiTransfer, Transfer),
  891    (   Transfer == none
  892    ->  Header = Rest
  893    ;   Header = [transfer_encoding(Transfer)|Rest]
  894    ).
  895http_update_transfer(if_possible, Request, CgiHeader, Transfer, Header) :-
  896    transfer(Request, Transfer),
  897    Transfer \== none,
  898    !,
  899    Header = [transfer_encoding(Transfer)|CgiHeader].
  900http_update_transfer(_, _, CgiHeader, none, CgiHeader).
  901
  902join_transfer(chunked, chunked, chunked) :- !.
  903join_transfer(_, _, none).
 transfer(+Header, -Connection)
Extract the desired connection from a header.
  910transfer(Header, Transfer) :-
  911    (   memberchk(transfer_encoding(Transfer0), Header)
  912    ->  Transfer = Transfer0
  913    ;   memberchk(http_version(1-X), Header),
  914        X >= 1
  915    ->  Transfer = chunked
  916    ;   Transfer = none
  917    ).
 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.
  926content_length_in_encoding(Enc, Stream, Bytes) :-
  927    stream_property(Stream, position(Here)),
  928    setup_call_cleanup(
  929        open_null_stream(Out),
  930        ( set_stream(Out, encoding(Enc)),
  931          catch(copy_stream_data(Stream, Out), _, fail),
  932          flush_output(Out),
  933          byte_count(Out, Bytes)
  934        ),
  935        ( close(Out, [force(true)]),
  936          set_stream_position(Stream, Here)
  937        )).
  938
  939
  940                 /*******************************
  941                 *          POST SUPPORT        *
  942                 *******************************/
 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:
 1045http_post_data(Data, Out, HdrExtra) :-
 1046    http:post_data_hook(Data, Out, HdrExtra),
 1047    !.
 1048http_post_data(html(HTML), Out, HdrExtra) :-
 1049    !,
 1050    phrase(post_header(html(HTML), HdrExtra), Header),
 1051    send_request_header(Out, Header),
 1052    print_html(Out, HTML).
 1053http_post_data(xml(XML), Out, HdrExtra) :-
 1054    !,
 1055    http_post_data(xml(text/xml, XML, []), Out, HdrExtra).
 1056http_post_data(xml(Type, XML), Out, HdrExtra) :-
 1057    !,
 1058    http_post_data(xml(Type, XML, []), Out, HdrExtra).
 1059http_post_data(xml(Type, XML, Options), Out, HdrExtra) :-
 1060    !,
 1061    setup_call_cleanup(
 1062        new_memory_file(MemFile),
 1063        (   setup_call_cleanup(
 1064                open_memory_file(MemFile, write, MemOut),
 1065                xml_write(MemOut, XML, Options),
 1066                close(MemOut)),
 1067            http_post_data(memory_file(Type, MemFile), Out, HdrExtra)
 1068        ),
 1069        free_memory_file(MemFile)).
 1070http_post_data(file(File), Out, HdrExtra) :-
 1071    !,
 1072    (   file_mime_type(File, Type)
 1073    ->  true
 1074    ;   Type = text/plain
 1075    ),
 1076    http_post_data(file(Type, File), Out, HdrExtra).
 1077http_post_data(file(Type, File), Out, HdrExtra) :-
 1078    !,
 1079    phrase(post_header(file(Type, File), HdrExtra), Header),
 1080    send_request_header(Out, Header),
 1081    setup_call_cleanup(
 1082        open(File, read, In, [type(binary)]),
 1083        copy_stream_data(In, Out),
 1084        close(In)).
 1085http_post_data(memory_file(Type, Handle), Out, HdrExtra) :-
 1086    !,
 1087    phrase(post_header(memory_file(Type, Handle), HdrExtra), Header),
 1088    send_request_header(Out, Header),
 1089    setup_call_cleanup(
 1090        open_memory_file(Handle, read, In, [encoding(octet)]),
 1091        copy_stream_data(In, Out),
 1092        close(In)).
 1093http_post_data(codes(Codes), Out, HdrExtra) :-
 1094    !,
 1095    http_post_data(codes(text/plain, Codes), Out, HdrExtra).
 1096http_post_data(codes(Type, Codes), Out, HdrExtra) :-
 1097    !,
 1098    phrase(post_header(codes(Type, Codes), HdrExtra), Header),
 1099    send_request_header(Out, Header),
 1100    setup_call_cleanup(
 1101        set_stream(Out, encoding(utf8)),
 1102        format(Out, '~s', [Codes]),
 1103        set_stream(Out, encoding(octet))).
 1104http_post_data(bytes(Type, Bytes), Out, HdrExtra) :-
 1105    !,
 1106    phrase(post_header(bytes(Type, Bytes), HdrExtra), Header),
 1107    send_request_header(Out, Header),
 1108    format(Out, '~s', [Bytes]).
 1109http_post_data(atom(Atom), Out, HdrExtra) :-
 1110    !,
 1111    http_post_data(atom(text/plain, Atom), Out, HdrExtra).
 1112http_post_data(atom(Type, Atom), Out, HdrExtra) :-
 1113    !,
 1114    phrase(post_header(atom(Type, Atom), HdrExtra), Header),
 1115    send_request_header(Out, Header),
 1116    setup_call_cleanup(
 1117        set_stream(Out, encoding(utf8)),
 1118        write(Out, Atom),
 1119        set_stream(Out, encoding(octet))).
 1120http_post_data(string(String), Out, HdrExtra) :-
 1121    !,
 1122    http_post_data(atom(text/plain, String), Out, HdrExtra).
 1123http_post_data(string(Type, String), Out, HdrExtra) :-
 1124    !,
 1125    phrase(post_header(string(Type, String), HdrExtra), Header),
 1126    send_request_header(Out, Header),
 1127    setup_call_cleanup(
 1128        set_stream(Out, encoding(utf8)),
 1129        write(Out, String),
 1130        set_stream(Out, encoding(octet))).
 1131http_post_data(cgi_stream(In, _Len), Out, HdrExtra) :-
 1132    !,
 1133    debug(obsolete, 'Obsolete 2nd argument in cgi_stream(In,Len)', []),
 1134    http_post_data(cgi_stream(In), Out, HdrExtra).
 1135http_post_data(cgi_stream(In), Out, HdrExtra) :-
 1136    !,
 1137    http_read_header(In, Header0),
 1138    http_update_encoding(Header0, Encoding, Header),
 1139    content_length_in_encoding(Encoding, In, Size),
 1140    http_join_headers(HdrExtra, Header, Hdr2),
 1141    phrase(post_header(cgi_data(Size), Hdr2), HeaderText),
 1142    send_request_header(Out, HeaderText),
 1143    setup_call_cleanup(
 1144        set_stream(Out, encoding(Encoding)),
 1145        copy_stream_data(In, Out),
 1146        set_stream(Out, encoding(octet))).
 1147http_post_data(form(Fields), Out, HdrExtra) :-
 1148    !,
 1149    parse_url_search(Codes, Fields),
 1150    length(Codes, Size),
 1151    http_join_headers(HdrExtra,
 1152                      [ content_type('application/x-www-form-urlencoded')
 1153                      ], Header),
 1154    phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1155    send_request_header(Out, HeaderChars),
 1156    format(Out, '~s', [Codes]).
 1157http_post_data(form_data(Data), Out, HdrExtra) :-
 1158    !,
 1159    setup_call_cleanup(
 1160        new_memory_file(MemFile),
 1161        ( setup_call_cleanup(
 1162              open_memory_file(MemFile, write, MimeOut),
 1163              mime_pack(Data, MimeOut, Boundary),
 1164              close(MimeOut)),
 1165          size_memory_file(MemFile, Size, octet),
 1166          format(string(ContentType),
 1167                 'multipart/form-data; boundary=~w', [Boundary]),
 1168          http_join_headers(HdrExtra,
 1169                            [ mime_version('1.0'),
 1170                              content_type(ContentType)
 1171                            ], Header),
 1172          phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1173          send_request_header(Out, HeaderChars),
 1174          setup_call_cleanup(
 1175              open_memory_file(MemFile, read, In, [encoding(octet)]),
 1176              copy_stream_data(In, Out),
 1177              close(In))
 1178        ),
 1179        free_memory_file(MemFile)).
 1180http_post_data(List, Out, HdrExtra) :-          % multipart-mixed
 1181    is_list(List),
 1182    !,
 1183    setup_call_cleanup(
 1184        new_memory_file(MemFile),
 1185        ( setup_call_cleanup(
 1186              open_memory_file(MemFile, write, MimeOut),
 1187              mime_pack(List, MimeOut, Boundary),
 1188              close(MimeOut)),
 1189          size_memory_file(MemFile, Size, octet),
 1190          format(string(ContentType),
 1191                 'multipart/mixed; boundary=~w', [Boundary]),
 1192          http_join_headers(HdrExtra,
 1193                            [ mime_version('1.0'),
 1194                              content_type(ContentType)
 1195                            ], Header),
 1196          phrase(post_header(cgi_data(Size), Header), HeaderChars),
 1197          send_request_header(Out, HeaderChars),
 1198          setup_call_cleanup(
 1199              open_memory_file(MemFile, read, In, [encoding(octet)]),
 1200              copy_stream_data(In, Out),
 1201              close(In))
 1202        ),
 1203        free_memory_file(MemFile)).
 post_header(+Data, +HeaderExtra)//
Generate the POST header, emitting HeaderExtra, followed by the HTTP Content-length and Content-type fields.
 1210post_header(html(Tokens), HdrExtra) -->
 1211    header_fields(HdrExtra, Len),
 1212    content_length(html(Tokens), Len),
 1213    content_type(text/html),
 1214    "\r\n".
 1215post_header(file(Type, File), HdrExtra) -->
 1216    header_fields(HdrExtra, Len),
 1217    content_length(file(File), Len),
 1218    content_type(Type),
 1219    "\r\n".
 1220post_header(memory_file(Type, File), HdrExtra) -->
 1221    header_fields(HdrExtra, Len),
 1222    content_length(memory_file(File), Len),
 1223    content_type(Type),
 1224    "\r\n".
 1225post_header(cgi_data(Size), HdrExtra) -->
 1226    header_fields(HdrExtra, Len),
 1227    content_length(Size, Len),
 1228    "\r\n".
 1229post_header(codes(Type, Codes), HdrExtra) -->
 1230    header_fields(HdrExtra, Len),
 1231    content_length(codes(Codes, utf8), Len),
 1232    content_type(Type, utf8),
 1233    "\r\n".
 1234post_header(bytes(Type, Bytes), HdrExtra) -->
 1235    header_fields(HdrExtra, Len),
 1236    content_length(bytes(Bytes), Len),
 1237    content_type(Type),
 1238    "\r\n".
 1239post_header(atom(Type, Atom), HdrExtra) -->
 1240    header_fields(HdrExtra, Len),
 1241    content_length(atom(Atom, utf8), Len),
 1242    content_type(Type, utf8),
 1243    "\r\n".
 1244post_header(string(Type, String), HdrExtra) -->
 1245    header_fields(HdrExtra, Len),
 1246    content_length(string(String, utf8), Len),
 1247    content_type(Type, utf8),
 1248    "\r\n".
 1249
 1250
 1251                 /*******************************
 1252                 *       OUTPUT HEADER DCG      *
 1253                 *******************************/
 http_reply_header(+Out:stream, +What, +HdrExtra) is det
Create a reply header using reply_header//3 and send it to Stream.
 1260http_reply_header(Out, What, HdrExtra) :-
 1261    phrase(reply_header(What, HdrExtra, _Code), String),
 1262    !,
 1263    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.
 1287reply_header(Data, Dict) -->
 1288    { _{header:HdrExtra, code:Code} :< Dict },
 1289    reply_header(Data, HdrExtra, Code).
 1290
 1291reply_header(string(String), HdrExtra, Code) -->
 1292    reply_header(string(text/plain, String), HdrExtra, Code).
 1293reply_header(string(Type, String), HdrExtra, Code) -->
 1294    vstatus(ok, Code, HdrExtra),
 1295    date(now),
 1296    header_fields(HdrExtra, CLen),
 1297    content_length(codes(String, utf8), CLen),
 1298    content_type(Type, utf8),
 1299    "\r\n".
 1300reply_header(bytes(Type, Bytes), HdrExtra, Code) -->
 1301    vstatus(ok, Code, HdrExtra),
 1302    date(now),
 1303    header_fields(HdrExtra, CLen),
 1304    content_length(bytes(Bytes), CLen),
 1305    content_type(Type),
 1306    "\r\n".
 1307reply_header(html(Tokens), HdrExtra, Code) -->
 1308    vstatus(ok, Code, HdrExtra),
 1309    date(now),
 1310    header_fields(HdrExtra, CLen),
 1311    content_length(html(Tokens), CLen),
 1312    content_type(text/html),
 1313    "\r\n".
 1314reply_header(file(Type, File), HdrExtra, Code) -->
 1315    vstatus(ok, Code, HdrExtra),
 1316    date(now),
 1317    modified(file(File)),
 1318    header_fields(HdrExtra, CLen),
 1319    content_length(file(File), CLen),
 1320    content_type(Type),
 1321    "\r\n".
 1322reply_header(gzip_file(Type, File), HdrExtra, Code) -->
 1323    vstatus(ok, Code, HdrExtra),
 1324    date(now),
 1325    modified(file(File)),
 1326    header_fields(HdrExtra, CLen),
 1327    content_length(file(File), CLen),
 1328    content_type(Type),
 1329    content_encoding(gzip),
 1330    "\r\n".
 1331reply_header(file(Type, File, Range), HdrExtra, Code) -->
 1332    vstatus(partial_content, Code, HdrExtra),
 1333    date(now),
 1334    modified(file(File)),
 1335    header_fields(HdrExtra, CLen),
 1336    content_length(file(File, Range), CLen),
 1337    content_type(Type),
 1338    "\r\n".
 1339reply_header(tmp_file(Type, File), HdrExtra, Code) -->
 1340    vstatus(ok, Code, HdrExtra),
 1341    date(now),
 1342    header_fields(HdrExtra, CLen),
 1343    content_length(file(File), CLen),
 1344    content_type(Type),
 1345    "\r\n".
 1346reply_header(cgi_data(Size), HdrExtra, Code) -->
 1347    vstatus(ok, Code, HdrExtra),
 1348    date(now),
 1349    header_fields(HdrExtra, CLen),
 1350    content_length(Size, CLen),
 1351    "\r\n".
 1352reply_header(chunked_data, HdrExtra, Code) -->
 1353    vstatus(ok, Code, HdrExtra),
 1354    date(now),
 1355    header_fields(HdrExtra, _),
 1356    (   {memberchk(transfer_encoding(_), HdrExtra)}
 1357    ->  ""
 1358    ;   transfer_encoding(chunked)
 1359    ),
 1360    "\r\n".
 1361% non-200 replies without a body (e.g., 1xx, 204, 304)
 1362reply_header(status(Status), HdrExtra, Code) -->
 1363    vstatus(Status, Code),
 1364    header_fields(HdrExtra, Clen),
 1365    { Clen = 0 },
 1366    "\r\n".
 1367% non-200 replies with a body
 1368reply_header(Data, HdrExtra, Code) -->
 1369    { status_reply_headers(Data,
 1370                           body(Type, Encoding, Content),
 1371                           ReplyHeaders),
 1372      http_join_headers(ReplyHeaders, HdrExtra, Headers),
 1373      functor(Data, CodeName, _)
 1374    },
 1375    vstatus(CodeName, Code, Headers),
 1376    date(now),
 1377    header_fields(Headers, CLen),
 1378    content_length(codes(Content, Encoding), CLen),
 1379    content_type(Type, Encoding),
 1380    "\r\n".
 1381
 1382status_reply_headers(created(Location, Body), Body,
 1383                     [ location(Location) ]).
 1384status_reply_headers(moved(To, Body), Body,
 1385                     [ location(To) ]).
 1386status_reply_headers(moved_temporary(To, Body), Body,
 1387                     [ location(To) ]).
 1388status_reply_headers(gone(_URL, Body), Body, []).
 1389status_reply_headers(see_other(To, Body), Body,
 1390                     [ location(To) ]).
 1391status_reply_headers(authorise(Method, Body), Body,
 1392                     [ www_authenticate(Method) ]).
 1393status_reply_headers(not_found(_URL, Body), Body, []).
 1394status_reply_headers(forbidden(_URL, Body), Body, []).
 1395status_reply_headers(method_not_allowed(_Method, _URL, Body), Body, []).
 1396status_reply_headers(server_error(_Error, Body), Body, []).
 1397status_reply_headers(service_unavailable(_Why, Body), Body, []).
 1398status_reply_headers(not_acceptable(_Why, Body), Body, []).
 1399status_reply_headers(bad_request(_Error, Body), Body, []).
 vstatus(+Status, -Code)// is det
 vstatus(+Status, -Code, +HdrExtra)// is det
Emit the HTTP header for Status
 1407vstatus(_Status, Code, HdrExtra) -->
 1408    {memberchk(status(Code), HdrExtra)},
 1409    !,
 1410    vstatus(_NewStatus, Code).
 1411vstatus(Status, Code, _) -->
 1412    vstatus(Status, Code).
 1413
 1414vstatus(Status, Code) -->
 1415    "HTTP/1.1 ",
 1416    status_number(Status, Code),
 1417    " ",
 1418    status_comment(Status),
 1419    "\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.
 1428status_number(Status, Code) -->
 1429    { var(Status) },
 1430    !,
 1431    integer(Code),
 1432    { status_number(Status, Code) },
 1433    !.
 1434status_number(Status, Code) -->
 1435    { status_number(Status, Code) },
 1436    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.
 1450% Unrecognized status codes that are within a defined code class.
 1451% RFC 7231 states:
 1452%   "[...] a client MUST understand the class of any status code,
 1453%    as indicated by the first digit, and treat an unrecognized status code
 1454%    as being equivalent to the `x00` status code of that class [...]
 1455%   "
 1456% @see http://tools.ietf.org/html/rfc7231#section-6
 1457
 1458status_number(Status, Code) :-
 1459    nonvar(Status),
 1460    !,
 1461    status_number_fact(Status, Code).
 1462status_number(Status, Code) :-
 1463    nonvar(Code),
 1464    !,
 1465    (   between(100, 599, Code)
 1466    ->  (   status_number_fact(Status, Code)
 1467        ->  true
 1468        ;   ClassCode is Code // 100 * 100,
 1469            status_number_fact(Status, ClassCode)
 1470        )
 1471    ;   domain_error(http_code, Code)
 1472    ).
 1473
 1474status_number_fact(continue,                   100).
 1475status_number_fact(switching_protocols,        101).
 1476status_number_fact(ok,                         200).
 1477status_number_fact(created,                    201).
 1478status_number_fact(accepted,                   202).
 1479status_number_fact(non_authoritative_info,     203).
 1480status_number_fact(no_content,                 204).
 1481status_number_fact(reset_content,              205).
 1482status_number_fact(partial_content,            206).
 1483status_number_fact(multiple_choices,           300).
 1484status_number_fact(moved,                      301).
 1485status_number_fact(moved_temporary,            302).
 1486status_number_fact(see_other,                  303).
 1487status_number_fact(not_modified,               304).
 1488status_number_fact(use_proxy,                  305).
 1489status_number_fact(unused,                     306).
 1490status_number_fact(temporary_redirect,         307).
 1491status_number_fact(bad_request,                400).
 1492status_number_fact(authorise,                  401).
 1493status_number_fact(payment_required,           402).
 1494status_number_fact(forbidden,                  403).
 1495status_number_fact(not_found,                  404).
 1496status_number_fact(method_not_allowed,         405).
 1497status_number_fact(not_acceptable,             406).
 1498status_number_fact(request_timeout,            408).
 1499status_number_fact(conflict,                   409).
 1500status_number_fact(gone,                       410).
 1501status_number_fact(length_required,            411).
 1502status_number_fact(payload_too_large,          413).
 1503status_number_fact(uri_too_long,               414).
 1504status_number_fact(unsupported_media_type,     415).
 1505status_number_fact(expectation_failed,         417).
 1506status_number_fact(upgrade_required,           426).
 1507status_number_fact(server_error,               500).
 1508status_number_fact(not_implemented,            501).
 1509status_number_fact(bad_gateway,                502).
 1510status_number_fact(service_unavailable,        503).
 1511status_number_fact(gateway_timeout,            504).
 1512status_number_fact(http_version_not_supported, 505).
 status_comment(+Code:atom)// is det
Emit standard HTTP human-readable comment on the reply-status.
 1519status_comment(continue) -->
 1520    "Continue".
 1521status_comment(switching_protocols) -->
 1522    "Switching Protocols".
 1523status_comment(ok) -->
 1524    "OK".
 1525status_comment(created) -->
 1526    "Created".
 1527status_comment(accepted) -->
 1528    "Accepted".
 1529status_comment(non_authoritative_info) -->
 1530    "Non-Authoritative Information".
 1531status_comment(no_content) -->
 1532    "No Content".
 1533status_comment(reset_content) -->
 1534    "Reset Content".
 1535status_comment(created) -->
 1536    "Created".
 1537status_comment(partial_content) -->
 1538    "Partial content".
 1539status_comment(multiple_choices) -->
 1540    "Multiple Choices".
 1541status_comment(moved) -->
 1542    "Moved Permanently".
 1543status_comment(moved_temporary) -->
 1544    "Moved Temporary".
 1545status_comment(see_other) -->
 1546    "See Other".
 1547status_comment(not_modified) -->
 1548    "Not Modified".
 1549status_comment(use_proxy) -->
 1550    "Use Proxy".
 1551status_comment(unused) -->
 1552    "Unused".
 1553status_comment(temporary_redirect) -->
 1554    "Temporary Redirect".
 1555status_comment(bad_request) -->
 1556    "Bad Request".
 1557status_comment(authorise) -->
 1558    "Authorization Required".
 1559status_comment(payment_required) -->
 1560    "Payment Required".
 1561status_comment(forbidden) -->
 1562    "Forbidden".
 1563status_comment(not_found) -->
 1564    "Not Found".
 1565status_comment(method_not_allowed) -->
 1566    "Method Not Allowed".
 1567status_comment(not_acceptable) -->
 1568    "Not Acceptable".
 1569status_comment(request_timeout) -->
 1570    "Request Timeout".
 1571status_comment(conflict) -->
 1572    "Conflict".
 1573status_comment(gone) -->
 1574    "Gone".
 1575status_comment(length_required) -->
 1576    "Length Required".
 1577status_comment(payload_too_large) -->
 1578    "Payload Too Large".
 1579status_comment(uri_too_long) -->
 1580    "URI Too Long".
 1581status_comment(unsupported_media_type) -->
 1582    "Unsupported Media Type".
 1583status_comment(expectation_failed) -->
 1584    "Expectation Failed".
 1585status_comment(upgrade_required) -->
 1586    "Upgrade Required".
 1587status_comment(server_error) -->
 1588    "Internal Server Error".
 1589status_comment(not_implemented) -->
 1590    "Not Implemented".
 1591status_comment(bad_gateway) -->
 1592    "Bad Gateway".
 1593status_comment(service_unavailable) -->
 1594    "Service Unavailable".
 1595status_comment(gateway_timeout) -->
 1596    "Gateway Timeout".
 1597status_comment(http_version_not_supported) -->
 1598    "HTTP Version Not Supported".
 1599
 1600date(Time) -->
 1601    "Date: ",
 1602    (   { Time == now }
 1603    ->  now
 1604    ;   rfc_date(Time)
 1605    ),
 1606    "\r\n".
 1607
 1608modified(file(File)) -->
 1609    !,
 1610    { time_file(File, Time)
 1611    },
 1612    modified(Time).
 1613modified(Time) -->
 1614    "Last-modified: ",
 1615    (   { Time == now }
 1616    ->  now
 1617    ;   rfc_date(Time)
 1618    ),
 1619    "\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
 1629content_length(file(File, bytes(From, To)), Len) -->
 1630    !,
 1631    { size_file(File, Size),
 1632      (   To == end
 1633      ->  Len is Size - From,
 1634          RangeEnd is Size - 1
 1635      ;   Len is To+1 - From,       % To is index of last byte
 1636          RangeEnd = To
 1637      )
 1638    },
 1639    content_range(bytes, From, RangeEnd, Size),
 1640    content_length(Len, Len).
 1641content_length(Reply, Len) -->
 1642    { length_of(Reply, Len)
 1643    },
 1644    "Content-Length: ", integer(Len),
 1645    "\r\n".
 1646
 1647
 1648length_of(_, Len) :-
 1649    nonvar(Len),
 1650    !.
 1651length_of(string(String, Encoding), Len) :-
 1652    length_of(codes(String, Encoding), Len).
 1653length_of(codes(String, Encoding), Len) :-
 1654    !,
 1655    setup_call_cleanup(
 1656        open_null_stream(Out),
 1657        ( set_stream(Out, encoding(Encoding)),
 1658          format(Out, '~s', [String]),
 1659          byte_count(Out, Len)
 1660        ),
 1661        close(Out)).
 1662length_of(atom(Atom, Encoding), Len) :-
 1663    !,
 1664    setup_call_cleanup(
 1665        open_null_stream(Out),
 1666        ( set_stream(Out, encoding(Encoding)),
 1667          format(Out, '~a', [Atom]),
 1668          byte_count(Out, Len)
 1669        ),
 1670        close(Out)).
 1671length_of(file(File), Len) :-
 1672    !,
 1673    size_file(File, Len).
 1674length_of(memory_file(Handle), Len) :-
 1675    !,
 1676    size_memory_file(Handle, Len, octet).
 1677length_of(html_tokens(Tokens), Len) :-
 1678    !,
 1679    html_print_length(Tokens, Len).
 1680length_of(html(Tokens), Len) :-     % deprecated
 1681    !,
 1682    html_print_length(Tokens, Len).
 1683length_of(bytes(Bytes), Len) :-
 1684    !,
 1685    (   string(Bytes)
 1686    ->  string_length(Bytes, Len)
 1687    ;   length(Bytes, Len)          % assuming a list of 0..255
 1688    ).
 1689length_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.
 1697content_range(Unit, From, RangeEnd, Size) -->
 1698    "Content-Range: ", atom(Unit), " ",
 1699    integer(From), "-", integer(RangeEnd), "/", integer(Size),
 1700    "\r\n".
 1701
 1702content_encoding(Encoding) -->
 1703    "Content-Encoding: ", atom(Encoding), "\r\n".
 1704
 1705transfer_encoding(Encoding) -->
 1706    "Transfer-Encoding: ", atom(Encoding), "\r\n".
 1707
 1708content_type(Type) -->
 1709    content_type(Type, _).
 1710
 1711content_type(Type, Charset) -->
 1712    ctype(Type),
 1713    charset(Charset),
 1714    "\r\n".
 1715
 1716ctype(Main/Sub) -->
 1717    !,
 1718    "Content-Type: ",
 1719    atom(Main),
 1720    "/",
 1721    atom(Sub).
 1722ctype(Type) -->
 1723    !,
 1724    "Content-Type: ",
 1725    atom(Type).
 1726
 1727charset(Var) -->
 1728    { var(Var) },
 1729    !.
 1730charset(utf8) -->
 1731    !,
 1732    "; charset=UTF-8".
 1733charset(CharSet) -->
 1734    "; charset=",
 1735    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.
 1743header_field(Name, Value) -->
 1744    { var(Name) },                 % parsing
 1745    !,
 1746    field_name(Name),
 1747    ":",
 1748    whites,
 1749    read_field_value(ValueChars),
 1750    blanks_to_nl,
 1751    !,
 1752    {   field_to_prolog(Name, ValueChars, Value)
 1753    ->  true
 1754    ;   atom_codes(Value, ValueChars),
 1755        domain_error(Name, Value)
 1756    }.
 1757header_field(Name, Value) -->
 1758    field_name(Name),
 1759    ": ",
 1760    field_value(Name, Value),
 1761    "\r\n".
 read_field_value(-Codes)//
Read a field eagerly upto the next whitespace
 1767read_field_value([H|T]) -->
 1768    [H],
 1769    { \+ code_type(H, space) },
 1770    !,
 1771    read_field_value(T).
 1772read_field_value([]) -->
 1773    "".
 1774read_field_value([H|T]) -->
 1775    [H],
 1776    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.
 1783send_reply_header(Out, String) :-
 1784    debug(http(send_reply), "< ~s", [String]),
 1785    format(Out, '~s', [String]).
 1786
 1787send_request_header(Out, String) :-
 1788    debug(http(send_request), "> ~s", [String]),
 1789    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.
 1829http_parse_header_value(Field, Value, Prolog) :-
 1830    known_field(Field, _, Type),
 1831    (   already_parsed(Type, Value)
 1832    ->  Prolog = Value
 1833    ;   to_codes(Value, Codes),
 1834        parse_header_value(Field, Codes, Prolog)
 1835    ).
 1836
 1837already_parsed(integer, V)    :- !, integer(V).
 1838already_parsed(list(Type), L) :- !, is_list(L), maplist(already_parsed(Type), L).
 1839already_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.
 1847known_field(content_length,      true,  integer).
 1848known_field(status,              true,  integer).
 1849known_field(cookie,              true,  list(_=_)).
 1850known_field(set_cookie,          true,  list(set_cookie(_Name,_Value,_Options))).
 1851known_field(host,                true,  _Host:_Port).
 1852known_field(range,               maybe, bytes(_,_)).
 1853known_field(accept,              maybe, list(media(_Type, _Parms, _Q, _Exts))).
 1854known_field(content_disposition, maybe, disposition(_Name, _Attributes)).
 1855known_field(content_type,        false, media(_Type/_Sub, _Attributes)).
 1856
 1857to_codes(In, Codes) :-
 1858    (   is_list(In)
 1859    ->  Codes = In
 1860    ;   atom_codes(In, Codes)
 1861    ).
 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.
 1869field_to_prolog(Field, Codes, Prolog) :-
 1870    known_field(Field, true, _Type),
 1871    !,
 1872    (   parse_header_value(Field, Codes, Prolog0)
 1873    ->  Prolog = Prolog0
 1874    ).
 1875field_to_prolog(Field, Codes, Prolog) :-
 1876    known_field(Field, maybe, _Type),
 1877    parse_header_value(Field, Codes, Prolog0),
 1878    !,
 1879    Prolog = Prolog0.
 1880field_to_prolog(_, Codes, Atom) :-
 1881    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.
 1888parse_header_value(content_length, ValueChars, ContentLength) :-
 1889    number_codes(ContentLength, ValueChars).
 1890parse_header_value(status, ValueChars, Code) :-
 1891    (   phrase(" ", L, _),
 1892        append(Pre, L, ValueChars)
 1893    ->  number_codes(Code, Pre)
 1894    ;   number_codes(Code, ValueChars)
 1895    ).
 1896parse_header_value(cookie, ValueChars, Cookies) :-
 1897    debug(cookie, 'Cookie: ~s', [ValueChars]),
 1898    phrase(cookies(Cookies), ValueChars).
 1899parse_header_value(set_cookie, ValueChars, SetCookie) :-
 1900    debug(cookie, 'SetCookie: ~s', [ValueChars]),
 1901    phrase(set_cookie(SetCookie), ValueChars).
 1902parse_header_value(host, ValueChars, Host) :-
 1903    (   append(HostChars, [0':|PortChars], ValueChars),
 1904        catch(number_codes(Port, PortChars), _, fail)
 1905    ->  atom_codes(HostName, HostChars),
 1906        Host = HostName:Port
 1907    ;   atom_codes(Host, ValueChars)
 1908    ).
 1909parse_header_value(range, ValueChars, Range) :-
 1910    phrase(range(Range), ValueChars).
 1911parse_header_value(accept, ValueChars, Media) :-
 1912    parse_accept(ValueChars, Media).
 1913parse_header_value(content_disposition, ValueChars, Disposition) :-
 1914    phrase(content_disposition(Disposition), ValueChars).
 1915parse_header_value(content_type, ValueChars, Type) :-
 1916    phrase(parse_content_type(Type), ValueChars).
 field_value(+Name, +Value)//
 1920field_value(_, set_cookie(Name, Value, Options)) -->
 1921    !,
 1922    atom(Name), "=", atom(Value),
 1923    value_options(Options, cookie).
 1924field_value(_, disposition(Disposition, Options)) -->
 1925    !,
 1926    atom(Disposition), value_options(Options, disposition).
 1927field_value(www_authenticate, Auth) -->
 1928    auth_field_value(Auth).
 1929field_value(_, Atomic) -->
 1930    atom(Atomic).
 auth_field_value(+AuthValue)//
Emit the authentication requirements (WWW-Authenticate field).
 1936auth_field_value(negotiate(Data)) -->
 1937    "Negotiate ",
 1938    { base64(Data, DataBase64),
 1939      atom_codes(DataBase64, Codes)
 1940    },
 1941    string(Codes).
 1942auth_field_value(negotiate) -->
 1943    "Negotiate".
 1944auth_field_value(basic) -->
 1945    !,
 1946    "Basic".
 1947auth_field_value(basic(Realm)) -->
 1948    "Basic Realm=\"", atom(Realm), "\"".
 1949auth_field_value(digest) -->
 1950    !,
 1951    "Digest".
 1952auth_field_value(digest(Details)) -->
 1953    "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.
 1962value_options([], _) --> [].
 1963value_options([H|T], Field) -->
 1964    "; ", value_option(H, Field),
 1965    value_options(T, Field).
 1966
 1967value_option(secure=true, cookie) -->
 1968    !,
 1969    "secure".
 1970value_option(Name=Value, Type) -->
 1971    { string_option(Name, Type) },
 1972    !,
 1973    atom(Name), "=",
 1974    qstring(Value).
 1975value_option(Name=Value, Type) -->
 1976    { token_option(Name, Type) },
 1977    !,
 1978    atom(Name), "=", atom(Value).
 1979value_option(Name=Value, _Type) -->
 1980    atom(Name), "=",
 1981    option_value(Value).
 1982
 1983string_option(filename, disposition).
 1984
 1985token_option(path, cookie).
 1986
 1987option_value(Value) -->
 1988    { number(Value) },
 1989    !,
 1990    number(Value).
 1991option_value(Value) -->
 1992    { (   atom(Value)
 1993      ->  true
 1994      ;   string(Value)
 1995      ),
 1996      forall(string_code(_, Value, C),
 1997             token_char(C))
 1998    },
 1999    !,
 2000    atom(Value).
 2001option_value(Atomic) -->
 2002    qstring(Atomic).
 2003
 2004qstring(Atomic) -->
 2005    { string_codes(Atomic, Codes) },
 2006    "\"",
 2007    qstring_codes(Codes),
 2008    "\"".
 2009
 2010qstring_codes([]) --> [].
 2011qstring_codes([H|T]) --> qstring_code(H), qstring_codes(T).
 2012
 2013qstring_code(C) --> {qstring_esc(C)}, !, "\\", [C].
 2014qstring_code(C) --> [C].
 2015
 2016qstring_esc(0'").
 2017qstring_esc(C) :- ctl(C).
 2018
 2019
 2020                 /*******************************
 2021                 *        ACCEPT HEADERS        *
 2022                 *******************************/
 2023
 2024:- dynamic accept_cache/2. 2025:- volatile accept_cache/2. 2026
 2027parse_accept(Codes, Media) :-
 2028    atom_codes(Atom, Codes),
 2029    (   accept_cache(Atom, Media0)
 2030    ->  Media = Media0
 2031    ;   phrase(accept(Media0), Codes),
 2032        keysort(Media0, Media1),
 2033        pairs_values(Media1, Media2),
 2034        assertz(accept_cache(Atom, Media2)),
 2035        Media = Media2
 2036    ).
 accept(-Media)// is semidet
Parse an HTTP Accept: header
 2042accept([H|T]) -->
 2043    blanks,
 2044    media_range(H),
 2045    blanks,
 2046    (   ","
 2047    ->  accept(T)
 2048    ;   {T=[]}
 2049    ).
 2050
 2051media_range(s(SortQuality,Spec)-media(Type, TypeParams, Quality, AcceptExts)) -->
 2052    media_type(Type),
 2053    blanks,
 2054    (   ";"
 2055    ->  blanks,
 2056        parameters_and_quality(TypeParams, Quality, AcceptExts)
 2057    ;   { TypeParams = [],
 2058          Quality = 1.0,
 2059          AcceptExts = []
 2060        }
 2061    ),
 2062    { SortQuality is float(-Quality),
 2063      rank_specialised(Type, TypeParams, Spec)
 2064    }.
 content_disposition(-Disposition)//
Parse Content-Disposition value
 2071content_disposition(disposition(Disposition, Options)) -->
 2072    token(Disposition), blanks,
 2073    value_parameters(Options).
 parse_content_type(-Type)//
Parse Content-Type value into a term media(Type/SubType, Parameters).
 2080parse_content_type(media(Type, Parameters)) -->
 2081    media_type(Type), blanks,
 2082    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?
 2093rank_specialised(Type/SubType, TypeParams, v(VT, VS, SortVP)) :-
 2094    var_or_given(Type, VT),
 2095    var_or_given(SubType, VS),
 2096    length(TypeParams, VP),
 2097    SortVP is -VP.
 2098
 2099var_or_given(V, Val) :-
 2100    (   var(V)
 2101    ->  Val = 0
 2102    ;   Val = -1
 2103    ).
 2104
 2105media_type(Type/SubType) -->
 2106    type(Type), "/", type(SubType).
 2107
 2108type(_) -->
 2109    "*",
 2110    !.
 2111type(Type) -->
 2112    token(Type).
 2113
 2114parameters_and_quality(Params, Quality, AcceptExts) -->
 2115    token(Name),
 2116    blanks, "=", blanks,
 2117    (   { Name == q }
 2118    ->  float(Quality), blanks,
 2119        value_parameters(AcceptExts),
 2120        { Params = [] }
 2121    ;   { Params = [Name=Value|T] },
 2122        parameter_value(Value),
 2123        blanks,
 2124        (   ";"
 2125        ->  blanks,
 2126            parameters_and_quality(T, Quality, AcceptExts)
 2127        ;   { T = [],
 2128              Quality = 1.0,
 2129              AcceptExts = []
 2130            }
 2131        )
 2132    ).
 value_parameters(-Params:list) is det
Accept (";" <parameter>)*, returning a list of Name=Value, where both Name and Value are atoms.
 2139value_parameters([H|T]) -->
 2140    ";",
 2141    !,
 2142    blanks, token(Name), blanks,
 2143    (   "="
 2144    ->  blanks,
 2145        (   token(Value)
 2146        ->  []
 2147        ;   quoted_string(Value)
 2148        ),
 2149        { H = (Name=Value) }
 2150    ;   { H = Name }
 2151    ),
 2152    blanks,
 2153    value_parameters(T).
 2154value_parameters([]) -->
 2155    [].
 2156
 2157parameter_value(Value) --> token(Value), !.
 2158parameter_value(Value) --> quoted_string(Value).
 token(-Name)// is semidet
Process an HTTP header token from the input.
 2165token(Name) -->
 2166    token_char(C1),
 2167    token_chars(Cs),
 2168    { atom_codes(Name, [C1|Cs]) }.
 2169
 2170token_chars([H|T]) -->
 2171    token_char(H),
 2172    !,
 2173    token_chars(T).
 2174token_chars([]) --> [].
 2175
 2176token_char(C) :-
 2177    \+ ctl(C),
 2178    \+ separator_code(C).
 2179
 2180ctl(C) :- between(0,31,C), !.
 2181ctl(127).
 2182
 2183separator_code(0'().
 2184separator_code(0')).
 2185separator_code(0'<).
 2186separator_code(0'>).
 2187separator_code(0'@).
 2188separator_code(0',).
 2189separator_code(0';).
 2190separator_code(0':).
 2191separator_code(0'\\).
 2192separator_code(0'").
 2193separator_code(0'/).
 2194separator_code(0'[).
 2195separator_code(0']).
 2196separator_code(0'?).
 2197separator_code(0'=).
 2198separator_code(0'{).
 2199separator_code(0'}).
 2200separator_code(0'\s).
 2201separator_code(0'\t).
 2202
 2203term_expansion(token_char(x) --> [x], Clauses) :-
 2204    findall((token_char(C)-->[C]),
 2205            (   between(0, 255, C),
 2206                token_char(C)
 2207            ),
 2208            Clauses).
 2209
 2210token_char(x) --> [x].
 quoted_string(-Text)// is semidet
True if input starts with a quoted string representing Text.
 2216quoted_string(Text) -->
 2217    "\"",
 2218    quoted_text(Codes),
 2219    { atom_codes(Text, Codes) }.
 2220
 2221quoted_text([]) -->
 2222    "\"",
 2223    !.
 2224quoted_text([H|T]) -->
 2225    "\\", !, [H],
 2226    quoted_text(T).
 2227quoted_text([H|T]) -->
 2228    [H],
 2229    !,
 2230    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.
 2241header_fields([], _) --> [].
 2242header_fields([content_length(CLen)|T], CLen) -->
 2243    !,
 2244    (   { var(CLen) }
 2245    ->  ""
 2246    ;   header_field(content_length, CLen)
 2247    ),
 2248    header_fields(T, CLen).           % Continue or return first only?
 2249header_fields([status(_)|T], CLen) -->   % handled by vstatus//3.
 2250    !,
 2251    header_fields(T, CLen).
 2252header_fields([H|T], CLen) -->
 2253    { H =.. [Name, Value] },
 2254    header_field(Name, Value),
 2255    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
 2272:- public
 2273    field_name//1. 2274
 2275field_name(Name) -->
 2276    { var(Name) },
 2277    !,
 2278    rd_field_chars(Chars),
 2279    { atom_codes(Name, Chars) }.
 2280field_name(mime_version) -->
 2281    !,
 2282    "MIME-Version".
 2283field_name(www_authenticate) -->
 2284    !,
 2285    "WWW-Authenticate".
 2286field_name(Name) -->
 2287    { atom_codes(Name, Chars) },
 2288    wr_field_chars(Chars).
 2289
 2290rd_field_chars_no_fold([C|T]) -->
 2291    [C],
 2292    { rd_field_char(C, _) },
 2293    !,
 2294    rd_field_chars_no_fold(T).
 2295rd_field_chars_no_fold([]) -->
 2296    [].
 2297
 2298rd_field_chars([C0|T]) -->
 2299    [C],
 2300    { rd_field_char(C, C0) },
 2301    !,
 2302    rd_field_chars(T).
 2303rd_field_chars([]) -->
 2304    [].
 separators(-CharCodes) is det
CharCodes is a list of separators according to RFC2616
 2310separators("()<>@,;:\\\"/[]?={} \t").
 2311
 2312term_expansion(rd_field_char('expand me',_), Clauses) :-
 2313
 2314    Clauses = [ rd_field_char(0'-, 0'_)
 2315              | Cls
 2316              ],
 2317    separators(SepString),
 2318    string_codes(SepString, Seps),
 2319    findall(rd_field_char(In, Out),
 2320            (   between(32, 127, In),
 2321                \+ memberchk(In, Seps),
 2322                In \== 0'-,         % 0'
 2323                code_type(Out, to_lower(In))),
 2324            Cls).
 2325
 2326rd_field_char('expand me', _).                  % avoid recursion
 2327
 2328wr_field_chars([C|T]) -->
 2329    !,
 2330    { code_type(C, to_lower(U)) },
 2331    [U],
 2332    wr_field_chars2(T).
 2333wr_field_chars([]) -->
 2334    [].
 2335
 2336wr_field_chars2([]) --> [].
 2337wr_field_chars2([C|T]) -->              % 0'
 2338    (   { C == 0'_ }
 2339    ->  "-",
 2340        wr_field_chars(T)
 2341    ;   [C],
 2342        wr_field_chars2(T)
 2343    ).
 now//
Current time using rfc_date//1.
 2349now -->
 2350    { get_time(Time)
 2351    },
 2352    rfc_date(Time).
 rfc_date(+Time)// is det
Write time according to RFC1123 specification as required by the RFC2616 HTTP protocol specs.
 2359rfc_date(Time, String, Tail) :-
 2360    stamp_date_time(Time, Date, 'UTC'),
 2361    format_time(codes(String, Tail),
 2362                '%a, %d %b %Y %T GMT',
 2363                Date, posix).
 http_timestamp(+Time:timestamp, -Text:atom) is det
Generate a description of a Time in HTTP format (RFC1123)
 2369http_timestamp(Time, Atom) :-
 2370    stamp_date_time(Time, Date, 'UTC'),
 2371    format_time(atom(Atom),
 2372                '%a, %d %b %Y %T GMT',
 2373                Date, posix).
 2374
 2375
 2376                 /*******************************
 2377                 *         REQUEST DCG          *
 2378                 *******************************/
 2379
 2380request(Fd, [method(Method),request_uri(ReqURI)|Header]) -->
 2381    method(Method),
 2382    blanks,
 2383    nonblanks(Query),
 2384    { atom_codes(ReqURI, Query),
 2385      request_uri_parts(ReqURI, Header, Rest)
 2386    },
 2387    request_header(Fd, Rest),
 2388    !.
 2389request(Fd, [unknown(What)|Header]) -->
 2390    string(What),
 2391    eos,
 2392    !,
 2393    {   http_read_header(Fd, Header)
 2394    ->  true
 2395    ;   Header = []
 2396    }.
 2397
 2398method(get)     --> "GET", !.
 2399method(put)     --> "PUT", !.
 2400method(head)    --> "HEAD", !.
 2401method(post)    --> "POST", !.
 2402method(delete)  --> "DELETE", !.
 2403method(patch)   --> "PATCH", !.
 2404method(options) --> "OPTIONS", !.
 2405method(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.
 2419request_uri_parts(ReqURI, [path(Path)|Parts], Rest) :-
 2420    uri_components(ReqURI, Components),
 2421    uri_data(path, Components, PathText),
 2422    uri_encoded(path, Path, PathText),
 2423    phrase(uri_parts(Components), Parts, Rest).
 2424
 2425uri_parts(Components) -->
 2426    uri_search(Components),
 2427    uri_fragment(Components).
 2428
 2429uri_search(Components) -->
 2430    { uri_data(search, Components, Search),
 2431      nonvar(Search),
 2432      catch(uri_query_components(Search, Query),
 2433            error(syntax_error(_),_),
 2434            fail)
 2435    },
 2436    !,
 2437    [ search(Query) ].
 2438uri_search(_) --> [].
 2439
 2440uri_fragment(Components) -->
 2441    { uri_data(fragment, Components, String),
 2442      nonvar(String),
 2443      !,
 2444      uri_encoded(fragment, Fragment, String)
 2445    },
 2446    [ fragment(Fragment) ].
 2447uri_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.
 2454request_header(_, []) -->               % Old-style non-version header
 2455    blanks,
 2456    eos,
 2457    !.
 2458request_header(Fd, [http_version(Version)|Header]) -->
 2459    http_version(Version),
 2460    blanks,
 2461    eos,
 2462    !,
 2463    {   Version = 1-_
 2464    ->  http_read_header(Fd, Header)
 2465    ;   Header = []
 2466    }.
 2467
 2468http_version(Version) -->
 2469    blanks,
 2470    "HTTP/",
 2471    http_version_number(Version).
 2472
 2473http_version_number(Major-Minor) -->
 2474    integer(Major),
 2475    ".",
 2476    integer(Minor).
 2477
 2478
 2479                 /*******************************
 2480                 *            COOKIES           *
 2481                 *******************************/
 cookies(-List)// is semidet
Translate a cookie description into a list Name=Value.
 2487cookies([Name=Value|T]) -->
 2488    blanks,
 2489    cookie(Name, Value),
 2490    !,
 2491    blanks,
 2492    (   ";"
 2493    ->  cookies(T)
 2494    ;   { T = [] }
 2495    ).
 2496cookies(List) -->
 2497    string(Skipped),
 2498    ";",
 2499    !,
 2500    { print_message(warning, http(skipped_cookie(Skipped))) },
 2501    cookies(List).
 2502cookies([]) -->
 2503    blanks.
 2504
 2505cookie(Name, Value) -->
 2506    cookie_name(Name),
 2507    blanks, "=", blanks,
 2508    cookie_value(Value).
 2509
 2510cookie_name(Name) -->
 2511    { var(Name) },
 2512    !,
 2513    rd_field_chars_no_fold(Chars),
 2514    { atom_codes(Name, Chars) }.
 2515
 2516cookie_value(Value) -->
 2517    quoted_string(Value),
 2518    !.
 2519cookie_value(Value) -->
 2520    chars_to_semicolon_or_blank(Chars),
 2521    { atom_codes(Value, Chars)
 2522    }.
 2523
 2524chars_to_semicolon_or_blank([]), ";" -->
 2525    ";",
 2526    !.
 2527chars_to_semicolon_or_blank([]) -->
 2528    " ",
 2529    blanks,
 2530    eos,
 2531    !.
 2532chars_to_semicolon_or_blank([H|T]) -->
 2533    [H],
 2534    !,
 2535    chars_to_semicolon_or_blank(T).
 2536chars_to_semicolon_or_blank([]) -->
 2537    [].
 2538
 2539set_cookie(set_cookie(Name, Value, Options)) -->
 2540    ws,
 2541    cookie(Name, Value),
 2542    cookie_options(Options).
 2543
 2544cookie_options([H|T]) -->
 2545    ws,
 2546    ";",
 2547    ws,
 2548    cookie_option(H),
 2549    !,
 2550    cookie_options(T).
 2551cookie_options([]) -->
 2552    ws.
 2553
 2554ws --> " ", !, ws.
 2555ws --> [].
 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.
 2567cookie_option(Name=Value) -->
 2568    rd_field_chars(NameChars), ws,
 2569    { atom_codes(Name, NameChars) },
 2570    (   "="
 2571    ->  ws,
 2572        chars_to_semicolon(ValueChars),
 2573        { atom_codes(Value, ValueChars)
 2574        }
 2575    ;   { Value = true }
 2576    ).
 2577
 2578chars_to_semicolon([H|T]) -->
 2579    [H],
 2580    { H \== 32, H \== 0'; },
 2581    !,
 2582    chars_to_semicolon(T).
 2583chars_to_semicolon([]), ";" -->
 2584    ws, ";",
 2585    !.
 2586chars_to_semicolon([H|T]) -->
 2587    [H],
 2588    chars_to_semicolon(T).
 2589chars_to_semicolon([]) -->
 2590    [].
 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.
 2600range(bytes(From, To)) -->
 2601    "bytes", whites, "=", whites, integer(From), "-",
 2602    (   integer(To)
 2603    ->  ""
 2604    ;   { To = end }
 2605    ).
 2606
 2607
 2608                 /*******************************
 2609                 *           REPLY DCG          *
 2610                 *******************************/
 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.
 2627reply(Fd, [http_version(HttpVersion), status(Code, Status, Comment)|Header]) -->
 2628    http_version(HttpVersion),
 2629    blanks,
 2630    (   status_number(Status, Code)
 2631    ->  []
 2632    ;   integer(Status)
 2633    ),
 2634    blanks,
 2635    string(CommentCodes),
 2636    blanks_to_nl,
 2637    !,
 2638    blanks,
 2639    { atom_codes(Comment, CommentCodes),
 2640      http_read_header(Fd, Header)
 2641    }.
 2642
 2643
 2644                 /*******************************
 2645                 *            READ HEADER       *
 2646                 *******************************/
 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)
 2654http_read_header(Fd, Header) :-
 2655    read_header_data(Fd, Text),
 2656    http_parse_header(Text, Header).
 2657
 2658read_header_data(Fd, Header) :-
 2659    read_line_to_codes(Fd, Header, Tail),
 2660    read_header_data(Header, Fd, Tail),
 2661    debug(http(header), 'Header = ~n~s~n', [Header]).
 2662
 2663read_header_data([0'\r,0'\n], _, _) :- !.
 2664read_header_data([0'\n], _, _) :- !.
 2665read_header_data([], _, _) :- !.
 2666read_header_data(_, Fd, Tail) :-
 2667    read_line_to_codes(Fd, Tail, NewTail),
 2668    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)
 2677http_parse_header(Text, Header) :-
 2678    phrase(header(Header), Text),
 2679    debug(http(header), 'Field: ~p', [Header]).
 2680
 2681header(List) -->
 2682    header_field(Name, Value),
 2683    !,
 2684    { mkfield(Name, Value, List, Tail)
 2685    },
 2686    blanks,
 2687    header(Tail).
 2688header([]) -->
 2689    blanks,
 2690    eos,
 2691    !.
 2692header(_) -->
 2693    string(S), blanks_to_nl,
 2694    !,
 2695    { string_codes(Line, S),
 2696      syntax_error(http_parameter(Line))
 2697    }.
 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.

 2711:- multifile
 2712    http:http_address//0. 2713
 2714address -->
 2715    http:http_address,
 2716    !.
 2717address -->
 2718    { gethostname(Host) },
 2719    html(address([ a(href('http://www.swi-prolog.org'), 'SWI-Prolog'),
 2720                   ' httpd at ', Host
 2721                 ])).
 2722
 2723mkfield(host, Host:Port, [host(Host),port(Port)|Tail], Tail) :- !.
 2724mkfield(Name, Value, [Att|Tail], Tail) :-
 2725    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.
 2764                 /*******************************
 2765                 *            MESSAGES          *
 2766                 *******************************/
 2767
 2768:- multifile
 2769    prolog:message//1,
 2770    prolog:error_message//1. 2771
 2772prolog:error_message(http_write_short(Data, Sent)) -->
 2773    data(Data),
 2774    [ ': remote hangup after ~D bytes'-[Sent] ].
 2775prolog:error_message(syntax_error(http_request(Request))) -->
 2776    [ 'Illegal HTTP request: ~s'-[Request] ].
 2777prolog:error_message(syntax_error(http_parameter(Line))) -->
 2778    [ 'Illegal HTTP parameter: ~s'-[Line] ].
 2779
 2780prolog:message(http(skipped_cookie(S))) -->
 2781    [ 'Skipped illegal cookie: ~s'-[S] ].
 2782
 2783data(bytes(MimeType, _Bytes)) -->
 2784    !,
 2785    [ 'bytes(~p, ...)'-[MimeType] ].
 2786data(Data) -->
 2787    [ '~p'-[Data] ]