35
36:- module(http_header,
37 [ http_read_request/2, 38 http_read_reply_header/2, 39 http_reply/2, 40 http_reply/3, 41 http_reply/4, 42 http_reply/5, 43 44 http_reply/6, 45 46 http_reply_header/3, 47 http_status_reply/4, 48 http_status_reply/5, 49 50
51 http_timestamp/2, 52
53 http_post_data/3, 54
55 http_read_header/2, 56 http_parse_header/2, 57 http_parse_header_value/3, 58 http_join_headers/3, 59 http_update_encoding/3, 60 http_update_connection/4, 61 http_update_transfer/4 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:- use_module(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, 98 http:status_reply/3, 99 http:serialize_reply/2, 100 http:post_data_hook/3, 101 http:mime_type_encoding/2. 102
104
105:- setting(http:chunked_transfer, oneof([never,on_request,if_possible]),
106 on_request, 'When to use Transfer-Encoding: Chunked'). 107
108
115
116:- discontiguous
117 term_expansion/2. 118
119
120 123
129
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 ).
152
153
158
(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 176
223
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, +). 259
266
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 ).
351
352
383
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).
418
429
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).
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).
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).
473
478
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)).
492
501
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 ).
525
535
550
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), 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]).
745
750
([], 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).
760
761
770
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).
789
794
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).
802mime_type_encoding('application/sparql-results+json', utf8).
803mime_type_encoding('application/sparql-results+xml', utf8).
804
812
813
818
819http_update_connection(CgiHeader, Request, Connect,
820 [connection(Connect)|Rest]) :-
821 select(connection(CgiConn), CgiHeader, Rest),
822 !,
823 connection(Request, ReqConnection),
824 join_connection(ReqConnection, CgiConn, Connect).
825http_update_connection(CgiHeader, Request, Connect,
826 [connection(Connect)|CgiHeader]) :-
827 connection(Request, Connect).
828
829join_connection(Keep1, Keep2, Connection) :-
830 ( downcase_atom(Keep1, 'keep-alive'),
831 downcase_atom(Keep2, 'keep-alive')
832 -> Connection = 'Keep-Alive'
833 ; Connection = close
834 ).
835
836
840
841connection(Header, Close) :-
842 ( memberchk(connection(Connection), Header)
843 -> Close = Connection
844 ; memberchk(http_version(1-X), Header),
845 X >= 1
846 -> Close = 'Keep-Alive'
847 ; Close = close
848 ).
849
850
866
867http_update_transfer(Request, CgiHeader, Transfer, Header) :-
868 setting(http:chunked_transfer, When),
869 http_update_transfer(When, Request, CgiHeader, Transfer, Header).
870
871http_update_transfer(never, _, CgiHeader, none, Header) :-
872 !,
873 delete(CgiHeader, transfer_encoding(_), Header).
874http_update_transfer(_, _, CgiHeader, none, Header) :-
875 memberchk(location(_), CgiHeader),
876 !,
877 delete(CgiHeader, transfer_encoding(_), Header).
878http_update_transfer(_, Request, CgiHeader, Transfer, Header) :-
879 select(transfer_encoding(CgiTransfer), CgiHeader, Rest),
880 !,
881 transfer(Request, ReqConnection),
882 join_transfer(ReqConnection, CgiTransfer, Transfer),
883 ( Transfer == none
884 -> Header = Rest
885 ; Header = [transfer_encoding(Transfer)|Rest]
886 ).
887http_update_transfer(if_possible, Request, CgiHeader, Transfer, Header) :-
888 transfer(Request, Transfer),
889 Transfer \== none,
890 !,
891 Header = [transfer_encoding(Transfer)|CgiHeader].
892http_update_transfer(_, _, CgiHeader, none, CgiHeader).
893
894join_transfer(chunked, chunked, chunked) :- !.
895join_transfer(_, _, none).
896
897
901
902transfer(Header, Transfer) :-
903 ( memberchk(transfer_encoding(Transfer0), Header)
904 -> Transfer = Transfer0
905 ; memberchk(http_version(1-X), Header),
906 X >= 1
907 -> Transfer = chunked
908 ; Transfer = none
909 ).
910
911
917
918content_length_in_encoding(Enc, Stream, Bytes) :-
919 stream_property(Stream, position(Here)),
920 setup_call_cleanup(
921 open_null_stream(Out),
922 ( set_stream(Out, encoding(Enc)),
923 catch(copy_stream_data(Stream, Out), _, fail),
924 flush_output(Out),
925 byte_count(Out, Bytes)
926 ),
927 ( close(Out, [force(true)]),
928 set_stream_position(Stream, Here)
929 )).
930
931
932 935
1036
1037http_post_data(Data, Out, HdrExtra) :-
1038 http:post_data_hook(Data, Out, HdrExtra),
1039 !.
1040http_post_data(html(HTML), Out, HdrExtra) :-
1041 !,
1042 phrase(post_header(html(HTML), HdrExtra), Header),
1043 send_request_header(Out, Header),
1044 print_html(Out, HTML).
1045http_post_data(xml(XML), Out, HdrExtra) :-
1046 !,
1047 http_post_data(xml(text/xml, XML, []), Out, HdrExtra).
1048http_post_data(xml(Type, XML), Out, HdrExtra) :-
1049 !,
1050 http_post_data(xml(Type, XML, []), Out, HdrExtra).
1051http_post_data(xml(Type, XML, Options), Out, HdrExtra) :-
1052 !,
1053 setup_call_cleanup(
1054 new_memory_file(MemFile),
1055 ( setup_call_cleanup(
1056 open_memory_file(MemFile, write, MemOut),
1057 xml_write(MemOut, XML, Options),
1058 close(MemOut)),
1059 http_post_data(memory_file(Type, MemFile), Out, HdrExtra)
1060 ),
1061 free_memory_file(MemFile)).
1062http_post_data(file(File), Out, HdrExtra) :-
1063 !,
1064 ( file_mime_type(File, Type)
1065 -> true
1066 ; Type = text/plain
1067 ),
1068 http_post_data(file(Type, File), Out, HdrExtra).
1069http_post_data(file(Type, File), Out, HdrExtra) :-
1070 !,
1071 phrase(post_header(file(Type, File), HdrExtra), Header),
1072 send_request_header(Out, Header),
1073 setup_call_cleanup(
1074 open(File, read, In, [type(binary)]),
1075 copy_stream_data(In, Out),
1076 close(In)).
1077http_post_data(memory_file(Type, Handle), Out, HdrExtra) :-
1078 !,
1079 phrase(post_header(memory_file(Type, Handle), HdrExtra), Header),
1080 send_request_header(Out, Header),
1081 setup_call_cleanup(
1082 open_memory_file(Handle, read, In, [encoding(octet)]),
1083 copy_stream_data(In, Out),
1084 close(In)).
1085http_post_data(codes(Codes), Out, HdrExtra) :-
1086 !,
1087 http_post_data(codes(text/plain, Codes), Out, HdrExtra).
1088http_post_data(codes(Type, Codes), Out, HdrExtra) :-
1089 !,
1090 phrase(post_header(codes(Type, Codes), HdrExtra), Header),
1091 send_request_header(Out, Header),
1092 setup_call_cleanup(
1093 set_stream(Out, encoding(utf8)),
1094 format(Out, '~s', [Codes]),
1095 set_stream(Out, encoding(octet))).
1096http_post_data(bytes(Type, Bytes), Out, HdrExtra) :-
1097 !,
1098 phrase(post_header(bytes(Type, Bytes), HdrExtra), Header),
1099 send_request_header(Out, Header),
1100 format(Out, '~s', [Bytes]).
1101http_post_data(atom(Atom), Out, HdrExtra) :-
1102 !,
1103 http_post_data(atom(text/plain, Atom), Out, HdrExtra).
1104http_post_data(atom(Type, Atom), Out, HdrExtra) :-
1105 !,
1106 phrase(post_header(atom(Type, Atom), HdrExtra), Header),
1107 send_request_header(Out, Header),
1108 setup_call_cleanup(
1109 set_stream(Out, encoding(utf8)),
1110 write(Out, Atom),
1111 set_stream(Out, encoding(octet))).
1112http_post_data(string(String), Out, HdrExtra) :-
1113 !,
1114 http_post_data(atom(text/plain, String), Out, HdrExtra).
1115http_post_data(string(Type, String), Out, HdrExtra) :-
1116 !,
1117 phrase(post_header(string(Type, String), HdrExtra), Header),
1118 send_request_header(Out, Header),
1119 setup_call_cleanup(
1120 set_stream(Out, encoding(utf8)),
1121 write(Out, String),
1122 set_stream(Out, encoding(octet))).
1123http_post_data(cgi_stream(In, _Len), Out, HdrExtra) :-
1124 !,
1125 debug(obsolete, 'Obsolete 2nd argument in cgi_stream(In,Len)', []),
1126 http_post_data(cgi_stream(In), Out, HdrExtra).
1127http_post_data(cgi_stream(In), Out, HdrExtra) :-
1128 !,
1129 http_read_header(In, Header0),
1130 http_update_encoding(Header0, Encoding, Header),
1131 content_length_in_encoding(Encoding, In, Size),
1132 http_join_headers(HdrExtra, Header, Hdr2),
1133 phrase(post_header(cgi_data(Size), Hdr2), HeaderText),
1134 send_request_header(Out, HeaderText),
1135 setup_call_cleanup(
1136 set_stream(Out, encoding(Encoding)),
1137 copy_stream_data(In, Out),
1138 set_stream(Out, encoding(octet))).
1139http_post_data(form(Fields), Out, HdrExtra) :-
1140 !,
1141 parse_url_search(Codes, Fields),
1142 length(Codes, Size),
1143 http_join_headers(HdrExtra,
1144 [ content_type('application/x-www-form-urlencoded')
1145 ], Header),
1146 phrase(post_header(cgi_data(Size), Header), HeaderChars),
1147 send_request_header(Out, HeaderChars),
1148 format(Out, '~s', [Codes]).
1149http_post_data(form_data(Data), Out, HdrExtra) :-
1150 !,
1151 setup_call_cleanup(
1152 new_memory_file(MemFile),
1153 ( setup_call_cleanup(
1154 open_memory_file(MemFile, write, MimeOut),
1155 mime_pack(Data, MimeOut, Boundary),
1156 close(MimeOut)),
1157 size_memory_file(MemFile, Size, octet),
1158 format(string(ContentType),
1159 'multipart/form-data; boundary=~w', [Boundary]),
1160 http_join_headers(HdrExtra,
1161 [ mime_version('1.0'),
1162 content_type(ContentType)
1163 ], Header),
1164 phrase(post_header(cgi_data(Size), Header), HeaderChars),
1165 send_request_header(Out, HeaderChars),
1166 setup_call_cleanup(
1167 open_memory_file(MemFile, read, In, [encoding(octet)]),
1168 copy_stream_data(In, Out),
1169 close(In))
1170 ),
1171 free_memory_file(MemFile)).
1172http_post_data(List, Out, HdrExtra) :- 1173 is_list(List),
1174 !,
1175 setup_call_cleanup(
1176 new_memory_file(MemFile),
1177 ( setup_call_cleanup(
1178 open_memory_file(MemFile, write, MimeOut),
1179 mime_pack(List, MimeOut, Boundary),
1180 close(MimeOut)),
1181 size_memory_file(MemFile, Size, octet),
1182 format(string(ContentType),
1183 'multipart/mixed; boundary=~w', [Boundary]),
1184 http_join_headers(HdrExtra,
1185 [ mime_version('1.0'),
1186 content_type(ContentType)
1187 ], Header),
1188 phrase(post_header(cgi_data(Size), Header), HeaderChars),
1189 send_request_header(Out, HeaderChars),
1190 setup_call_cleanup(
1191 open_memory_file(MemFile, read, In, [encoding(octet)]),
1192 copy_stream_data(In, Out),
1193 close(In))
1194 ),
1195 free_memory_file(MemFile)).
1196
1201
(html(Tokens), HdrExtra) -->
1203 header_fields(HdrExtra, Len),
1204 content_length(html(Tokens), Len),
1205 content_type(text/html),
1206 "\r\n".
1207post_header(file(Type, File), HdrExtra) -->
1208 header_fields(HdrExtra, Len),
1209 content_length(file(File), Len),
1210 content_type(Type),
1211 "\r\n".
1212post_header(memory_file(Type, File), HdrExtra) -->
1213 header_fields(HdrExtra, Len),
1214 content_length(memory_file(File), Len),
1215 content_type(Type),
1216 "\r\n".
1217post_header(cgi_data(Size), HdrExtra) -->
1218 header_fields(HdrExtra, Len),
1219 content_length(Size, Len),
1220 "\r\n".
1221post_header(codes(Type, Codes), HdrExtra) -->
1222 header_fields(HdrExtra, Len),
1223 content_length(codes(Codes, utf8), Len),
1224 content_type(Type, utf8),
1225 "\r\n".
1226post_header(bytes(Type, Bytes), HdrExtra) -->
1227 header_fields(HdrExtra, Len),
1228 content_length(bytes(Bytes), Len),
1229 content_type(Type),
1230 "\r\n".
1231post_header(atom(Type, Atom), HdrExtra) -->
1232 header_fields(HdrExtra, Len),
1233 content_length(atom(Atom, utf8), Len),
1234 content_type(Type, utf8),
1235 "\r\n".
1236post_header(string(Type, String), HdrExtra) -->
1237 header_fields(HdrExtra, Len),
1238 content_length(string(String, utf8), Len),
1239 content_type(Type, utf8),
1240 "\r\n".
1241
1242
1243 1246
1251
(Out, What, HdrExtra) :-
1253 phrase(reply_header(What, HdrExtra, _Code), String),
1254 !,
1255 send_reply_header(Out, String).
1256
1278
(Data, Dict) -->
1280 { _{header:HdrExtra, code:Code} :< Dict },
1281 reply_header(Data, HdrExtra, Code).
1282
(string(String), HdrExtra, Code) -->
1284 reply_header(string(text/plain, String), HdrExtra, Code).
1285reply_header(string(Type, String), HdrExtra, Code) -->
1286 vstatus(ok, Code, HdrExtra),
1287 date(now),
1288 header_fields(HdrExtra, CLen),
1289 content_length(codes(String, utf8), CLen),
1290 content_type(Type, utf8),
1291 "\r\n".
1292reply_header(bytes(Type, Bytes), HdrExtra, Code) -->
1293 vstatus(ok, Code, HdrExtra),
1294 date(now),
1295 header_fields(HdrExtra, CLen),
1296 content_length(bytes(Bytes), CLen),
1297 content_type(Type),
1298 "\r\n".
1299reply_header(html(Tokens), HdrExtra, Code) -->
1300 vstatus(ok, Code, HdrExtra),
1301 date(now),
1302 header_fields(HdrExtra, CLen),
1303 content_length(html(Tokens), CLen),
1304 content_type(text/html),
1305 "\r\n".
1306reply_header(file(Type, File), HdrExtra, Code) -->
1307 vstatus(ok, Code, HdrExtra),
1308 date(now),
1309 modified(file(File)),
1310 header_fields(HdrExtra, CLen),
1311 content_length(file(File), CLen),
1312 content_type(Type),
1313 "\r\n".
1314reply_header(gzip_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 content_encoding(gzip),
1322 "\r\n".
1323reply_header(file(Type, File, Range), HdrExtra, Code) -->
1324 vstatus(partial_content, Code, HdrExtra),
1325 date(now),
1326 modified(file(File)),
1327 header_fields(HdrExtra, CLen),
1328 content_length(file(File, Range), CLen),
1329 content_type(Type),
1330 "\r\n".
1331reply_header(tmp_file(Type, File), HdrExtra, Code) -->
1332 vstatus(ok, Code, HdrExtra),
1333 date(now),
1334 header_fields(HdrExtra, CLen),
1335 content_length(file(File), CLen),
1336 content_type(Type),
1337 "\r\n".
1338reply_header(cgi_data(Size), HdrExtra, Code) -->
1339 vstatus(ok, Code, HdrExtra),
1340 date(now),
1341 header_fields(HdrExtra, CLen),
1342 content_length(Size, CLen),
1343 "\r\n".
1344reply_header(chunked_data, HdrExtra, Code) -->
1345 vstatus(ok, Code, HdrExtra),
1346 date(now),
1347 header_fields(HdrExtra, _),
1348 ( {memberchk(transfer_encoding(_), HdrExtra)}
1349 -> ""
1350 ; transfer_encoding(chunked)
1351 ),
1352 "\r\n".
1354reply_header(status(Status), HdrExtra, Code) -->
1355 vstatus(Status, Code),
1356 header_fields(HdrExtra, Clen),
1357 { Clen = 0 },
1358 "\r\n".
1360reply_header(Data, HdrExtra, Code) -->
1361 { status_reply_headers(Data,
1362 body(Type, Encoding, Content),
1363 ReplyHeaders),
1364 http_join_headers(ReplyHeaders, HdrExtra, Headers),
1365 functor(Data, CodeName, _)
1366 },
1367 vstatus(CodeName, Code, Headers),
1368 date(now),
1369 header_fields(Headers, CLen),
1370 content_length(codes(Content, Encoding), CLen),
1371 content_type(Type, Encoding),
1372 "\r\n".
1373
(created(Location, Body), Body,
1375 [ location(Location) ]).
1376status_reply_headers(moved(To, Body), Body,
1377 [ location(To) ]).
1378status_reply_headers(moved_temporary(To, Body), Body,
1379 [ location(To) ]).
1380status_reply_headers(gone(_URL, Body), Body, []).
1381status_reply_headers(see_other(To, Body), Body,
1382 [ location(To) ]).
1383status_reply_headers(authorise(Method, Body), Body,
1384 [ www_authenticate(Method) ]).
1385status_reply_headers(not_found(_URL, Body), Body, []).
1386status_reply_headers(forbidden(_URL, Body), Body, []).
1387status_reply_headers(method_not_allowed(_Method, _URL, Body), Body, []).
1388status_reply_headers(server_error(_Error, Body), Body, []).
1389status_reply_headers(service_unavailable(_Why, Body), Body, []).
1390status_reply_headers(not_acceptable(_Why, Body), Body, []).
1391status_reply_headers(bad_request(_Error, Body), Body, []).
1392
1393
1398
1399vstatus(_Status, Code, HdrExtra) -->
1400 {memberchk(status(Code), HdrExtra)},
1401 !,
1402 vstatus(_NewStatus, Code).
1403vstatus(Status, Code, _) -->
1404 vstatus(Status, Code).
1405
1406vstatus(Status, Code) -->
1407 "HTTP/1.1 ",
1408 status_number(Status, Code),
1409 " ",
1410 status_comment(Status),
1411 "\r\n".
1412
1419
1420status_number(Status, Code) -->
1421 { var(Status) },
1422 !,
1423 integer(Code),
1424 { status_number(Status, Code) },
1425 !.
1426status_number(Status, Code) -->
1427 { status_number(Status, Code) },
1428 integer(Code).
1429
1441
1449
1450status_number(Status, Code) :-
1451 nonvar(Status),
1452 !,
1453 status_number_fact(Status, Code).
1454status_number(Status, Code) :-
1455 nonvar(Code),
1456 !,
1457 ( between(100, 599, Code)
1458 -> ( status_number_fact(Status, Code)
1459 -> true
1460 ; ClassCode is Code // 100 * 100,
1461 status_number_fact(Status, ClassCode)
1462 )
1463 ; domain_error(http_code, Code)
1464 ).
1465
1466status_number_fact(continue, 100).
1467status_number_fact(switching_protocols, 101).
1468status_number_fact(ok, 200).
1469status_number_fact(created, 201).
1470status_number_fact(accepted, 202).
1471status_number_fact(non_authoritative_info, 203).
1472status_number_fact(no_content, 204).
1473status_number_fact(reset_content, 205).
1474status_number_fact(partial_content, 206).
1475status_number_fact(multiple_choices, 300).
1476status_number_fact(moved, 301).
1477status_number_fact(moved_temporary, 302).
1478status_number_fact(see_other, 303).
1479status_number_fact(not_modified, 304).
1480status_number_fact(use_proxy, 305).
1481status_number_fact(unused, 306).
1482status_number_fact(temporary_redirect, 307).
1483status_number_fact(bad_request, 400).
1484status_number_fact(authorise, 401).
1485status_number_fact(payment_required, 402).
1486status_number_fact(forbidden, 403).
1487status_number_fact(not_found, 404).
1488status_number_fact(method_not_allowed, 405).
1489status_number_fact(not_acceptable, 406).
1490status_number_fact(request_timeout, 408).
1491status_number_fact(conflict, 409).
1492status_number_fact(gone, 410).
1493status_number_fact(length_required, 411).
1494status_number_fact(payload_too_large, 413).
1495status_number_fact(uri_too_long, 414).
1496status_number_fact(unsupported_media_type, 415).
1497status_number_fact(expectation_failed, 417).
1498status_number_fact(upgrade_required, 426).
1499status_number_fact(server_error, 500).
1500status_number_fact(not_implemented, 501).
1501status_number_fact(bad_gateway, 502).
1502status_number_fact(service_unavailable, 503).
1503status_number_fact(gateway_timeout, 504).
1504status_number_fact(http_version_not_supported, 505).
1505
1506
1510
(continue) -->
1512 "Continue".
1513status_comment(switching_protocols) -->
1514 "Switching Protocols".
1515status_comment(ok) -->
1516 "OK".
1517status_comment(created) -->
1518 "Created".
1519status_comment(accepted) -->
1520 "Accepted".
1521status_comment(non_authoritative_info) -->
1522 "Non-Authoritative Information".
1523status_comment(no_content) -->
1524 "No Content".
1525status_comment(reset_content) -->
1526 "Reset Content".
1527status_comment(created) -->
1528 "Created".
1529status_comment(partial_content) -->
1530 "Partial content".
1531status_comment(multiple_choices) -->
1532 "Multiple Choices".
1533status_comment(moved) -->
1534 "Moved Permanently".
1535status_comment(moved_temporary) -->
1536 "Moved Temporary".
1537status_comment(see_other) -->
1538 "See Other".
1539status_comment(not_modified) -->
1540 "Not Modified".
1541status_comment(use_proxy) -->
1542 "Use Proxy".
1543status_comment(unused) -->
1544 "Unused".
1545status_comment(temporary_redirect) -->
1546 "Temporary Redirect".
1547status_comment(bad_request) -->
1548 "Bad Request".
1549status_comment(authorise) -->
1550 "Authorization Required".
1551status_comment(payment_required) -->
1552 "Payment Required".
1553status_comment(forbidden) -->
1554 "Forbidden".
1555status_comment(not_found) -->
1556 "Not Found".
1557status_comment(method_not_allowed) -->
1558 "Method Not Allowed".
1559status_comment(not_acceptable) -->
1560 "Not Acceptable".
1561status_comment(request_timeout) -->
1562 "Request Timeout".
1563status_comment(conflict) -->
1564 "Conflict".
1565status_comment(gone) -->
1566 "Gone".
1567status_comment(length_required) -->
1568 "Length Required".
1569status_comment(payload_too_large) -->
1570 "Payload Too Large".
1571status_comment(uri_too_long) -->
1572 "URI Too Long".
1573status_comment(unsupported_media_type) -->
1574 "Unsupported Media Type".
1575status_comment(expectation_failed) -->
1576 "Expectation Failed".
1577status_comment(upgrade_required) -->
1578 "Upgrade Required".
1579status_comment(server_error) -->
1580 "Internal Server Error".
1581status_comment(not_implemented) -->
1582 "Not Implemented".
1583status_comment(bad_gateway) -->
1584 "Bad Gateway".
1585status_comment(service_unavailable) -->
1586 "Service Unavailable".
1587status_comment(gateway_timeout) -->
1588 "Gateway Timeout".
1589status_comment(http_version_not_supported) -->
1590 "HTTP Version Not Supported".
1591
1592date(Time) -->
1593 "Date: ",
1594 ( { Time == now }
1595 -> now
1596 ; rfc_date(Time)
1597 ),
1598 "\r\n".
1599
1600modified(file(File)) -->
1601 !,
1602 { time_file(File, Time)
1603 },
1604 modified(Time).
1605modified(Time) -->
1606 "Last-modified: ",
1607 ( { Time == now }
1608 -> now
1609 ; rfc_date(Time)
1610 ),
1611 "\r\n".
1612
1613
1620
1621content_length(file(File, bytes(From, To)), Len) -->
1622 !,
1623 { size_file(File, Size),
1624 ( To == end
1625 -> Len is Size - From,
1626 RangeEnd is Size - 1
1627 ; Len is To+1 - From, 1628 RangeEnd = To
1629 )
1630 },
1631 content_range(bytes, From, RangeEnd, Size),
1632 content_length(Len, Len).
1633content_length(Reply, Len) -->
1634 { length_of(Reply, Len)
1635 },
1636 "Content-Length: ", integer(Len),
1637 "\r\n".
1638
1639
1640length_of(_, Len) :-
1641 nonvar(Len),
1642 !.
1643length_of(string(String, Encoding), Len) :-
1644 length_of(codes(String, Encoding), Len).
1645length_of(codes(String, Encoding), Len) :-
1646 !,
1647 setup_call_cleanup(
1648 open_null_stream(Out),
1649 ( set_stream(Out, encoding(Encoding)),
1650 format(Out, '~s', [String]),
1651 byte_count(Out, Len)
1652 ),
1653 close(Out)).
1654length_of(atom(Atom, Encoding), Len) :-
1655 !,
1656 setup_call_cleanup(
1657 open_null_stream(Out),
1658 ( set_stream(Out, encoding(Encoding)),
1659 format(Out, '~a', [Atom]),
1660 byte_count(Out, Len)
1661 ),
1662 close(Out)).
1663length_of(file(File), Len) :-
1664 !,
1665 size_file(File, Len).
1666length_of(memory_file(Handle), Len) :-
1667 !,
1668 size_memory_file(Handle, Len, octet).
1669length_of(html_tokens(Tokens), Len) :-
1670 !,
1671 html_print_length(Tokens, Len).
1672length_of(html(Tokens), Len) :- 1673 !,
1674 html_print_length(Tokens, Len).
1675length_of(bytes(Bytes), Len) :-
1676 !,
1677 ( string(Bytes)
1678 -> string_length(Bytes, Len)
1679 ; length(Bytes, Len) 1680 ).
1681length_of(Len, Len).
1682
1683
1688
1689content_range(Unit, From, RangeEnd, Size) -->
1690 "Content-Range: ", atom(Unit), " ",
1691 integer(From), "-", integer(RangeEnd), "/", integer(Size),
1692 "\r\n".
1693
1694content_encoding(Encoding) -->
1695 "Content-Encoding: ", atom(Encoding), "\r\n".
1696
1697transfer_encoding(Encoding) -->
1698 "Transfer-Encoding: ", atom(Encoding), "\r\n".
1699
1700content_type(Type) -->
1701 content_type(Type, _).
1702
1703content_type(Type, Charset) -->
1704 ctype(Type),
1705 charset(Charset),
1706 "\r\n".
1707
1708ctype(Main/Sub) -->
1709 !,
1710 "Content-Type: ",
1711 atom(Main),
1712 "/",
1713 atom(Sub).
1714ctype(Type) -->
1715 !,
1716 "Content-Type: ",
1717 atom(Type).
1718
1719charset(Var) -->
1720 { var(Var) },
1721 !.
1722charset(utf8) -->
1723 !,
1724 "; charset=UTF-8".
1725charset(CharSet) -->
1726 "; charset=",
1727 atom(CharSet).
1728
1734
(Name, Value) -->
1736 { var(Name) }, 1737 !,
1738 field_name(Name),
1739 ":",
1740 whites,
1741 read_field_value(ValueChars),
1742 blanks_to_nl,
1743 !,
1744 { field_to_prolog(Name, ValueChars, Value)
1745 -> true
1746 ; atom_codes(Value, ValueChars),
1747 domain_error(Name, Value)
1748 }.
1749header_field(Name, Value) -->
1750 field_name(Name),
1751 ": ",
1752 field_value(Name, Value),
1753 "\r\n".
1754
1758
1759read_field_value([H|T]) -->
1760 [H],
1761 { \+ code_type(H, space) },
1762 !,
1763 read_field_value(T).
1764read_field_value([]) -->
1765 "".
1766read_field_value([H|T]) -->
1767 [H],
1768 read_field_value(T).
1769
1774
(Out, String) :-
1776 debug(http(send_reply), "< ~s", [String]),
1777 format(Out, '~s', [String]).
1778
(Out, String) :-
1780 debug(http(send_request), "> ~s", [String]),
1781 format(Out, '~s', [String]).
1782
1820
(Field, Value, Prolog) :-
1822 known_field(Field, _, Type),
1823 ( already_parsed(Type, Value)
1824 -> Prolog = Value
1825 ; to_codes(Value, Codes),
1826 parse_header_value(Field, Codes, Prolog)
1827 ).
1828
1829already_parsed(integer, V) :- !, integer(V).
1830already_parsed(list(Type), L) :- !, is_list(L), maplist(already_parsed(Type), L).
1831already_parsed(Term, V) :- subsumes_term(Term, V).
1832
1833
1838
1839known_field(content_length, true, integer).
1840known_field(status, true, integer).
1841known_field(cookie, true, list(_=_)).
1842known_field(set_cookie, true, list(set_cookie(_Name,_Value,_Options))).
1843known_field(host, true, _Host:_Port).
1844known_field(range, maybe, bytes(_,_)).
1845known_field(accept, maybe, list(media(_Type, _Parms, _Q, _Exts))).
1846known_field(content_disposition, maybe, disposition(_Name, _Attributes)).
1847known_field(content_type, false, media(_Type/_Sub, _Attributes)).
1848
1849to_codes(In, Codes) :-
1850 ( is_list(In)
1851 -> Codes = In
1852 ; atom_codes(In, Codes)
1853 ).
1854
1860
1861field_to_prolog(Field, Codes, Prolog) :-
1862 known_field(Field, true, _Type),
1863 !,
1864 ( parse_header_value(Field, Codes, Prolog0)
1865 -> Prolog = Prolog0
1866 ).
1867field_to_prolog(Field, Codes, Prolog) :-
1868 known_field(Field, maybe, _Type),
1869 parse_header_value(Field, Codes, Prolog0),
1870 !,
1871 Prolog = Prolog0.
1872field_to_prolog(_, Codes, Atom) :-
1873 atom_codes(Atom, Codes).
1874
1879
(content_length, ValueChars, ContentLength) :-
1881 number_codes(ContentLength, ValueChars).
1882parse_header_value(status, ValueChars, Code) :-
1883 ( phrase(" ", L, _),
1884 append(Pre, L, ValueChars)
1885 -> number_codes(Code, Pre)
1886 ; number_codes(Code, ValueChars)
1887 ).
1888parse_header_value(cookie, ValueChars, Cookies) :-
1889 debug(cookie, 'Cookie: ~s', [ValueChars]),
1890 phrase(cookies(Cookies), ValueChars).
1891parse_header_value(set_cookie, ValueChars, SetCookie) :-
1892 debug(cookie, 'SetCookie: ~s', [ValueChars]),
1893 phrase(set_cookie(SetCookie), ValueChars).
1894parse_header_value(host, ValueChars, Host) :-
1895 ( append(HostChars, [0':|PortChars], ValueChars),
1896 catch(number_codes(Port, PortChars), _, fail)
1897 -> atom_codes(HostName, HostChars),
1898 Host = HostName:Port
1899 ; atom_codes(Host, ValueChars)
1900 ).
1901parse_header_value(range, ValueChars, Range) :-
1902 phrase(range(Range), ValueChars).
1903parse_header_value(accept, ValueChars, Media) :-
1904 parse_accept(ValueChars, Media).
1905parse_header_value(content_disposition, ValueChars, Disposition) :-
1906 phrase(content_disposition(Disposition), ValueChars).
1907parse_header_value(content_type, ValueChars, Type) :-
1908 phrase(parse_content_type(Type), ValueChars).
1909
1911
1912field_value(_, set_cookie(Name, Value, Options)) -->
1913 !,
1914 atom(Name), "=", atom(Value),
1915 value_options(Options, cookie).
1916field_value(_, disposition(Disposition, Options)) -->
1917 !,
1918 atom(Disposition), value_options(Options, disposition).
1919field_value(www_authenticate, Auth) -->
1920 auth_field_value(Auth).
1921field_value(_, Atomic) -->
1922 atom(Atomic).
1923
1927
1928auth_field_value(negotiate(Data)) -->
1929 "Negotiate ",
1930 { base64(Data, DataBase64),
1931 atom_codes(DataBase64, Codes)
1932 },
1933 string(Codes).
1934auth_field_value(negotiate) -->
1935 "Negotiate".
1936auth_field_value(basic) -->
1937 !,
1938 "Basic".
1939auth_field_value(basic(Realm)) -->
1940 "Basic Realm=\"", atom(Realm), "\"".
1941auth_field_value(digest) -->
1942 !,
1943 "Digest".
1944auth_field_value(digest(Details)) -->
1945 "Digest ", atom(Details).
1946
1953
1954value_options([], _) --> [].
1955value_options([H|T], Field) -->
1956 "; ", value_option(H, Field),
1957 value_options(T, Field).
1958
1959value_option(secure=true, cookie) -->
1960 !,
1961 "secure".
1962value_option(Name=Value, Type) -->
1963 { string_option(Name, Type) },
1964 !,
1965 atom(Name), "=",
1966 qstring(Value).
1967value_option(Name=Value, Type) -->
1968 { token_option(Name, Type) },
1969 !,
1970 atom(Name), "=", atom(Value).
1971value_option(Name=Value, _Type) -->
1972 atom(Name), "=",
1973 option_value(Value).
1974
1975string_option(filename, disposition).
1976
1977token_option(path, cookie).
1978
1979option_value(Value) -->
1980 { number(Value) },
1981 !,
1982 number(Value).
1983option_value(Value) -->
1984 { ( atom(Value)
1985 -> true
1986 ; string(Value)
1987 ),
1988 forall(string_code(_, Value, C),
1989 token_char(C))
1990 },
1991 !,
1992 atom(Value).
1993option_value(Atomic) -->
1994 qstring(Atomic).
1995
1996qstring(Atomic) -->
1997 { string_codes(Atomic, Codes) },
1998 "\"",
1999 qstring_codes(Codes),
2000 "\"".
2001
2002qstring_codes([]) --> [].
2003qstring_codes([H|T]) --> qstring_code(H), qstring_codes(T).
2004
2005qstring_code(C) --> {qstring_esc(C)}, !, "\\", [C].
2006qstring_code(C) --> [C].
2007
2008qstring_esc(0'").
2009qstring_esc(C) :- ctl(C).
2010
2011
2012 2015
2016:- dynamic accept_cache/2. 2017:- volatile accept_cache/2. 2018
2019parse_accept(Codes, Media) :-
2020 atom_codes(Atom, Codes),
2021 ( accept_cache(Atom, Media0)
2022 -> Media = Media0
2023 ; phrase(accept(Media0), Codes),
2024 keysort(Media0, Media1),
2025 pairs_values(Media1, Media2),
2026 assertz(accept_cache(Atom, Media2)),
2027 Media = Media2
2028 ).
2029
2033
2034accept([H|T]) -->
2035 blanks,
2036 media_range(H),
2037 blanks,
2038 ( ","
2039 -> accept(T)
2040 ; {T=[]}
2041 ).
2042
2043media_range(s(SortQuality,Spec)-media(Type, TypeParams, Quality, AcceptExts)) -->
2044 media_type(Type),
2045 blanks,
2046 ( ";"
2047 -> blanks,
2048 parameters_and_quality(TypeParams, Quality, AcceptExts)
2049 ; { TypeParams = [],
2050 Quality = 1.0,
2051 AcceptExts = []
2052 }
2053 ),
2054 { SortQuality is float(-Quality),
2055 rank_specialised(Type, TypeParams, Spec)
2056 }.
2057
2058
2062
2063content_disposition(disposition(Disposition, Options)) -->
2064 token(Disposition), blanks,
2065 value_parameters(Options).
2066
2071
2072parse_content_type(media(Type, Parameters)) -->
2073 media_type(Type), blanks,
2074 value_parameters(Parameters).
2075
2076
2084
2085rank_specialised(Type/SubType, TypeParams, v(VT, VS, SortVP)) :-
2086 var_or_given(Type, VT),
2087 var_or_given(SubType, VS),
2088 length(TypeParams, VP),
2089 SortVP is -VP.
2090
2091var_or_given(V, Val) :-
2092 ( var(V)
2093 -> Val = 0
2094 ; Val = -1
2095 ).
2096
2097media_type(Type/SubType) -->
2098 type(Type), "/", type(SubType).
2099
2100type(_) -->
2101 "*",
2102 !.
2103type(Type) -->
2104 token(Type).
2105
2106parameters_and_quality(Params, Quality, AcceptExts) -->
2107 token(Name),
2108 blanks, "=", blanks,
2109 ( { Name == q }
2110 -> float(Quality), blanks,
2111 value_parameters(AcceptExts),
2112 { Params = [] }
2113 ; { Params = [Name=Value|T] },
2114 parameter_value(Value),
2115 blanks,
2116 ( ";"
2117 -> blanks,
2118 parameters_and_quality(T, Quality, AcceptExts)
2119 ; { T = [],
2120 Quality = 1.0,
2121 AcceptExts = []
2122 }
2123 )
2124 ).
2125
2130
2131value_parameters([H|T]) -->
2132 ";",
2133 !,
2134 blanks, token(Name), blanks,
2135 ( "="
2136 -> blanks,
2137 ( token(Value)
2138 -> []
2139 ; quoted_string(Value)
2140 ),
2141 { H = (Name=Value) }
2142 ; { H = Name }
2143 ),
2144 blanks,
2145 value_parameters(T).
2146value_parameters([]) -->
2147 [].
2148
2149parameter_value(Value) --> token(Value), !.
2150parameter_value(Value) --> quoted_string(Value).
2151
2152
2156
2157token(Name) -->
2158 token_char(C1),
2159 token_chars(Cs),
2160 { atom_codes(Name, [C1|Cs]) }.
2161
2162token_chars([H|T]) -->
2163 token_char(H),
2164 !,
2165 token_chars(T).
2166token_chars([]) --> [].
2167
2168token_char(C) :-
2169 \+ ctl(C),
2170 \+ separator_code(C).
2171
2172ctl(C) :- between(0,31,C), !.
2173ctl(127).
2174
2175separator_code(0'().
2176separator_code(0')).
2177separator_code(0'<).
2178separator_code(0'>).
2179separator_code(0'@).
2180separator_code(0',).
2181separator_code(0';).
2182separator_code(0':).
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'\s).
2193separator_code(0'\t).
2194
2195term_expansion(token_char(x) --> [x], Clauses) :-
2196 findall((token_char(C)-->[C]),
2197 ( between(0, 255, C),
2198 token_char(C)
2199 ),
2200 Clauses).
2201
2202token_char(x) --> [x].
2203
2207
2208quoted_string(Text) -->
2209 "\"",
2210 quoted_text(Codes),
2211 { atom_codes(Text, Codes) }.
2212
2213quoted_text([]) -->
2214 "\"",
2215 !.
2216quoted_text([H|T]) -->
2217 "\\", !, [H],
2218 quoted_text(T).
2219quoted_text([H|T]) -->
2220 [H],
2221 !,
2222 quoted_text(T).
2223
2224
2232
([], _) --> [].
2234header_fields([content_length(CLen)|T], CLen) -->
2235 !,
2236 ( { var(CLen) }
2237 -> ""
2238 ; header_field(content_length, CLen)
2239 ),
2240 header_fields(T, CLen). 2241header_fields([status(_)|T], CLen) --> 2242 !,
2243 header_fields(T, CLen).
2244header_fields([H|T], CLen) -->
2245 { H =.. [Name, Value] },
2246 header_field(Name, Value),
2247 header_fields(T, CLen).
2248
2249
2263
2264:- public
2265 field_name//1. 2266
2267field_name(Name) -->
2268 { var(Name) },
2269 !,
2270 rd_field_chars(Chars),
2271 { atom_codes(Name, Chars) }.
2272field_name(mime_version) -->
2273 !,
2274 "MIME-Version".
2275field_name(www_authenticate) -->
2276 !,
2277 "WWW-Authenticate".
2278field_name(Name) -->
2279 { atom_codes(Name, Chars) },
2280 wr_field_chars(Chars).
2281
2282rd_field_chars_no_fold([C|T]) -->
2283 [C],
2284 { rd_field_char(C, _) },
2285 !,
2286 rd_field_chars_no_fold(T).
2287rd_field_chars_no_fold([]) -->
2288 [].
2289
2290rd_field_chars([C0|T]) -->
2291 [C],
2292 { rd_field_char(C, C0) },
2293 !,
2294 rd_field_chars(T).
2295rd_field_chars([]) -->
2296 [].
2297
2301
2302separators("()<>@,;:\\\"/[]?={} \t").
2303
2304term_expansion(rd_field_char('expand me',_), Clauses) :-
2305
2306 Clauses = [ rd_field_char(0'-, 0'_)
2307 | Cls
2308 ],
2309 separators(SepString),
2310 string_codes(SepString, Seps),
2311 findall(rd_field_char(In, Out),
2312 ( between(32, 127, In),
2313 \+ memberchk(In, Seps),
2314 In \== 0'-, 2315 code_type(Out, to_lower(In))),
2316 Cls).
2317
2318rd_field_char('expand me', _). 2319
2320wr_field_chars([C|T]) -->
2321 !,
2322 { code_type(C, to_lower(U)) },
2323 [U],
2324 wr_field_chars2(T).
2325wr_field_chars([]) -->
2326 [].
2327
2328wr_field_chars2([]) --> [].
2329wr_field_chars2([C|T]) --> 2330 ( { C == 0'_ }
2331 -> "-",
2332 wr_field_chars(T)
2333 ; [C],
2334 wr_field_chars2(T)
2335 ).
2336
2340
2341now -->
2342 { get_time(Time)
2343 },
2344 rfc_date(Time).
2345
2350
2351rfc_date(Time, String, Tail) :-
2352 stamp_date_time(Time, Date, 'UTC'),
2353 format_time(codes(String, Tail),
2354 '%a, %d %b %Y %T GMT',
2355 Date, posix).
2356
2360
2361http_timestamp(Time, Atom) :-
2362 stamp_date_time(Time, Date, 'UTC'),
2363 format_time(atom(Atom),
2364 '%a, %d %b %Y %T GMT',
2365 Date, posix).
2366
2367
2368 2371
2372request(Fd, [method(Method),request_uri(ReqURI)|Header]) -->
2373 method(Method),
2374 blanks,
2375 nonblanks(Query),
2376 { atom_codes(ReqURI, Query),
2377 request_uri_parts(ReqURI, Header, Rest)
2378 },
2379 request_header(Fd, Rest),
2380 !.
2381request(Fd, [unknown(What)|Header]) -->
2382 string(What),
2383 eos,
2384 !,
2385 { http_read_header(Fd, Header)
2386 -> true
2387 ; Header = []
2388 }.
2389
2390method(get) --> "GET", !.
2391method(put) --> "PUT", !.
2392method(head) --> "HEAD", !.
2393method(post) --> "POST", !.
2394method(delete) --> "DELETE", !.
2395method(patch) --> "PATCH", !.
2396method(options) --> "OPTIONS", !.
2397method(trace) --> "TRACE", !.
2398
2410
2411request_uri_parts(ReqURI, [path(Path)|Parts], Rest) :-
2412 uri_components(ReqURI, Components),
2413 uri_data(path, Components, PathText),
2414 uri_encoded(path, Path, PathText),
2415 phrase(uri_parts(Components), Parts, Rest).
2416
2417uri_parts(Components) -->
2418 uri_search(Components),
2419 uri_fragment(Components).
2420
2421uri_search(Components) -->
2422 { uri_data(search, Components, Search),
2423 nonvar(Search),
2424 catch(uri_query_components(Search, Query),
2425 error(syntax_error(_),_),
2426 fail)
2427 },
2428 !,
2429 [ search(Query) ].
2430uri_search(_) --> [].
2431
2432uri_fragment(Components) -->
2433 { uri_data(fragment, Components, String),
2434 nonvar(String),
2435 !,
2436 uri_encoded(fragment, Fragment, String)
2437 },
2438 [ fragment(Fragment) ].
2439uri_fragment(_) --> [].
2440
2445
(_, []) --> 2447 blanks,
2448 eos,
2449 !.
2450request_header(Fd, [http_version(Version)|Header]) -->
2451 http_version(Version),
2452 blanks,
2453 eos,
2454 !,
2455 { Version = 1-_
2456 -> http_read_header(Fd, Header)
2457 ; Header = []
2458 }.
2459
2460http_version(Version) -->
2461 blanks,
2462 "HTTP/",
2463 http_version_number(Version).
2464
2465http_version_number(Major-Minor) -->
2466 integer(Major),
2467 ".",
2468 integer(Minor).
2469
2470
2471 2474
2478
2479cookies([Name=Value|T]) -->
2480 blanks,
2481 cookie(Name, Value),
2482 !,
2483 blanks,
2484 ( ";"
2485 -> cookies(T)
2486 ; { T = [] }
2487 ).
2488cookies(List) -->
2489 string(Skipped),
2490 ";",
2491 !,
2492 { print_message(warning, http(skipped_cookie(Skipped))) },
2493 cookies(List).
2494cookies([]) -->
2495 blanks.
2496
2497cookie(Name, Value) -->
2498 cookie_name(Name),
2499 blanks, "=", blanks,
2500 cookie_value(Value).
2501
2502cookie_name(Name) -->
2503 { var(Name) },
2504 !,
2505 rd_field_chars_no_fold(Chars),
2506 { atom_codes(Name, Chars) }.
2507
2508cookie_value(Value) -->
2509 quoted_string(Value),
2510 !.
2511cookie_value(Value) -->
2512 chars_to_semicolon_or_blank(Chars),
2513 { atom_codes(Value, Chars)
2514 }.
2515
2516chars_to_semicolon_or_blank([]), ";" -->
2517 ";",
2518 !.
2519chars_to_semicolon_or_blank([]) -->
2520 " ",
2521 blanks,
2522 eos,
2523 !.
2524chars_to_semicolon_or_blank([H|T]) -->
2525 [H],
2526 !,
2527 chars_to_semicolon_or_blank(T).
2528chars_to_semicolon_or_blank([]) -->
2529 [].
2530
2531set_cookie(set_cookie(Name, Value, Options)) -->
2532 ws,
2533 cookie(Name, Value),
2534 cookie_options(Options).
2535
2536cookie_options([H|T]) -->
2537 ws,
2538 ";",
2539 ws,
2540 cookie_option(H),
2541 !,
2542 cookie_options(T).
2543cookie_options([]) -->
2544 ws.
2545
2546ws --> " ", !, ws.
2547ws --> [].
2548
2549
2558
2559cookie_option(Name=Value) -->
2560 rd_field_chars(NameChars), ws,
2561 { atom_codes(Name, NameChars) },
2562 ( "="
2563 -> ws,
2564 chars_to_semicolon(ValueChars),
2565 { atom_codes(Value, ValueChars)
2566 }
2567 ; { Value = true }
2568 ).
2569
2570chars_to_semicolon([H|T]) -->
2571 [H],
2572 { H \== 32, H \== 0'; },
2573 !,
2574 chars_to_semicolon(T).
2575chars_to_semicolon([]), ";" -->
2576 ws, ";",
2577 !.
2578chars_to_semicolon([H|T]) -->
2579 [H],
2580 chars_to_semicolon(T).
2581chars_to_semicolon([]) -->
2582 [].
2583
2591
2592range(bytes(From, To)) -->
2593 "bytes", whites, "=", whites, integer(From), "-",
2594 ( integer(To)
2595 -> ""
2596 ; { To = end }
2597 ).
2598
2599
2600 2603
2618
2619reply(Fd, [http_version(HttpVersion), status(Code, Status, Comment)|Header]) -->
2620 http_version(HttpVersion),
2621 blanks,
2622 ( status_number(Status, Code)
2623 -> []
2624 ; integer(Status)
2625 ),
2626 blanks,
2627 string(CommentCodes),
2628 blanks_to_nl,
2629 !,
2630 blanks,
2631 { atom_codes(Comment, CommentCodes),
2632 http_read_header(Fd, Header)
2633 }.
2634
2635
2636 2639
2645
(Fd, Header) :-
2647 read_header_data(Fd, Text),
2648 http_parse_header(Text, Header).
2649
(Fd, Header) :-
2651 read_line_to_codes(Fd, Header, Tail),
2652 read_header_data(Header, Fd, Tail),
2653 debug(http(header), 'Header = ~n~s~n', [Header]).
2654
([0'\r,0'\n], _, _) :- !.
2656read_header_data([0'\n], _, _) :- !.
2657read_header_data([], _, _) :- !.
2658read_header_data(_, Fd, Tail) :-
2659 read_line_to_codes(Fd, Tail, NewTail),
2660 read_header_data(Tail, Fd, NewTail).
2661
2668
(Text, Header) :-
2670 phrase(header(Header), Text),
2671 debug(http(header), 'Field: ~p', [Header]).
2672
(List) -->
2674 header_field(Name, Value),
2675 !,
2676 { mkfield(Name, Value, List, Tail)
2677 },
2678 blanks,
2679 header(Tail).
2680header([]) -->
2681 blanks,
2682 eos,
2683 !.
2684header(_) -->
2685 string(S), blanks_to_nl,
2686 !,
2687 { string_codes(Line, S),
2688 syntax_error(http_parameter(Line))
2689 }.
2690
2702
2703:- multifile
2704 http:http_address//0. 2705
2706address -->
2707 http:http_address,
2708 !.
2709address -->
2710 { gethostname(Host) },
2711 html(address([ a(href('http://www.swi-prolog.org'), 'SWI-Prolog'),
2712 ' httpd at ', Host
2713 ])).
2714
2715mkfield(host, Host:Port, [host(Host),port(Port)|Tail], Tail) :- !.
2716mkfield(Name, Value, [Att|Tail], Tail) :-
2717 Att =.. [Name, Value].
2718
2724
2754
2755
2756 2759
2760:- multifile
2761 prolog:message//1,
2762 prolog:error_message//1. 2763
2764prolog:error_message(http_write_short(Data, Sent)) -->
2765 data(Data),
2766 [ ': remote hangup after ~D bytes'-[Sent] ].
2767prolog:error_message(syntax_error(http_request(Request))) -->
2768 [ 'Illegal HTTP request: ~s'-[Request] ].
2769prolog:error_message(syntax_error(http_parameter(Line))) -->
2770 [ 'Illegal HTTP parameter: ~s'-[Line] ].
2771
2772prolog:message(http(skipped_cookie(S))) -->
2773 [ 'Skipped illegal cookie: ~s'-[S] ].
2774
2775data(bytes(MimeType, _Bytes)) -->
2776 !,
2777 [ 'bytes(~p, ...)'-[MimeType] ].
2778data(Data) -->
2779 [ '~p'-[Data] ]