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:- use_module(library(readutil)). 64:- use_module(library(debug)). 65:- use_module(library(error)). 66:- use_module(library(option)). 67:- use_module(library(lists)). 68:- use_module(library(url)). 69:- use_module(library(uri)). 70:- use_module(library(memfile)). 71:- use_module(library(settings)). 72:- use_module(library(error)). 73:- use_module(library(pairs)). 74:- use_module(library(socket)). 75:- use_module(library(dcg/basics)). 76:- use_module(html_write). 77:- use_module(http_exception). 78:- use_module(mimetype). 79:- use_module(mimepack). 80
81:- multifile
82 http:status_page/3, 83 http:post_data_hook/3, 84 http:mime_type_encoding/2. 85
87
88:- setting(http:chunked_transfer, oneof([never,on_request,if_possible]),
89 on_request, 'When to use Transfer-Encoding: Chunked'). 90
91
98
99
100 103
109
110http_read_request(In, Request) :-
111 catch(read_line_to_codes(In, Codes), E, true),
112 ( var(E)
113 -> ( Codes == end_of_file
114 -> debug(http(header), 'end-of-file', []),
115 Request = end_of_file
116 ; debug(http(header), 'First line: ~s', [Codes]),
117 Request = [input(In)|Request1],
118 phrase(request(In, Request1), Codes),
119 ( Request1 = [unknown(Text)|_]
120 -> string_codes(S, Text),
121 syntax_error(http_request(S))
122 ; true
123 )
124 )
125 ; ( debugging(http(request))
126 -> message_to_string(E, Msg),
127 debug(http(request), "Exception reading 1st line: ~s", [Msg])
128 ; true
129 ),
130 Request = end_of_file
131 ).
132
133
138
(In, [input(In)|Reply]) :-
140 read_line_to_codes(In, Codes),
141 ( Codes == end_of_file
142 -> debug(http(header), 'end-of-file', []),
143 throw(error(syntax(http_reply_header, end_of_file), _))
144 ; debug(http(header), 'First line: ~s~n', [Codes]),
145 ( phrase(reply(In, Reply), Codes)
146 -> true
147 ; atom_codes(Header, Codes),
148 syntax_error(http_reply_header(Header))
149 )
150 ).
151
152
153 156
203
204http_reply(What, Out) :-
205 http_reply(What, Out, [connection(close)], _).
206
207http_reply(Data, Out, HdrExtra) :-
208 http_reply(Data, Out, HdrExtra, _Code).
209
210http_reply(Data, Out, HdrExtra, Code) :-
211 http_reply(Data, Out, HdrExtra, [], Code).
212
213http_reply(Data, Out, HdrExtra, Context, Code) :-
214 http_reply(Data, Out, HdrExtra, Context, [method(get)], Code).
215
216http_reply(Data, Out, HdrExtra, _Context, Request, Code) :-
217 byte_count(Out, C0),
218 memberchk(method(Method), Request),
219 catch(http_reply_data(Data, Out, HdrExtra, Method, Code), E, true),
220 !,
221 ( var(E)
222 -> true
223 ; E = error(io_error(write, _), _)
224 -> byte_count(Out, C1),
225 Sent is C1 - C0,
226 throw(error(http_write_short(Data, Sent), _))
227 ; E = error(timeout_error(write, _), _)
228 -> throw(E)
229 ; map_exception_to_http_status(E, Status, NewHdr, NewContext),
230 http_status_reply(Status, Out, NewHdr, NewContext, Request, Code)
231 ).
232http_reply(Status, Out, HdrExtra, Context, Request, Code) :-
233 http_status_reply(Status, Out, HdrExtra, Context, Request, Code).
234
235:- meta_predicate
236 if_no_head(+, 0). 237
244
245http_reply_data(Data, Out, HdrExtra, Method, Code) :-
246 http_reply_data_(Data, Out, HdrExtra, Method, Code),
247 flush_output(Out).
248
249http_reply_data_(html(HTML), Out, HdrExtra, Method, Code) :-
250 !,
251 phrase(reply_header(html(HTML), HdrExtra, Code), Header),
252 format(Out, '~s', [Header]),
253 if_no_head(Method, print_html(Out, HTML)).
254http_reply_data_(file(Type, File), Out, HdrExtra, Method, Code) :-
255 !,
256 phrase(reply_header(file(Type, File), HdrExtra, Code), Header),
257 reply_file(Out, File, Header, Method).
258http_reply_data_(gzip_file(Type, File), Out, HdrExtra, Method, Code) :-
259 !,
260 phrase(reply_header(gzip_file(Type, File), HdrExtra, Code), Header),
261 reply_file(Out, File, Header, Method).
262http_reply_data_(file(Type, File, Range), Out, HdrExtra, Method, Code) :-
263 !,
264 phrase(reply_header(file(Type, File, Range), HdrExtra, Code), Header),
265 reply_file_range(Out, File, Header, Range, Method).
266http_reply_data_(tmp_file(Type, File), Out, HdrExtra, Method, Code) :-
267 !,
268 phrase(reply_header(tmp_file(Type, File), HdrExtra, Code), Header),
269 reply_file(Out, File, Header, Method).
270http_reply_data_(bytes(Type, Bytes), Out, HdrExtra, Method, Code) :-
271 !,
272 phrase(reply_header(bytes(Type, Bytes), HdrExtra, Code), Header),
273 format(Out, '~s', [Header]),
274 if_no_head(Method, format(Out, '~s', [Bytes])).
275http_reply_data_(stream(In, Len), Out, HdrExtra, Method, Code) :-
276 !,
277 phrase(reply_header(cgi_data(Len), HdrExtra, Code), Header),
278 copy_stream(Out, In, Header, Method, 0, end).
279http_reply_data_(cgi_stream(In, Len), Out, HdrExtra, Method, Code) :-
280 !,
281 http_read_header(In, CgiHeader),
282 seek(In, 0, current, Pos),
283 Size is Len - Pos,
284 http_join_headers(HdrExtra, CgiHeader, Hdr2),
285 phrase(reply_header(cgi_data(Size), Hdr2, Code), Header),
286 copy_stream(Out, In, Header, Method, 0, end).
287
288if_no_head(head, _) :- !.
289if_no_head(_, Goal) :-
290 call(Goal).
291
292reply_file(Out, _File, Header, head) :-
293 !,
294 format(Out, '~s', [Header]).
295reply_file(Out, File, Header, _) :-
296 setup_call_cleanup(
297 open(File, read, In, [type(binary)]),
298 copy_stream(Out, In, Header, 0, end),
299 close(In)).
300
301reply_file_range(Out, _File, Header, _Range, head) :-
302 !,
303 format(Out, '~s', [Header]).
304reply_file_range(Out, File, Header, bytes(From, To), _) :-
305 setup_call_cleanup(
306 open(File, read, In, [type(binary)]),
307 copy_stream(Out, In, Header, From, To),
308 close(In)).
309
310copy_stream(Out, _, Header, head, _, _) :-
311 !,
312 format(Out, '~s', [Header]).
313copy_stream(Out, In, Header, _, From, To) :-
314 copy_stream(Out, In, Header, From, To).
315
316copy_stream(Out, In, Header, From, To) :-
317 ( From == 0
318 -> true
319 ; seek(In, From, bof, _)
320 ),
321 peek_byte(In, _),
322 format(Out, '~s', [Header]),
323 ( To == end
324 -> copy_stream_data(In, Out)
325 ; Len is To - From,
326 copy_stream_data(In, Out, Len)
327 ).
328
329
360
361http_status_reply(Status, Out, HdrExtra, Code) :-
362 http_status_reply(Status, Out, HdrExtra, [], Code).
363
364http_status_reply(Status, Out, HdrExtra, Context, Code) :-
365 http_status_reply(Status, Out, HdrExtra, Context, [method(get)], Code).
366
367http_status_reply(Status, Out, HdrExtra, Context, Request, Code) :-
368 option(method(Method), Request, get),
369 setup_call_cleanup(
370 set_stream(Out, encoding(utf8)),
371 status_reply_flush(Status, Out, HdrExtra, Context, Method, Code),
372 set_stream(Out, encoding(octet))),
373 !.
374
375status_reply_flush(Status, Out, HdrExtra, Context, Method, Code) :-
376 status_reply(Status, Out, HdrExtra, Context, Method, Code),
377 flush_output(Out).
378
379status_reply(no_content, Out, HdrExtra, _Context, _Method, Code) :-
380 !,
381 phrase(reply_header(status(no_content), HdrExtra, Code), Header),
382 format(Out, '~s', [Header]).
383status_reply(switching_protocols(_Goal,Options), Out,
384 HdrExtra0, _Context, _Method, Code) :-
385 !,
386 ( option(headers(Extra1), Options)
387 -> true
388 ; option(header(Extra1), Options, [])
389 ),
390 http_join_headers(HdrExtra0, Extra1, HdrExtra),
391 phrase(reply_header(status(switching_protocols), HdrExtra, Code), Header),
392 format(Out, '~s', [Header]).
393status_reply(created(Location), Out, HdrExtra, _Context, Method, Code) :-
394 !,
395 phrase(page([ title('201 Created')
396 ],
397 [ h1('Created'),
398 p(['The document was created ',
399 a(href(Location), ' Here')
400 ]),
401 \address
402 ]),
403 HTML),
404 phrase(reply_header(created(Location, HTML), HdrExtra, Code), Header),
405 format(Out, '~s', [Header]),
406 print_html_if_no_head(Method, Out, HTML).
407status_reply(moved(To), Out, HdrExtra, _Context, Method, Code) :-
408 !,
409 phrase(page([ title('301 Moved Permanently')
410 ],
411 [ h1('Moved Permanently'),
412 p(['The document has moved ',
413 a(href(To), ' Here')
414 ]),
415 \address
416 ]),
417 HTML),
418 phrase(reply_header(moved(To, HTML), HdrExtra, Code), Header),
419 format(Out, '~s', [Header]),
420 print_html_if_no_head(Method, Out, HTML).
421status_reply(moved_temporary(To), Out, HdrExtra, _Context, Method, Code) :-
422 !,
423 phrase(page([ title('302 Moved Temporary')
424 ],
425 [ h1('Moved Temporary'),
426 p(['The document is currently ',
427 a(href(To), ' Here')
428 ]),
429 \address
430 ]),
431 HTML),
432 phrase(reply_header(moved_temporary(To, HTML),
433 HdrExtra, Code), Header),
434 format(Out, '~s', [Header]),
435 print_html_if_no_head(Method, Out, HTML).
436status_reply(see_other(To),Out,HdrExtra, _Context, Method, Code) :-
437 !,
438 phrase(page([ title('303 See Other')
439 ],
440 [ h1('See Other'),
441 p(['See other document ',
442 a(href(To), ' Here')
443 ]),
444 \address
445 ]),
446 HTML),
447 phrase(reply_header(see_other(To, HTML), HdrExtra, Code), Header),
448 format(Out, '~s', [Header]),
449 print_html_if_no_head(Method, Out, HTML).
450status_reply(bad_request(ErrorTerm), Out, HdrExtra, _Context, Method, Code) :-
451 !,
452 '$messages':translate_message(ErrorTerm, Lines, []),
453 phrase(page([ title('400 Bad Request')
454 ],
455 [ h1('Bad Request'),
456 p(\html_message_lines(Lines)),
457 \address
458 ]),
459 HTML),
460 phrase(reply_header(status(bad_request, HTML),
461 HdrExtra, Code), Header),
462 format(Out, '~s', [Header]),
463 print_html_if_no_head(Method, Out, HTML).
464status_reply(not_found(URL), Out, HdrExtra, Context, Method, Code) :-
465 !,
466 status_page_hook(not_found(URL), 404, Context, HTML),
467 phrase(reply_header(status(not_found, HTML), HdrExtra, Code), Header),
468 format(Out, '~s', [Header]),
469 print_html_if_no_head(Method, Out, HTML).
470status_reply(method_not_allowed(Method, URL), Out, HdrExtra, Context, QMethod, Code) :-
471 !,
472 upcase_atom(Method, UMethod),
473 status_page_hook(method_not_allowed(UMethod,URL), 405, Context, HTML),
474 phrase(reply_header(status(method_not_allowed, HTML),
475 HdrExtra, Code), Header),
476 format(Out, '~s', [Header]),
477 if_no_head(QMethod, print_html(Out, HTML)).
478status_reply(forbidden(URL), Out, HdrExtra, Context, Method, Code) :-
479 !,
480 status_page_hook(forbidden(URL), 403, Context, HTML),
481 phrase(reply_header(status(forbidden, HTML), HdrExtra, Code), Header),
482 format(Out, '~s', [Header]),
483 print_html_if_no_head(Method, Out, HTML).
484status_reply(authorise(basic, ''), Out, HdrExtra, Context, Method, Code) :-
485 !,
486 status_reply(authorise(basic), Out, HdrExtra, Context, Method, Code).
487status_reply(authorise(basic, Realm), Out, HdrExtra, Context, Method, Code) :-
488 !,
489 status_reply(authorise(basic(Realm)), Out, HdrExtra, Context,
490 Method, Code).
491status_reply(authorise(Method), Out, HdrExtra, Context, QMethod, Code) :-
492 !,
493 status_page_hook(authorise(Method), 401, Context, HTML),
494 phrase(reply_header(authorise(Method, HTML),
495 HdrExtra, Code), Header),
496 format(Out, '~s', [Header]),
497 print_html_if_no_head(QMethod, Out, HTML).
498status_reply(not_modified, Out, HdrExtra, _Context, _Method, Code) :-
499 !,
500 phrase(reply_header(status(not_modified), HdrExtra, Code), Header),
501 format(Out, '~s', [Header]).
502status_reply(server_error(ErrorTerm), Out, HdrExtra, _Context, Method, Code) :-
503 in_or_exclude_backtrace(ErrorTerm, ErrorTerm1),
504 '$messages':translate_message(ErrorTerm1, Lines, []),
505 phrase(page([ title('500 Internal server error')
506 ],
507 [ h1('Internal server error'),
508 p(\html_message_lines(Lines)),
509 \address
510 ]),
511 HTML),
512 phrase(reply_header(status(server_error, HTML),
513 HdrExtra, Code), Header),
514 format(Out, '~s', [Header]),
515 print_html_if_no_head(Method, Out, HTML).
516status_reply(not_acceptable(WhyHTML), Out, HdrExtra, _Context,
517 Method, Code) :-
518 !,
519 phrase(page([ title('406 Not Acceptable')
520 ],
521 [ h1('Not Acceptable'),
522 WhyHTML,
523 \address
524 ]),
525 HTML),
526 phrase(reply_header(status(not_acceptable, HTML), HdrExtra, Code), Header),
527 format(Out, '~s', [Header]),
528 print_html_if_no_head(Method, Out, HTML).
529status_reply(unavailable(WhyHTML), Out, HdrExtra, _Context, Method, Code) :-
530 !,
531 phrase(page([ title('503 Service Unavailable')
532 ],
533 [ h1('Service Unavailable'),
534 WhyHTML,
535 \address
536 ]),
537 HTML),
538 phrase(reply_header(status(service_unavailable, HTML), HdrExtra, Code),
539 Header),
540 format(Out, '~s', [Header]),
541 print_html_if_no_head(Method, Out, HTML).
542status_reply(resource_error(ErrorTerm), Out, HdrExtra, Context, Method, Code) :-
543 !,
544 '$messages':translate_message(ErrorTerm, Lines, []),
545 status_reply(unavailable(p(\html_message_lines(Lines))),
546 Out, HdrExtra, Context, Method, Code).
547status_reply(busy, Out, HdrExtra, Context, Method, Code) :-
548 !,
549 HTML = p(['The server is temporarily out of resources, ',
550 'please try again later']),
551 http_status_reply(unavailable(HTML), Out, HdrExtra, Context,
552 Method, Code).
553
554print_html_if_no_head(head, _, _) :- !.
555print_html_if_no_head(_, Out, HTML) :-
556 print_html(Out, HTML).
557
565
566status_page_hook(Term, Status, Context, HTML) :-
567 ( http:status_page(Term, Context, HTML)
568 ; http:status_page(Status, Context, HTML) 569 ),
570 !.
571
572status_page_hook(authorise(_Method), 401, _Context, HTML):-
573 phrase(page([ title('401 Authorization Required')
574 ],
575 [ h1('Authorization Required'),
576 p(['This server could not verify that you ',
577 'are authorized to access the document ',
578 'requested. Either you supplied the wrong ',
579 'credentials (e.g., bad password), or your ',
580 'browser doesn\'t understand how to supply ',
581 'the credentials required.'
582 ]),
583 \address
584 ]),
585 HTML).
586status_page_hook(forbidden(URL), 403, _Context, HTML) :-
587 phrase(page([ title('403 Forbidden')
588 ],
589 [ h1('Forbidden'),
590 p(['You don\'t have permission to access ', URL,
591 ' on this server'
592 ]),
593 \address
594 ]),
595 HTML).
596status_page_hook(not_found(URL), 404, _Context, HTML) :-
597 phrase(page([ title('404 Not Found')
598 ],
599 [ h1('Not Found'),
600 p(['The requested URL ', tt(URL),
601 ' was not found on this server'
602 ]),
603 \address
604 ]),
605 HTML).
606status_page_hook(method_not_allowed(UMethod,URL), 405, _Context, HTML) :-
607 phrase(page([ title('405 Method not allowed')
608 ],
609 [ h1('Method not allowed'),
610 p(['The requested URL ', tt(URL),
611 ' does not support method ', tt(UMethod), '.'
612 ]),
613 \address
614 ]),
615 HTML).
616
617
618html_message_lines([]) -->
619 [].
620html_message_lines([nl|T]) -->
621 !,
622 html([br([])]),
623 html_message_lines(T).
624html_message_lines([flush]) -->
625 [].
626html_message_lines([Fmt-Args|T]) -->
627 !,
628 { format(string(S), Fmt, Args)
629 },
630 html([S]),
631 html_message_lines(T).
632html_message_lines([Fmt|T]) -->
633 !,
634 { format(string(S), Fmt, [])
635 },
636 html([S]),
637 html_message_lines(T).
638
643
([], H, H).
645http_join_headers([H|T], Hdr0, Hdr) :-
646 functor(H, N, A),
647 functor(H2, N, A),
648 member(H2, Hdr0),
649 !,
650 http_join_headers(T, Hdr0, Hdr).
651http_join_headers([H|T], Hdr0, [H|Hdr]) :-
652 http_join_headers(T, Hdr0, Hdr).
653
654
663
664http_update_encoding(Header0, utf8, [content_type(Type)|Header]) :-
665 select(content_type(Type0), Header0, Header),
666 sub_atom(Type0, 0, _, _, 'text/'),
667 !,
668 ( sub_atom(Type0, S, _, _, ';')
669 -> sub_atom(Type0, 0, S, _, B)
670 ; B = Type0
671 ),
672 atom_concat(B, '; charset=UTF-8', Type).
673http_update_encoding(Header, Encoding, Header) :-
674 memberchk(content_type(Type), Header),
675 ( ( sub_atom(Type, _, _, _, 'UTF-8')
676 ; sub_atom(Type, _, _, _, 'utf-8')
677 )
678 -> Encoding = utf8
679 ; http:mime_type_encoding(Type, Encoding)
680 -> true
681 ; mime_type_encoding(Type, Encoding)
682 ).
683http_update_encoding(Header, octet, Header).
684
689
690mime_type_encoding('application/json', utf8).
691mime_type_encoding('application/jsonrequest', utf8).
692mime_type_encoding('application/x-prolog', utf8).
693mime_type_encoding('application/n-quads', utf8).
694mime_type_encoding('application/n-triples', utf8).
695mime_type_encoding('application/sparql-query', utf8).
696mime_type_encoding('application/trig', utf8).
697
705
706
711
712http_update_connection(CgiHeader, Request, Connect,
713 [connection(Connect)|Rest]) :-
714 select(connection(CgiConn), CgiHeader, Rest),
715 !,
716 connection(Request, ReqConnection),
717 join_connection(ReqConnection, CgiConn, Connect).
718http_update_connection(CgiHeader, Request, Connect,
719 [connection(Connect)|CgiHeader]) :-
720 connection(Request, Connect).
721
722join_connection(Keep1, Keep2, Connection) :-
723 ( downcase_atom(Keep1, 'keep-alive'),
724 downcase_atom(Keep2, 'keep-alive')
725 -> Connection = 'Keep-Alive'
726 ; Connection = close
727 ).
728
729
733
734connection(Header, Close) :-
735 ( memberchk(connection(Connection), Header)
736 -> Close = Connection
737 ; memberchk(http_version(1-X), Header),
738 X >= 1
739 -> Close = 'Keep-Alive'
740 ; Close = close
741 ).
742
743
759
760http_update_transfer(Request, CgiHeader, Transfer, Header) :-
761 setting(http:chunked_transfer, When),
762 http_update_transfer(When, Request, CgiHeader, Transfer, Header).
763
764http_update_transfer(never, _, CgiHeader, none, Header) :-
765 !,
766 delete(CgiHeader, transfer_encoding(_), Header).
767http_update_transfer(_, _, CgiHeader, none, Header) :-
768 memberchk(location(_), CgiHeader),
769 !,
770 delete(CgiHeader, transfer_encoding(_), Header).
771http_update_transfer(_, Request, CgiHeader, Transfer, Header) :-
772 select(transfer_encoding(CgiTransfer), CgiHeader, Rest),
773 !,
774 transfer(Request, ReqConnection),
775 join_transfer(ReqConnection, CgiTransfer, Transfer),
776 ( Transfer == none
777 -> Header = Rest
778 ; Header = [transfer_encoding(Transfer)|Rest]
779 ).
780http_update_transfer(if_possible, Request, CgiHeader, Transfer, Header) :-
781 transfer(Request, Transfer),
782 Transfer \== none,
783 !,
784 Header = [transfer_encoding(Transfer)|CgiHeader].
785http_update_transfer(_, _, CgiHeader, none, CgiHeader).
786
787join_transfer(chunked, chunked, chunked) :- !.
788join_transfer(_, _, none).
789
790
794
795transfer(Header, Transfer) :-
796 ( memberchk(transfer_encoding(Transfer0), Header)
797 -> Transfer = Transfer0
798 ; memberchk(http_version(1-X), Header),
799 X >= 1
800 -> Transfer = chunked
801 ; Transfer = none
802 ).
803
804
810
811content_length_in_encoding(Enc, Stream, Bytes) :-
812 stream_property(Stream, position(Here)),
813 setup_call_cleanup(
814 open_null_stream(Out),
815 ( set_stream(Out, encoding(Enc)),
816 catch(copy_stream_data(Stream, Out), _, fail),
817 flush_output(Out),
818 byte_count(Out, Bytes)
819 ),
820 ( close(Out, [force(true)]),
821 set_stream_position(Stream, Here)
822 )).
823
824
825 828
919
920http_post_data(Data, Out, HdrExtra) :-
921 http:post_data_hook(Data, Out, HdrExtra),
922 !.
923http_post_data(html(HTML), Out, HdrExtra) :-
924 !,
925 phrase(post_header(html(HTML), HdrExtra), Header),
926 format(Out, '~s', [Header]),
927 print_html(Out, HTML).
928http_post_data(xml(XML), Out, HdrExtra) :-
929 !,
930 http_post_data(xml(text/xml, XML, []), Out, HdrExtra).
931http_post_data(xml(Type, XML), Out, HdrExtra) :-
932 !,
933 http_post_data(xml(Type, XML, []), Out, HdrExtra).
934http_post_data(xml(Type, XML, Options), Out, HdrExtra) :-
935 !,
936 setup_call_cleanup(
937 new_memory_file(MemFile),
938 ( setup_call_cleanup(
939 open_memory_file(MemFile, write, MemOut),
940 xml_write(MemOut, XML, Options),
941 close(MemOut)),
942 http_post_data(memory_file(Type, MemFile), Out, HdrExtra)
943 ),
944 free_memory_file(MemFile)).
945http_post_data(file(File), Out, HdrExtra) :-
946 !,
947 ( file_mime_type(File, Type)
948 -> true
949 ; Type = text/plain
950 ),
951 http_post_data(file(Type, File), Out, HdrExtra).
952http_post_data(file(Type, File), Out, HdrExtra) :-
953 !,
954 phrase(post_header(file(Type, File), HdrExtra), Header),
955 format(Out, '~s', [Header]),
956 setup_call_cleanup(
957 open(File, read, In, [type(binary)]),
958 copy_stream_data(In, Out),
959 close(In)).
960http_post_data(memory_file(Type, Handle), Out, HdrExtra) :-
961 !,
962 phrase(post_header(memory_file(Type, Handle), HdrExtra), Header),
963 format(Out, '~s', [Header]),
964 setup_call_cleanup(
965 open_memory_file(Handle, read, In, [encoding(octet)]),
966 copy_stream_data(In, Out),
967 close(In)).
968http_post_data(codes(Codes), Out, HdrExtra) :-
969 !,
970 http_post_data(codes(text/plain, Codes), Out, HdrExtra).
971http_post_data(codes(Type, Codes), Out, HdrExtra) :-
972 !,
973 phrase(post_header(codes(Type, Codes), HdrExtra), Header),
974 format(Out, '~s', [Header]),
975 setup_call_cleanup(
976 set_stream(Out, encoding(utf8)),
977 format(Out, '~s', [Codes]),
978 set_stream(Out, encoding(octet))).
979http_post_data(bytes(Type, Bytes), Out, HdrExtra) :-
980 !,
981 phrase(post_header(bytes(Type, Bytes), HdrExtra), Header),
982 format(Out, '~s~s', [Header, Bytes]).
983http_post_data(atom(Atom), Out, HdrExtra) :-
984 !,
985 http_post_data(atom(text/plain, Atom), Out, HdrExtra).
986http_post_data(atom(Type, Atom), Out, HdrExtra) :-
987 !,
988 phrase(post_header(atom(Type, Atom), HdrExtra), Header),
989 format(Out, '~s', [Header]),
990 setup_call_cleanup(
991 set_stream(Out, encoding(utf8)),
992 write(Out, Atom),
993 set_stream(Out, encoding(octet))).
994http_post_data(cgi_stream(In, _Len), Out, HdrExtra) :-
995 !,
996 debug(obsolete, 'Obsolete 2nd argument in cgi_stream(In,Len)', []),
997 http_post_data(cgi_stream(In), Out, HdrExtra).
998http_post_data(cgi_stream(In), Out, HdrExtra) :-
999 !,
1000 http_read_header(In, Header0),
1001 http_update_encoding(Header0, Encoding, Header),
1002 content_length_in_encoding(Encoding, In, Size),
1003 http_join_headers(HdrExtra, Header, Hdr2),
1004 phrase(post_header(cgi_data(Size), Hdr2), HeaderText),
1005 format(Out, '~s', [HeaderText]),
1006 setup_call_cleanup(
1007 set_stream(Out, encoding(Encoding)),
1008 copy_stream_data(In, Out),
1009 set_stream(Out, encoding(octet))).
1010http_post_data(form(Fields), Out, HdrExtra) :-
1011 !,
1012 parse_url_search(Codes, Fields),
1013 length(Codes, Size),
1014 http_join_headers(HdrExtra,
1015 [ content_type('application/x-www-form-urlencoded')
1016 ], Header),
1017 phrase(post_header(cgi_data(Size), Header), HeaderChars),
1018 format(Out, '~s', [HeaderChars]),
1019 format(Out, '~s', [Codes]).
1020http_post_data(form_data(Data), Out, HdrExtra) :-
1021 !,
1022 setup_call_cleanup(
1023 new_memory_file(MemFile),
1024 ( setup_call_cleanup(
1025 open_memory_file(MemFile, write, MimeOut),
1026 mime_pack(Data, MimeOut, Boundary),
1027 close(MimeOut)),
1028 size_memory_file(MemFile, Size, octet),
1029 format(string(ContentType),
1030 'multipart/form-data; boundary=~w', [Boundary]),
1031 http_join_headers(HdrExtra,
1032 [ mime_version('1.0'),
1033 content_type(ContentType)
1034 ], Header),
1035 phrase(post_header(cgi_data(Size), Header), HeaderChars),
1036 format(Out, '~s', [HeaderChars]),
1037 setup_call_cleanup(
1038 open_memory_file(MemFile, read, In, [encoding(octet)]),
1039 copy_stream_data(In, Out),
1040 close(In))
1041 ),
1042 free_memory_file(MemFile)).
1043http_post_data(List, Out, HdrExtra) :- 1044 is_list(List),
1045 !,
1046 setup_call_cleanup(
1047 new_memory_file(MemFile),
1048 ( setup_call_cleanup(
1049 open_memory_file(MemFile, write, MimeOut),
1050 mime_pack(List, MimeOut, Boundary),
1051 close(MimeOut)),
1052 size_memory_file(MemFile, Size, octet),
1053 format(string(ContentType),
1054 'multipart/mixed; boundary=~w', [Boundary]),
1055 http_join_headers(HdrExtra,
1056 [ mime_version('1.0'),
1057 content_type(ContentType)
1058 ], Header),
1059 phrase(post_header(cgi_data(Size), Header), HeaderChars),
1060 format(Out, '~s', [HeaderChars]),
1061 setup_call_cleanup(
1062 open_memory_file(MemFile, read, In, [encoding(octet)]),
1063 copy_stream_data(In, Out),
1064 close(In))
1065 ),
1066 free_memory_file(MemFile)).
1067
1072
(html(Tokens), HdrExtra) -->
1074 header_fields(HdrExtra, Len),
1075 content_length(html(Tokens), Len),
1076 content_type(text/html),
1077 "\r\n".
1078post_header(file(Type, File), HdrExtra) -->
1079 header_fields(HdrExtra, Len),
1080 content_length(file(File), Len),
1081 content_type(Type),
1082 "\r\n".
1083post_header(memory_file(Type, File), HdrExtra) -->
1084 header_fields(HdrExtra, Len),
1085 content_length(memory_file(File), Len),
1086 content_type(Type),
1087 "\r\n".
1088post_header(cgi_data(Size), HdrExtra) -->
1089 header_fields(HdrExtra, Len),
1090 content_length(Size, Len),
1091 "\r\n".
1092post_header(codes(Type, Codes), HdrExtra) -->
1093 header_fields(HdrExtra, Len),
1094 content_length(codes(Codes, utf8), Len),
1095 content_type(Type, utf8),
1096 "\r\n".
1097post_header(bytes(Type, Bytes), HdrExtra) -->
1098 header_fields(HdrExtra, Len),
1099 content_length(bytes(Bytes), Len),
1100 content_type(Type),
1101 "\r\n".
1102post_header(atom(Type, Atom), HdrExtra) -->
1103 header_fields(HdrExtra, Len),
1104 content_length(atom(Atom, utf8), Len),
1105 content_type(Type, utf8),
1106 "\r\n".
1107
1108
1109 1112
1117
(Out, What, HdrExtra) :-
1119 phrase(reply_header(What, HdrExtra, _Code), String),
1120 !,
1121 format(Out, '~s', [String]).
1122
1140
(string(String), HdrExtra, Code) -->
1142 reply_header(string(text/plain, String), HdrExtra, Code).
1143reply_header(string(Type, String), HdrExtra, Code) -->
1144 vstatus(ok, Code, HdrExtra),
1145 date(now),
1146 header_fields(HdrExtra, CLen),
1147 content_length(codes(String, utf8), CLen),
1148 content_type(Type, utf8),
1149 "\r\n".
1150reply_header(bytes(Type, Bytes), HdrExtra, Code) -->
1151 vstatus(ok, Code, HdrExtra),
1152 date(now),
1153 header_fields(HdrExtra, CLen),
1154 content_length(bytes(Bytes), CLen),
1155 content_type(Type),
1156 "\r\n".
1157reply_header(html(Tokens), HdrExtra, Code) -->
1158 vstatus(ok, Code, HdrExtra),
1159 date(now),
1160 header_fields(HdrExtra, CLen),
1161 content_length(html(Tokens), CLen),
1162 content_type(text/html),
1163 "\r\n".
1164reply_header(file(Type, File), HdrExtra, Code) -->
1165 vstatus(ok, Code, HdrExtra),
1166 date(now),
1167 modified(file(File)),
1168 header_fields(HdrExtra, CLen),
1169 content_length(file(File), CLen),
1170 content_type(Type),
1171 "\r\n".
1172reply_header(gzip_file(Type, File), HdrExtra, Code) -->
1173 vstatus(ok, Code, HdrExtra),
1174 date(now),
1175 modified(file(File)),
1176 header_fields(HdrExtra, CLen),
1177 content_length(file(File), CLen),
1178 content_type(Type),
1179 content_encoding(gzip),
1180 "\r\n".
1181reply_header(file(Type, File, Range), HdrExtra, Code) -->
1182 vstatus(partial_content, Code, HdrExtra),
1183 date(now),
1184 modified(file(File)),
1185 header_fields(HdrExtra, CLen),
1186 content_length(file(File, Range), CLen),
1187 content_type(Type),
1188 "\r\n".
1189reply_header(tmp_file(Type, File), HdrExtra, Code) -->
1190 vstatus(ok, Code, HdrExtra),
1191 date(now),
1192 header_fields(HdrExtra, CLen),
1193 content_length(file(File), CLen),
1194 content_type(Type),
1195 "\r\n".
1196reply_header(cgi_data(Size), HdrExtra, Code) -->
1197 vstatus(ok, Code, HdrExtra),
1198 date(now),
1199 header_fields(HdrExtra, CLen),
1200 content_length(Size, CLen),
1201 "\r\n".
1202reply_header(chunked_data, HdrExtra, Code) -->
1203 vstatus(ok, Code, HdrExtra),
1204 date(now),
1205 header_fields(HdrExtra, _),
1206 ( {memberchk(transfer_encoding(_), HdrExtra)}
1207 -> ""
1208 ; transfer_encoding(chunked)
1209 ),
1210 "\r\n".
1211reply_header(moved(To, Tokens), HdrExtra, Code) -->
1212 vstatus(moved, Code, HdrExtra),
1213 date(now),
1214 header_field('Location', To),
1215 header_fields(HdrExtra, CLen),
1216 content_length(html(Tokens), CLen),
1217 content_type(text/html, utf8),
1218 "\r\n".
1219reply_header(created(Location, Tokens), HdrExtra, Code) -->
1220 vstatus(created, Code, HdrExtra),
1221 date(now),
1222 header_field('Location', Location),
1223 header_fields(HdrExtra, CLen),
1224 content_length(html(Tokens), CLen),
1225 content_type(text/html, utf8),
1226 "\r\n".
1227reply_header(moved_temporary(To, Tokens), HdrExtra, Code) -->
1228 vstatus(moved_temporary, Code, HdrExtra),
1229 date(now),
1230 header_field('Location', To),
1231 header_fields(HdrExtra, CLen),
1232 content_length(html(Tokens), CLen),
1233 content_type(text/html, utf8),
1234 "\r\n".
1235reply_header(see_other(To,Tokens),HdrExtra, Code) -->
1236 vstatus(see_other, Code, HdrExtra),
1237 date(now),
1238 header_field('Location',To),
1239 header_fields(HdrExtra, CLen),
1240 content_length(html(Tokens), CLen),
1241 content_type(text/html, utf8),
1242 "\r\n".
1243reply_header(status(Status), HdrExtra, Code) --> 1244 vstatus(Status, Code),
1245 header_fields(HdrExtra, Clen),
1246 { Clen = 0 },
1247 "\r\n".
1248reply_header(status(Status, Tokens), HdrExtra, Code) -->
1249 vstatus(Status, Code),
1250 date(now),
1251 header_fields(HdrExtra, CLen),
1252 content_length(html(Tokens), CLen),
1253 content_type(text/html, utf8),
1254 "\r\n".
1255reply_header(authorise(Method, Tokens), HdrExtra, Code) -->
1256 vstatus(authorise, Code),
1257 date(now),
1258 authenticate(Method),
1259 header_fields(HdrExtra, CLen),
1260 content_length(html(Tokens), CLen),
1261 content_type(text/html, utf8),
1262 "\r\n".
1263
1268
1269vstatus(_Status, Code, HdrExtra) -->
1270 {memberchk(status(Code), HdrExtra)},
1271 !,
1272 vstatus(_NewStatus, Code).
1273vstatus(Status, Code, _) -->
1274 vstatus(Status, Code).
1275
1276vstatus(Status, Code) -->
1277 "HTTP/1.1 ",
1278 status_number(Status, Code),
1279 " ",
1280 status_comment(Status),
1281 "\r\n".
1282
1289
1290status_number(Status, Code) -->
1291 { var(Status) },
1292 !,
1293 integer(Code),
1294 { status_number(Status, Code) },
1295 !.
1296status_number(Status, Code) -->
1297 { status_number(Status, Code) },
1298 integer(Code).
1299
1311
1319
1320status_number(Status, Code):-
1321 nonvar(Status),
1322 !,
1323 status_number_fact(Status, Code).
1324status_number(Status, Code):-
1325 nonvar(Code),
1326 !,
1327 ( between(100, 599, Code)
1328 -> ( status_number_fact(Status, Code)
1329 -> true
1330 ; ClassCode is Code // 100 * 100,
1331 status_number_fact(Status, ClassCode)
1332 )
1333 ; domain_error(http_code, Code)
1334 ).
1335
1336status_number_fact(continue, 100).
1337status_number_fact(switching_protocols, 101).
1338status_number_fact(ok, 200).
1339status_number_fact(created, 201).
1340status_number_fact(accepted, 202).
1341status_number_fact(non_authoritative_info, 203).
1342status_number_fact(no_content, 204).
1343status_number_fact(reset_content, 205).
1344status_number_fact(partial_content, 206).
1345status_number_fact(multiple_choices, 300).
1346status_number_fact(moved, 301).
1347status_number_fact(moved_temporary, 302).
1348status_number_fact(see_other, 303).
1349status_number_fact(not_modified, 304).
1350status_number_fact(use_proxy, 305).
1351status_number_fact(unused, 306).
1352status_number_fact(temporary_redirect, 307).
1353status_number_fact(bad_request, 400).
1354status_number_fact(authorise, 401).
1355status_number_fact(payment_required, 402).
1356status_number_fact(forbidden, 403).
1357status_number_fact(not_found, 404).
1358status_number_fact(method_not_allowed, 405).
1359status_number_fact(not_acceptable, 406).
1360status_number_fact(request_timeout, 408).
1361status_number_fact(conflict, 409).
1362status_number_fact(gone, 410).
1363status_number_fact(length_required, 411).
1364status_number_fact(payload_too_large, 413).
1365status_number_fact(uri_too_long, 414).
1366status_number_fact(unsupported_media_type, 415).
1367status_number_fact(expectation_failed, 417).
1368status_number_fact(upgrade_required, 426).
1369status_number_fact(server_error, 500).
1370status_number_fact(not_implemented, 501).
1371status_number_fact(bad_gateway, 502).
1372status_number_fact(service_unavailable, 503).
1373status_number_fact(gateway_timeout, 504).
1374status_number_fact(http_version_not_supported, 505).
1375
1376
1380
(continue) -->
1382 "Continue".
1383status_comment(switching_protocols) -->
1384 "Switching Protocols".
1385status_comment(ok) -->
1386 "OK".
1387status_comment(created) -->
1388 "Created".
1389status_comment(accepted) -->
1390 "Accepted".
1391status_comment(non_authoritative_info) -->
1392 "Non-Authoritative Information".
1393status_comment(no_content) -->
1394 "No Content".
1395status_comment(reset_content) -->
1396 "Reset Content".
1397status_comment(created) -->
1398 "Created".
1399status_comment(partial_content) -->
1400 "Partial content".
1401status_comment(multiple_choices) -->
1402 "Multiple Choices".
1403status_comment(moved) -->
1404 "Moved Permanently".
1405status_comment(moved_temporary) -->
1406 "Moved Temporary".
1407status_comment(see_other) -->
1408 "See Other".
1409status_comment(not_modified) -->
1410 "Not Modified".
1411status_comment(use_proxy) -->
1412 "Use Proxy".
1413status_comment(unused) -->
1414 "Unused".
1415status_comment(temporary_redirect) -->
1416 "Temporary Redirect".
1417status_comment(bad_request) -->
1418 "Bad Request".
1419status_comment(authorise) -->
1420 "Authorization Required".
1421status_comment(payment_required) -->
1422 "Payment Required".
1423status_comment(forbidden) -->
1424 "Forbidden".
1425status_comment(not_found) -->
1426 "Not Found".
1427status_comment(method_not_allowed) -->
1428 "Method Not Allowed".
1429status_comment(not_acceptable) -->
1430 "Not Acceptable".
1431status_comment(request_timeout) -->
1432 "Request Timeout".
1433status_comment(conflict) -->
1434 "Conflict".
1435status_comment(gone) -->
1436 "Gone".
1437status_comment(length_required) -->
1438 "Length Required".
1439status_comment(payload_too_large) -->
1440 "Payload Too Large".
1441status_comment(uri_too_long) -->
1442 "URI Too Long".
1443status_comment(unsupported_media_type) -->
1444 "Unsupported Media Type".
1445status_comment(expectation_failed) -->
1446 "Expectation Failed".
1447status_comment(upgrade_required) -->
1448 "Upgrade Required".
1449status_comment(server_error) -->
1450 "Internal Server Error".
1451status_comment(not_implemented) -->
1452 "Not Implemented".
1453status_comment(bad_gateway) -->
1454 "Bad Gateway".
1455status_comment(service_unavailable) -->
1456 "Service Unavailable".
1457status_comment(gateway_timeout) -->
1458 "Gateway Timeout".
1459status_comment(http_version_not_supported) -->
1460 "HTTP Version Not Supported".
1461
1462authenticate(negotiate(Data)) -->
1463 "WWW-Authenticate: Negotiate ",
1464 { base64(Data, DataBase64),
1465 atom_codes(DataBase64, Codes)
1466 },
1467 string(Codes), "\r\n".
1468authenticate(negotiate) -->
1469 "WWW-Authenticate: Negotiate\r\n".
1470
1471authenticate(basic) -->
1472 !,
1473 "WWW-Authenticate: Basic\r\n".
1474authenticate(basic(Realm)) -->
1475 "WWW-Authenticate: Basic Realm=\"", atom(Realm), "\"\r\n".
1476
1477authenticate(digest) -->
1478 !,
1479 "WWW-Authenticate: Digest\r\n".
1480authenticate(digest(Details)) -->
1481 "WWW-Authenticate: Digest ", atom(Details), "\r\n".
1482
1483
1484date(Time) -->
1485 "Date: ",
1486 ( { Time == now }
1487 -> now
1488 ; rfc_date(Time)
1489 ),
1490 "\r\n".
1491
1492modified(file(File)) -->
1493 !,
1494 { time_file(File, Time)
1495 },
1496 modified(Time).
1497modified(Time) -->
1498 "Last-modified: ",
1499 ( { Time == now }
1500 -> now
1501 ; rfc_date(Time)
1502 ),
1503 "\r\n".
1504
1505
1512
1513content_length(file(File, bytes(From, To)), Len) -->
1514 !,
1515 { size_file(File, Size),
1516 ( To == end
1517 -> Len is Size - From,
1518 RangeEnd is Size - 1
1519 ; Len is To+1 - From, 1520 RangeEnd = To
1521 )
1522 },
1523 content_range(bytes, From, RangeEnd, Size),
1524 content_length(Len, Len).
1525content_length(Reply, Len) -->
1526 { length_of(Reply, Len)
1527 },
1528 "Content-Length: ", integer(Len),
1529 "\r\n".
1530
1531
1532length_of(_, Len) :-
1533 nonvar(Len),
1534 !.
1535length_of(codes(String, Encoding), Len) :-
1536 !,
1537 setup_call_cleanup(
1538 open_null_stream(Out),
1539 ( set_stream(Out, encoding(Encoding)),
1540 format(Out, '~s', [String]),
1541 byte_count(Out, Len)
1542 ),
1543 close(Out)).
1544length_of(atom(Atom, Encoding), Len) :-
1545 !,
1546 setup_call_cleanup(
1547 open_null_stream(Out),
1548 ( set_stream(Out, encoding(Encoding)),
1549 format(Out, '~a', [Atom]),
1550 byte_count(Out, Len)
1551 ),
1552 close(Out)).
1553length_of(file(File), Len) :-
1554 !,
1555 size_file(File, Len).
1556length_of(memory_file(Handle), Len) :-
1557 !,
1558 size_memory_file(Handle, Len, octet).
1559length_of(html(Tokens), Len) :-
1560 !,
1561 html_print_length(Tokens, Len).
1562length_of(bytes(Bytes), Len) :-
1563 !,
1564 ( string(Bytes)
1565 -> string_length(Bytes, Len)
1566 ; length(Bytes, Len) 1567 ).
1568length_of(Len, Len).
1569
1570
1575
1576content_range(Unit, From, RangeEnd, Size) -->
1577 "Content-Range: ", atom(Unit), " ",
1578 integer(From), "-", integer(RangeEnd), "/", integer(Size),
1579 "\r\n".
1580
1581content_encoding(Encoding) -->
1582 "Content-Encoding: ", atom(Encoding), "\r\n".
1583
1584transfer_encoding(Encoding) -->
1585 "Transfer-Encoding: ", atom(Encoding), "\r\n".
1586
1587content_type(Type) -->
1588 content_type(Type, _).
1589
1590content_type(Type, Charset) -->
1591 ctype(Type),
1592 charset(Charset),
1593 "\r\n".
1594
1595ctype(Main/Sub) -->
1596 !,
1597 "Content-Type: ",
1598 atom(Main),
1599 "/",
1600 atom(Sub).
1601ctype(Type) -->
1602 !,
1603 "Content-Type: ",
1604 atom(Type).
1605
1606charset(Var) -->
1607 { var(Var) },
1608 !.
1609charset(utf8) -->
1610 !,
1611 "; charset=UTF-8".
1612charset(CharSet) -->
1613 "; charset=",
1614 atom(CharSet).
1615
1621
(Name, Value) -->
1623 { var(Name) }, 1624 !,
1625 field_name(Name),
1626 ":",
1627 whites,
1628 read_field_value(ValueChars),
1629 blanks_to_nl,
1630 !,
1631 { field_to_prolog(Name, ValueChars, Value)
1632 -> true
1633 ; atom_codes(Value, ValueChars),
1634 domain_error(Name, Value)
1635 }.
1636header_field(Name, Value) -->
1637 field_name(Name),
1638 ": ",
1639 field_value(Value),
1640 "\r\n".
1641
1645
1646read_field_value([H|T]) -->
1647 [H],
1648 { \+ code_type(H, space) },
1649 !,
1650 read_field_value(T).
1651read_field_value([]) -->
1652 "".
1653read_field_value([H|T]) -->
1654 [H],
1655 read_field_value(T).
1656
1657
1687
(Field, Value, Prolog) :-
1689 known_field(Field, _),
1690 to_codes(Value, Codes),
1691 parse_header_value(Field, Codes, Prolog).
1692
1697
1698known_field(content_length, true).
1699known_field(status, true).
1700known_field(cookie, true).
1701known_field(set_cookie, true).
1702known_field(host, true).
1703known_field(range, maybe).
1704known_field(accept, maybe).
1705known_field(content_disposition, maybe).
1706known_field(content_type, false).
1707
1708to_codes(In, Codes) :-
1709 ( is_list(In)
1710 -> Codes = In
1711 ; atom_codes(In, Codes)
1712 ).
1713
1719
1720field_to_prolog(Field, Codes, Prolog) :-
1721 known_field(Field, true),
1722 !,
1723 ( parse_header_value(Field, Codes, Prolog0)
1724 -> Prolog = Prolog0
1725 ).
1726field_to_prolog(Field, Codes, Prolog) :-
1727 known_field(Field, maybe),
1728 parse_header_value(Field, Codes, Prolog0),
1729 !,
1730 Prolog = Prolog0.
1731field_to_prolog(_, Codes, Atom) :-
1732 atom_codes(Atom, Codes).
1733
1738
(content_length, ValueChars, ContentLength) :-
1740 number_codes(ContentLength, ValueChars).
1741parse_header_value(status, ValueChars, Code) :-
1742 ( phrase(" ", L, _),
1743 append(Pre, L, ValueChars)
1744 -> number_codes(Code, Pre)
1745 ; number_codes(Code, ValueChars)
1746 ).
1747parse_header_value(cookie, ValueChars, Cookies) :-
1748 debug(cookie, 'Cookie: ~s', [ValueChars]),
1749 phrase(cookies(Cookies), ValueChars).
1750parse_header_value(set_cookie, ValueChars, SetCookie) :-
1751 debug(cookie, 'SetCookie: ~s', [ValueChars]),
1752 phrase(set_cookie(SetCookie), ValueChars).
1753parse_header_value(host, ValueChars, Host) :-
1754 ( append(HostChars, [0':|PortChars], ValueChars),
1755 catch(number_codes(Port, PortChars), _, fail)
1756 -> atom_codes(HostName, HostChars),
1757 Host = HostName:Port
1758 ; atom_codes(Host, ValueChars)
1759 ).
1760parse_header_value(range, ValueChars, Range) :-
1761 phrase(range(Range), ValueChars).
1762parse_header_value(accept, ValueChars, Media) :-
1763 parse_accept(ValueChars, Media).
1764parse_header_value(content_disposition, ValueChars, Disposition) :-
1765 phrase(content_disposition(Disposition), ValueChars).
1766parse_header_value(content_type, ValueChars, Type) :-
1767 phrase(parse_content_type(Type), ValueChars).
1768
1769field_value(set_cookie(Name, Value, Options)) -->
1770 !,
1771 atom(Name), "=", atom(Value),
1772 value_options(Options, cookie).
1773field_value(disposition(Disposition, Options)) -->
1774 !,
1775 atom(Disposition), value_options(Options, disposition).
1776field_value(Atomic) -->
1777 atom(Atomic).
1778
1785
1786value_options([], _) --> [].
1787value_options([H|T], Field) -->
1788 "; ", value_option(H, Field),
1789 value_options(T, Field).
1790
1791value_option(secure=true, cookie) -->
1792 !,
1793 "secure".
1794value_option(Name=Value, Type) -->
1795 { string_option(Name, Type) },
1796 !,
1797 atom(Name), "=",
1798 qstring(Value).
1799value_option(Name=Value, Type) -->
1800 { token_option(Name, Type) },
1801 !,
1802 atom(Name), "=", atom(Value).
1803value_option(Name=Value, _Type) -->
1804 atom(Name), "=",
1805 option_value(Value).
1806
1807string_option(filename, disposition).
1808
1809token_option(path, cookie).
1810
1811option_value(Value) -->
1812 { number(Value) },
1813 !,
1814 number(Value).
1815option_value(Value) -->
1816 { ( atom(Value)
1817 -> true
1818 ; string(Value)
1819 ),
1820 forall(string_code(_, Value, C),
1821 token_char(C))
1822 },
1823 !,
1824 atom(Value).
1825option_value(Atomic) -->
1826 qstring(Atomic).
1827
1828qstring(Atomic) -->
1829 { string_codes(Atomic, Codes) },
1830 "\"",
1831 qstring_codes(Codes),
1832 "\"".
1833
1834qstring_codes([]) --> [].
1835qstring_codes([H|T]) --> qstring_code(H), qstring_codes(T).
1836
1837qstring_code(C) --> {qstring_esc(C)}, !, "\\", [C].
1838qstring_code(C) --> [C].
1839
1840qstring_esc(0'").
1841qstring_esc(C) :- ctl(C).
1842
1843
1844 1847
1848:- dynamic accept_cache/2. 1849:- volatile accept_cache/2. 1850
1851parse_accept(Codes, Media) :-
1852 atom_codes(Atom, Codes),
1853 ( accept_cache(Atom, Media0)
1854 -> Media = Media0
1855 ; phrase(accept(Media0), Codes),
1856 keysort(Media0, Media1),
1857 pairs_values(Media1, Media2),
1858 assertz(accept_cache(Atom, Media2)),
1859 Media = Media2
1860 ).
1861
1865
1866accept([H|T]) -->
1867 blanks,
1868 media_range(H),
1869 blanks,
1870 ( ","
1871 -> accept(T)
1872 ; {T=[]}
1873 ).
1874
1875media_range(s(SortQuality,Spec)-media(Type, TypeParams, Quality, AcceptExts)) -->
1876 media_type(Type),
1877 blanks,
1878 ( ";"
1879 -> blanks,
1880 parameters_and_quality(TypeParams, Quality, AcceptExts)
1881 ; { TypeParams = [],
1882 Quality = 1.0,
1883 AcceptExts = []
1884 }
1885 ),
1886 { SortQuality is float(-Quality),
1887 rank_specialised(Type, TypeParams, Spec)
1888 }.
1889
1890
1894
1895content_disposition(disposition(Disposition, Options)) -->
1896 token(Disposition), blanks,
1897 value_parameters(Options).
1898
1903
1904parse_content_type(media(Type, Parameters)) -->
1905 media_type(Type), blanks,
1906 value_parameters(Parameters).
1907
1908
1916
1917rank_specialised(Type/SubType, TypeParams, v(VT, VS, SortVP)) :-
1918 var_or_given(Type, VT),
1919 var_or_given(SubType, VS),
1920 length(TypeParams, VP),
1921 SortVP is -VP.
1922
1923var_or_given(V, Val) :-
1924 ( var(V)
1925 -> Val = 0
1926 ; Val = -1
1927 ).
1928
1929media_type(Type/SubType) -->
1930 type(Type), "/", type(SubType).
1931
1932type(_) -->
1933 "*",
1934 !.
1935type(Type) -->
1936 token(Type).
1937
1938parameters_and_quality(Params, Quality, AcceptExts) -->
1939 token(Name),
1940 blanks, "=", blanks,
1941 ( { Name == q }
1942 -> float(Quality), blanks,
1943 value_parameters(AcceptExts),
1944 { Params = [] }
1945 ; { Params = [Name=Value|T] },
1946 parameter_value(Value),
1947 blanks,
1948 ( ";"
1949 -> blanks,
1950 parameters_and_quality(T, Quality, AcceptExts)
1951 ; { T = [],
1952 Quality = 1.0,
1953 AcceptExts = []
1954 }
1955 )
1956 ).
1957
1962
1963value_parameters([H|T]) -->
1964 ";",
1965 !,
1966 blanks, token(Name), blanks,
1967 ( "="
1968 -> blanks,
1969 ( token(Value)
1970 -> []
1971 ; quoted_string(Value)
1972 ),
1973 { H = (Name=Value) }
1974 ; { H = Name }
1975 ),
1976 blanks,
1977 value_parameters(T).
1978value_parameters([]) -->
1979 [].
1980
1981parameter_value(Value) --> token(Value), !.
1982parameter_value(Value) --> quoted_string(Value).
1983
1984
1988
1989token(Name) -->
1990 token_char(C1),
1991 token_chars(Cs),
1992 { atom_codes(Name, [C1|Cs]) }.
1993
1994token_chars([H|T]) -->
1995 token_char(H),
1996 !,
1997 token_chars(T).
1998token_chars([]) --> [].
1999
2000token_char(C) --> [C], { token_char(C) }.
2001
2002token_char(C) :-
2003 \+ ctl(C),
2004 \+ separator_code(C).
2005
2006ctl(C) :- between(0,31,C), !.
2007ctl(127).
2008
2009separator_code(0'().
2010separator_code(0')).
2011separator_code(0'<).
2012separator_code(0'>).
2013separator_code(0'@).
2014separator_code(0',).
2015separator_code(0';).
2016separator_code(0':).
2017separator_code(0'\\).
2018separator_code(0'").
2019separator_code(0'/).
2020separator_code(0'[).
2021separator_code(0']).
2022separator_code(0'?).
2023separator_code(0'=).
2024separator_code(0'{).
2025separator_code(0'}).
2026separator_code(0'\s).
2027separator_code(0'\t).
2028
2029
2033
2034quoted_string(Text) -->
2035 "\"",
2036 quoted_text(Codes),
2037 { atom_codes(Text, Codes) }.
2038
2039quoted_text([]) -->
2040 "\"",
2041 !.
2042quoted_text([H|T]) -->
2043 "\\", !, [H],
2044 quoted_text(T).
2045quoted_text([H|T]) -->
2046 [H],
2047 !,
2048 quoted_text(T).
2049
2050
2058
([], _) --> [].
2060header_fields([content_length(CLen)|T], CLen) -->
2061 !,
2062 ( { var(CLen) }
2063 -> ""
2064 ; header_field(content_length, CLen)
2065 ),
2066 header_fields(T, CLen). 2067header_fields([status(_)|T], CLen) --> 2068 !,
2069 header_fields(T, CLen).
2070header_fields([H|T], CLen) -->
2071 { H =.. [Name, Value] },
2072 header_field(Name, Value),
2073 header_fields(T, CLen).
2074
2075
2089
2090:- public
2091 field_name//1. 2092
2093field_name(Name) -->
2094 { var(Name) },
2095 !,
2096 rd_field_chars(Chars),
2097 { atom_codes(Name, Chars) }.
2098field_name(mime_version) -->
2099 !,
2100 "MIME-Version".
2101field_name(Name) -->
2102 { atom_codes(Name, Chars) },
2103 wr_field_chars(Chars).
2104
2105rd_field_chars_no_fold([C|T]) -->
2106 [C],
2107 { rd_field_char(C, _) },
2108 !,
2109 rd_field_chars_no_fold(T).
2110rd_field_chars_no_fold([]) -->
2111 [].
2112
2113rd_field_chars([C0|T]) -->
2114 [C],
2115 { rd_field_char(C, C0) },
2116 !,
2117 rd_field_chars(T).
2118rd_field_chars([]) -->
2119 [].
2120
2124
2125separators("()<>@,;:\\\"/[]?={} \t").
2126
2127term_expansion(rd_field_char('expand me',_), Clauses) :-
2128
2129 Clauses = [ rd_field_char(0'-, 0'_)
2130 | Cls
2131 ],
2132 separators(SepString),
2133 string_codes(SepString, Seps),
2134 findall(rd_field_char(In, Out),
2135 ( between(32, 127, In),
2136 \+ memberchk(In, Seps),
2137 In \== 0'-, 2138 code_type(Out, to_lower(In))),
2139 Cls).
2140
2141rd_field_char('expand me', _). 2142
2143wr_field_chars([C|T]) -->
2144 !,
2145 { code_type(C, to_lower(U)) },
2146 [U],
2147 wr_field_chars2(T).
2148wr_field_chars([]) -->
2149 [].
2150
2151wr_field_chars2([]) --> [].
2152wr_field_chars2([C|T]) --> 2153 ( { C == 0'_ }
2154 -> "-",
2155 wr_field_chars(T)
2156 ; [C],
2157 wr_field_chars2(T)
2158 ).
2159
2163
2164now -->
2165 { get_time(Time)
2166 },
2167 rfc_date(Time).
2168
2173
2174rfc_date(Time, String, Tail) :-
2175 stamp_date_time(Time, Date, 'UTC'),
2176 format_time(codes(String, Tail),
2177 '%a, %d %b %Y %T GMT',
2178 Date, posix).
2179
2183
2184http_timestamp(Time, Atom) :-
2185 stamp_date_time(Time, Date, 'UTC'),
2186 format_time(atom(Atom),
2187 '%a, %d %b %Y %T GMT',
2188 Date, posix).
2189
2190
2191 2194
2195request(Fd, [method(Method),request_uri(ReqURI)|Header]) -->
2196 method(Method),
2197 blanks,
2198 nonblanks(Query),
2199 { atom_codes(ReqURI, Query),
2200 request_uri_parts(ReqURI, Header, Rest)
2201 },
2202 request_header(Fd, Rest),
2203 !.
2204request(Fd, [unknown(What)|Header]) -->
2205 string(What),
2206 eos,
2207 !,
2208 { http_read_header(Fd, Header)
2209 -> true
2210 ; Header = []
2211 }.
2212
2213method(get) --> "GET", !.
2214method(put) --> "PUT", !.
2215method(head) --> "HEAD", !.
2216method(post) --> "POST", !.
2217method(delete) --> "DELETE", !.
2218method(patch) --> "PATCH", !.
2219method(options) --> "OPTIONS", !.
2220method(trace) --> "TRACE", !.
2221
2233
2234request_uri_parts(ReqURI, [path(Path)|Parts], Rest) :-
2235 uri_components(ReqURI, Components),
2236 uri_data(path, Components, PathText),
2237 uri_encoded(path, Path, PathText),
2238 phrase(uri_parts(Components), Parts, Rest).
2239
2240uri_parts(Components) -->
2241 uri_search(Components),
2242 uri_fragment(Components).
2243
2244uri_search(Components) -->
2245 { uri_data(search, Components, Search),
2246 nonvar(Search),
2247 catch(uri_query_components(Search, Query),
2248 error(syntax_error(_),_),
2249 fail)
2250 },
2251 !,
2252 [ search(Query) ].
2253uri_search(_) --> [].
2254
2255uri_fragment(Components) -->
2256 { uri_data(fragment, Components, String),
2257 nonvar(String),
2258 !,
2259 uri_encoded(fragment, Fragment, String)
2260 },
2261 [ fragment(Fragment) ].
2262uri_fragment(_) --> [].
2263
2268
(_, []) --> 2270 blanks,
2271 eos,
2272 !.
2273request_header(Fd, [http_version(Version)|Header]) -->
2274 http_version(Version),
2275 blanks,
2276 eos,
2277 !,
2278 { Version = 1-_
2279 -> http_read_header(Fd, Header)
2280 ; Header = []
2281 }.
2282
2283http_version(Version) -->
2284 blanks,
2285 "HTTP/",
2286 http_version_number(Version).
2287
2288http_version_number(Major-Minor) -->
2289 integer(Major),
2290 ".",
2291 integer(Minor).
2292
2293
2294 2297
2301
2302cookies([Name=Value|T]) -->
2303 blanks,
2304 cookie(Name, Value),
2305 !,
2306 blanks,
2307 ( ";"
2308 -> cookies(T)
2309 ; { T = [] }
2310 ).
2311cookies(List) -->
2312 string(Skipped),
2313 ";",
2314 !,
2315 { print_message(warning, http(skipped_cookie(Skipped))) },
2316 cookies(List).
2317cookies([]) -->
2318 blanks.
2319
2320cookie(Name, Value) -->
2321 cookie_name(Name),
2322 blanks, "=", blanks,
2323 cookie_value(Value).
2324
2325cookie_name(Name) -->
2326 { var(Name) },
2327 !,
2328 rd_field_chars_no_fold(Chars),
2329 { atom_codes(Name, Chars) }.
2330
2331cookie_value(Value) -->
2332 quoted_string(Value),
2333 !.
2334cookie_value(Value) -->
2335 chars_to_semicolon_or_blank(Chars),
2336 { atom_codes(Value, Chars)
2337 }.
2338
2339chars_to_semicolon_or_blank([H|T]) -->
2340 [H],
2341 { H \== 32, H \== 0'; },
2342 !,
2343 chars_to_semicolon_or_blank(T).
2344chars_to_semicolon_or_blank([]) -->
2345 [].
2346
2347set_cookie(set_cookie(Name, Value, Options)) -->
2348 ws,
2349 cookie(Name, Value),
2350 cookie_options(Options).
2351
2352cookie_options([H|T]) -->
2353 ws,
2354 ";",
2355 ws,
2356 cookie_option(H),
2357 !,
2358 cookie_options(T).
2359cookie_options([]) -->
2360 ws.
2361
2362ws --> " ", !, ws.
2363ws --> [].
2364
2365
2375
2376cookie_option(Name=Value) -->
2377 rd_field_chars(NameChars), ws,
2378 { atom_codes(Name, NameChars) },
2379 ( "="
2380 -> ws,
2381 chars_to_semicolon(ValueChars),
2382 { atom_codes(Value, ValueChars)
2383 }
2384 ; { Value = true }
2385 ).
2386
2387chars_to_semicolon([H|T]) -->
2388 [H],
2389 { H \== 32, H \== 0'; },
2390 !,
2391 chars_to_semicolon(T).
2392chars_to_semicolon([]), ";" -->
2393 ws, ";",
2394 !.
2395chars_to_semicolon([H|T]) -->
2396 [H],
2397 chars_to_semicolon(T).
2398chars_to_semicolon([]) -->
2399 [].
2400
2408
2409range(bytes(From, To)) -->
2410 "bytes", whites, "=", whites, integer(From), "-",
2411 ( integer(To)
2412 -> ""
2413 ; { To = end }
2414 ).
2415
2416
2417 2420
2435
2436reply(Fd, [http_version(HttpVersion), status(Code, Status, Comment)|Header]) -->
2437 http_version(HttpVersion),
2438 blanks,
2439 ( status_number(Status, Code)
2440 -> []
2441 ; integer(Status)
2442 ),
2443 blanks,
2444 string(CommentCodes),
2445 blanks_to_nl,
2446 !,
2447 blanks,
2448 { atom_codes(Comment, CommentCodes),
2449 http_read_header(Fd, Header)
2450 }.
2451
2452
2453 2456
2462
(Fd, Header) :-
2464 read_header_data(Fd, Text),
2465 http_parse_header(Text, Header).
2466
(Fd, Header) :-
2468 read_line_to_codes(Fd, Header, Tail),
2469 read_header_data(Header, Fd, Tail),
2470 debug(http(header), 'Header = ~n~s~n', [Header]).
2471
([0'\r,0'\n], _, _) :- !.
2473read_header_data([0'\n], _, _) :- !.
2474read_header_data([], _, _) :- !.
2475read_header_data(_, Fd, Tail) :-
2476 read_line_to_codes(Fd, Tail, NewTail),
2477 read_header_data(Tail, Fd, NewTail).
2478
2485
(Text, Header) :-
2487 phrase(header(Header), Text),
2488 debug(http(header), 'Field: ~p', [Header]).
2489
(List) -->
2491 header_field(Name, Value),
2492 !,
2493 { mkfield(Name, Value, List, Tail)
2494 },
2495 blanks,
2496 header(Tail).
2497header([]) -->
2498 blanks,
2499 eos,
2500 !.
2501header(_) -->
2502 string(S), blanks_to_nl,
2503 !,
2504 { string_codes(Line, S),
2505 syntax_error(http_parameter(Line))
2506 }.
2507
2519
2520:- multifile
2521 http:http_address//0. 2522
2523address -->
2524 http:http_address,
2525 !.
2526address -->
2527 { gethostname(Host) },
2528 html(address([ a(href('http://www.swi-prolog.org'), 'SWI-Prolog'),
2529 ' httpd at ', Host
2530 ])).
2531
2532mkfield(host, Host:Port, [host(Host),port(Port)|Tail], Tail) :- !.
2533mkfield(Name, Value, [Att|Tail], Tail) :-
2534 Att =.. [Name, Value].
2535
2541
2563
2564
2565 2568
2569:- multifile
2570 prolog:message//1,
2571 prolog:error_message//1. 2572
2573prolog:error_message(http_write_short(Data, Sent)) -->
2574 data(Data),
2575 [ ': remote hangup after ~D bytes'-[Sent] ].
2576prolog:error_message(syntax_error(http_request(Request))) -->
2577 [ 'Illegal HTTP request: ~s'-[Request] ].
2578prolog:error_message(syntax_error(http_parameter(Line))) -->
2579 [ 'Illegal HTTP parameter: ~s'-[Line] ].
2580
2581prolog:message(http(skipped_cookie(S))) -->
2582 [ 'Skipped illegal cookie: ~s'-[S] ].
2583
2584data(bytes(MimeType, _Bytes)) -->
2585 !,
2586 [ 'bytes(~p, ...)'-[MimeType] ].
2587data(Data) -->
2588 [ '~p'-[Data] ]