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
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 ; message_to_string(E, Msg),
126 debug(http(request), 'Exception reading 1st line: ~s', [Msg]),
127 Request = end_of_file
128 ).
129
130
135
(In, [input(In)|Reply]) :-
137 read_line_to_codes(In, Codes),
138 ( Codes == end_of_file
139 -> debug(http(header), 'end-of-file', []),
140 throw(error(syntax(http_reply_header, end_of_file), _))
141 ; debug(http(header), 'First line: ~s~n', [Codes]),
142 ( phrase(reply(In, Reply), Codes)
143 -> true
144 ; atom_codes(Header, Codes),
145 syntax_error(http_reply_header(Header))
146 )
147 ).
148
149
150 153
200
201http_reply(What, Out) :-
202 http_reply(What, Out, [connection(close)], _).
203
204http_reply(Data, Out, HdrExtra) :-
205 http_reply(Data, Out, HdrExtra, _Code).
206
207http_reply(Data, Out, HdrExtra, Code) :-
208 http_reply(Data, Out, HdrExtra, [], Code).
209
210http_reply(Data, Out, HdrExtra, Context, Code) :-
211 http_reply(Data, Out, HdrExtra, Context, [method(get)], Code).
212
213http_reply(Data, Out, HdrExtra, _Context, Request, Code) :-
214 byte_count(Out, C0),
215 memberchk(method(Method), Request),
216 catch(http_reply_data(Data, Out, HdrExtra, Method, Code), E, true),
217 !,
218 ( var(E)
219 -> true
220 ; E = error(io_error(write, _), _)
221 -> byte_count(Out, C1),
222 Sent is C1 - C0,
223 throw(error(http_write_short(Data, Sent), _))
224 ; E = error(timeout_error(write, _), _)
225 -> throw(E)
226 ; map_exception_to_http_status(E, Status, NewHdr, NewContext),
227 http_status_reply(Status, Out, NewHdr, NewContext, Request, Code)
228 ).
229http_reply(Status, Out, HdrExtra, Context, Request, Code) :-
230 http_status_reply(Status, Out, HdrExtra, Context, Request, Code).
231
232:- meta_predicate
233 if_no_head(+, 0).
234
241
242http_reply_data(Data, Out, HdrExtra, Method, Code) :-
243 http_reply_data_(Data, Out, HdrExtra, Method, Code),
244 flush_output(Out).
245
246http_reply_data_(html(HTML), Out, HdrExtra, Method, Code) :-
247 !,
248 phrase(reply_header(html(HTML), HdrExtra, Code), Header),
249 format(Out, '~s', [Header]),
250 if_no_head(Method, print_html(Out, HTML)).
251http_reply_data_(file(Type, File), Out, HdrExtra, Method, Code) :-
252 !,
253 phrase(reply_header(file(Type, File), HdrExtra, Code), Header),
254 reply_file(Out, File, Header, Method).
255http_reply_data_(gzip_file(Type, File), Out, HdrExtra, Method, Code) :-
256 !,
257 phrase(reply_header(gzip_file(Type, File), HdrExtra, Code), Header),
258 reply_file(Out, File, Header, Method).
259http_reply_data_(file(Type, File, Range), Out, HdrExtra, Method, Code) :-
260 !,
261 phrase(reply_header(file(Type, File, Range), HdrExtra, Code), Header),
262 reply_file_range(Out, File, Header, Range, Method).
263http_reply_data_(tmp_file(Type, File), Out, HdrExtra, Method, Code) :-
264 !,
265 phrase(reply_header(tmp_file(Type, File), HdrExtra, Code), Header),
266 reply_file(Out, File, Header, Method).
267http_reply_data_(bytes(Type, Bytes), Out, HdrExtra, Method, Code) :-
268 !,
269 phrase(reply_header(bytes(Type, Bytes), HdrExtra, Code), Header),
270 format(Out, '~s', [Header]),
271 if_no_head(Method, format(Out, '~s', [Bytes])).
272http_reply_data_(stream(In, Len), Out, HdrExtra, Method, Code) :-
273 !,
274 phrase(reply_header(cgi_data(Len), HdrExtra, Code), Header),
275 copy_stream(Out, In, Header, Method, 0, end).
276http_reply_data_(cgi_stream(In, Len), Out, HdrExtra, Method, Code) :-
277 !,
278 http_read_header(In, CgiHeader),
279 seek(In, 0, current, Pos),
280 Size is Len - Pos,
281 http_join_headers(HdrExtra, CgiHeader, Hdr2),
282 phrase(reply_header(cgi_data(Size), Hdr2, Code), Header),
283 copy_stream(Out, In, Header, Method, 0, end).
284
285if_no_head(head, _) :- !.
286if_no_head(_, Goal) :-
287 call(Goal).
288
289reply_file(Out, _File, Header, head) :-
290 !,
291 format(Out, '~s', [Header]).
292reply_file(Out, File, Header, _) :-
293 setup_call_cleanup(
294 open(File, read, In, [type(binary)]),
295 copy_stream(Out, In, Header, 0, end),
296 close(In)).
297
298reply_file_range(Out, _File, Header, _Range, head) :-
299 !,
300 format(Out, '~s', [Header]).
301reply_file_range(Out, File, Header, bytes(From, To), _) :-
302 setup_call_cleanup(
303 open(File, read, In, [type(binary)]),
304 copy_stream(Out, In, Header, From, To),
305 close(In)).
306
307copy_stream(Out, _, Header, head, _, _) :-
308 !,
309 format(Out, '~s', [Header]).
310copy_stream(Out, In, Header, _, From, To) :-
311 copy_stream(Out, In, Header, From, To).
312
313copy_stream(Out, In, Header, From, To) :-
314 ( From == 0
315 -> true
316 ; seek(In, From, bof, _)
317 ),
318 peek_byte(In, _),
319 format(Out, '~s', [Header]),
320 ( To == end
321 -> copy_stream_data(In, Out)
322 ; Len is To - From,
323 copy_stream_data(In, Out, Len)
324 ).
325
326
357
358http_status_reply(Status, Out, HdrExtra, Code) :-
359 http_status_reply(Status, Out, HdrExtra, [], Code).
360
361http_status_reply(Status, Out, HdrExtra, Context, Code) :-
362 http_status_reply(Status, Out, HdrExtra, Context, [method(get)], Code).
363
364http_status_reply(Status, Out, HdrExtra, Context, Request, Code) :-
365 option(method(Method), Request, get),
366 setup_call_cleanup(
367 set_stream(Out, encoding(utf8)),
368 status_reply_flush(Status, Out, HdrExtra, Context, Method, Code),
369 set_stream(Out, encoding(octet))),
370 !.
371
372status_reply_flush(Status, Out, HdrExtra, Context, Method, Code) :-
373 status_reply(Status, Out, HdrExtra, Context, Method, Code),
374 flush_output(Out).
375
376status_reply(no_content, Out, HdrExtra, _Context, _Method, Code) :-
377 !,
378 phrase(reply_header(status(no_content), HdrExtra, Code), Header),
379 format(Out, '~s', [Header]).
380status_reply(switching_protocols(_Goal,Options), Out,
381 HdrExtra0, _Context, _Method, Code) :-
382 !,
383 ( option(headers(Extra1), Options)
384 -> true
385 ; option(header(Extra1), Options, [])
386 ),
387 http_join_headers(HdrExtra0, Extra1, HdrExtra),
388 phrase(reply_header(status(switching_protocols), HdrExtra, Code), Header),
389 format(Out, '~s', [Header]).
390status_reply(created(Location), Out, HdrExtra, _Context, Method, Code) :-
391 !,
392 phrase(page([ title('201 Created')
393 ],
394 [ h1('Created'),
395 p(['The document was created ',
396 a(href(Location), ' Here')
397 ]),
398 \address
399 ]),
400 HTML),
401 phrase(reply_header(created(Location, HTML), HdrExtra, Code), Header),
402 format(Out, '~s', [Header]),
403 print_html_if_no_head(Method, Out, HTML).
404status_reply(moved(To), Out, HdrExtra, _Context, Method, Code) :-
405 !,
406 phrase(page([ title('301 Moved Permanently')
407 ],
408 [ h1('Moved Permanently'),
409 p(['The document has moved ',
410 a(href(To), ' Here')
411 ]),
412 \address
413 ]),
414 HTML),
415 phrase(reply_header(moved(To, HTML), HdrExtra, Code), Header),
416 format(Out, '~s', [Header]),
417 print_html_if_no_head(Method, Out, HTML).
418status_reply(moved_temporary(To), Out, HdrExtra, _Context, Method, Code) :-
419 !,
420 phrase(page([ title('302 Moved Temporary')
421 ],
422 [ h1('Moved Temporary'),
423 p(['The document is currently ',
424 a(href(To), ' Here')
425 ]),
426 \address
427 ]),
428 HTML),
429 phrase(reply_header(moved_temporary(To, HTML),
430 HdrExtra, Code), Header),
431 format(Out, '~s', [Header]),
432 print_html_if_no_head(Method, Out, HTML).
433status_reply(see_other(To),Out,HdrExtra, _Context, Method, Code) :-
434 !,
435 phrase(page([ title('303 See Other')
436 ],
437 [ h1('See Other'),
438 p(['See other document ',
439 a(href(To), ' Here')
440 ]),
441 \address
442 ]),
443 HTML),
444 phrase(reply_header(see_other(To, HTML), HdrExtra, Code), Header),
445 format(Out, '~s', [Header]),
446 print_html_if_no_head(Method, Out, HTML).
447status_reply(bad_request(ErrorTerm), Out, HdrExtra, _Context, Method, Code) :-
448 !,
449 '$messages':translate_message(ErrorTerm, Lines, []),
450 phrase(page([ title('400 Bad Request')
451 ],
452 [ h1('Bad Request'),
453 p(\html_message_lines(Lines)),
454 \address
455 ]),
456 HTML),
457 phrase(reply_header(status(bad_request, HTML),
458 HdrExtra, Code), Header),
459 format(Out, '~s', [Header]),
460 print_html_if_no_head(Method, Out, HTML).
461status_reply(not_found(URL), Out, HdrExtra, Context, Method, Code) :-
462 !,
463 status_page_hook(not_found(URL), 404, Context, HTML),
464 phrase(reply_header(status(not_found, HTML), HdrExtra, Code), Header),
465 format(Out, '~s', [Header]),
466 print_html_if_no_head(Method, Out, HTML).
467status_reply(method_not_allowed(Method, URL), Out, HdrExtra, Context, QMethod, Code) :-
468 !,
469 upcase_atom(Method, UMethod),
470 status_page_hook(method_not_allowed(UMethod,URL), 405, Context, HTML),
471 phrase(reply_header(status(method_not_allowed, HTML),
472 HdrExtra, Code), Header),
473 format(Out, '~s', [Header]),
474 if_no_head(QMethod, print_html(Out, HTML)).
475status_reply(forbidden(URL), Out, HdrExtra, Context, Method, Code) :-
476 !,
477 status_page_hook(forbidden(URL), 403, Context, HTML),
478 phrase(reply_header(status(forbidden, HTML), HdrExtra, Code), Header),
479 format(Out, '~s', [Header]),
480 print_html_if_no_head(Method, Out, HTML).
481status_reply(authorise(basic, ''), Out, HdrExtra, Context, Method, Code) :-
482 !,
483 status_reply(authorise(basic), Out, HdrExtra, Context, Method, Code).
484status_reply(authorise(basic, Realm), Out, HdrExtra, Context, Method, Code) :-
485 !,
486 status_reply(authorise(basic(Realm)), Out, HdrExtra, Context,
487 Method, Code).
488status_reply(authorise(Method), Out, HdrExtra, Context, QMethod, Code) :-
489 !,
490 status_page_hook(authorise(Method), 401, Context, HTML),
491 phrase(reply_header(authorise(Method, HTML),
492 HdrExtra, Code), Header),
493 format(Out, '~s', [Header]),
494 print_html_if_no_head(QMethod, Out, HTML).
495status_reply(not_modified, Out, HdrExtra, _Context, _Method, Code) :-
496 !,
497 phrase(reply_header(status(not_modified), HdrExtra, Code), Header),
498 format(Out, '~s', [Header]).
499status_reply(server_error(ErrorTerm), Out, HdrExtra, _Context, Method, Code) :-
500 in_or_exclude_backtrace(ErrorTerm, ErrorTerm1),
501 '$messages':translate_message(ErrorTerm1, Lines, []),
502 phrase(page([ title('500 Internal server error')
503 ],
504 [ h1('Internal server error'),
505 p(\html_message_lines(Lines)),
506 \address
507 ]),
508 HTML),
509 phrase(reply_header(status(server_error, HTML),
510 HdrExtra, Code), Header),
511 format(Out, '~s', [Header]),
512 print_html_if_no_head(Method, Out, HTML).
513status_reply(not_acceptable(WhyHTML), Out, HdrExtra, _Context,
514 Method, Code) :-
515 !,
516 phrase(page([ title('406 Not Acceptable')
517 ],
518 [ h1('Not Acceptable'),
519 WhyHTML,
520 \address
521 ]),
522 HTML),
523 phrase(reply_header(status(not_acceptable, HTML), HdrExtra, Code), Header),
524 format(Out, '~s', [Header]),
525 print_html_if_no_head(Method, Out, HTML).
526status_reply(unavailable(WhyHTML), Out, HdrExtra, _Context, Method, Code) :-
527 !,
528 phrase(page([ title('503 Service Unavailable')
529 ],
530 [ h1('Service Unavailable'),
531 WhyHTML,
532 \address
533 ]),
534 HTML),
535 phrase(reply_header(status(service_unavailable, HTML), HdrExtra, Code),
536 Header),
537 format(Out, '~s', [Header]),
538 print_html_if_no_head(Method, Out, HTML).
539status_reply(resource_error(ErrorTerm), Out, HdrExtra, Context, Method, Code) :-
540 !,
541 '$messages':translate_message(ErrorTerm, Lines, []),
542 status_reply(unavailable(p(\html_message_lines(Lines))),
543 Out, HdrExtra, Context, Method, Code).
544status_reply(busy, Out, HdrExtra, Context, Method, Code) :-
545 !,
546 HTML = p(['The server is temporarily out of resources, ',
547 'please try again later']),
548 http_status_reply(unavailable(HTML), Out, HdrExtra, Context,
549 Method, Code).
550
551print_html_if_no_head(head, _, _) :- !.
552print_html_if_no_head(_, Out, HTML) :-
553 print_html(Out, HTML).
554
562
563status_page_hook(Term, Status, Context, HTML) :-
564 ( http:status_page(Term, Context, HTML)
565 ; http:status_page(Status, Context, HTML) % deprecated
566 ),
567 !.
568
569status_page_hook(authorise(_Method), 401, _Context, HTML):-
570 phrase(page([ title('401 Authorization Required')
571 ],
572 [ h1('Authorization Required'),
573 p(['This server could not verify that you ',
574 'are authorized to access the document ',
575 'requested. Either you supplied the wrong ',
576 'credentials (e.g., bad password), or your ',
577 'browser doesn\'t understand how to supply ',
578 'the credentials required.'
579 ]),
580 \address
581 ]),
582 HTML).
583status_page_hook(forbidden(URL), 403, _Context, HTML) :-
584 phrase(page([ title('403 Forbidden')
585 ],
586 [ h1('Forbidden'),
587 p(['You don\'t have permission to access ', URL,
588 ' on this server'
589 ]),
590 \address
591 ]),
592 HTML).
593status_page_hook(not_found(URL), 404, _Context, HTML) :-
594 phrase(page([ title('404 Not Found')
595 ],
596 [ h1('Not Found'),
597 p(['The requested URL ', tt(URL),
598 ' was not found on this server'
599 ]),
600 \address
601 ]),
602 HTML).
603status_page_hook(method_not_allowed(UMethod,URL), 405, _Context, HTML) :-
604 phrase(page([ title('405 Method not allowed')
605 ],
606 [ h1('Method not allowed'),
607 p(['The requested URL ', tt(URL),
608 ' does not support method ', tt(UMethod), '.'
609 ]),
610 \address
611 ]),
612 HTML).
613
614
615html_message_lines([]) -->
616 [].
617html_message_lines([nl|T]) -->
618 !,
619 html([br([])]),
620 html_message_lines(T).
621html_message_lines([flush]) -->
622 [].
623html_message_lines([Fmt-Args|T]) -->
624 !,
625 { format(string(S), Fmt, Args)
626 },
627 html([S]),
628 html_message_lines(T).
629html_message_lines([Fmt|T]) -->
630 !,
631 { format(string(S), Fmt, [])
632 },
633 html([S]),
634 html_message_lines(T).
635
640
([], H, H).
642http_join_headers([H|T], Hdr0, Hdr) :-
643 functor(H, N, A),
644 functor(H2, N, A),
645 member(H2, Hdr0),
646 !,
647 http_join_headers(T, Hdr0, Hdr).
648http_join_headers([H|T], Hdr0, [H|Hdr]) :-
649 http_join_headers(T, Hdr0, Hdr).
650
651
660
661http_update_encoding(Header0, utf8, [content_type(Type)|Header]) :-
662 select(content_type(Type0), Header0, Header),
663 sub_atom(Type0, 0, _, _, 'text/'),
664 !,
665 ( sub_atom(Type0, S, _, _, ';')
666 -> sub_atom(Type0, 0, S, _, B)
667 ; B = Type0
668 ),
669 atom_concat(B, '; charset=UTF-8', Type).
670http_update_encoding(Header, Encoding, Header) :-
671 memberchk(content_type(Type), Header),
672 ( ( sub_atom(Type, _, _, _, 'UTF-8')
673 ; sub_atom(Type, _, _, _, 'utf-8')
674 )
675 -> Encoding = utf8
676 ; mime_type_encoding(Type, Encoding)
677 ).
678http_update_encoding(Header, octet, Header).
679
683
684mime_type_encoding('application/json', utf8).
685mime_type_encoding('application/jsonrequest', utf8).
686
687
692
693http_update_connection(CgiHeader, Request, Connect,
694 [connection(Connect)|Rest]) :-
695 select(connection(CgiConn), CgiHeader, Rest),
696 !,
697 connection(Request, ReqConnection),
698 join_connection(ReqConnection, CgiConn, Connect).
699http_update_connection(CgiHeader, Request, Connect,
700 [connection(Connect)|CgiHeader]) :-
701 connection(Request, Connect).
702
703join_connection(Keep1, Keep2, Connection) :-
704 ( downcase_atom(Keep1, 'keep-alive'),
705 downcase_atom(Keep2, 'keep-alive')
706 -> Connection = 'Keep-Alive'
707 ; Connection = close
708 ).
709
710
714
715connection(Header, Close) :-
716 ( memberchk(connection(Connection), Header)
717 -> Close = Connection
718 ; memberchk(http_version(1-X), Header),
719 X >= 1
720 -> Close = 'Keep-Alive'
721 ; Close = close
722 ).
723
724
740
741http_update_transfer(Request, CgiHeader, Transfer, Header) :-
742 setting(http:chunked_transfer, When),
743 http_update_transfer(When, Request, CgiHeader, Transfer, Header).
744
745http_update_transfer(never, _, CgiHeader, none, Header) :-
746 !,
747 delete(CgiHeader, transfer_encoding(_), Header).
748http_update_transfer(_, _, CgiHeader, none, Header) :-
749 memberchk(location(_), CgiHeader),
750 !,
751 delete(CgiHeader, transfer_encoding(_), Header).
752http_update_transfer(_, Request, CgiHeader, Transfer, Header) :-
753 select(transfer_encoding(CgiTransfer), CgiHeader, Rest),
754 !,
755 transfer(Request, ReqConnection),
756 join_transfer(ReqConnection, CgiTransfer, Transfer),
757 ( Transfer == none
758 -> Header = Rest
759 ; Header = [transfer_encoding(Transfer)|Rest]
760 ).
761http_update_transfer(if_possible, Request, CgiHeader, Transfer, Header) :-
762 transfer(Request, Transfer),
763 Transfer \== none,
764 !,
765 Header = [transfer_encoding(Transfer)|CgiHeader].
766http_update_transfer(_, _, CgiHeader, none, CgiHeader).
767
768join_transfer(chunked, chunked, chunked) :- !.
769join_transfer(_, _, none).
770
771
775
776transfer(Header, Transfer) :-
777 ( memberchk(transfer_encoding(Transfer0), Header)
778 -> Transfer = Transfer0
779 ; memberchk(http_version(1-X), Header),
780 X >= 1
781 -> Transfer = chunked
782 ; Transfer = none
783 ).
784
785
791
792content_length_in_encoding(Enc, Stream, Bytes) :-
793 stream_property(Stream, position(Here)),
794 setup_call_cleanup(
795 open_null_stream(Out),
796 ( set_stream(Out, encoding(Enc)),
797 catch(copy_stream_data(Stream, Out), _, fail),
798 flush_output(Out),
799 byte_count(Out, Bytes)
800 ),
801 ( close(Out, [force(true)]),
802 set_stream_position(Stream, Here)
803 )).
804
805
806 809
900
901http_post_data(Data, Out, HdrExtra) :-
902 http:post_data_hook(Data, Out, HdrExtra),
903 !.
904http_post_data(html(HTML), Out, HdrExtra) :-
905 !,
906 phrase(post_header(html(HTML), HdrExtra), Header),
907 format(Out, '~s', [Header]),
908 print_html(Out, HTML).
909http_post_data(xml(XML), Out, HdrExtra) :-
910 !,
911 http_post_data(xml(text/xml, XML, []), Out, HdrExtra).
912http_post_data(xml(Type, XML), Out, HdrExtra) :-
913 !,
914 http_post_data(xml(Type, XML, []), Out, HdrExtra).
915http_post_data(xml(Type, XML, Options), Out, HdrExtra) :-
916 !,
917 setup_call_cleanup(
918 new_memory_file(MemFile),
919 ( setup_call_cleanup(
920 open_memory_file(MemFile, write, MemOut),
921 xml_write(MemOut, XML, Options),
922 close(MemOut)),
923 http_post_data(memory_file(Type, MemFile), Out, HdrExtra)
924 ),
925 free_memory_file(MemFile)).
926http_post_data(file(File), Out, HdrExtra) :-
927 !,
928 ( file_mime_type(File, Type)
929 -> true
930 ; Type = text/plain
931 ),
932 http_post_data(file(Type, File), Out, HdrExtra).
933http_post_data(file(Type, File), Out, HdrExtra) :-
934 !,
935 phrase(post_header(file(Type, File), HdrExtra), Header),
936 format(Out, '~s', [Header]),
937 setup_call_cleanup(
938 open(File, read, In, [type(binary)]),
939 copy_stream_data(In, Out),
940 close(In)).
941http_post_data(memory_file(Type, Handle), Out, HdrExtra) :-
942 !,
943 phrase(post_header(memory_file(Type, Handle), HdrExtra), Header),
944 format(Out, '~s', [Header]),
945 setup_call_cleanup(
946 open_memory_file(Handle, read, In, [encoding(octet)]),
947 copy_stream_data(In, Out),
948 close(In)).
949http_post_data(codes(Codes), Out, HdrExtra) :-
950 !,
951 http_post_data(codes(text/plain, Codes), Out, HdrExtra).
952http_post_data(codes(Type, Codes), Out, HdrExtra) :-
953 !,
954 phrase(post_header(codes(Type, Codes), HdrExtra), Header),
955 format(Out, '~s', [Header]),
956 setup_call_cleanup(
957 set_stream(Out, encoding(utf8)),
958 format(Out, '~s', [Codes]),
959 set_stream(Out, encoding(octet))).
960http_post_data(bytes(Type, Bytes), Out, HdrExtra) :-
961 !,
962 phrase(post_header(bytes(Type, Bytes), HdrExtra), Header),
963 format(Out, '~s~s', [Header, Bytes]).
964http_post_data(atom(Atom), Out, HdrExtra) :-
965 !,
966 http_post_data(atom(text/plain, Atom), Out, HdrExtra).
967http_post_data(atom(Type, Atom), Out, HdrExtra) :-
968 !,
969 phrase(post_header(atom(Type, Atom), HdrExtra), Header),
970 format(Out, '~s', [Header]),
971 setup_call_cleanup(
972 set_stream(Out, encoding(utf8)),
973 write(Out, Atom),
974 set_stream(Out, encoding(octet))).
975http_post_data(cgi_stream(In, _Len), Out, HdrExtra) :-
976 !,
977 debug(obsolete, 'Obsolete 2nd argument in cgi_stream(In,Len)', []),
978 http_post_data(cgi_stream(In), Out, HdrExtra).
979http_post_data(cgi_stream(In), Out, HdrExtra) :-
980 !,
981 http_read_header(In, Header0),
982 http_update_encoding(Header0, Encoding, Header),
983 content_length_in_encoding(Encoding, In, Size),
984 http_join_headers(HdrExtra, Header, Hdr2),
985 phrase(post_header(cgi_data(Size), Hdr2), HeaderText),
986 format(Out, '~s', [HeaderText]),
987 setup_call_cleanup(
988 set_stream(Out, encoding(Encoding)),
989 copy_stream_data(In, Out),
990 set_stream(Out, encoding(octet))).
991http_post_data(form(Fields), Out, HdrExtra) :-
992 !,
993 parse_url_search(Codes, Fields),
994 length(Codes, Size),
995 http_join_headers(HdrExtra,
996 [ content_type('application/x-www-form-urlencoded')
997 ], Header),
998 phrase(post_header(cgi_data(Size), Header), HeaderChars),
999 format(Out, '~s', [HeaderChars]),
1000 format(Out, '~s', [Codes]).
1001http_post_data(form_data(Data), Out, HdrExtra) :-
1002 !,
1003 setup_call_cleanup(
1004 new_memory_file(MemFile),
1005 ( setup_call_cleanup(
1006 open_memory_file(MemFile, write, MimeOut),
1007 mime_pack(Data, MimeOut, Boundary),
1008 close(MimeOut)),
1009 size_memory_file(MemFile, Size, octet),
1010 format(string(ContentType),
1011 'multipart/form-data; boundary=~w', [Boundary]),
1012 http_join_headers(HdrExtra,
1013 [ mime_version('1.0'),
1014 content_type(ContentType)
1015 ], Header),
1016 phrase(post_header(cgi_data(Size), Header), HeaderChars),
1017 format(Out, '~s', [HeaderChars]),
1018 setup_call_cleanup(
1019 open_memory_file(MemFile, read, In, [encoding(octet)]),
1020 copy_stream_data(In, Out),
1021 close(In))
1022 ),
1023 free_memory_file(MemFile)).
1024http_post_data(List, Out, HdrExtra) :- 1025 is_list(List),
1026 !,
1027 setup_call_cleanup(
1028 new_memory_file(MemFile),
1029 ( setup_call_cleanup(
1030 open_memory_file(MemFile, write, MimeOut),
1031 mime_pack(List, MimeOut, Boundary),
1032 close(MimeOut)),
1033 size_memory_file(MemFile, Size, octet),
1034 format(string(ContentType),
1035 'multipart/mixed; boundary=~w', [Boundary]),
1036 http_join_headers(HdrExtra,
1037 [ mime_version('1.0'),
1038 content_type(ContentType)
1039 ], Header),
1040 phrase(post_header(cgi_data(Size), Header), HeaderChars),
1041 format(Out, '~s', [HeaderChars]),
1042 setup_call_cleanup(
1043 open_memory_file(MemFile, read, In, [encoding(octet)]),
1044 copy_stream_data(In, Out),
1045 close(In))
1046 ),
1047 free_memory_file(MemFile)).
1048
1053
(html(Tokens), HdrExtra) -->
1055 header_fields(HdrExtra, Len),
1056 content_length(html(Tokens), Len),
1057 content_type(text/html),
1058 "\r\n".
1059post_header(file(Type, File), HdrExtra) -->
1060 header_fields(HdrExtra, Len),
1061 content_length(file(File), Len),
1062 content_type(Type),
1063 "\r\n".
1064post_header(memory_file(Type, File), HdrExtra) -->
1065 header_fields(HdrExtra, Len),
1066 content_length(memory_file(File), Len),
1067 content_type(Type),
1068 "\r\n".
1069post_header(cgi_data(Size), HdrExtra) -->
1070 header_fields(HdrExtra, Len),
1071 content_length(Size, Len),
1072 "\r\n".
1073post_header(codes(Type, Codes), HdrExtra) -->
1074 header_fields(HdrExtra, Len),
1075 content_length(codes(Codes, utf8), Len),
1076 content_type(Type, utf8),
1077 "\r\n".
1078post_header(bytes(Type, Bytes), HdrExtra) -->
1079 header_fields(HdrExtra, Len),
1080 content_length(bytes(Bytes), Len),
1081 content_type(Type),
1082 "\r\n".
1083post_header(atom(Type, Atom), HdrExtra) -->
1084 header_fields(HdrExtra, Len),
1085 content_length(atom(Atom, utf8), Len),
1086 content_type(Type, utf8),
1087 "\r\n".
1088
1089
1090 1093
1098
(Out, What, HdrExtra) :-
1100 phrase(reply_header(What, HdrExtra, _Code), String),
1101 !,
1102 format(Out, '~s', [String]).
1103
1121
(string(String), HdrExtra, Code) -->
1123 reply_header(string(text/plain, String), HdrExtra, Code).
1124reply_header(string(Type, String), HdrExtra, Code) -->
1125 vstatus(ok, Code, HdrExtra),
1126 date(now),
1127 header_fields(HdrExtra, CLen),
1128 content_length(codes(String, utf8), CLen),
1129 content_type(Type, utf8),
1130 "\r\n".
1131reply_header(bytes(Type, Bytes), HdrExtra, Code) -->
1132 vstatus(ok, Code, HdrExtra),
1133 date(now),
1134 header_fields(HdrExtra, CLen),
1135 content_length(bytes(Bytes), CLen),
1136 content_type(Type),
1137 "\r\n".
1138reply_header(html(Tokens), HdrExtra, Code) -->
1139 vstatus(ok, Code, HdrExtra),
1140 date(now),
1141 header_fields(HdrExtra, CLen),
1142 content_length(html(Tokens), CLen),
1143 content_type(text/html),
1144 "\r\n".
1145reply_header(file(Type, File), HdrExtra, Code) -->
1146 vstatus(ok, Code, HdrExtra),
1147 date(now),
1148 modified(file(File)),
1149 header_fields(HdrExtra, CLen),
1150 content_length(file(File), CLen),
1151 content_type(Type),
1152 "\r\n".
1153reply_header(gzip_file(Type, File), HdrExtra, Code) -->
1154 vstatus(ok, Code, HdrExtra),
1155 date(now),
1156 modified(file(File)),
1157 header_fields(HdrExtra, CLen),
1158 content_length(file(File), CLen),
1159 content_type(Type),
1160 content_encoding(gzip),
1161 "\r\n".
1162reply_header(file(Type, File, Range), HdrExtra, Code) -->
1163 vstatus(partial_content, Code, HdrExtra),
1164 date(now),
1165 modified(file(File)),
1166 header_fields(HdrExtra, CLen),
1167 content_length(file(File, Range), CLen),
1168 content_type(Type),
1169 "\r\n".
1170reply_header(tmp_file(Type, File), HdrExtra, Code) -->
1171 vstatus(ok, Code, HdrExtra),
1172 date(now),
1173 header_fields(HdrExtra, CLen),
1174 content_length(file(File), CLen),
1175 content_type(Type),
1176 "\r\n".
1177reply_header(cgi_data(Size), HdrExtra, Code) -->
1178 vstatus(ok, Code, HdrExtra),
1179 date(now),
1180 header_fields(HdrExtra, CLen),
1181 content_length(Size, CLen),
1182 "\r\n".
1183reply_header(chunked_data, HdrExtra, Code) -->
1184 vstatus(ok, Code, HdrExtra),
1185 date(now),
1186 header_fields(HdrExtra, _),
1187 ( {memberchk(transfer_encoding(_), HdrExtra)}
1188 -> ""
1189 ; transfer_encoding(chunked)
1190 ),
1191 "\r\n".
1192reply_header(moved(To, Tokens), HdrExtra, Code) -->
1193 vstatus(moved, Code, HdrExtra),
1194 date(now),
1195 header_field('Location', To),
1196 header_fields(HdrExtra, CLen),
1197 content_length(html(Tokens), CLen),
1198 content_type(text/html, utf8),
1199 "\r\n".
1200reply_header(created(Location, Tokens), HdrExtra, Code) -->
1201 vstatus(created, Code, HdrExtra),
1202 date(now),
1203 header_field('Location', Location),
1204 header_fields(HdrExtra, CLen),
1205 content_length(html(Tokens), CLen),
1206 content_type(text/html, utf8),
1207 "\r\n".
1208reply_header(moved_temporary(To, Tokens), HdrExtra, Code) -->
1209 vstatus(moved_temporary, Code, HdrExtra),
1210 date(now),
1211 header_field('Location', To),
1212 header_fields(HdrExtra, CLen),
1213 content_length(html(Tokens), CLen),
1214 content_type(text/html, utf8),
1215 "\r\n".
1216reply_header(see_other(To,Tokens),HdrExtra, Code) -->
1217 vstatus(see_other, Code, HdrExtra),
1218 date(now),
1219 header_field('Location',To),
1220 header_fields(HdrExtra, CLen),
1221 content_length(html(Tokens), CLen),
1222 content_type(text/html, utf8),
1223 "\r\n".
1224reply_header(status(Status), HdrExtra, Code) --> 1225 vstatus(Status, Code),
1226 header_fields(HdrExtra, Clen),
1227 { Clen = 0 },
1228 "\r\n".
1229reply_header(status(Status, Tokens), HdrExtra, Code) -->
1230 vstatus(Status, Code),
1231 date(now),
1232 header_fields(HdrExtra, CLen),
1233 content_length(html(Tokens), CLen),
1234 content_type(text/html, utf8),
1235 "\r\n".
1236reply_header(authorise(Method, Tokens), HdrExtra, Code) -->
1237 vstatus(authorise, Code),
1238 date(now),
1239 authenticate(Method),
1240 header_fields(HdrExtra, CLen),
1241 content_length(html(Tokens), CLen),
1242 content_type(text/html, utf8),
1243 "\r\n".
1244
1249
1250vstatus(_Status, Code, HdrExtra) -->
1251 {memberchk(status(Code), HdrExtra)},
1252 !,
1253 vstatus(_NewStatus, Code).
1254vstatus(Status, Code, _) -->
1255 vstatus(Status, Code).
1256
1257vstatus(Status, Code) -->
1258 "HTTP/1.1 ",
1259 status_number(Status, Code),
1260 " ",
1261 status_comment(Status),
1262 "\r\n".
1263
1270
1271status_number(Status, Code) -->
1272 { var(Status) },
1273 !,
1274 integer(Code),
1275 { status_number(Status, Code) },
1276 !.
1277status_number(Status, Code) -->
1278 { status_number(Status, Code) },
1279 integer(Code).
1280
1292
1300
1301status_number(Status, Code):-
1302 nonvar(Status),
1303 !,
1304 status_number_fact(Status, Code).
1305status_number(Status, Code):-
1306 nonvar(Code),
1307 !,
1308 ( between(100, 599, Code)
1309 -> ( status_number_fact(Status, Code)
1310 -> true
1311 ; ClassCode is Code // 100 * 100,
1312 status_number_fact(Status, ClassCode)
1313 )
1314 ; domain_error(http_code, Code)
1315 ).
1316
1317status_number_fact(continue, 100).
1318status_number_fact(switching_protocols, 101).
1319status_number_fact(ok, 200).
1320status_number_fact(created, 201).
1321status_number_fact(accepted, 202).
1322status_number_fact(non_authoritative_info, 203).
1323status_number_fact(no_content, 204).
1324status_number_fact(reset_content, 205).
1325status_number_fact(partial_content, 206).
1326status_number_fact(multiple_choices, 300).
1327status_number_fact(moved, 301).
1328status_number_fact(moved_temporary, 302).
1329status_number_fact(see_other, 303).
1330status_number_fact(not_modified, 304).
1331status_number_fact(use_proxy, 305).
1332status_number_fact(unused, 306).
1333status_number_fact(temporary_redirect, 307).
1334status_number_fact(bad_request, 400).
1335status_number_fact(authorise, 401).
1336status_number_fact(payment_required, 402).
1337status_number_fact(forbidden, 403).
1338status_number_fact(not_found, 404).
1339status_number_fact(method_not_allowed, 405).
1340status_number_fact(not_acceptable, 406).
1341status_number_fact(request_timeout, 408).
1342status_number_fact(conflict, 409).
1343status_number_fact(gone, 410).
1344status_number_fact(length_required, 411).
1345status_number_fact(payload_too_large, 413).
1346status_number_fact(uri_too_long, 414).
1347status_number_fact(unsupported_media_type, 415).
1348status_number_fact(expectation_failed, 417).
1349status_number_fact(upgrade_required, 426).
1350status_number_fact(server_error, 500).
1351status_number_fact(not_implemented, 501).
1352status_number_fact(bad_gateway, 502).
1353status_number_fact(service_unavailable, 503).
1354status_number_fact(gateway_timeout, 504).
1355status_number_fact(http_version_not_supported, 505).
1356
1357
1361
(continue) -->
1363 "Continue".
1364status_comment(switching_protocols) -->
1365 "Switching Protocols".
1366status_comment(ok) -->
1367 "OK".
1368status_comment(created) -->
1369 "Created".
1370status_comment(accepted) -->
1371 "Accepted".
1372status_comment(non_authoritative_info) -->
1373 "Non-Authoritative Information".
1374status_comment(no_content) -->
1375 "No Content".
1376status_comment(reset_content) -->
1377 "Reset Content".
1378status_comment(created) -->
1379 "Created".
1380status_comment(partial_content) -->
1381 "Partial content".
1382status_comment(multiple_choices) -->
1383 "Multiple Choices".
1384status_comment(moved) -->
1385 "Moved Permanently".
1386status_comment(moved_temporary) -->
1387 "Moved Temporary".
1388status_comment(see_other) -->
1389 "See Other".
1390status_comment(not_modified) -->
1391 "Not Modified".
1392status_comment(use_proxy) -->
1393 "Use Proxy".
1394status_comment(unused) -->
1395 "Unused".
1396status_comment(temporary_redirect) -->
1397 "Temporary Redirect".
1398status_comment(bad_request) -->
1399 "Bad Request".
1400status_comment(authorise) -->
1401 "Authorization Required".
1402status_comment(payment_required) -->
1403 "Payment Required".
1404status_comment(forbidden) -->
1405 "Forbidden".
1406status_comment(not_found) -->
1407 "Not Found".
1408status_comment(method_not_allowed) -->
1409 "Method Not Allowed".
1410status_comment(not_acceptable) -->
1411 "Not Acceptable".
1412status_comment(request_timeout) -->
1413 "Request Timeout".
1414status_comment(conflict) -->
1415 "Conflict".
1416status_comment(gone) -->
1417 "Gone".
1418status_comment(length_required) -->
1419 "Length Required".
1420status_comment(payload_too_large) -->
1421 "Payload Too Large".
1422status_comment(uri_too_long) -->
1423 "URI Too Long".
1424status_comment(unsupported_media_type) -->
1425 "Unsupported Media Type".
1426status_comment(expectation_failed) -->
1427 "Expectation Failed".
1428status_comment(upgrade_required) -->
1429 "Upgrade Required".
1430status_comment(server_error) -->
1431 "Internal Server Error".
1432status_comment(not_implemented) -->
1433 "Not Implemented".
1434status_comment(bad_gateway) -->
1435 "Bad Gateway".
1436status_comment(service_unavailable) -->
1437 "Service Unavailable".
1438status_comment(gateway_timeout) -->
1439 "Gateway Timeout".
1440status_comment(http_version_not_supported) -->
1441 "HTTP Version Not Supported".
1442
1443authenticate(negotiate(Data)) -->
1444 "WWW-Authenticate: Negotiate ",
1445 { base64(Data, DataBase64),
1446 atom_codes(DataBase64, Codes)
1447 },
1448 string(Codes), "\r\n".
1449authenticate(negotiate) -->
1450 "WWW-Authenticate: Negotiate\r\n".
1451
1452authenticate(basic) -->
1453 !,
1454 "WWW-Authenticate: Basic\r\n".
1455authenticate(basic(Realm)) -->
1456 "WWW-Authenticate: Basic Realm=\"", atom(Realm), "\"\r\n".
1457
1458authenticate(digest) -->
1459 !,
1460 "WWW-Authenticate: Digest\r\n".
1461authenticate(digest(Details)) -->
1462 "WWW-Authenticate: Digest ", atom(Details), "\r\n".
1463
1464
1465date(Time) -->
1466 "Date: ",
1467 ( { Time == now }
1468 -> now
1469 ; rfc_date(Time)
1470 ),
1471 "\r\n".
1472
1473modified(file(File)) -->
1474 !,
1475 { time_file(File, Time)
1476 },
1477 modified(Time).
1478modified(Time) -->
1479 "Last-modified: ",
1480 ( { Time == now }
1481 -> now
1482 ; rfc_date(Time)
1483 ),
1484 "\r\n".
1485
1486
1493
1494content_length(file(File, bytes(From, To)), Len) -->
1495 !,
1496 { size_file(File, Size),
1497 ( To == end
1498 -> Len is Size - From,
1499 RangeEnd is Size - 1
1500 ; Len is To+1 - From, 1501 RangeEnd = To
1502 )
1503 },
1504 content_range(bytes, From, RangeEnd, Size),
1505 content_length(Len, Len).
1506content_length(Reply, Len) -->
1507 { length_of(Reply, Len)
1508 },
1509 "Content-Length: ", integer(Len),
1510 "\r\n".
1511
1512
1513length_of(_, Len) :-
1514 nonvar(Len),
1515 !.
1516length_of(codes(String, Encoding), Len) :-
1517 !,
1518 setup_call_cleanup(
1519 open_null_stream(Out),
1520 ( set_stream(Out, encoding(Encoding)),
1521 format(Out, '~s', [String]),
1522 byte_count(Out, Len)
1523 ),
1524 close(Out)).
1525length_of(atom(Atom, Encoding), Len) :-
1526 !,
1527 setup_call_cleanup(
1528 open_null_stream(Out),
1529 ( set_stream(Out, encoding(Encoding)),
1530 format(Out, '~a', [Atom]),
1531 byte_count(Out, Len)
1532 ),
1533 close(Out)).
1534length_of(file(File), Len) :-
1535 !,
1536 size_file(File, Len).
1537length_of(memory_file(Handle), Len) :-
1538 !,
1539 size_memory_file(Handle, Len, octet).
1540length_of(html(Tokens), Len) :-
1541 !,
1542 html_print_length(Tokens, Len).
1543length_of(bytes(Bytes), Len) :-
1544 !,
1545 ( string(Bytes)
1546 -> string_length(Bytes, Len)
1547 ; length(Bytes, Len) 1548 ).
1549length_of(Len, Len).
1550
1551
1556
1557content_range(Unit, From, RangeEnd, Size) -->
1558 "Content-Range: ", atom(Unit), " ",
1559 integer(From), "-", integer(RangeEnd), "/", integer(Size),
1560 "\r\n".
1561
1562content_encoding(Encoding) -->
1563 "Content-Encoding: ", atom(Encoding), "\r\n".
1564
1565transfer_encoding(Encoding) -->
1566 "Transfer-Encoding: ", atom(Encoding), "\r\n".
1567
1568content_type(Type) -->
1569 content_type(Type, _).
1570
1571content_type(Type, Charset) -->
1572 ctype(Type),
1573 charset(Charset),
1574 "\r\n".
1575
1576ctype(Main/Sub) -->
1577 !,
1578 "Content-Type: ",
1579 atom(Main),
1580 "/",
1581 atom(Sub).
1582ctype(Type) -->
1583 !,
1584 "Content-Type: ",
1585 atom(Type).
1586
1587charset(Var) -->
1588 { var(Var) },
1589 !.
1590charset(utf8) -->
1591 !,
1592 "; charset=UTF-8".
1593charset(CharSet) -->
1594 "; charset=",
1595 atom(CharSet).
1596
1602
(Name, Value) -->
1604 { var(Name) }, 1605 !,
1606 field_name(Name),
1607 ":",
1608 whites,
1609 read_field_value(ValueChars),
1610 blanks_to_nl,
1611 !,
1612 { field_to_prolog(Name, ValueChars, Value)
1613 -> true
1614 ; atom_codes(Value, ValueChars),
1615 domain_error(Name, Value)
1616 }.
1617header_field(Name, Value) -->
1618 field_name(Name),
1619 ": ",
1620 field_value(Value),
1621 "\r\n".
1622
1626
1627read_field_value([H|T]) -->
1628 [H],
1629 { \+ code_type(H, space) },
1630 !,
1631 read_field_value(T).
1632read_field_value([]) -->
1633 "".
1634read_field_value([H|T]) -->
1635 [H],
1636 read_field_value(T).
1637
1638
1668
(Field, Value, Prolog) :-
1670 known_field(Field, _),
1671 to_codes(Value, Codes),
1672 parse_header_value(Field, Codes, Prolog).
1673
1678
1679known_field(content_length, true).
1680known_field(status, true).
1681known_field(cookie, true).
1682known_field(set_cookie, true).
1683known_field(host, true).
1684known_field(range, maybe).
1685known_field(accept, maybe).
1686known_field(content_disposition, maybe).
1687known_field(content_type, false).
1688
1689to_codes(In, Codes) :-
1690 ( is_list(In)
1691 -> Codes = In
1692 ; atom_codes(In, Codes)
1693 ).
1694
1700
1701field_to_prolog(Field, Codes, Prolog) :-
1702 known_field(Field, true),
1703 !,
1704 ( parse_header_value(Field, Codes, Prolog0)
1705 -> Prolog = Prolog0
1706 ).
1707field_to_prolog(Field, Codes, Prolog) :-
1708 known_field(Field, maybe),
1709 parse_header_value(Field, Codes, Prolog0),
1710 !,
1711 Prolog = Prolog0.
1712field_to_prolog(_, Codes, Atom) :-
1713 atom_codes(Atom, Codes).
1714
1719
(content_length, ValueChars, ContentLength) :-
1721 number_codes(ContentLength, ValueChars).
1722parse_header_value(status, ValueChars, Code) :-
1723 ( phrase(" ", L, _),
1724 append(Pre, L, ValueChars)
1725 -> number_codes(Code, Pre)
1726 ; number_codes(Code, ValueChars)
1727 ).
1728parse_header_value(cookie, ValueChars, Cookies) :-
1729 debug(cookie, 'Cookie: ~s', [ValueChars]),
1730 phrase(cookies(Cookies), ValueChars).
1731parse_header_value(set_cookie, ValueChars, SetCookie) :-
1732 debug(cookie, 'SetCookie: ~s', [ValueChars]),
1733 phrase(set_cookie(SetCookie), ValueChars).
1734parse_header_value(host, ValueChars, Host) :-
1735 ( append(HostChars, [0':|PortChars], ValueChars),
1736 catch(number_codes(Port, PortChars), _, fail)
1737 -> atom_codes(HostName, HostChars),
1738 Host = HostName:Port
1739 ; atom_codes(Host, ValueChars)
1740 ).
1741parse_header_value(range, ValueChars, Range) :-
1742 phrase(range(Range), ValueChars).
1743parse_header_value(accept, ValueChars, Media) :-
1744 parse_accept(ValueChars, Media).
1745parse_header_value(content_disposition, ValueChars, Disposition) :-
1746 phrase(content_disposition(Disposition), ValueChars).
1747parse_header_value(content_type, ValueChars, Type) :-
1748 phrase(parse_content_type(Type), ValueChars).
1749
1750field_value(set_cookie(Name, Value, Options)) -->
1751 !,
1752 atom(Name), "=", atom(Value),
1753 value_options(Options, cookie).
1754field_value(disposition(Disposition, Options)) -->
1755 !,
1756 atom(Disposition), value_options(Options, disposition).
1757field_value(Atomic) -->
1758 atom(Atomic).
1759
1766
1767value_options([], _) --> [].
1768value_options([H|T], Field) -->
1769 "; ", value_option(H, Field),
1770 value_options(T, Field).
1771
1772value_option(secure=true, cookie) -->
1773 !,
1774 "secure".
1775value_option(Name=Value, Type) -->
1776 { string_option(Name, Type) },
1777 !,
1778 atom(Name), "=",
1779 qstring(Value).
1780value_option(Name=Value, Type) -->
1781 { token_option(Name, Type) },
1782 !,
1783 atom(Name), "=", atom(Value).
1784value_option(Name=Value, _Type) -->
1785 atom(Name), "=",
1786 option_value(Value).
1787
1788string_option(filename, disposition).
1789
1790token_option(path, cookie).
1791
1792option_value(Value) -->
1793 { number(Value) },
1794 !,
1795 number(Value).
1796option_value(Value) -->
1797 { ( atom(Value)
1798 -> true
1799 ; string(Value)
1800 ),
1801 forall(string_code(_, Value, C),
1802 token_char(C))
1803 },
1804 !,
1805 atom(Value).
1806option_value(Atomic) -->
1807 qstring(Atomic).
1808
1809qstring(Atomic) -->
1810 { string_codes(Atomic, Codes) },
1811 "\"",
1812 qstring_codes(Codes),
1813 "\"".
1814
1815qstring_codes([]) --> [].
1816qstring_codes([H|T]) --> qstring_code(H), qstring_codes(T).
1817
1818qstring_code(C) --> {qstring_esc(C)}, !, "\\", [C].
1819qstring_code(C) --> [C].
1820
1821qstring_esc(0'").
1822qstring_esc(C) :- ctl(C).
1823
1824
1825 1828
1829:- dynamic accept_cache/2.
1830:- volatile accept_cache/2.
1831
1832parse_accept(Codes, Media) :-
1833 atom_codes(Atom, Codes),
1834 ( accept_cache(Atom, Media0)
1835 -> Media = Media0
1836 ; phrase(accept(Media0), Codes),
1837 keysort(Media0, Media1),
1838 pairs_values(Media1, Media2),
1839 assertz(accept_cache(Atom, Media2)),
1840 Media = Media2
1841 ).
1842
1846
1847accept([H|T]) -->
1848 blanks,
1849 media_range(H),
1850 blanks,
1851 ( ","
1852 -> accept(T)
1853 ; {T=[]}
1854 ).
1855
1856media_range(s(SortQuality,Spec)-media(Type, TypeParams, Quality, AcceptExts)) -->
1857 media_type(Type),
1858 blanks,
1859 ( ";"
1860 -> blanks,
1861 parameters_and_quality(TypeParams, Quality, AcceptExts)
1862 ; { TypeParams = [],
1863 Quality = 1.0,
1864 AcceptExts = []
1865 }
1866 ),
1867 { SortQuality is float(-Quality),
1868 rank_specialised(Type, TypeParams, Spec)
1869 }.
1870
1871
1875
1876content_disposition(disposition(Disposition, Options)) -->
1877 token(Disposition), blanks,
1878 value_parameters(Options).
1879
1884
1885parse_content_type(media(Type, Parameters)) -->
1886 media_type(Type), blanks,
1887 value_parameters(Parameters).
1888
1889
1897
1898rank_specialised(Type/SubType, TypeParams, v(VT, VS, SortVP)) :-
1899 var_or_given(Type, VT),
1900 var_or_given(SubType, VS),
1901 length(TypeParams, VP),
1902 SortVP is -VP.
1903
1904var_or_given(V, Val) :-
1905 ( var(V)
1906 -> Val = 0
1907 ; Val = -1
1908 ).
1909
1910media_type(Type/SubType) -->
1911 type(Type), "/", type(SubType).
1912
1913type(_) -->
1914 "*",
1915 !.
1916type(Type) -->
1917 token(Type).
1918
1919parameters_and_quality(Params, Quality, AcceptExts) -->
1920 token(Name),
1921 blanks, "=", blanks,
1922 ( { Name == q }
1923 -> float(Quality), blanks,
1924 value_parameters(AcceptExts),
1925 { Params = [] }
1926 ; { Params = [Name=Value|T] },
1927 parameter_value(Value),
1928 blanks,
1929 ( ";"
1930 -> blanks,
1931 parameters_and_quality(T, Quality, AcceptExts)
1932 ; { T = [],
1933 Quality = 1.0,
1934 AcceptExts = []
1935 }
1936 )
1937 ).
1938
1943
1944value_parameters([H|T]) -->
1945 ";",
1946 !,
1947 blanks, token(Name), blanks,
1948 ( "="
1949 -> blanks,
1950 ( token(Value)
1951 -> []
1952 ; quoted_string(Value)
1953 ),
1954 { H = (Name=Value) }
1955 ; { H = Name }
1956 ),
1957 blanks,
1958 value_parameters(T).
1959value_parameters([]) -->
1960 [].
1961
1962parameter_value(Value) --> token(Value), !.
1963parameter_value(Value) --> quoted_string(Value).
1964
1965
1969
1970token(Name) -->
1971 token_char(C1),
1972 token_chars(Cs),
1973 { atom_codes(Name, [C1|Cs]) }.
1974
1975token_chars([H|T]) -->
1976 token_char(H),
1977 !,
1978 token_chars(T).
1979token_chars([]) --> [].
1980
1981token_char(C) --> [C], { token_char(C) }.
1982
1983token_char(C) :-
1984 \+ ctl(C),
1985 \+ separator_code(C).
1986
1987ctl(C) :- between(0,31,C), !.
1988ctl(127).
1989
1990separator_code(0'().
1991separator_code(0')).
1992separator_code(0'<).
1993separator_code(0'>).
1994separator_code(0'@).
1995separator_code(0',).
1996separator_code(0';).
1997separator_code(0':).
1998separator_code(0'\\).
1999separator_code(0'").
2000separator_code(0'/).
2001separator_code(0'[).
2002separator_code(0']).
2003separator_code(0'?).
2004separator_code(0'=).
2005separator_code(0'{).
2006separator_code(0'}).
2007separator_code(0'\s).
2008separator_code(0'\t).
2009
2010
2014
2015quoted_string(Text) -->
2016 "\"",
2017 quoted_text(Codes),
2018 { atom_codes(Text, Codes) }.
2019
2020quoted_text([]) -->
2021 "\"",
2022 !.
2023quoted_text([H|T]) -->
2024 "\\", !, [H],
2025 quoted_text(T).
2026quoted_text([H|T]) -->
2027 [H],
2028 !,
2029 quoted_text(T).
2030
2031
2039
([], _) --> [].
2041header_fields([content_length(CLen)|T], CLen) -->
2042 !,
2043 ( { var(CLen) }
2044 -> ""
2045 ; header_field(content_length, CLen)
2046 ),
2047 header_fields(T, CLen). 2048header_fields([status(_)|T], CLen) --> 2049 !,
2050 header_fields(T, CLen).
2051header_fields([H|T], CLen) -->
2052 { H =.. [Name, Value] },
2053 header_field(Name, Value),
2054 header_fields(T, CLen).
2055
2056
2070
2071:- public
2072 field_name//1.
2073
2074field_name(Name) -->
2075 { var(Name) },
2076 !,
2077 rd_field_chars(Chars),
2078 { atom_codes(Name, Chars) }.
2079field_name(mime_version) -->
2080 !,
2081 "MIME-Version".
2082field_name(Name) -->
2083 { atom_codes(Name, Chars) },
2084 wr_field_chars(Chars).
2085
2086rd_field_chars_no_fold([C|T]) -->
2087 [C],
2088 { rd_field_char(C, _) },
2089 !,
2090 rd_field_chars_no_fold(T).
2091rd_field_chars_no_fold([]) -->
2092 [].
2093
2094rd_field_chars([C0|T]) -->
2095 [C],
2096 { rd_field_char(C, C0) },
2097 !,
2098 rd_field_chars(T).
2099rd_field_chars([]) -->
2100 [].
2101
2105
2106separators("()<>@,;:\\\"/[]?={} \t").
2107
2108term_expansion(rd_field_char('expand me',_), Clauses) :-
2109
2110 Clauses = [ rd_field_char(0'-, 0'_)
2111 | Cls
2112 ],
2113 separators(SepString),
2114 string_codes(SepString, Seps),
2115 findall(rd_field_char(In, Out),
2116 ( between(32, 127, In),
2117 \+ memberchk(In, Seps),
2118 In \== 0'-, 2119 code_type(Out, to_lower(In))),
2120 Cls).
2121
2122rd_field_char('expand me', _). 2123
2124wr_field_chars([C|T]) -->
2125 !,
2126 { code_type(C, to_lower(U)) },
2127 [U],
2128 wr_field_chars2(T).
2129wr_field_chars([]) -->
2130 [].
2131
2132wr_field_chars2([]) --> [].
2133wr_field_chars2([C|T]) --> 2134 ( { C == 0'_ }
2135 -> "-",
2136 wr_field_chars(T)
2137 ; [C],
2138 wr_field_chars2(T)
2139 ).
2140
2144
2145now -->
2146 { get_time(Time)
2147 },
2148 rfc_date(Time).
2149
2154
2155rfc_date(Time, String, Tail) :-
2156 stamp_date_time(Time, Date, 'UTC'),
2157 format_time(codes(String, Tail),
2158 '%a, %d %b %Y %T GMT',
2159 Date, posix).
2160
2164
2165http_timestamp(Time, Atom) :-
2166 stamp_date_time(Time, Date, 'UTC'),
2167 format_time(atom(Atom),
2168 '%a, %d %b %Y %T GMT',
2169 Date, posix).
2170
2171
2172 2175
2176request(Fd, [method(Method),request_uri(ReqURI)|Header]) -->
2177 method(Method),
2178 blanks,
2179 nonblanks(Query),
2180 { atom_codes(ReqURI, Query),
2181 request_uri_parts(ReqURI, Header, Rest)
2182 },
2183 request_header(Fd, Rest),
2184 !.
2185request(Fd, [unknown(What)|Header]) -->
2186 string(What),
2187 eos,
2188 !,
2189 { http_read_header(Fd, Header)
2190 -> true
2191 ; Header = []
2192 }.
2193
2194method(get) --> "GET", !.
2195method(put) --> "PUT", !.
2196method(head) --> "HEAD", !.
2197method(post) --> "POST", !.
2198method(delete) --> "DELETE", !.
2199method(patch) --> "PATCH", !.
2200method(options) --> "OPTIONS", !.
2201method(trace) --> "TRACE", !.
2202
2214
2215request_uri_parts(ReqURI, [path(Path)|Parts], Rest) :-
2216 uri_components(ReqURI, Components),
2217 uri_data(path, Components, PathText),
2218 uri_encoded(path, Path, PathText),
2219 phrase(uri_parts(Components), Parts, Rest).
2220
2221uri_parts(Components) -->
2222 uri_search(Components),
2223 uri_fragment(Components).
2224
2225uri_search(Components) -->
2226 { uri_data(search, Components, Search),
2227 nonvar(Search),
2228 catch(uri_query_components(Search, Query),
2229 error(syntax_error(_),_),
2230 fail)
2231 },
2232 !,
2233 [ search(Query) ].
2234uri_search(_) --> [].
2235
2236uri_fragment(Components) -->
2237 { uri_data(fragment, Components, String),
2238 nonvar(String),
2239 !,
2240 uri_encoded(fragment, Fragment, String)
2241 },
2242 [ fragment(Fragment) ].
2243uri_fragment(_) --> [].
2244
2249
(_, []) --> 2251 blanks,
2252 eos,
2253 !.
2254request_header(Fd, [http_version(Version)|Header]) -->
2255 http_version(Version),
2256 blanks,
2257 eos,
2258 !,
2259 { Version = 1-_
2260 -> http_read_header(Fd, Header)
2261 ; Header = []
2262 }.
2263
2264http_version(Version) -->
2265 blanks,
2266 "HTTP/",
2267 http_version_number(Version).
2268
2269http_version_number(Major-Minor) -->
2270 integer(Major),
2271 ".",
2272 integer(Minor).
2273
2274
2275 2278
2282
2283cookies([Name=Value|T]) -->
2284 blanks,
2285 cookie(Name, Value),
2286 !,
2287 blanks,
2288 ( ";"
2289 -> cookies(T)
2290 ; { T = [] }
2291 ).
2292cookies(List) -->
2293 string(Skipped),
2294 ";",
2295 !,
2296 { print_message(warning, http(skipped_cookie(Skipped))) },
2297 cookies(List).
2298cookies([]) -->
2299 blanks.
2300
2301cookie(Name, Value) -->
2302 cookie_name(Name),
2303 blanks, "=", blanks,
2304 cookie_value(Value).
2305
2306cookie_name(Name) -->
2307 { var(Name) },
2308 !,
2309 rd_field_chars_no_fold(Chars),
2310 { atom_codes(Name, Chars) }.
2311
2312cookie_value(Value) -->
2313 quoted_string(Value),
2314 !.
2315cookie_value(Value) -->
2316 chars_to_semicolon_or_blank(Chars),
2317 { atom_codes(Value, Chars)
2318 }.
2319
2320chars_to_semicolon_or_blank([H|T]) -->
2321 [H],
2322 { H \== 32, H \== 0'; },
2323 !,
2324 chars_to_semicolon_or_blank(T).
2325chars_to_semicolon_or_blank([]) -->
2326 [].
2327
2328set_cookie(set_cookie(Name, Value, Options)) -->
2329 ws,
2330 cookie(Name, Value),
2331 cookie_options(Options).
2332
2333cookie_options([H|T]) -->
2334 ws,
2335 ";",
2336 ws,
2337 cookie_option(H),
2338 !,
2339 cookie_options(T).
2340cookie_options([]) -->
2341 ws.
2342
2343ws --> " ", !, ws.
2344ws --> [].
2345
2346
2356
2357cookie_option(Name=Value) -->
2358 rd_field_chars(NameChars), ws,
2359 { atom_codes(Name, NameChars) },
2360 ( "="
2361 -> ws,
2362 chars_to_semicolon(ValueChars),
2363 { atom_codes(Value, ValueChars)
2364 }
2365 ; { Value = true }
2366 ).
2367
2368chars_to_semicolon([H|T]) -->
2369 [H],
2370 { H \== 32, H \== 0'; },
2371 !,
2372 chars_to_semicolon(T).
2373chars_to_semicolon([]), ";" -->
2374 ws, ";",
2375 !.
2376chars_to_semicolon([H|T]) -->
2377 [H],
2378 chars_to_semicolon(T).
2379chars_to_semicolon([]) -->
2380 [].
2381
2389
2390range(bytes(From, To)) -->
2391 "bytes", whites, "=", whites, integer(From), "-",
2392 ( integer(To)
2393 -> ""
2394 ; { To = end }
2395 ).
2396
2397
2398 2401
2416
2417reply(Fd, [http_version(HttpVersion), status(Code, Status, Comment)|Header]) -->
2418 http_version(HttpVersion),
2419 blanks,
2420 ( status_number(Status, Code)
2421 -> []
2422 ; integer(Status)
2423 ),
2424 blanks,
2425 string(CommentCodes),
2426 blanks_to_nl,
2427 !,
2428 blanks,
2429 { atom_codes(Comment, CommentCodes),
2430 http_read_header(Fd, Header)
2431 }.
2432
2433
2434 2437
2443
(Fd, Header) :-
2445 read_header_data(Fd, Text),
2446 http_parse_header(Text, Header).
2447
(Fd, Header) :-
2449 read_line_to_codes(Fd, Header, Tail),
2450 read_header_data(Header, Fd, Tail),
2451 debug(http(header), 'Header = ~n~s~n', [Header]).
2452
([0'\r,0'\n], _, _) :- !.
2454read_header_data([0'\n], _, _) :- !.
2455read_header_data([], _, _) :- !.
2456read_header_data(_, Fd, Tail) :-
2457 read_line_to_codes(Fd, Tail, NewTail),
2458 read_header_data(Tail, Fd, NewTail).
2459
2466
(Text, Header) :-
2468 phrase(header(Header), Text),
2469 debug(http(header), 'Field: ~p', [Header]).
2470
(List) -->
2472 header_field(Name, Value),
2473 !,
2474 { mkfield(Name, Value, List, Tail)
2475 },
2476 blanks,
2477 header(Tail).
2478header([]) -->
2479 blanks,
2480 eos,
2481 !.
2482header(_) -->
2483 string(S), blanks_to_nl,
2484 !,
2485 { string_codes(Line, S),
2486 syntax_error(http_parameter(Line))
2487 }.
2488
2500
2501:- multifile
2502 http:http_address//0.
2503
2504address -->
2505 http:http_address,
2506 !.
2507address -->
2508 { gethostname(Host) },
2509 html(address([ a(href('http://www.swi-prolog.org'), 'SWI-Prolog'),
2510 ' httpd at ', Host
2511 ])).
2512
2513mkfield(host, Host:Port, [host(Host),port(Port)|Tail], Tail) :- !.
2514mkfield(Name, Value, [Att|Tail], Tail) :-
2515 Att =.. [Name, Value].
2516
2522
2544
2545
2546 2549
2550:- multifile
2551 prolog:message//1,
2552 prolog:error_message//1.
2553
2554prolog:error_message(http_write_short(Data, Sent)) -->
2555 data(Data),
2556 [ ': remote hangup after ~D bytes'-[Sent] ].
2557prolog:error_message(syntax_error(http_request(Request))) -->
2558 [ 'Illegal HTTP request: ~s'-[Request] ].
2559prolog:error_message(syntax_error(http_parameter(Line))) -->
2560 [ 'Illegal HTTP parameter: ~s'-[Line] ].
2561
2562prolog:message(http(skipped_cookie(S))) -->
2563 [ 'Skipped illegal cookie: ~s'-[S] ].
2564
2565data(bytes(MimeType, _Bytes)) -->
2566 !,
2567 [ 'bytes(~p, ...)'-[MimeType] ].
2568data(Data) -->
2569 [ '~p'-[Data] ].