From 96d28e7f42ead1ddde6bccca9fba6831710a531f Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Tue, 15 Jan 2008 17:40:47 +0000 Subject: [PATCH] Merge libsoup-2.4 branch to trunk * Merge libsoup-2.4 branch to trunk svn path=/trunk/; revision=1041 --- ChangeLog | 805 ++++++++++++++++++++- README | 2 +- configure.in | 18 +- docs/reference/Makefile.am | 14 +- docs/reference/client-howto.xml | 180 ++--- docs/reference/libsoup-docs.sgml | 24 +- docs/reference/libsoup-overrides.txt | 5 - docs/reference/libsoup-sections.txt | 762 +++++++++----------- docs/reference/libsoup.types | 33 +- docs/reference/porting-2.2-2.4.xml | 878 ++++++++++++++++++++++ docs/reference/server-howto.xml | 205 ++++-- docs/reference/tmpl/libsoup-unused.sgml | 497 ------------- docs/reference/tmpl/soup-address.sgml | 162 ----- docs/reference/tmpl/soup-auth.sgml | 117 --- docs/reference/tmpl/soup-connection-ntlm.sgml | 26 - docs/reference/tmpl/soup-connection.sgml | 254 ------- docs/reference/tmpl/soup-dns.sgml | 123 ---- docs/reference/tmpl/soup-md5-utils.sgml | 49 -- docs/reference/tmpl/soup-message-filter.sgml | 28 - docs/reference/tmpl/soup-message-private.sgml | 82 --- docs/reference/tmpl/soup-message-queue.sgml | 102 --- docs/reference/tmpl/soup-message.sgml | 522 -------------- docs/reference/tmpl/soup-misc.sgml | 277 ------- docs/reference/tmpl/soup-server-auth.sgml | 127 ---- docs/reference/tmpl/soup-server-message.sgml | 95 --- docs/reference/tmpl/soup-server.sgml | 249 ------- docs/reference/tmpl/soup-session-async.sgml | 45 -- docs/reference/tmpl/soup-session-sync.sgml | 49 -- docs/reference/tmpl/soup-session.sgml | 219 ------ docs/reference/tmpl/soup-soap-message.sgml | 333 --------- docs/reference/tmpl/soup-soap-response.sgml | 200 ------ docs/reference/tmpl/soup-socket.sgml | 348 --------- docs/reference/tmpl/soup-ssl.sgml | 110 --- docs/reference/tmpl/soup-status.sgml | 152 ---- docs/reference/tmpl/soup-uri.sgml | 146 ---- docs/reference/tmpl/soup-xmlrpc-message.sgml | 216 ------ docs/reference/tmpl/soup-xmlrpc-response.sgml | 231 ------ libsoup/Makefile.am | 84 ++- libsoup/soup-address.c | 166 +++-- libsoup/soup-address.h | 48 +- libsoup/soup-auth-basic.c | 27 +- libsoup/soup-auth-digest.c | 478 ++++++------ libsoup/soup-auth-digest.h | 37 + libsoup/soup-auth-domain-basic.c | 294 ++++++++ libsoup/soup-auth-domain-basic.h | 52 ++ libsoup/soup-auth-domain-digest.c | 454 ++++++++++++ libsoup/soup-auth-domain-digest.h | 62 ++ libsoup/soup-auth-domain.c | 415 +++++++++++ libsoup/soup-auth-domain.h | 69 ++ libsoup/soup-auth-manager.c | 451 ++++++++++++ libsoup/soup-auth-manager.h | 27 + libsoup/soup-auth-ntlm.c | 134 ++++ libsoup/soup-auth-ntlm.h | 35 + libsoup/soup-auth.c | 337 +++++++-- libsoup/soup-auth.h | 36 +- libsoup/soup-connection-ntlm.c | 78 +- libsoup/soup-connection.c | 196 ++--- libsoup/soup-connection.h | 43 +- libsoup/soup-date.c | 596 +++++++++++---- libsoup/soup-date.h | 50 +- libsoup/soup-dns.c | 199 +++-- libsoup/soup-dns.h | 23 +- libsoup/soup-enum-types.c.tmpl | 33 + libsoup/soup-enum-types.h.tmpl | 24 + libsoup/soup-form.c | 163 +++++ libsoup/soup-form.h | 20 + libsoup/soup-gnutls.c | 17 +- libsoup/soup-headers.c | 557 +++++++++----- libsoup/soup-headers.h | 60 +- libsoup/soup-logger.c | 638 ++++++++++++++++ libsoup/soup-logger.h | 76 ++ libsoup/soup-marshal.list | 7 +- libsoup/soup-md5-utils.c | 306 -------- libsoup/soup-md5-utils.h | 42 -- libsoup/soup-message-body.c | 429 +++++++++++ libsoup/soup-message-body.h | 63 ++ libsoup/soup-message-client-io.c | 85 +-- libsoup/soup-message-filter.c | 30 - libsoup/soup-message-filter.h | 34 - libsoup/soup-message-handlers.c | 268 ------- libsoup/soup-message-headers.c | 540 ++++++++++++++ libsoup/soup-message-headers.h | 71 ++ libsoup/soup-message-io.c | 276 ++++--- libsoup/soup-message-private.h | 45 +- libsoup/soup-message-queue.c | 4 +- libsoup/soup-message-queue.h | 7 - libsoup/soup-message-server-io.c | 127 ++-- libsoup/soup-message.c | 984 +++++++++++++------------ libsoup/soup-message.h | 290 ++------ libsoup/soup-method.c | 83 --- libsoup/soup-method.h | 79 +- libsoup/soup-misc.c | 92 +-- libsoup/soup-misc.h | 48 +- libsoup/soup-path-map.c | 186 +++++ libsoup/soup-path-map.h | 26 + libsoup/soup-server-auth.c | 459 ------------ libsoup/soup-server-auth.h | 90 --- libsoup/soup-server-message.c | 141 ---- libsoup/soup-server-message.h | 49 -- libsoup/soup-server.c | 999 +++++++++++++++++++------- libsoup/soup-server.h | 103 +-- libsoup/soup-session-async.c | 52 +- libsoup/soup-session-async.h | 5 + libsoup/soup-session-private.h | 32 + libsoup/soup-session-sync.c | 37 +- libsoup/soup-session-sync.h | 5 + libsoup/soup-session.c | 731 +++++-------------- libsoup/soup-session.h | 49 +- libsoup/soup-soap-message.c | 827 --------------------- libsoup/soup-soap-message.h | 96 --- libsoup/soup-soap-response.c | 554 -------------- libsoup/soup-soap-response.h | 66 -- libsoup/soup-socket.c | 748 +++++++++---------- libsoup/soup-socket.h | 89 +-- libsoup/soup-ssl.h | 17 - libsoup/soup-status.c | 153 ++++ libsoup/soup-status.h | 156 +--- libsoup/soup-types.h | 33 +- libsoup/soup-uri.c | 811 +++++++++++++++------ libsoup/soup-uri.h | 110 +-- libsoup/soup-value-utils.c | 292 ++++++++ libsoup/soup-value-utils.h | 69 ++ libsoup/soup-xmlrpc-message.c | 402 ----------- libsoup/soup-xmlrpc-message.h | 80 --- libsoup/soup-xmlrpc-response.c | 649 ----------------- libsoup/soup-xmlrpc-response.h | 88 --- libsoup/soup-xmlrpc.c | 796 ++++++++++++++++++++ libsoup/soup-xmlrpc.h | 67 ++ libsoup/soup.h | 15 + tests/Makefile.am | 46 +- tests/apache-wrapper.c | 69 -- tests/apache-wrapper.h | 4 - tests/auth-test.c | 310 +++++--- tests/context-test.c | 214 ++---- tests/continue-test.c | 456 ++++++++++++ tests/date.c | 111 ++- tests/dict.c | 164 ----- tests/dns.c | 3 +- tests/get.c | 30 +- tests/getbug.c | 81 ++- tests/header-parsing.c | 386 ++++++---- tests/httpd.conf.in | 10 + tests/ntlm-test.c | 261 +++---- tests/proxy-test.c | 73 +- tests/pull-api.c | 264 +++---- tests/query-test.c | 231 ++++++ tests/revserver.c | 187 ----- tests/server-auth-test.c | 351 +++++++++ tests/simple-httpd.c | 166 +++-- tests/simple-proxy.c | 70 +- tests/ssl-test.c | 48 +- tests/test-utils.c | 284 ++++++++ tests/test-utils.h | 19 + tests/uri-parsing.c | 165 +++-- tests/xmlrpc-test.c | 544 ++++++-------- 155 files changed, 15607 insertions(+), 15596 deletions(-) create mode 100644 docs/reference/porting-2.2-2.4.xml delete mode 100644 docs/reference/tmpl/libsoup-unused.sgml delete mode 100644 docs/reference/tmpl/soup-address.sgml delete mode 100644 docs/reference/tmpl/soup-auth.sgml delete mode 100644 docs/reference/tmpl/soup-connection-ntlm.sgml delete mode 100644 docs/reference/tmpl/soup-connection.sgml delete mode 100644 docs/reference/tmpl/soup-dns.sgml delete mode 100644 docs/reference/tmpl/soup-md5-utils.sgml delete mode 100644 docs/reference/tmpl/soup-message-filter.sgml delete mode 100644 docs/reference/tmpl/soup-message-private.sgml delete mode 100644 docs/reference/tmpl/soup-message-queue.sgml delete mode 100644 docs/reference/tmpl/soup-message.sgml delete mode 100644 docs/reference/tmpl/soup-misc.sgml delete mode 100644 docs/reference/tmpl/soup-server-auth.sgml delete mode 100644 docs/reference/tmpl/soup-server-message.sgml delete mode 100644 docs/reference/tmpl/soup-server.sgml delete mode 100644 docs/reference/tmpl/soup-session-async.sgml delete mode 100644 docs/reference/tmpl/soup-session-sync.sgml delete mode 100644 docs/reference/tmpl/soup-session.sgml delete mode 100644 docs/reference/tmpl/soup-soap-message.sgml delete mode 100644 docs/reference/tmpl/soup-soap-response.sgml delete mode 100644 docs/reference/tmpl/soup-socket.sgml delete mode 100644 docs/reference/tmpl/soup-ssl.sgml delete mode 100644 docs/reference/tmpl/soup-status.sgml delete mode 100644 docs/reference/tmpl/soup-uri.sgml delete mode 100644 docs/reference/tmpl/soup-xmlrpc-message.sgml delete mode 100644 docs/reference/tmpl/soup-xmlrpc-response.sgml create mode 100644 libsoup/soup-auth-domain-basic.c create mode 100644 libsoup/soup-auth-domain-basic.h create mode 100644 libsoup/soup-auth-domain-digest.c create mode 100644 libsoup/soup-auth-domain-digest.h create mode 100644 libsoup/soup-auth-domain.c create mode 100644 libsoup/soup-auth-domain.h create mode 100644 libsoup/soup-auth-manager.c create mode 100644 libsoup/soup-auth-manager.h create mode 100644 libsoup/soup-auth-ntlm.c create mode 100644 libsoup/soup-auth-ntlm.h create mode 100644 libsoup/soup-enum-types.c.tmpl create mode 100644 libsoup/soup-enum-types.h.tmpl create mode 100644 libsoup/soup-form.c create mode 100644 libsoup/soup-form.h create mode 100644 libsoup/soup-logger.c create mode 100644 libsoup/soup-logger.h delete mode 100644 libsoup/soup-md5-utils.c delete mode 100644 libsoup/soup-md5-utils.h create mode 100644 libsoup/soup-message-body.c create mode 100644 libsoup/soup-message-body.h delete mode 100644 libsoup/soup-message-filter.c delete mode 100644 libsoup/soup-message-filter.h delete mode 100644 libsoup/soup-message-handlers.c create mode 100644 libsoup/soup-message-headers.c create mode 100644 libsoup/soup-message-headers.h delete mode 100644 libsoup/soup-method.c create mode 100644 libsoup/soup-path-map.c create mode 100644 libsoup/soup-path-map.h delete mode 100644 libsoup/soup-server-auth.c delete mode 100644 libsoup/soup-server-auth.h delete mode 100644 libsoup/soup-server-message.c delete mode 100644 libsoup/soup-server-message.h create mode 100644 libsoup/soup-session-private.h delete mode 100644 libsoup/soup-soap-message.c delete mode 100644 libsoup/soup-soap-message.h delete mode 100644 libsoup/soup-soap-response.c delete mode 100644 libsoup/soup-soap-response.h create mode 100644 libsoup/soup-value-utils.c create mode 100644 libsoup/soup-value-utils.h delete mode 100644 libsoup/soup-xmlrpc-message.c delete mode 100644 libsoup/soup-xmlrpc-message.h delete mode 100644 libsoup/soup-xmlrpc-response.c delete mode 100644 libsoup/soup-xmlrpc-response.h create mode 100644 libsoup/soup-xmlrpc.c create mode 100644 libsoup/soup-xmlrpc.h delete mode 100644 tests/apache-wrapper.c delete mode 100644 tests/apache-wrapper.h create mode 100644 tests/continue-test.c delete mode 100644 tests/dict.c create mode 100644 tests/query-test.c delete mode 100644 tests/revserver.c create mode 100644 tests/server-auth-test.c create mode 100644 tests/test-utils.c create mode 100644 tests/test-utils.h diff --git a/ChangeLog b/ChangeLog index b97a83b..3f625e1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,808 @@ -2008-01-09 Benjamin Otte +2008-01-15 Dan Winship + + * Merge libsoup-2.4 branch to trunk + +2008-01-15 Dan Winship + + * libsoup/soup-dns.c (resolve_status): Fix the logic here + +2008-01-15 Dan Winship + + * docs/reference/porting-2.2-2.4.xml: add a few more updates + +2008-01-15 Dan Winship + + * libsoup/soup-auth-digest.c: Use GChecksum for MD5 + + * libsoup/soup-md5-utils.[ch]: gone + +2008-01-15 Dan Winship + + * libsoup/soup-server.c (soup_server_run_async): + (soup_server_quit): Don't ref/unref the server here. It doesn't + match the way other things work. #494128, Mathias Hasselmann. + +2008-01-14 Dan Winship + + * libsoup/soup-address.h: + * libsoup/soup-auth-domain-basic.h: + * libsoup/soup-auth-domain-digest.h: + * libsoup/soup-auth-domain.h: + * libsoup/soup-auth.h: + * libsoup/soup-logger.h: + * libsoup/soup-message.h: + * libsoup/soup-server.h: + * libsoup/soup-session-async.h: + * libsoup/soup-session-sync.h: + * libsoup/soup-session.h: + * libsoup/soup-socket.h: Add padding for future expansion to class + structs + +2008-01-14 Dan Winship + + * libsoup/soup-uri.c: Add more documentation. + (soup_uri_is_https): gone, replaced by SOUP_URI_SCHEME_HTTP / + SOUP_URI_SCHEME_HTTPS + (soup_uri_new): allow passing NULL to get back an "empty" SoupURI. + (soup_uri_to_string): rename just_path to just_path_and_query, to + avoid fooling people. + (soup_uri_decode, soup_uri_normalize): Change these to return the + decoded/normalized string rather than modifying it in place. + (soup_uri_set_scheme, etc): provide setters for SoupURI parts. + (soup_uri_set_query_from_form): set uri->query via + soup_form_encode_urlencoded(). + +2008-01-14 Dan Winship + + * configure.in: require glib 2.15.0, and gio + + * libsoup/soup-dns.c (soup_dns_lookup_resolve) + (soup_dns_lookup_resolve_async): Add GCancellables, and support + cancellation of DNS lookups. + (resolve_address, resolve_name): If we get a DNS failure (eg, + because we're disconnected from the network), don't cache that + result, just try again next time someone asks. [#508593] + + * libsoup/soup-address.c (soup_address_resolve_async) + (soup_address_resolve_sync): Add GCancellables, pass them to + soup-dns. + + * libsoup/soup-socket.c (soup_socket_connect_async) + (soup_socket_connect_sync): Add GCancellables and implement + cancellation. + (soup_socket_start_ssl, soup_socket_start_proxy_ssl) + (soup_socket_read, soup_socket_read_until, soup_socket_write): add + GCancellables, though these routines don't actually implement + cancellation yet. + (soup_socket_disconnect): Don't close() the socket if someone is + doing I/O on it, as that creates a race condition. (The fd number + might be quickly recycled.) Instead, keep the socket open but + dead, via shutdown(). + +2008-01-14 Benjamin Otte * libsoup/soup-socket.c: (soup_socket_class_init): clarify docs for - new-connetion signal. + new-connection signal. + +2008-01-14 Dan Winship + + * tests/test-utils.c: renamed from apache-wrappers and expanded. + (test_init): do option parsing and general setup + (test_cleanup): print error count and do cleanup + (debug_printf): define here rather than in each test, and rename + from dprintf [#501631] + (soup_test_server_new): create a SoupServer, optionally in its own + thread, and clean it up when exiting. + (soup_test_session_new): create a SoupSession, optionally with + an attached SoupLogger (if requested via command line) + + * tests/*.c: use test-utils + +2008-01-13 Dan Winship + + * libsoup/soup-logger.c: New HTTP debug logging object. (Based on + E2K_DEBUG and its clones.) + + * libsoup/soup-message.c (soup_message_class_init) + (soup_message_add_header_handler) + (soup_message_add_status_code_handler): Change things around a + little; remove the "requeuing or cancelling the message stops + signal emission" rule, and instead make that be a feature of just + the header and status code handlers. (Makes the basic signal + handlers behave more predictably.) + +2008-01-11 Dan Winship + + * libsoup/soup-auth-domain.c (soup_auth_domain_set_filter): + * libsoup/soup-auth-domain-basic.c + (soup_auth_domain_basic_set_auth_callback): + * libsoup/soup-auth-domain-digest.c + (soup_auth_domain_digest_set_auth_callback): + * libsoup/soup-message.c (soup_message_cleanup_response) + (soup_message_set_flags, soup_message_set_http_version) + (soup_message_set_uri, soup_message_set_status) + (soup_message_set_status_full): + * libsoup/soup-message-client-io.c (parse_response_headers): + * libsoup/soup-message-server-io.c (parse_request_headers): + Call g_object_notify() when changing properties. + + * libsoup/soup-session.c (soup_session_class_init): bump the + default value of SOUP_SESSION_MAX_CONNS_PER_HOST down to 2, per + RFC 2616. + + * libsoup/soup-message-body.c (soup_buffer_copy): When copying a + TEMPORARY buffer, keep a reference to the copy, so that a second + copy will get that same buffer, rather than actually copying it + again. + + * libsoup/soup-types.h: remove SoupMessageFilter, which doesn't + exist any more + +2008-01-07 Dan Winship + + * libsoup/soup-session.c (soup_session_class_init): Change + request_started signal to have a SoupSocket as its last parameter. + + * libsoup/soup-server.c: Fix request_* signals to all be (server, + msg, client) rather than (server, client, msg). + +2008-01-07 Dan Winship + + * docs/reference/porting-2.2-2.4.xml: Notes on porting from 2.2 to + 2.4 + +2008-01-07 Dan Winship + + * libsoup/*.c: Move gtk-doc stuff from docs/reference/tmpl/ to the + C files themselves. Some updates. + + * docs/reference/Makefile.am: fix (kludge?) this up to not require + tmpl/ to exist + + * docs/reference/client-howto.xml: + * docs/reference/server-howto.xml: update + +2008-01-06 Dan Winship + + * libsoup/soup-soap-message.c: + * libsoup/soup-soap-response.c: For the second time, remove SOAP + support from libsoup... These APIs are not really all that helpful + in the grand scheme of SOAPiness, and are only used by the + Evolution GroupWise backend, which can just import this code and + integrate it better there. + + * libsoup/soup-misc.c (soup_xml_real_node): + * libsoup/soup-xmlrpc.c: Move soup_xml_real_node out of soup-misc + to soup-xmlrpc, and make it private. libxml is no longer exposed + in the public API. + +2008-01-06 Dan Winship + + * libsoup/soup-date.c (soup_date_new_from_now): new method to + generate a date relative to now. + (soup_date_new, etc): document SoupDate methods + + * libsoup/soup-server.c (got_headers): set Date header, as + required by RFC 2616 + +2008-01-06 Dan Winship + + * libsoup/soup-server.c (got_headers): if raw_paths isn't set, + decode the request's uri->path before doing anything else + (soup_server_class_init): add "raw-paths" property, to tell + SoupServer to NOT decode the Request-URI path. + + * libsoup/soup-auth-domain.c (soup_auth_domain_covers): Revert + earlier path-decoding change; that happens at the SoupServer level + now. + +2008-01-06 Dan Winship + + * libsoup/soup-message-body.c (soup_buffer_get_type): Register + SoupBuffer as a boxed type. + + * libsoup/soup-message.c (soup_message_class_init): Use + SOUP_TYPE_BUFFER in got_chunk signal definition + + * libsoup/soup-server.c (soup_client_context_get_type): Register + SoupClientContext as a pointer type + (soup_server_class_init): use SOUP_TYPE_CLIENT_CONTEXT in signal + definitions. + + * libsoup/soup-marshal.list: clean this up + +2008-01-06 Dan Winship + + * libsoup/soup-server.c (SoupClientContext): Make this opaque. + (soup_client_context_get_socket) + (soup_client_context_get_auth_domain) + (soup_client_context_get_auth_user): New accessors + (soup_server_class_init): Make the signals take a + SoupClientContext rather than a SoupSocket. + (start_request, check_auth, call_handler, request_finished): Clean + these up by using a SoupClientContext to communicate between them. + (soup_server_add_handler): tweak the argument order to match the + gtk standard (callback, user_data, destroynotify). + +2008-01-06 Dan Winship + + * libsoup/soup-address.c: remove the "dns_result" signal, which + was just an implementation detail of soup_address_resolve_async(). + +2008-01-06 Dan Winship + + * libsoup/*.c: misc documentation updates/gtk-doc fixes + + * libsoup/soup-server.c: finally start documenting this properly. + + * libsoup/soup-status.h (SoupStatusClass): kill this, since + soup_message_add_status_class_handler() is gone now. + + * libsoup/soup-status.c (soup_status_get_phrase): Update docs to + explain that you probably don't want to use this. + + * libsoup/soup-misc.h (SOUP_SSL_ERROR, SoupSSLError): Move these + here, since soup-ssl.h isn't installed. + + * docs/references: start updating this... + +2008-01-04 Dan Winship + + * libsoup/soup-message-body.c (soup_buffer_new) + (soup_message_body_append): Reorder the arguments to match + soup_message_set_request/response, so it's not confusing. + + * libsoup/soup-message.c (wrote_chunk): remove the "chunk" arg + from the signal, as it turns out to be *in*convenient, since most + callers use this signal to mean "need another chunk", so they want + it to have the same prototype as "wrote_headers", which means + "need first chunk". + +2008-01-04 Dan Winship + + * libsoup/soup-auth-domain.c: add documentation + (soup_auth_domain_set_filter): take a GDestroyNotify, for better + bindability + + * libsoup/soup-auth-domain-basic.c: + * libsoup/soup-auth-domain-digest.c: Add documentation. Replace + authentication signals with more-easily-bindable authentication + callbacks (with GDestroyNotifys). + (soup_auth_domain_digest_evil_check_password): Add this for the + benefit of code that depends on being able to do the equivalent + of the old soup_server_auth_check_passwd(). + +2008-01-02 Dan Winship + + * libsoup/soup-message-body.h (SoupMessageBody): add data and + length parameters like SoupBuffer, to make this easier for callers + to use. + + * libsoup/soup-message-body.c (soup_message_body_append) + (soup_message_body_append_buffer) + (soup_message_body_truncate): Update body->length + (soup_message_body_flatten): Fill in body->data (and NUL-terminate + it as an added bonus). + + * libsoup/soup-message.c (got_body): flatten the newly-gotten + body. + (soup_message_get_request, soup_message_get_response): gone + + * libsoup/soup-message-client-io.c (get_request_headers): + * libsoup/soup-message-server-io.c (get_response_headers): + * libsoup/soup-soap-message.c (soup_soap_message_parse_response): + * tests/*.c: simplify + +2008-01-02 Dan Winship + + * libsoup/Makefile.am (soup_headers): oops, move soup-auth.h here + +2008-01-02 Dan Winship + + * libsoup/soup-form.c: new HTML-form-related methods (just URI + decoding/encoding at the moment). + + * libsoup/soup-server.h (SoupServerCallback): change the prototype + to include the decoded path and query rather than the undecoded + URI. + + * libsoup/soup-server.c (call_handler): %-decode the URI path + before looking up a handler. Decode query if available. Pass path + and query to the callback. + + * libsoup/soup-auth-domain.c (soup_auth_domain_covers): fix this + to %-decode the URI path before testing it + + * libsoup/soup-message-body.c (soup_message_body_append): allow + 0-length appends + + * tests/query-test.c: URI query parsing test + +2008-01-02 Dan Winship + + * libsoup/soup-uri.c: + * libsoup/soup-uri.h: Change all the "const SoupURI *" to just + "SoupURI *", since the const is just there to be annoying. + + * */*.c: update + +2008-01-02 Dan Winship + + * libsoup/soup-message-body.c (soup_message_body_get_length) + (soup_message_body_get_chunk): Use goffset rather than gsize for + references to the entire size of the message body. (SoupBuffer + still uses gsize, so individual chunks can only be G_MAXSIZE + long.) + + * libsoup/soup-message-headers.c + (soup_message_headers_get_content_length): + (soup_message_headers_set_content_length): Likewise, use goffset. + +2008-01-02 Dan Winship + + * libsoup/soup-message-headers.c (soup_message_headers_get): + Renamed from soup_message_headers_find, and with new behavior; now + multiple headers with the same name are automatically merged + together into a single comma-separated value, to ensure that apps + treat multivalued headers the same regardless of how upstream + servers generate them. + (soup_message_headers_find_nth): no longer needed/wanted + + * libsoup/soup-auth-manager.c: Update to deal with + SoupMessageHeaders change. (Ugh.) + + * tests/header-parsing.c: Update multiple-values test, and undo a + change that mistakenly got committed while debugging something + earlier. + +2008-01-01 Dan Winship + + * libsoup/soup-auth-manager.c: + * libsoup/soup-dns.c: + * libsoup/soup-gnutls.c: + * libsoup/soup-message.c: + * libsoup/soup-message-io.c: + * libsoup/soup-message-queue.c: + * libsoup/soup-misc.c: + * libsoup/soup-path-map.c: + * libsoup/soup-server.c: + * libsoup/soup-session.c: + * libsoup/soup-session-sync.c: + * libsoup/soup-socket.c: Use g_slice. + +2008-01-01 Dan Winship + + * libsoup/soup-session.c (soup_session_cancel_message): add a + "status_code" argument rather than having the caller set the + status code separately, to prevent a race condition. + +2008-01-01 Dan Winship + + * libsoup/soup-session.c (soup_session_queue_message): change the + callback type to include the SoupSession as a parameter as well. + + * *.c: update + +2007-12-31 Dan Winship + + * libsoup/soup-session.c (soup_session_class_init): change + the "authenticate" signal to include a SoupAuth rather than its + components, and to have a "retrying" parameter rather than + separating "authenticate" and "reauthenticate". + + * libsoup/soup-connection.c (soup_connection_class_init): Likewise + + * libsoup/soup-auth-manager.c (authenticate_auth): update + + * libsoup/soup-auth.c: make various attributes into gobject + properties. + (soup_auth_is_for_proxy): check whether an auth is plain or proxy + (soup_auth_get_host): get the hostname associated with an auth + + * libsoup/soup-auth-ntlm.c: dummy class used by SoupConnectionNTLM + in the authenticate signal + + * libsoup/soup-connection-ntlm.c (ntlm_authorize_pre): update for + authenticate signals changes; use a fake SoupAuthNTLM to assist. + +2007-12-20 Dan Winship + + * libsoup/soup-message.c (soup_message_add_header_handler) + (soup_message_add_status_code_handler): Make these be wrappers + around g_signal_connect() rather than having a completely separate + system. + (soup_message_class_init): improve signal docs. Use + "got_foo_signal_wrapper" to wrap the got-foo signals. + (got_foo_signal_wrapper): Wraps the marshaller for the got-foo + signals and cancels the signal emission if the message gets + cancelled or requeued. + (got_informational, got_headers, got_chunk, got_body): remove + no-longer-needed default implementations. + + * libsoup/soup-message-handlers.c: gone + + * tests/ntlm-test.c (do_message): Simplify now that callback + processing doesn't happen in two separate phases. + +2007-12-20 Dan Winship + + * libsoup/soup-auth-domain.c: + * libsoup/soup-auth-domain-basic.c: + * libsoup/soup-auth-domain-digest.c: New server-side auth system. + + * libsoup/soup-server.c: remove SoupServerAuth / SoupAuthContext + stuff, add SoupAuthDomain support. + (SoupServerCallbackFn): improve the args here + (SoupClientContext): renamed from SoupServerContext and made less + redundant + + * libsoup/soup-server-auth.c: gone! + + * libsoup/soup-auth-digest.c (soup_auth_digest_parse_algorithm) + (soup_auth_digest_get_algorithm, soup_auth_digest_parse_qop) + (soup_auth_digest_get_qop, soup_auth_digest_compute_hex_urp) + (soup_auth_digest_compute_hex_a1) + (soup_auth_digest_compute_response): New routines shared between + client-side and server-side digest auth. + + * tests/server-auth-test.c: test server-side auth, using curl for + the client side + + * configure.in: check for curl, for server-auth-test + +2007-12-20 Dan Winship + + * libsoup/soup-headers.c (soup_header_parse_list) + (soup_header_parse_quality_list): New methods to parse list-type + headers (with optional qvalues) correctly. + (soup_header_parse_param_list): Rename to match the other methods, + and update the semantics a bit. + (soup_header_contains): Correctly check for a token in a list + + * libsoup/soup-message.c (soup_message_is_keepalive): + * libsoup/soup-message-client-io.c (get_request_headers): + * libsoup/soup-message-server-io.c (parse_request_headers): Use + soup_header_contains() with Connection headers. + + * tests/header-parsing.c (do_qvalue_tests): add + soup_header_parse_quality_list() test + +2007-12-20 Dan Winship + + * libsoup/soup-auth-manager.c: Move auth-related code from + SoupSession and SoupAuth here, and make various cleanups and + beginnings of cleanups. + + * libsoup/soup-session.c: lots of stuff moved to + soup-auth-manager.c + + * libsoup/soup-auth.c (soup_auth_new_from_headers): partly moved + to soup-auth-manager.c, partly renamed to soup_auth_new(). + (soup_auth_update): new method to update an existing auth based on + a new WWW-Authenticate/Proxy-Authenticate header. Also replaces + the old "construct" method. + + * libsoup/soup-auth-digest.c (update): Implement. If the new auth + has stale=true, don't invalidate the auth, just update the nonce. + (get_authorization): add a header handler to the message to catch + Authentication-Info/Proxy-Authentication-Info headers so that if + there's a nextnonce, we can start using it. #471380. + + * libsoup/soup-auth-basic.c (update): Implement. (Updating an + existing Basic auth always invalidates it.) + + * tests/http.conf.in: + * tests/auth-test.c: add a test for digest nonce handling + +2007-12-20 Dan Winship + + * libsoup/soup-path-map.c: New type representing a sparse + path->something mapping + + * libsoup/soup-server.c: Use SoupPathMap to record handlers. Make + SoupServerHandler a private type. + (soup_server_new): Rewrite this to just be a thin wrapper, and put + all of the code into a constructor override. #491653 + (soup_server_add_handler): Turn the "unregister" arg into a + GDestroyNotify, for better bindability. + +2007-12-19 Dan Winship + + * libsoup/soup-server.c: define new request_started, request_read, + request_finished, and request_aborted signals, for finer-grained + tracking than normal handlers allow. + (check_auth): split this out of call_handler, and run it + immediately after "got_headers", not "got_body", so that we can + preemptively reject "Expect: 100-continue" messages that will + require auth. + + * libsoup/soup-message-io.c (io_write, io_read): Fix up + 100-continue processing + + * tests/continue-test.c: new test of client/server 100-continue + processing + +2007-12-19 Dan Winship + + * libsoup/soup-socket.c: Cleanup. Remove the "connect_result" + signal. Make local_address and remote_address + into (construct-only) properties. + (soup_socket_connect_async, soup_socket_connect_sync): Replace + soup_socket_connect. _async takes a callback+user_data (like the + old soup_socket_client_new_async), but doesn't implement the + callback in terms of a connect_result signal. + (soup_socket_client_new_async, soup_socket_client_new_sync): Gone. + (Unused since the async_context addition anyway). Replaced by the + new construct properties and connect methods. + (soup_socket_read, soup_socket_read_until, soup_socket_write): + Make these actually take a GError rather than doing an ugly hack + to preserve the old API. + (SOUP_SOCKET_FLAG_NODELAY, SOUP_SOCKET_FLAG_REUSEADDR) + (SOUP_SOCKET_FLAG_CLOEXEC): kill these off (all three are always + TRUE now); SoupSocket is libsoup's socket API; it's not + necessarily intended to be generically useful for everyone. + + * *.c: Update for SoupSocket changes + +2007-12-19 Dan Winship + + * libsoup/soup-server-message.c: Kill! + + * libsoup/soup-message-server-io.c (parse_request_headers): + Generate the full request URL from the socket's data, since we no + longer have soup_server_message_get_server(). + + * libsoup/soup-server.c (request_finished, call_handler) + (start_request, new_connection): update + +2007-12-19 Dan Winship + + * libsoup/soup-message-headers.c: Add some more fields to + SoupMessageHeaders, and start caching the parsed values of certain + important headers. + (soup_message_headers_get/set_encoding): replaces old SoupMessage + methods, and only deals with the declared transfer encoding, not + the wire encoding. + (soup_message_headers_get/set_content_length): Handle + Content-Length. + (soup_message_headers_get_expectations): Handle Expect. (Replaces + the SOUP_MESSAGE_EXPECT_CONTINUE flag). + + * libsoup/soup-message.c (soup_message_get_request_encoding): + (soup_message_get_response_encoding): + (soup_message_set_response_encoding): replaced by + SoupMessageHeaders methods. + + * libsoup/soup-message-client-io.c: + * libsoup/soup-message-server-io.c: + * libsoup/soup-message-io.c: Update for SoupMessageHeaders changes + with encoding/content-length stuff. + +2007-12-19 Dan Winship + + * libsoup/soup-message-body.c (SoupMessageBody): new opaque type + for request/response bodies allowing less hacky handling of + chunked encoding. + (SoupBuffer): refcounted buffer type + + * libsoup/soup-message.h (SoupMessage): turn request and response + members into SoupMessageBody. + (SoupOwnership, SoupDataBuffer): gone, replaced by + SoupMessageBody/SoupBuffer. + + * libsoup/soup-message.c (soup_message_wrote_chunk) + (soup_message_got_chunk): add the chunk as a signal param rather + than having it be visible in msg->request/response. + (soup_message_add_chunk, soup_message_add_final_chunk) + (soup_message_pop_chunk): replaced by SoupMessageBody methods now. + +2007-12-19 Dan Winship + + * libsoup/soup-xmlrpc.c: + * libsoup/soup-value-utils.c: Oops. Change the API a bunch so this + works on x86; apparently I was doing illegal things with va_lists + before that only work on x86_64. + +2007-12-14 Dan Winship + + * libsoup/soup-message.c: use GObject properties for SoupMessage + fields. + + * libsoup/soup-message-server-io.c: + * libsoup/soup-soap-message.c: update for that + +2007-12-14 Dan Winship + + * libsoup/soup-uri.c: Rename from SoupUri to SoupURI. Use the + slice allocator and register as a boxed type. + (SoupURI): Rename "protocol" field to "scheme" and "passwd" to + "password". Make scheme an interned string. Replace + SOUP_PROTOCOL_HTTPS with soup_uri_is_https(). + + * *.c: update + +2007-12-14 Dan Winship + + * libsoup/Makefile.am: Use glib-mkenums to build soup-enum-types.c + and soup-enum-types.h + + * libsoup/soup-address.h (SoupAddressFamily): redo this definition + again, to make glib-mkenums happy. + +2007-12-13 Dan Winship + + * libsoup/soup-xmlrpc.c: New easier-to-use and + easier-to-do-language-bindings-of XML-RPC code. + + * libsoup/soup-xmlrpc-message.c: + * libsoup/soup-xmlrpc-response.c: gone + + * libsoup/soup-value-utils.c: Utilites for working with + GValueArray, and GHashTables of GValues, used by soup-xmlrpc. + + * tests/getbug.c: + * tests/xmlrpc-test.c: Update to use new XML-RPC stuff + +2007-12-13 Dan Winship + + * libsoup/soup-date.c: Make a SoupDate type, and redo in terms of + that rather than struct tm and time_t. Also be much more liberal + when parsing. + + * libsoup/soup-xmlrpc-message.c (soup_xmlrpc_message_write_datetime): + * libsoup/soup-xmlrpc-response.c (soup_xmlrpc_value_get_datetime): + Use SoupDate. + + * tests/date.c: Use SoupDate, test parsing lots more formats + + * tests/xmlrpc-test.c: update for SoupDate + +2007-12-12 Dan Winship + + * libsoup/soup-message.c: + * libsoup/soup-message-private.h: Remove SoupMessageStatus, + msg->status, and soup_message_io_* from the public API, as they + all really belong to the session, not the message. (For now + they've just been moved to soup-message-private.h, but some day + they'll be fully refactored away from SoupMessage.) + + * libsoup/soup-server.c (soup_server_pause_message) + (soup_server_unpause_message): + * libsoup/soup-session.c (soup_session_pause_message) + (soup_session_unpause_message): session/server-level methods to + replace soup_message_io_pause() and soup_message_io_unpause(). + + * libsoup/soup-server-message.c: Remove some unused methods + + * */*.c: Update + +2007-12-05 Dan Winship + + * libsoup/soup-connection.c: + * libsoup/soup-session.c: replace message filters with a + "request_started" signal + + * libsoup/soup-message-filter.c: gone + + * libsoup/soup-types.h (SOUP_MAKE_INTERFACE): no longer needed + +2007-12-05 Dan Winship + + * libsoup/soup-uri.c: Update for RFC 3986 changes, bgo 266516, and + general conformance + (soup_uri_get_protocol): match protocols case-insensitively + (soup_uri_new_with_base): Don't fully %-decode the fragment, + query, and path, but do %-decode anything which isn't supposed to + be encoded. Recognize IPv6 address literals. Use stricter + "../"-stripping rules on the path. Reject URIs with junk between + the port number and the path. + (soup_uri_to_string): Update for the fact that the host might be + an IPv6 literal, and for the fact that path, query, and fragment + are now pre-escaped. + (soup_uri_equal): compare hostnames case-insensitively + (uri_encoded_char): update to match RFC 3986 + (append_uri_encoded): use uppercase hex letters as recommended by + RFC 3986. + (soup_uri_normalize): decode only %-escapes that don't belong + there. + + * docs/reference/tmpl/soup-uri.sgml: add some more SoupUri docs + + * tests/uri-parsing.c: Add new tests from RFC 3986, RFC 2732, RFC + 2616, bgo 266516, and elsewhere. Update some tests to match new + parsing/unparsing rules. + +2007-12-05 Dan Winship + + * libsoup/soup-message.c (soup_message_new) + (soup_message_new_from_uri): g_intern_string() the method name + rather than assuming it's static. Also remove the NULL==GET + assumption. + + * libsoup/soup-method.c: + * libsoup/soup-method.h: remove the SOUP_METHOD_ID_* macros, and + have the SOUP_METHOD_* macros return interned strings + + * libsoup/soup-server.h (SoupServerContext): remove method_id + field. + + * libsoup/soup-server-message.c (finalize): no longer needed, + since smsg->method is now an interned string just like with a + normal SoupMessage. + + * libsoup/soup-soap-message.c (soup_soap_message_new_from_uri): + remove NULL==GET assumption + + * *.c: update + +2007-12-05 Dan Winship + + * libsoup/soup-message.h (SoupHTTPVersion): rename (from + SoupHttpVersion) + + * libsoup/soup-message-headers.c: New opaque type representing + message headers, and new methods that work on it. Uses an array + rather than a hash table, to preserve header ordering as required + by RFC 2616. (Also fixes the API wart that + "soup_message_get_header", etc, did not actually take a + SoupMessage.) + + * libsoup/soup-message.c: Kill off old header-manipulating + methods. + + * libsoup/soup-headers.c (soup_headers_parse_request): return a + guint rather than gboolean, so we can properly return + SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED where appropriate. Also fix + up HTTP-Version parsing to conform with the RFC. + (soup_headers_parse_status_line): Likewise update HTTP-Version + parsing. + + * libsoup/soup-message-server-io.c (parse_request_headers): set + return status appropriately on parse errors + + * tests/header-parsing.c: update / add more tests + + * *.c: update + +2007-12-05 Dan Winship + + * libsoup/soup-misc.c: remove deprecated base64 methods + + * tests/auth-test.c (identify_auth): oops, update to use + g_base64_decode. + +2007-12-05 Dan Winship + + * libsoup/Makefile.am (libsoupinclude_HEADERS): remove + soup-connection.h and soup-message-queue.h + + * libsoup/soup-types.h: remove SoupConnection and SoupMessageQueue + which are no longer public + + * libsoup/soup.h: sync this to reality for the first time in years + + * libsoup/soup-session.c (soup_session_get_queue): Add this, for + subclasses, as the queue is no longer a public part of the session + struct. + + * libsoup/soup-message.h: + * libsoup/soup-message-private.h: Move soup_message_send_request() + and soup_message_receive_request() to soup-message-private.h, + remove soup_message_send_request_internal(). + + * libsoup/soup-session-private.h: Move "protected" SoupSession + methods (soup_session_get_connection, + soup_session_try_prune_connection) here from soup-session.h + Add soup_session_get_queue. + +2007-12-05 Dan Winship + + * configure.in: bump version to 2.3.0 and SOUP_API_VERSION to 2.4, + and drop AGE/CURRENT/REVISION all to 0. + + * libsoup/Makefile.am: Rename library to libsoup-2.4.la + + (start of libsoup-2.4 branch) 2007-11-26 Dan Winship diff --git a/README b/README index d2f656a..bf3b70a 100644 --- a/README +++ b/README @@ -8,7 +8,7 @@ Features: * Proxy support, including authentication and SSL tunneling * Client support for Digest, NTLM, and Basic authentication * Server support for Digest and Basic authentication - * Basic client-side SOAP and XML-RPC support + * XML-RPC support See the documentation in docs/reference/ and the test programs in tests/ for simple examples of how to use the code. The diff --git a/configure.in b/configure.in index f04ecc6..ac8c9df 100644 --- a/configure.in +++ b/configure.in @@ -3,7 +3,7 @@ dnl *** Initialize automake and set version *** dnl ******************************************* AC_PREREQ(2.53) -AC_INIT(libsoup, 2.2.104) +AC_INIT(libsoup, 2.3.0) AC_CONFIG_SRCDIR(libsoup.pc.in) AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION) @@ -11,14 +11,14 @@ AM_CONFIG_HEADER(config.h) AM_MAINTAINER_MODE AC_PROG_MAKE_SET -SOUP_API_VERSION=2.2 +SOUP_API_VERSION=2.4 AC_SUBST(SOUP_API_VERSION) # Increment on interface addition. Reset on removal. -SOUP_AGE=5 +SOUP_AGE=0 # Increment on interface add, remove, or change. -SOUP_CURRENT=13 +SOUP_CURRENT=0 # Increment on source change. Reset when CURRENT changes. SOUP_REVISION=0 @@ -73,7 +73,7 @@ dnl *********************** dnl *** Checks for glib *** dnl *********************** -AM_PATH_GLIB_2_0(2.12.0,,,gobject gthread) +AM_PATH_GLIB_2_0(2.15.0,,,gobject gthread gio) PKG_CHECK_MODULES(XML, libxml-2.0) AC_SUBST(XML_CFLAGS) @@ -272,6 +272,14 @@ fi AC_SUBST(IF_HAVE_PHP) AM_CONDITIONAL(HAVE_XMLRPC_EPI_PHP, test "$have_xmlrpc_epi_php" = yes) +dnl ******************************* +dnl *** curl (for regression tests) +dnl ******************************* +AC_PATH_PROG(CURL, curl, no) +if test "$CURL" != no; then + AC_DEFINE(HAVE_CURL, 1, [Whether or not curl can be used for tests]) +fi +AM_CONDITIONAL(HAVE_CURL, test "$CURL" != no) dnl ************************* dnl *** Output Everything *** diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am index 0dae970..f34e154 100644 --- a/docs/reference/Makefile.am +++ b/docs/reference/Makefile.am @@ -29,7 +29,13 @@ HFILE_GLOB= CFILE_GLOB= # Header files to ignore when scanning. -IGNORE_HFILES= soup.h soup-marshal.h soup-types.h +IGNORE_HFILES= soup.h soup-marshal.h \ + soup-message-private.h soup-session-private.h \ + soup-types.h soup-enum-types.h \ + soup-auth-basic.h soup-auth-digest.h soup-auth-ntlm.h \ + soup-connection.h soup-connection-ntlm.h \ + soup-dns.h soup-auth-manager.h soup-md5-utils.h \ + soup-message-queue.h soup-path-map.h soup-ssl.h # Images to copy into HTML directory. HTML_IMAGES = @@ -51,7 +57,11 @@ GTKDOC_CFLAGS = \ GTKDOC_LIBS = $(top_builddir)/libsoup/libsoup-$(SOUP_API_VERSION).la - # include common portion ... include $(top_srcdir)/gtk-doc.make +# kludges +tmpl/*.sgml: + +clean: clean-am + rm -rf tmpl diff --git a/docs/reference/client-howto.xml b/docs/reference/client-howto.xml index ac7caa0..c52f635 100644 --- a/docs/reference/client-howto.xml +++ b/docs/reference/client-howto.xml @@ -44,7 +44,7 @@ slightly different behavior: -If you want to do a mix of synchronous and asynchronous I/O, you will +If you want to do a mix of mainloop-based and blocking I/O, you will need to create two different session objects. @@ -142,8 +142,10 @@ only need to create the message and it's ready to send: In more complicated cases, you can use various SoupMessage methods to set the request -headers and body of the message: +linkend="SoupMessage">SoupMessage, SoupMessageHeaders, and SoupMessageBody methods to set the +request headers and body of the message: @@ -151,8 +153,8 @@ headers and body of the message: msg = soup_message_new ("POST", "http://example.com/form.cgi"); soup_message_set_request (msg, "application/x-www-form-urlencoded", - SOUP_BUFFER_USER_OWNED, formdata, strlen (formdata)); - soup_message_add_header (msg->request_headers, "Referer", referring_url); + SOUP_MEMORY_COPY, formdata, strlen (formdata)); + soup_message_headers_append (msg->request_headers, "Referer", referring_url); @@ -180,20 +182,18 @@ linkend="soup-session-send-message">soup_session_send_message -session can be either a SoupSessionSync or a -SoupSessionAsync; -if you use soup_session_send_message on an async -session, it will run the main loop itself until the message is -complete. +(If you use soup_session_send_message with a +SoupSessionAsync, +it will run the main loop itself until the message is complete.) -The return value from soup_session_send is a soup status code, indicating either a -transport error that prevented the message from being sent, or the +The return value from soup_session_send is a +soup status code, indicating either +a transport error that prevented the message from being sent, or the HTTP status that was returned by the server in response to the -message. +message. (The status is also available as +msg->status_code.) @@ -202,10 +202,8 @@ message. Sending a Message Asynchronously -To send a message asynchronously (which can only be done if you're -using SoupSessionAsync), use -soup_session_queue_message: +To send a message asynchronously, use soup_session_queue_message: @@ -215,7 +213,7 @@ linkend="SoupSessionAsync">SoupSessionAsync), use } static void -my_callback (SoupMessage *msg, gpointer user_data) +my_callback (SoupSession, *session, SoupMessage *msg, gpointer user_data) { /* Handle the response here */ } @@ -229,6 +227,15 @@ response be will be read. When the message is complete, passed to soup_session_queue_message. + +(If you use soup_session_queue_message +with a SoupSessionSync, the +message will be sent in another thread, with the callback eventually +being invoked in the session's SOUP_SESSION_ASYNC_CONTEXT.) + + @@ -245,11 +252,11 @@ asynchronously, you can look at the response fields in the status and textual status response from the server. response_headers contains the response headers, which you can investigate using soup_message_get_header and +linkend="soup-message-headers-get">soup_message_headers_get and soup_message_foreach_header. + linkend="soup-message-headers-foreach">soup_message_headers_foreach. The response body (if any) is in the -response field. +response_body field. @@ -272,17 +279,17 @@ it. Intermediate/Automatic Processing -You can also connect to various SoupMessage -signals, or set up handlers using soup_message_add_handler -and the other handler methods. Notably, SoupMessage signals +to do processing at intermediate stages of HTTP I/O. +SoupMessage also provides two convenience methods, +soup_message_add_header_handler, -soup_message_add_status_code_handler, -and -soup_message_add_status_class_handler -allow you to invoke a handler automatically for messages with certain -response headers or status codes. SoupSession uses -this internally to handle authentication and redirection. +and soup_message_add_status_code_handler, +which allow you to set up a signal handler that will only be invoked +for messages with certain response headers or status codes. +SoupSession uses this internally to handle authentication +and redirection. @@ -293,10 +300,9 @@ linkend="soup-session-send-message">soup_session_send_message To automatically set up handlers on all messages sent via a session, -you can create a SoupMessageFilter and attach it to -the session with soup_session_add_filter. +you can connect to the session's request_started +signal, and add handlers to each message from there. @@ -309,76 +315,36 @@ linkend="soup-session-add-filter">soup_session_add_filterauthenticate signal, -indicating the authentication type ("Basic", "Digest", or "NTLM") and -the realm name provided by the server. You should connect to this -signal and, if possible, fill in the username -and password parameters with authentication -information. (The session will g_free the strings -when it is done with them.) If the handler doesn't fill in those -parameters, then the session will just return the message to the -application with its 401 or 407 status. +providing you with a SoupAuth object indicating the +authentication type ("Basic", "Digest", or "NTLM") and the realm name +provided by the server. If you have a username and password available +(or can generate one), call soup_auth_authenticate +to give the information to libsoup. The session will automatically +requeue the message and try it again with that authentication +information. (If you don't call +soup_auth_authenticate, the session will just +return the message to the application with its 401 or 407 status.) -If the authenticate handler returns a username and -password, but the request still gets an authorization error using that -information, then the session will emit the reauthenticate signal. -This lets the application know that the information it provided -earlier was incorrect, and gives it a chance to try again. If this +If the server doesn't accept the username and password provided, the +session will emit authenticate again, with the +retrying parameter set to TRUE. This lets the +application know that the information it provided earlier was +incorrect, and gives it a chance to try again. If this username/password pair also doesn't work, the session will contine to -emit reauthenticate again and again until the -returned username/password successfully authentications, or until the -signal handler fails to provide a username, at which point -libsoup will allow the message to fail -(with status 401 or 407). +emit authenticate again and again until the +provided username/password successfully authenticates, or until the +signal handler fails to call soup_auth_authenticate, +at which point libsoup will allow the +message to fail (with status 401 or 407). - -There are basically three ways an application might want to use -the signals: - - - - - An interactive application that doesn't cache passwords could - just connect both authenticate and - reauthenticate to the same signal handler, - which would ask the user for a username and password and then - return that to soup. This handler would be called repeatedly - until the provided information worked, or until it failed to - return any information (eg, because the user hit "Cancel" - instead of "OK"). - - - - A slightly cleverer interactive application would look in its - password cache from the authenticate - handler, and return a password from there if one was - available. If no password was cached, it would just call its - reauthenticate handler to prompt the user. - The reauthenticate handler would first - clear any cached password for this host, auth type, and realm, - then ask the user as in the case above, and then store that - information in its cache before returning it to soup. (If the - password turns out to be incorrect, then - reauthenticate will be called again to - force it to be uncached.) - - - - A non-interactive program that only has access to cached - passwords would only connect to - authenticate. If the username and password - that authenticate returns fail, the session - will emit reauthenticate, but since the - application is not listening to that signal, no new username - and password will be returned there, so the message will be - returned to the application with a 401 or 407 status, which - the application can deal with as it needs to. - - - @@ -396,12 +362,12 @@ A few sample programs are available in the - dict and - getbug are trivial - demonstrations of the SOAP and XMLRPC interfaces, - respectively. + getbug is a trivial + demonstration of the XMLRPC interface. + (xmlrpc-test provides + a slightly more complicated example.) diff --git a/docs/reference/libsoup-docs.sgml b/docs/reference/libsoup-docs.sgml index 6c8011f..ce97a91 100644 --- a/docs/reference/libsoup-docs.sgml +++ b/docs/reference/libsoup-docs.sgml @@ -10,36 +10,32 @@ libsoup Tutorial + libsoup API Reference - - + + + + - - + + + - - - - + + - - libsoup internals - - - - diff --git a/docs/reference/libsoup-overrides.txt b/docs/reference/libsoup-overrides.txt index 9828402..06826ca 100644 --- a/docs/reference/libsoup-overrides.txt +++ b/docs/reference/libsoup-overrides.txt @@ -1,9 +1,4 @@ -soup_dns_lookup_get_address -struct sockaddr * -SoupDNSLookup *lookup - - soup_address_get_sockaddr struct sockaddr * SoupAddress *addr, diff --git a/docs/reference/libsoup-sections.txt b/docs/reference/libsoup-sections.txt index c9f24c9..afda49f 100644 --- a/docs/reference/libsoup-sections.txt +++ b/docs/reference/libsoup-sections.txt @@ -2,26 +2,13 @@ soup-message SoupMessage SoupMessage -SoupMessageStatus -SOUP_MESSAGE_IS_STARTING -SoupTransferEncoding -SoupOwnership -SoupDataBuffer -SoupMessageCallbackFn soup_message_new soup_message_new_from_uri soup_message_set_request soup_message_set_response -soup_message_add_header -soup_message_get_header -soup_message_get_header_list -soup_message_foreach_header -soup_message_remove_header -soup_message_clear_headers - -SoupHttpVersion +SoupHTTPVersion soup_message_set_http_version soup_message_get_http_version soup_message_get_uri @@ -32,25 +19,17 @@ soup_message_get_flags soup_message_set_status soup_message_set_status_full -soup_message_add_chunk -soup_message_add_final_chunk -soup_message_pop_chunk soup_message_is_keepalive -soup_message_get_request_encoding -soup_message_get_response_encoding -SoupHandlerPhase -soup_message_add_handler soup_message_add_header_handler soup_message_add_status_code_handler -soup_message_add_status_class_handler -soup_message_remove_handler - -soup_message_send_request -soup_message_read_request -soup_message_io_pause -soup_message_io_unpause -soup_message_io_stop + +SOUP_MESSAGE_METHOD +SOUP_MESSAGE_URI +SOUP_MESSAGE_HTTP_VERSION +SOUP_MESSAGE_FLAGS +SOUP_MESSAGE_STATUS_CODE +SOUP_MESSAGE_REASON_PHRASE SOUP_MESSAGE SOUP_IS_MESSAGE @@ -60,9 +39,21 @@ SOUP_MESSAGE_CLASS SOUP_IS_MESSAGE_CLASS SOUP_MESSAGE_GET_CLASS SoupMessageClass -SoupMessagePrivate -SOUP_MESSAGE_GET_PRIVATE +soup_message_wrote_informational +soup_message_wrote_headers +soup_message_wrote_chunk +soup_message_wrote_body +soup_message_got_informational +soup_message_got_headers +soup_message_got_chunk +soup_message_got_body +soup_message_finished +soup_message_restarted + + +
+soup-method SOUP_METHOD_OPTIONS SOUP_METHOD_GET SOUP_METHOD_HEAD @@ -79,35 +70,63 @@ SOUP_METHOD_MOVE SOUP_METHOD_LOCK SOUP_METHOD_UNLOCK SOUP_METHOD_PATCH -SoupMethodId -soup_method_get_id -soup_message_wrote_informational -soup_message_wrote_headers -soup_message_wrote_chunk -soup_message_wrote_body -soup_message_got_informational -soup_message_got_headers -soup_message_got_chunk -soup_message_got_body -soup_message_finished -soup_message_restarted -SoupMessageGetHeadersFn -SoupMessageParseHeadersFn -soup_message_cleanup_response -soup_message_io_client -soup_message_io_in_progress -soup_message_io_server -soup_message_run_handlers -soup_message_send_request_internal -soup_message_get_auth -soup_message_get_proxy_auth -soup_message_set_auth -soup_message_set_proxy_auth +
+ +
+soup-message-headers +SoupMessageHeaders +SoupMessageHeaders +SoupMessageHeadersType +soup_message_headers_new +soup_message_headers_free + +soup_message_headers_append +soup_message_headers_replace +soup_message_headers_remove +soup_message_headers_clear +soup_message_headers_get +SoupMessageHeadersForeachFunc +soup_message_headers_foreach + +SoupEncoding +soup_message_headers_get_encoding +soup_message_headers_set_encoding +soup_message_headers_get_content_length +soup_message_headers_set_content_length + +SoupExpectation +soup_message_headers_get_expectations +soup_message_headers_set_expectations +
+ +
+soup-message-body +SoupMessageBody +SoupBuffer +SoupMemoryUse +soup_buffer_new +soup_buffer_new_subbuffer +soup_buffer_copy +soup_buffer_free + +SoupMessageBody +soup_message_body_new +soup_message_body_free + +soup_message_body_append +soup_message_body_append_buffer +soup_message_body_truncate +soup_message_body_complete +soup_message_body_flatten +soup_message_body_get_chunk + +SOUP_TYPE_BUFFER +soup_buffer_get_type
soup-status -SoupStatusClass +SOUP_STATUS_IS_TRANSPORT_ERROR SOUP_STATUS_IS_INFORMATIONAL SOUP_STATUS_IS_SUCCESSFUL SOUP_STATUS_IS_REDIRECTION @@ -115,38 +134,48 @@ SOUP_STATUS_IS_CLIENT_ERROR SOUP_STATUS_IS_SERVER_ERROR SoupKnownStatusCode soup_status_get_phrase - -SOUP_STATUS_IS_TRANSPORT_ERROR + +SOUP_HTTP_ERROR + +soup_http_error_quark
soup-server SoupServer SoupServer -SoupServerContext -SoupServerCallbackFn -SoupServerUnregisterFn soup_server_new -soup_server_get_protocol +soup_server_is_https soup_server_get_port soup_server_get_listener soup_server_run soup_server_run_async soup_server_quit +soup_server_get_async_context -SoupServerHandler +SoupServerCallback soup_server_add_handler soup_server_remove_handler -soup_server_get_handler -soup_server_list_handlers -soup_server_context_get_client_address -soup_server_context_get_client_host + +SoupClientContext +soup_client_context_get_socket +soup_client_context_get_address +soup_client_context_get_host +soup_client_context_get_auth_domain +soup_client_context_get_auth_user + +soup_server_add_auth_domain +soup_server_remove_auth_domain + +soup_server_pause_message +soup_server_unpause_message SOUP_SERVER_PORT SOUP_SERVER_INTERFACE SOUP_SERVER_SSL_CERT_FILE SOUP_SERVER_SSL_KEY_FILE SOUP_SERVER_ASYNC_CONTEXT +SOUP_SERVER_RAW_PATHS SOUP_SERVER SOUP_IS_SERVER @@ -156,18 +185,87 @@ SOUP_SERVER_CLASS SOUP_IS_SERVER_CLASS SOUP_SERVER_GET_CLASS SoupServerClass - -soup_server_auth_check_passwd -soup_server_auth_context_challenge -soup_server_auth_free -soup_server_auth_get_user -soup_server_auth_new -SoupServerAuthBasic -SoupServerAuthCallbackFn -SoupServerAuthContext -SoupServerAuthDigest -SoupAuthType -SoupDigestAlgorithm +SOUP_TYPE_CLIENT_CONTEXT +soup_client_context_get_type +
+ +
+soup-auth-domain +SoupAuthDomain +SoupAuthDomain + +soup_auth_domain_add_path +soup_auth_domain_remove_path +SoupAuthDomainFilter +soup_auth_domain_set_filter +soup_auth_domain_get_realm + +soup_auth_domain_covers +soup_auth_domain_accepts +soup_auth_domain_challenge + +SOUP_AUTH_DOMAIN_REALM +SOUP_AUTH_DOMAIN_PROXY +SOUP_AUTH_DOMAIN_ADD_PATH +SOUP_AUTH_DOMAIN_REMOVE_PATH +SOUP_AUTH_DOMAIN_FILTER +SOUP_AUTH_DOMAIN_FILTER_DATA + +SOUP_AUTH_DOMAIN +SOUP_IS_AUTH_DOMAIN +SOUP_TYPE_AUTH_DOMAIN +soup_auth_domain_get_type +SOUP_AUTH_DOMAIN_CLASS +SOUP_IS_AUTH_DOMAIN_CLASS +SOUP_AUTH_DOMAIN_GET_CLASS +SoupAuthDomainClass +
+ +
+soup-auth-domain-basic +SoupAuthDomainBasic +SoupAuthDomainBasic +soup_auth_domain_basic_new + +SoupAuthDomainBasicAuthCallback +soup_auth_domain_basic_set_auth_callback + +SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK +SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA + +SOUP_AUTH_DOMAIN_BASIC +SOUP_IS_AUTH_DOMAIN_BASIC +SOUP_TYPE_AUTH_DOMAIN_BASIC +soup_auth_domain_basic_get_type +SOUP_AUTH_DOMAIN_BASIC_CLASS +SOUP_IS_AUTH_DOMAIN_BASIC_CLASS +SOUP_AUTH_DOMAIN_BASIC_GET_CLASS +SoupAuthDomainBasicClass +
+ +
+soup-auth-domain-digest +SoupAuthDomainDigest +SoupAuthDomainDigest +soup_auth_domain_digest_new + +SoupAuthDomainDigestAuthCallback +soup_auth_domain_digest_set_auth_callback +soup_auth_domain_digest_encode_password + +soup_auth_domain_digest_evil_check_password + +SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK +SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA + +SOUP_AUTH_DOMAIN_DIGEST +SOUP_IS_AUTH_DOMAIN_DIGEST +SOUP_TYPE_AUTH_DOMAIN_DIGEST +soup_auth_domain_digest_get_type +SOUP_AUTH_DOMAIN_DIGEST_CLASS +SOUP_IS_AUTH_DOMAIN_DIGEST_CLASS +SOUP_AUTH_DOMAIN_DIGEST_GET_CLASS +SoupAuthDomainDigestClass
@@ -179,10 +277,11 @@ SOUP_ADDRESS_ANY_PORT soup_address_new soup_address_new_from_sockaddr soup_address_new_any + SoupAddressCallback soup_address_resolve_async -soup_address_resolve_async_full soup_address_resolve_sync + soup_address_get_name soup_address_get_sockaddr soup_address_get_physical @@ -196,103 +295,27 @@ SOUP_ADDRESS_CLASS SOUP_IS_ADDRESS_CLASS SOUP_ADDRESS_GET_CLASS SoupAddressClass -
- -
-soup-soap-message -SoupSoapMessage -SoupSoapMessage -soup_soap_message_new -soup_soap_message_new_from_uri -soup_soap_message_start_envelope -soup_soap_message_end_envelope -soup_soap_message_start_body -soup_soap_message_end_body -soup_soap_message_start_element -soup_soap_message_end_element -soup_soap_message_start_fault -soup_soap_message_end_fault -soup_soap_message_start_fault_detail -soup_soap_message_end_fault_detail -soup_soap_message_start_header -soup_soap_message_end_header -soup_soap_message_start_header_element -soup_soap_message_end_header_element -soup_soap_message_write_int -soup_soap_message_write_double -soup_soap_message_write_base64 -soup_soap_message_write_time -soup_soap_message_write_string -soup_soap_message_write_buffer -soup_soap_message_set_element_type -soup_soap_message_set_null -soup_soap_message_add_attribute -soup_soap_message_add_namespace -soup_soap_message_set_default_namespace -soup_soap_message_set_encoding_style -soup_soap_message_reset -soup_soap_message_persist -soup_soap_message_get_namespace_prefix -soup_soap_message_get_xml_doc -soup_soap_message_parse_response - -SOUP_IS_SOAP_MESSAGE -SOUP_IS_SOAP_MESSAGE_CLASS -SOUP_SOAP_MESSAGE -SOUP_SOAP_MESSAGE_CLASS -SOUP_SOAP_MESSAGE_GET_CLASS -SOUP_TYPE_SOAP_MESSAGE -SoupSoapMessageClass -soup_soap_message_get_type -
- -
-soup-soap-response -SoupSoapResponse -SoupSoapResponse -SoupSoapParameter -soup_soap_response_new -soup_soap_response_new_from_string -soup_soap_response_set_method_name -soup_soap_parameter_get_first_child -soup_soap_parameter_get_first_child_by_name -soup_soap_parameter_get_int_value -soup_soap_parameter_get_name -soup_soap_parameter_get_next_child -soup_soap_parameter_get_next_child_by_name -soup_soap_parameter_get_property -soup_soap_parameter_get_string_value -soup_soap_response_from_string -soup_soap_response_get_first_parameter -soup_soap_response_get_first_parameter_by_name -soup_soap_response_get_method_name -soup_soap_response_get_next_parameter -soup_soap_response_get_next_parameter_by_name -soup_soap_response_get_parameters - -SOUP_IS_SOAP_RESPONSE -SOUP_IS_SOAP_RESPONSE_CLASS -SOUP_SOAP_RESPONSE -SOUP_SOAP_RESPONSE_CLASS -SOUP_SOAP_RESPONSE_GET_CLASS -SOUP_TYPE_SOAP_RESPONSE -SoupSoapResponseClass -soup_soap_response_get_type + +AF_INET6
soup-session SoupSession SoupSession + +SoupSessionCallback soup_session_queue_message soup_session_requeue_message soup_session_send_message -soup_session_abort -soup_session_add_filter soup_session_cancel_message -soup_session_get_connection -soup_session_remove_filter -soup_session_try_prune_connection +soup_session_abort + +soup_session_pause_message +soup_session_unpause_message + +soup_session_get_async_context + SOUP_SESSION_PROXY_URI SOUP_SESSION_MAX_CONNS SOUP_SESSION_MAX_CONNS_PER_HOST @@ -309,17 +332,6 @@ SOUP_SESSION_GET_CLASS SOUP_TYPE_SESSION SoupSessionClass soup_session_get_type - -SoupMessageQueue -SoupMessageQueueIter -soup_message_queue_append -soup_message_queue_destroy -soup_message_queue_first -soup_message_queue_free_iter -soup_message_queue_new -soup_message_queue_next -soup_message_queue_remove -soup_message_queue_remove_message
@@ -360,16 +372,27 @@ soup_session_sync_get_type soup-auth SoupAuth SoupAuth -SoupAuthBasic -SoupAuthDigest -soup_auth_new_from_header_list +soup_auth_new +soup_auth_update + +soup_auth_is_for_proxy soup_auth_get_scheme_name +soup_auth_get_host soup_auth_get_realm +soup_auth_get_info + soup_auth_authenticate soup_auth_is_authenticated + soup_auth_get_authorization soup_auth_get_protection_space soup_auth_free_protection_space + +SOUP_AUTH_SCHEME_NAME +SOUP_AUTH_REALM +SOUP_AUTH_HOST +SOUP_AUTH_IS_FOR_PROXY +SOUP_AUTH_IS_AUTHENTICATED SOUP_AUTH SOUP_IS_AUTH @@ -379,22 +402,6 @@ SOUP_AUTH_CLASS SOUP_IS_AUTH_CLASS SOUP_AUTH_GET_CLASS SoupAuthClass -SOUP_AUTH_BASIC -SOUP_IS_AUTH_BASIC -SOUP_TYPE_AUTH_BASIC -soup_auth_basic_get_type -SOUP_AUTH_BASIC_CLASS -SOUP_IS_AUTH_BASIC_CLASS -SOUP_AUTH_BASIC_GET_CLASS -SoupAuthBasicClass -SOUP_AUTH_DIGEST -SOUP_IS_AUTH_DIGEST -SOUP_TYPE_AUTH_DIGEST -soup_auth_digest_get_type -SOUP_AUTH_DIGEST_CLASS -SOUP_IS_AUTH_DIGEST_CLASS -SOUP_AUTH_DIGEST_GET_CLASS -SoupAuthDigestClass
@@ -402,31 +409,38 @@ SoupAuthDigestClass SoupSocket SoupSocket soup_socket_new -soup_socket_connect + +SoupSocketCallback +soup_socket_connect_async +soup_socket_connect_sync + soup_socket_listen + soup_socket_start_ssl soup_socket_start_proxy_ssl +soup_socket_is_ssl + soup_socket_disconnect soup_socket_is_connected -SoupSocketCallback -SoupSocketListenerCallback -soup_socket_client_new_async -soup_socket_client_new_sync -soup_socket_server_new + soup_socket_get_local_address soup_socket_get_remote_address + SoupSocketIOStatus soup_socket_read soup_socket_read_until soup_socket_write + +SOUP_SSL_ERROR +SoupSSLError + +SOUP_SOCKET_LOCAL_ADDRESS +SOUP_SOCKET_REMOTE_ADDRESS SOUP_SOCKET_FLAG_NONBLOCKING -SOUP_SOCKET_FLAG_NODELAY -SOUP_SOCKET_FLAG_REUSEADDR -SOUP_SOCKET_FLAG_CLOEXEC -SOUP_SOCKET_TIMEOUT SOUP_SOCKET_IS_SERVER SOUP_SOCKET_SSL_CREDENTIALS SOUP_SOCKET_ASYNC_CONTEXT +SOUP_SOCKET_TIMEOUT SOUP_SOCKET SOUP_IS_SOCKET @@ -436,260 +450,154 @@ SOUP_SOCKET_CLASS SOUP_IS_SOCKET_CLASS SOUP_SOCKET_GET_CLASS SoupSocketClass -
- -
-soup-connection-ntlm -SoupConnectionNTLM -SoupConnectionNTLM - -SOUP_CONNECTION_NTLM -SOUP_IS_CONNECTION_NTLM -SOUP_TYPE_CONNECTION_NTLM -soup_connection_ntlm_get_type -SOUP_CONNECTION_NTLM_CLASS -SOUP_IS_CONNECTION_NTLM_CLASS -SOUP_CONNECTION_NTLM_GET_CLASS -SoupConnectionNTLMClass -
- -
-soup-connection -SoupConnection -SoupConnection -soup_connection_new -SoupConnectionCallback -soup_connection_connect_async -soup_connection_connect_sync -soup_connection_disconnect -soup_connection_is_in_use -soup_connection_last_used -soup_connection_send_request -soup_connection_authenticate -soup_connection_reauthenticate -soup_connection_release -soup_connection_reserve -SOUP_CONNECTION_ORIGIN_URI -SOUP_CONNECTION_PROXY_URI -SOUP_CONNECTION_SSL_CREDENTIALS -SOUP_CONNECTION_MESSAGE_FILTER -SOUP_CONNECTION_ASYNC_CONTEXT -SOUP_CONNECTION_TIMEOUT - -SOUP_CONNECTION -SOUP_IS_CONNECTION -SOUP_TYPE_CONNECTION -soup_connection_get_type -SOUP_CONNECTION_CLASS -SOUP_IS_CONNECTION_CLASS -SOUP_CONNECTION_GET_CLASS -SoupConnectionClass -
- -
-soup-server-message -SoupServerMessage -SoupServerMessage -soup_server_message_new -soup_server_message_get_server -soup_server_message_set_encoding -soup_server_message_get_encoding -soup_server_message_start -soup_server_message_is_started -soup_server_message_finish -soup_server_message_is_finished - -SOUP_SERVER_MESSAGE -SOUP_IS_SERVER_MESSAGE -SOUP_TYPE_SERVER_MESSAGE -soup_server_message_get_type -SOUP_SERVER_MESSAGE_CLASS -SOUP_IS_SERVER_MESSAGE_CLASS -SOUP_SERVER_MESSAGE_GET_CLASS -SoupServerMessageClass -
- -
-soup-dns -soup_dns_init -soup_dns_ntop -SoupDNSLookup -soup_dns_lookup_name -soup_dns_lookup_address -soup_dns_lookup_resolve -SoupDNSCallback -soup_dns_lookup_resolve_async -soup_dns_lookup_cancel -soup_dns_lookup_get_hostname -soup_dns_lookup_get_address -soup_dns_lookup_free -
- -
-soup-ssl -soup_ssl_supported - -SoupSSLType -SoupSSLCredentials -soup_ssl_get_client_credentials -soup_ssl_free_client_credentials -soup_ssl_get_server_credentials -soup_ssl_free_server_credentials -soup_ssl_wrap_iochannel -SOUP_SSL_ERROR + soup_ssl_error_quark -SoupSocketError
soup-uri -SoupUri -SoupUri -SoupProtocol -SOUP_PROTOCOL_HTTP -SOUP_PROTOCOL_HTTPS +SoupURI +SoupURI soup_uri_new_with_base soup_uri_new soup_uri_to_string + soup_uri_copy -soup_uri_copy_root soup_uri_equal soup_uri_free + soup_uri_encode soup_uri_decode +soup_uri_normalize + +SOUP_URI_SCHEME_HTTP +SOUP_URI_SCHEME_HTTPS soup_uri_uses_default_port -
- -
-soup-message-filter -SoupMessageFilter -soup_message_filter_setup_message + +soup_uri_set_scheme +soup_uri_set_user +soup_uri_set_password +soup_uri_set_host +soup_uri_set_port +soup_uri_set_path +soup_uri_set_query +soup_uri_set_query_from_form +soup_uri_set_fragment -SOUP_IS_MESSAGE_FILTER -SOUP_IS_MESSAGE_FILTER_CLASS -SOUP_MESSAGE_FILTER -SOUP_MESSAGE_FILTER_CLASS -SOUP_MESSAGE_FILTER_GET_CLASS -SOUP_MESSAGE_FILTER_GET_CLASS -SOUP_TYPE_MESSAGE_FILTER -SoupMessageFilterClass -soup_message_filter_get_type +SOUP_TYPE_URI +soup_uri_get_type
soup-misc Soup Miscellaneous Utilities -soup_base64_decode -soup_base64_encode -soup_base64_decode_step -soup_base64_encode_step -soup_base64_encode_close - -soup_add_idle -soup_add_io_watch -soup_add_timeout -soup_signal_connect_once +SoupDate +SoupDateFormat +soup_date_new +soup_date_new_from_string +soup_date_new_from_time_t +soup_date_new_from_now +soup_date_to_string +soup_date_free -soup_date_parse -soup_date_iso8601_parse -soup_date_generate -soup_gmtime -soup_mktime_utc +soup_form_decode_urlencoded +soup_form_encode_urlencoded +soup_form_encode_urlencoded_list -soup_header_param_copy_token -soup_header_param_decode_token -soup_header_param_destroy_hash -soup_header_param_parse_list soup_headers_parse_request soup_headers_parse_response soup_headers_parse_status_line +soup_header_parse_list +soup_header_parse_quality_list +soup_header_free_list +soup_header_contains +soup_header_parse_param_list +soup_header_free_param_list + soup_str_case_equal soup_str_case_hash -soup_xml_real_node +soup_add_idle +soup_add_io_watch +soup_add_timeout + +soup_ssl_supported + +soup_date_copy +soup_signal_connect_once +SOUP_TYPE_DATE +soup_date_get_type
-soup-xmlrpc-message -SoupXmlrpcMessage -SoupXmlrpcMessage -soup_xmlrpc_message_new -soup_xmlrpc_message_new_from_uri -soup_xmlrpc_message_from_string - -soup_xmlrpc_message_start_call -soup_xmlrpc_message_end_call -soup_xmlrpc_message_start_param -soup_xmlrpc_message_end_param - -soup_xmlrpc_message_write_int -soup_xmlrpc_message_write_boolean -soup_xmlrpc_message_write_string -soup_xmlrpc_message_write_double -soup_xmlrpc_message_write_datetime -soup_xmlrpc_message_write_base64 - -soup_xmlrpc_message_start_struct -soup_xmlrpc_message_end_struct -soup_xmlrpc_message_start_member -soup_xmlrpc_message_end_member - -soup_xmlrpc_message_start_array -soup_xmlrpc_message_end_array - -soup_xmlrpc_message_to_string -soup_xmlrpc_message_persist - -soup_xmlrpc_message_parse_response - -SOUP_IS_XMLRPC_MESSAGE -SOUP_IS_XMLRPC_MESSAGE_CLASS -SOUP_TYPE_XMLRPC_MESSAGE -SOUP_XMLRPC_MESSAGE -SOUP_XMLRPC_MESSAGE_CLASS -SOUP_XMLRPC_MESSAGE_GET_CLASS -SoupXmlrpcMessageClass -soup_xmlrpc_message_get_type +soup-xmlrpc +XMLRPC Support + +soup_xmlrpc_build_method_call +soup_xmlrpc_request_new +soup_xmlrpc_parse_method_response +soup_xmlrpc_extract_method_response + +soup_xmlrpc_parse_method_call +soup_xmlrpc_extract_method_call +soup_xmlrpc_build_method_response +soup_xmlrpc_build_fault +soup_xmlrpc_set_response +soup_xmlrpc_set_fault + +SOUP_XMLRPC_ERROR +SoupXMLRPCError +SOUP_XMLRPC_FAULT + +soup_xmlrpc_error_quark +soup_xmlrpc_fault_quark
-soup-xmlrpc-response -SoupXmlrpcResponse -SoupXmlrpcResponse -soup_xmlrpc_response_new -soup_xmlrpc_response_new_from_string -soup_xmlrpc_response_from_string -soup_xmlrpc_response_to_string - -SoupXmlrpcValue -SoupXmlrpcValueType -soup_xmlrpc_response_is_fault -soup_xmlrpc_response_get_value - -soup_xmlrpc_value_get_type -soup_xmlrpc_value_get_int -soup_xmlrpc_value_get_double -soup_xmlrpc_value_get_boolean -soup_xmlrpc_value_get_string -soup_xmlrpc_value_get_datetime -soup_xmlrpc_value_get_base64 -soup_xmlrpc_value_get_struct - -SoupXmlrpcValueArrayIterator -soup_xmlrpc_value_array_get_iterator -soup_xmlrpc_value_array_iterator_prev -soup_xmlrpc_value_array_iterator_next -soup_xmlrpc_value_array_iterator_get_value - -soup_xmlrpc_value_dump +soup-value-utils +GValue Support +soup_value_hash_new +soup_value_hash_insert_value +soup_value_hash_insert +soup_value_hash_lookup + +soup_value_array_from_args +soup_value_array_to_args +soup_value_array_insert +soup_value_array_append +soup_value_array_get_nth + +SOUP_VALUE_SETV +SOUP_VALUE_GETV + +SOUP_TYPE_BYTE_ARRAY + +soup_byte_array_get_type +
+ +
+soup-logger +SoupLogger +SoupLogger +SoupLoggerLogLevel +soup_logger_new +soup_logger_attach +soup_logger_detach + +SoupLoggerFilter +soup_logger_set_request_filter +soup_logger_set_response_filter + +SoupLoggerPrinter +soup_logger_set_printer -SOUP_IS_XMLRPC_RESPONSE -SOUP_IS_XMLRPC_RESPONSE_CLASS -SOUP_TYPE_XMLRPC_RESPONSE -SOUP_XMLRPC_RESPONSE -SOUP_XMLRPC_RESPONSE_CLASS -SOUP_XMLRPC_RESPONSE_GET_CLASS -SoupXmlrpcResponseClass -soup_xmlrpc_response_get_type +SoupLoggerClass +soup_logger_get_type +SOUP_IS_LOGGER +SOUP_IS_LOGGER_CLASS +SOUP_LOGGER +SOUP_LOGGER_CLASS +SOUP_LOGGER_GET_CLASS +SOUP_TYPE_LOGGER
diff --git a/docs/reference/libsoup.types b/docs/reference/libsoup.types index 7c6a4d2..9effee1 100644 --- a/docs/reference/libsoup.types +++ b/docs/reference/libsoup.types @@ -1,37 +1,14 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include soup_address_get_type soup_auth_get_type -soup_auth_basic_get_type -soup_auth_digest_get_type -soup_connection_get_type -soup_connection_ntlm_get_type +soup_auth_domain_get_type +soup_auth_domain_basic_get_type +soup_auth_domain_digest_get_type soup_message_get_type -soup_message_filter_get_type -soup_server_message_get_type soup_server_get_type soup_session_get_type soup_session_sync_get_type soup_session_async_get_type -soup_soap_message_get_type -soup_soap_response_get_type soup_socket_get_type -soup_xmlrpc_message_get_type -soup_xmlrpc_response_get_type + diff --git a/docs/reference/porting-2.2-2.4.xml b/docs/reference/porting-2.2-2.4.xml new file mode 100644 index 0000000..4e01880 --- /dev/null +++ b/docs/reference/porting-2.2-2.4.xml @@ -0,0 +1,878 @@ + + + + +libsoup 2.2 to 2.4 porting notes +3 +LIBSOUP Library + + + +Porting notesNotes on porting from libsoup 2.2 to 2.4 + + + +Overview + + +After many API-compatible releases in the 2.2 series, +libsoup has now changed its API and bumped +its version number to 2.4. Changes were made for a variety of reasons: + + + + + To fix bugs and add features that couldn't be done ABI-compatibly. + + + + To make it easier to generate bindings for libsoup for + languages other than C. + + + + To clean up ugly/confusing old APIs + + + + To be more glib/gobject/gtk-like in general. + + + + + + +SoupMessage + + +SoupMessage has had a +number of API changes made, mostly to increase its +language-bindability. + + + +SoupMessageHeaders + + + SoupMessage's + request_headers and + response_headers fields are now an + opaque type (SoupMessageHeaders) + rather than being GHashTables. The method names have + changed slightly to reflect this: + + + + + soup_message_add_header + + → soup_message_headers_append + + + + soup_message_get_header + + → soup_message_headers_get + + + + soup_message_foreach_header + + → soup_message_headers_foreach + + + + soup_message_remove_header + + → soup_message_headers_remove + + + + soup_message_clear_headers + + → soup_message_headers_append + + + + + + soup_message_get_header_list has no equivalent; + if multiple copies of a header are present, + soup_message_headers_get will return all of + them, concatenated together and separated by commas; RFC 2616 says + that the two forms (multiple headers, and a single header with + comma-separated values) are equivalent; this change to libsoup + ensures that applications will treat them as equivalent. + + + + In addition, certain important header fields now have + dedicated get/set methods: + + + + + soup_message_headers_get_encoding / soup_message_headers_set_encoding + + + + soup_message_headers_get_content_length / soup_message_headers_set_content_length + + + + soup_message_headers_get_expectations / soup_message_headers_set_expectations + + + + + (soup_message_headers_set_expectation(msg, SOUP_EXPECTATION_CONTINUE) + replaces the SOUP_MESSAGE_EXPECT_CONTINUE + message flag). + + + + + +SoupMessageBody + + + Similarly, the request_body and + response fields (renamed from + request and response) are + now a new type, SoupMessageBody, + implemented in terms of SoupBuffer, a refcounted + memory buffer type with clearer semantics than the old + SoupDataBuffer/SoupOwnership. + + + + + SOUP_BUFFER_STATIC + + → SOUP_MEMORY_STATIC + + + + SOUP_BUFFER_SYSTEM_OWNED + + → SOUP_MEMORY_TAKE + (meaning libsoup + should take ownership of the memory from your). + + + + SOUP_BUFFER_USER_OWNED + + → SOUP_MEMORY_COPY + (meaning libsoup + should make a copy of the memory, because you + can't make any guarantees about how long it will + last.) + + + + + + A fourth SoupMemoryUse value is also available: SOUP_MEMORY_TEMPORARY, + which helps to avoid extra copies in some cases. + SOUP_MEMORY_TEMPORARY means that the memory + will last at least as long as the object you are handing it to (a + SoupBuffer, SoupMessageBody, or + SoupMessage), and so doesn't need to be copied right + away, but that if anyone makes a copy of the buffer, + libsoup needs to make a new copy of the + memory for them at that point, since the original pointer may not + remain valid for the lifetime of the new copy. + + + + (In the future, there may be additional SoupBuffer + and SoupMessageBody methods to work directly with + mmapped memory, splicing to file descriptors, etc.) + + + + soup_message_set_request + and soup_message_set_response + still work roughly like they used to. + + + + Unlike the old request and + response fields, the new + request_body and + response_body fields are not guaranteed + to be filled in at all times. (In particular, the + response_body is not filled in until it + has been fully read, although you can use soup_message_body_get_chunk + to iterate through the chunks before that point if you need to.) + + + + When request_body and + response_body are + filled in, they are '\0'-terminated for your + processing convenience. (The terminating 0 byte is not included in + their length.) + + + + + +Chunked encoding + + + The prototype of the SoupMessage::got_chunk + signal has been changed; it now includes the chunk as a + SoupBuffer parameter (rather than storing the chunk + data in msg->response as in 2.2). SOUP_MESSAGE_OVERWRITE_CHUNKS + is now somewhat poorly named, but still has essentially the same + semantics: if you set it, each chunk will be discarded after it is + read, and msg->response_body will not be filled + in with the complete response at the end of message processing. + + + + The API for sending chunked responses from a + SoupServer is also slightly different now: + + + + + soup_server_message_set_encoding + + → soup_message_headers_set_encoding + + + + soup_message_add_chunk + + → soup_message_body_append + or soup_message_body_append_buffer + + + + soup_message_add_final_chunk + + → soup_message_body_complete + + + + + + Since the new chunk-sending APIs require you to explicitly pass + the + request_headers/request_body + fields, rather than just assuming you're talking about the + response body, in theory it is now possible to use chunked + encoding with the request as well. As of the 2.3.0 release this + has not yet been tested. + + + + + +Methods + + + SoupMessage's + method field is now an interned + string, and you can compare the method directly against + the defines such as SOUP_METHOD_GET + (eg, in a SoupServer request handler). + soup_method_get_id and the + SOUP_METHOD_ID_* macros are now gone. + + + + +Handlers + + + soup_message_add_header_handler + and soup_message_add_status_code_handler + are now just clever wrappers around + g_signal_connect. In particular, you now pass + a signal name to them rather than a SoupHandlerPhase, + and you remove them with the normal signal handler remove methods. + However, they still retain the special behavior that if the + message has been cancelled or requeued when the time comes for the + handler to run, then the handler will be skipped. (Use plain + g_signal_connect if you don't want that + behavior.) + + + + +I/O-related <type>SoupMessage</type> methods + + + soup_message_io_pause and + soup_message_io_unpause have been moved to + SoupSession and SoupServer, to better + reflect the fact that the session/server control the I/O, and + SoupMessage is merely acted-upon by them. + + + + + soup_message_io_pause + + → soup_session_pause_message / soup_server_pause_message + + + + soup_message_io_unpause + + → soup_session_unpause_message / soup_server_unpause_message + + + + + + msg->status (the I/O status) is now + gone as well, because (a) it's really an internal state of + SoupSession, and (b) it's too easy to confuse + with msg->status_code (the HTTP status) + anyway. Code that used to check if status was + SOUP_MESSAGE_STATUS_FINISHED needs to + be rewritten to track whether or not the finished + signal has been emitted. + + + + + +HTTP-Version + + + SoupHttpVersion is now SoupHTTPVersion + + + + + + +SoupSession + + +<function>soup_session_queue_message</function> callback + + + soup_session_queue_message's + callback parameter now includes the SoupSession as a + parameter, reflecting the fact that it is a + SoupSession callback, not a SoupMessage + callback. (It has also been renamed, from + SoupMessageCallbackFn to SoupSessionCallback.) + + + + +Authentication + + + SoupSession's authenticate and + reauthenticate signals have been merged into a + single authenticate + signal with a retrying parameter to indicate if + it's the second (or later) try. Also, the signal now includes a + SoupAuth directly, + and you authenticate by calling soup_auth_authenticate + on the auth (rather than passing back a username and password from + the signal handler). + + + + +<type>SoupLogger</type> + + +SoupLogger is a +new object that copies the behavior of +evolution-exchange's +E2K_DEBUG and its clones. That is, it causes a +SoupSession to start logging some or all of its HTTP +traffic to stdout, for debugging purposes. + + + + +<type>SoupMessageFilter</type> + + + SoupMessageFilter is gone; code that used to use it + can now connect to the SoupSession::request-started + signal to get a chance to act on each message as it is sent. + (This is how SoupLogger works.) + + + + +Internal types + + + The SoupConnection and SoupMessageQueue + types (which should always have been internal to + SoupSession) have been removed from the public API. + + + + + + +SoupURI + +SoupUri has been renamed SoupURI, and its behavior has +changed in a few ways: + + + + + It no longer fully-decodes %-encoded URI components. This + is necessary to ensure that complicated URIs (eg, URIs + that include other URIs as query parameters) can be + round-tripped correctly. This corresponds to the old + broken_encoding behavior, but + that flag no longer exists, since it is the default and + there's no way to turn it off. + + + + In theory, this is an ABI-breaking change, especially for + SoupServers. + However, it is unlikely to actually break anything. (And + in the SoupServer case, servers now + fully-decode the path component + themselves unless you set the SOUP_SERVER_RAW_PATHS + flag on the server, so the behavior should still be the + same. + + + + + It uses the RFC3986 parsing rules, including support for IPv6 literal + addresses. + + + + + The field formerly called + protocol is now + scheme, to match the spec, and + it's an interned string rather than a quark. The names of + the predefined values have changed to match: + + + + + SOUP_PROTOCOL_HTTP + + → SOUP_URI_SCHEME_HTTP + + + + SOUP_PROTOCOL_HTTPS + + → SOUP_URI_SCHEME_HTTPS + + + + + + + +soup_uri_decode +now returns a new string rather than modifying its input string in +place. The new method soup_uri_normalize, +which removes some, but not all, %-encoding, behaves similarly. + + + +Finally, SoupURI (as well as most other struct types in +libsoup) now uses the glib "slice" +allocator, so any code that uses g_new to create +SoupURIs is wrong. If you want to create a URI "by hand", +you can call soup_uri_new, +passing NULL, and you will get back an empty +SoupURI. There are also now methods that can be used to +set its fields (eg, soup_uri_set_scheme, +soup_uri_set_path, +etc) rather than mucking with the fields directly. + + + +Forms + + +Related to SoupURI, there are some new helper methods for +dealing with HTML forms. soup_form_decode_urlencoded +decodes a URI query component (or an +application/x-www-form-urlencoded request body) +into a GHashTable. soup_form_encode_urlencoded +reverses the process, allowing you to fill in a +uri->query with a properly-encoded form dataset. +(SoupURI also provides soup_uri_set_query_from_form +to help with this.) + + + + + + + +XML-RPC and SOAP + + +SOAP + +SOAP support has been removed; the existing methods covered only a +teeny tiny subset of SOAP, which was really only useful to a single +application. (The code that was formerly in libsoup has been moved to +that application.). If you were using this code, you can resurrect a +libsoup-2.4-compatible version of it from revision 1016 of libsoup +svn. + + + + +XML-RPC + +The XML-RPC code has been completely rewritten to make it simpler to +implement XML-RPC clients and servers. (Note: the server-side code has +not been heavily tested yet.) The new XML-RPC API makes use of +GValues, with the following type mappings: + + + + + int + + → int (G_TYPE_INT) + + + + boolean + + → gboolean (G_TYPE_BOOLEAN) + + + + string + + → char * (G_TYPE_STRING) + + + + double + + → double (G_TYPE_DOUBLE) + + + + dateTime.iso8601 + + → SoupDate (SOUP_TYPE_DATE) + + + + base64 + + → GByteArray (SOUP_TYPE_BYTE_ARRAY) + + + + struct + + → GHashTable (G_TYPE_HASH_TABLE) + + + + array + + → GValueArray (G_TYPE_VALUE_ARRAY) + + + + + +SoupDate is discussed below. +SOUP_TYPE_BYTE_ARRAY is just a new +GType value defined by libsoup +to represent GByteArrays, which glib does not define a +GType for. + + + +libsoup provides some additional GValue support +methods for working with +GValueArrays, and GHashTables of +GValues, for the XML-RPC struct and +array types. Eg, you can use soup_value_hash_new +to create a GHashTable to use with the XML-RPC methods, +and soup_value_hash_insert +to add values to it without needing to muck with GValues +directly. + + + +The getbug and xmlrpc-test +programs in the libsoup sources provide +examples of how to use the new API. (Beware that +xmlrpc-test's use of the API is a little +complicated because of the way it sends all calls through a single +do_xmlrpc method.) + + + + + + +SoupServer + + +SoupServer handlers + + + The prototypes for soup_server_add_handler, + and for the SoupServer + handlers themselves have changed: + + + +typedef void (*SoupServerCallback) (SoupServer *server, + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *client, + gpointer user_data); + +void soup_server_add_handler (SoupServer *server, + const char *path, + SoupServerCallback callback, + gpointer data, + GDestroyNotify destroy); + + + + soup_server_add_handler no longer takes a + SoupServerAuthContext (see the discussion of server + authentication below), and the order of the final two arguments + has been swapped. (Additionally, SoupServerCallbackFn + has been renamed to SoupServerCallback, and the old + unregister parameter of type + SoupServerUnregisterFn is now a standard + GDestroyNotify. The change to + GDestroyNotify and the swapping of the final two + arguments is to make the method conform to standard glib/gtk + practices.) + + + + In SoupServerCallback, several bits of data that used + to be part of the context argument are now + provided directly, and context specifically + only contains more specifically-client-related information (such + as the SoupSocket that the request arrived on, and + information about authentication). + + + + path is the fully %-decoded path component + of msg's URI, and + query is a hash table containing + msg's URI's + query component decoded with soup_form_decode_urlencoded. + These are provided for your convenience; if you need the raw + query, you can get it out of msg's URI + directly. If you need the raw path, you'll need to set the SOUP_SERVER_RAW_PATHS + property on the server, which actually changes the behavior of the + server with respect to how paths are matched; see the + documentation for details. + + + + +Server-side authentication + + + SoupServer authentication has been completely + rewritten, with SoupServerAuthContext being replaced + with SoupAuthDomain. Among + other improvements, you no longer need to have the cleartext + password available to check against. See the + SoupAuthDomain documentation, the server tutorial, and + tests/server-auth-test.c. + + + + +<literal>Expect: 100-continue</literal> and other early <type>SoupMessage</type> processing + + + SoupServer now handles + "Expect: 100-continue" correctly. In + particular, if the client passes that header, and your server + requires authentication, then authentication will be checked + before reading the request body. + + + + If you want to do additional pre-request-body handling, you can + connect to SoupServer's request_started + signal, and connect to the request's got_headers + signal from there. (See the description of + request_started for information about other + related SoupServer signals.) + + + + +Date header + + + SoupServer now automatically sets the + Date header on all responses, as required by + RFC 2616. + + + + +SoupServerMessage + + + SoupServerMessage is now merged into + SoupMessage. + soup_server_message_set_encoding is replaced + with soup_message_headers_set_encoding + as described in the section on SoupMessage above. + + + + +<function>soup_server_run</function> / <function>soup_server_quit</function> + + + soup_server_run + and soup_server_run_async + no longer g_object_ref the server, and + soup_server_quit + no longer unrefs it. + + + + + + +Miscellaneous + + +SoupDate + + + The new SoupDate type + replaces the old soup_date_* methods, and has + an improved (more liberal) date parser. + + + + +Header parsing + + + soup-headers.h now has a few additional methods + for parsing list-type headers. + + + + +SoupAddress, SoupSocket + + + SoupSocket has had various simplifications made to + reflect the fact that this is specifically libsoup's socket + implementation, not some random generic socket API. + + + + Various SoupAddress and SoupSocket + methods now take arguments of the new GCancellable type, from + libgio. When porting old code, you can just pass + NULL for these. (soup_address_resolve_async + also takes another new argument, a GMainContext that + you'll want to pass NULL for.) If you pass a + GCancellable, you can use it to cleanly cancel the + address resolution / socket operation. + + + + + +Base64 methods + + + The deprecated base64 methods are now gone; use glib's base64 + methods instead. + + + + + + diff --git a/docs/reference/server-howto.xml b/docs/reference/server-howto.xml index 2d7971b..76c1918 100644 --- a/docs/reference/server-howto.xml +++ b/docs/reference/server-howto.xml @@ -21,17 +21,6 @@ most of your interactions with libsoup. In this case, SoupServer.
- - - Note that SoupServer isn't as polished as - SoupSession, and thus not as stable, and the APIs - will likely change in incompatible (but not - difficult-to-port-to) ways in the future to make things nicer. - We apologize in advance for the inconvenience. - - - - You create the server with soup_server_new, @@ -83,6 +72,16 @@ various additional options: other than the main thread.
+ + SOUP_SERVER_RAW_PATHS + + Set this to TRUE if you don't want + libsoup to decode %-encoding + in the Request-URI. (Eg, because you need to treat + "/foo/bar" and + "/foo%2Fbar" as different paths. + +
@@ -100,8 +99,8 @@ to set a callback to handle certain URI paths. -soup_server_add_handler (server, "/foo", NULL, server_callback, - unregister_callback, data); + soup_server_add_handler (server, "/foo", server_callback, + data, destroy_notify); @@ -131,18 +130,28 @@ A handler callback looks something like this: static void -server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) +server_callback (SoupServer *server, + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *client, + gpointer user_data) { ... } -msg is the request that has been received. -data is the same data that was passed to msg is the request that has been received and +user_data is the data that was passed to soup_server_add_handler. -The context argument contains some additional information -related to the request. +path is the path (from msg's +URI), and query contains the result of parsing the +URI query field. (It is NULL if there was no +query.) client is a SoupClientContext, +which contains additional information about the client (including its +IP address, and whether or not it used HTTP authentication). @@ -151,10 +160,10 @@ completely finished processing the message when you return from the callback, and that it can therefore begin sending the response. If you are not ready to send a response immediately (eg, you have to contact another server, or wait for data from a database), you must call soup_message_io_pause +linkend="soup-server-pause-message">soup_server_pause_message on the message before returning from the callback. This will delay sending a response until you call soup_message_io_unpause. +linkend="soup-server-unpause-message">soup_server_unpause_message. (You must also connect to the finished signal on the message in this case, so that you can break off processing if the client @@ -166,10 +175,8 @@ To set the response status, call soup_message_set_status or soup_message_set_status_full. -If the response requires a body, the callback must call soup_server_message_set_encoding -to indicate whether it will provide the response all at once with -Content-Length encoding, or in pieces with +If the response requires a body, you must decide whether to use +Content-Length encoding (the default), or chunked encoding. @@ -184,29 +191,34 @@ data available at once. static void -server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) +server_callback (SoupServer *server, + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *client, + gpointer user_data) { - MyServerData *server_data = data; - SoupUri *uri = soup_message_get_uri (msg); + MyServerData *server_data = user_data; const char *mime_type; GByteArray *body; - if (context->method_id != SOUP_METHOD_ID_GET) { + if (msg->method != SOUP_METHOD_GET) { soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); return; } - body = g_hash_table_lookup (server_data->bodies, uri->path); - mime_type = g_hash_table_lookup (server_data->mime_types, uri->path); + /* This is somewhat silly. Presumably your server will do + * something more interesting. + */ + body = g_hash_table_lookup (server_data->bodies, path); + mime_type = g_hash_table_lookup (server_data->mime_types, path); if (!body || !mime_type) { soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); return; } soup_message_set_status (msg, SOUP_STATUS_OK); - soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (msg), - SOUP_TRANSFER_CONTENT_LENGTH); - soup_message_set_response (msg, mime_type, SOUP_BUFFER_USER_OWNED, + soup_message_set_response (msg, mime_type, SOUP_MEMORY_COPY, body->data, body->len); } @@ -219,16 +231,22 @@ server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) If you want to supply the response body in chunks as it becomes available, use chunked encoding instead. In this -case, call soup_message_add_chunk with -each chunk of the response body as it becomes available, and call -soup_message_add_final_chunk +case, first call soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_CHUNKED) +to tell libsoup that you'll be using +chunked encoding. Then call soup_message_body_append +(or soup_message_body_append_buffer) +on msg->response_body with each chunk of the +response body as it becomes available, and call soup_message_body_complete when the response is complete. After each of these calls, you must also call soup_message_io_unpause to -cause the chunk to be sent. (You do not normally need to call -soup_message_io_pause, +linkend="soup-server-unpause-message">soup_server_unpause_message +to cause the chunk to be sent. (You do not normally need to call soup_server_pause_message, because I/O is automatically paused when doing a chunked transfer if no chunks are available.) @@ -239,7 +257,9 @@ linkend="SoupMessage-finished">finished signal on the message, so that you will be notified if the client disconnects between two chunks; SoupServer will unref the message if that happens, so you must stop adding new chunks to the response at that -point. +point. (An alternate possibility is to write each new chunk only when +the wrote_chunk signal +is emitted indicating that the previous one was written successfully.) @@ -257,59 +277,92 @@ using chunked encoding. To have SoupServer -handle HTTP authentication for you, pass a SoupAuthContext to soup_server_add_handler: +handle HTTP authentication for you, create a SoupAuthDomainBasic +or SoupAuthDomainDigest, +and pass it to soup_server_add_auth_domain: -SoupServerAuthContext auth_ctx; - -auth_ctx.types = SOUP_AUTH_TYPE_BASIC; -auth_ctx.callback = auth_callback; -auth_ctx.user_data = data; -auth_ctx.basic_info.realm = "My Realm"; - -soup_server_add_handler (server, "/bar", &auth_ctx, server_callback, - unregister_callback, data); + SoupAuthDomain *domain; + + domain = soup_auth_domain_basic_new ( + SOUP_AUTH_DOMAIN_REALM, "My Realm", + SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback, + SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA, auth_data, + SOUP_AUTH_DOMAIN_ADD_PATH, "/foo", + SOUP_AUTH_DOMAIN_ADD_PATH, "/bar/private", + NULL); + soup_server_add_auth_domain (server, domain); + g_object_unref (domain); -Then, every request that matches that handler will be passed to the -auth_callback first before being passed to the -server_callback: +Then, every request under one of the auth domain's paths will be +passed to the auth_callback first before being +passed to the server_callback: static gboolean -auth_callback (SoupServerAuthContext *auth_ctx, SoupServerAuth *auth, - SoupMessage *msg, gpointer user_data) +auth_callback (SoupAuthDomain *domain, SoupMessage *msg, + const char *username, const char *password, + gpointer user_data) { MyServerData *server_data = user_data; - const char *username, *password; + MyUserData *user; - if (!auth) + user = my_server_data_lookup_user (server_data, username); + if (!user) return FALSE; - username = soup_server_auth_get_user (auth); - password = g_hash_table_lookup (server_data->passwords, username); - if (!password) - return FALSE; - - return soup_server_auth_check_passwd (auth, password); + /* FIXME: Don't do this. Keeping a cleartext password database + * is bad. + */ + return strcmp (password, user->password) == 0; } -The auth parameter indicates the authentication -information passed by the client. If no -WWW-Authenticate header was present, this will be -NULL, so we return FALSE from -the callback to indicate that the server should return a 401 -Unauthorized response. If it is non-NULL, -we extract the username from it, and compare it against our stored -password. Assuming it matches, we return TRUE, and -the server callback is then invoked normally. +The SoupAuthDomainBasicAuthCallback +is given the username and password from the +Authorization header and must determine, in some +server-specific manner, whether or not to accept them. (In this +example we compare the password against a cleartext password database, +but it would be better to store the password somehow encoded, as in +the UNIX password database. Alternatively, you may need to delegate +the password check to PAM or some other service.) + + + +If you are using Digest authentication, note that SoupAuthDomainDigestAuthCallback +works completely differently (since the server doesn't receive the +cleartext password from the client in that case, so there's no way to +compare it directly). See the documentation for SoupAuthDomainDigest +for more details. + + + +You can have multiple SoupAuthDomains attached to a +SoupServer, either in separate parts of the path +hierarchy, or overlapping. (Eg, you might want to accept either Basic +or Digest authentication for a given path.) When more than one auth +domain covers a given path, the request will be accepted if the user +authenticates successfully against any of the +domains. + + + +If you want to require authentication for some requests under a +certain path, but not all of them (eg, you want to authenticate +PUTs, but not GETs), use a +SoupAuthDomainFilter. diff --git a/docs/reference/tmpl/libsoup-unused.sgml b/docs/reference/tmpl/libsoup-unused.sgml deleted file mode 100644 index f4901ef..0000000 --- a/docs/reference/tmpl/libsoup-unused.sgml +++ /dev/null @@ -1,497 +0,0 @@ - - -This implements the Basic HTTP Authentication mechanism, as described -in RFC 2617. It is created automatically by #SoupSession when needed. - - - - - - - - - - -HTTP Basic Authentication - - - -SoupAuthBasic - - - - -This implements the Digest HTTP Authentication mechanism, as described -in RFC 2617. It is created automatically by #SoupSession when needed. - - - - - - - - - - -HTTP Digest Authentication - - - -SoupAuthDigest - - - - - - - - - - - - - - - -MD5 hash utilities - - - -soup-md5-utils - - - - - - - - - - - - - - - - - - - - - - - -Soup Miscellaneous Utilities - - - - - - - - - - - - - - - - - - - -soup-message-private - - - - -#SoupMessageQueue maintains the queue of pending messages in a -#SoupSession. - - - - - - - - - - -Message queue object - - - -soup-message-queue - - - - - - - - - - - - - - - -Server-side authentication structures - - - -soup-server-auth - - - - - - - -@SOUP_AUTH_TYPE_BASIC: -@SOUP_AUTH_TYPE_DIGEST: - - - - - - -@SOUP_ALGORITHM_MD5: -@SOUP_ALGORITHM_MD5_SESS: - - - - - - - - - - - - -@msg: -@headers: -@encoding: -@user_data: - - - - - - -@msg: -@headers: -@header_len: -@encoding: -@content_len: -@user_data: -@Returns: - - - - - - - - - - - - - - - - - - -@type: -@user: -@passwd: - - - - - - -@auth_ctx: -@auth: -@msg: -@data: -@Returns: - - - - - - -@types: -@callback: -@user_data: - - - - - - -@type: -@algorithm: -@integrity: -@realm: -@user: -@nonce: -@nonce_count: -@cnonce: -@digest_uri: -@digest_response: -@request_method: - - - - - - -@ctx: -@digest: - - - - - - -@ctx: -@digest: - - - - - - -@ctx: - - - - - - -@ctx: -@buf: -@len: - - - - - - -@req: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: -@sock: -@get_headers_cb: -@parse_headers_cb: -@user_data: - - - - - - -@msg: -@sock: -@get_headers_cb: -@parse_headers_cb: -@user_data: - - - - - - -@queue: -@msg: - - - - - - -@queue: - - - - - - -@queue: -@iter: -@Returns: - - - - - - -@queue: -@iter: - - - - - - -@Returns: - - - - - - -@queue: -@iter: -@Returns: - - - - - - -@queue: -@iter: -@Returns: - - - - - - -@queue: -@msg: - - - - - - -@msg: - - - - - - -@msg: -@phase: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@auth: -@passwd: -@Returns: - - - - - - -@auth_ctx: -@msg: -@header_name: - - - - - - -@auth: - - - - - - -@auth: -@Returns: - - - - - - -@auth_ctx: -@auth_hdrs: -@msg: -@Returns: - diff --git a/docs/reference/tmpl/soup-address.sgml b/docs/reference/tmpl/soup-address.sgml deleted file mode 100644 index 5cebb3b..0000000 --- a/docs/reference/tmpl/soup-address.sgml +++ /dev/null @@ -1,162 +0,0 @@ - -SoupAddress - - -Higher-level IP address object - - - -#SoupAddress represents the address of a TCP connection endpoint; both -the IP address and the port. (It is somewhat like an object-oriented -version of struct #sockaddr.) - - - -If libsoup was built with IPv6 support, #SoupAddress will allow both -IPv4 and IPv6 addresses. - - - - - - - - - - - - - - - - - - - - - -@addr: -@status: - - - - - - -@SOUP_ADDRESS_FAMILY_IPV4: -@SOUP_ADDRESS_FAMILY_IPV6: - - - - - - - - - - - - - -@name: -@port: -@Returns: - - - - - - - -@sa: -@len: -@Returns: - - - - - - - -@family: -@port: -@Returns: - - - - - - - -@addr: -@status: -@data: - - - - - - - -@addr: -@callback: -@user_data: - - - - - - - -@addr: -@async_context: -@callback: -@user_data: - - - - - - - -@addr: -@Returns: - - - - - - - -@addr: -@Returns: - - - - - - - -@addr: -@len: -@Returns: - - - - - - - -@addr: -@Returns: - - - - - - - -@addr: -@Returns: - - diff --git a/docs/reference/tmpl/soup-auth.sgml b/docs/reference/tmpl/soup-auth.sgml deleted file mode 100644 index e612ffb..0000000 --- a/docs/reference/tmpl/soup-auth.sgml +++ /dev/null @@ -1,117 +0,0 @@ - -SoupAuth - - -HTTP Authentication support - - - -#SoupAuth objects store the authentication data associated with a -given bit of webspace. They are created and maintained automatically -by #SoupSession. - - - - -#SoupSession, #SoupConnectionNTLM - - - - - - - -The abstract base class for handling authentication. Specific HTTP -Authentication mechanisms are implemented by its subclasses. (NTLM -authentication, which works quite differently from normal HTTP -authentication, is handled by #SoupConnectionNTLM.) - - - - - -An object representing Basic HTTP authentication. - - - - - -An object representing Digest HTTP authentication. - - - - - - - - -@vals: -@Returns: - - - - - - - -@auth: -@Returns: - - - - - - - -@auth: -@Returns: - - - - - - - -@auth: -@username: -@password: - - - - - - - -@auth: -@Returns: - - - - - - - -@auth: -@msg: -@Returns: - - - - - - - -@auth: -@source_uri: -@Returns: - - - - - - - -@auth: -@space: - - diff --git a/docs/reference/tmpl/soup-connection-ntlm.sgml b/docs/reference/tmpl/soup-connection-ntlm.sgml deleted file mode 100644 index f72ad2a..0000000 --- a/docs/reference/tmpl/soup-connection-ntlm.sgml +++ /dev/null @@ -1,26 +0,0 @@ - -SoupConnectionNTLM - - -NTLM-authenticated connection - - - -#SoupSession automatically creates #SoupConnectionNTLM rather than -#SoupConnection if you set the %SOUP_SESSION_USE_NTLM flag. - - - - - - - - - - - - - - - - diff --git a/docs/reference/tmpl/soup-connection.sgml b/docs/reference/tmpl/soup-connection.sgml deleted file mode 100644 index 3af9305..0000000 --- a/docs/reference/tmpl/soup-connection.sgml +++ /dev/null @@ -1,254 +0,0 @@ - -SoupConnection - - -a single possibly-persistent HTTP connection - - - -#SoupConnection represents a single connection to an HTTP server -(possibly via a proxy). Connection objects are created and destroyed -automatically by #SoupSession. - - - - - - - - - - - - - - - - - - - - - -@conn: -@msg: -@auth_type: -@auth_realm: -@username: -@password: - - - - - - -@conn: -@status: - - - - - - -@conn: - - - - - - -@conn: -@msg: -@auth_type: -@auth_realm: -@username: -@password: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@propname1: -@Varargs: -@Returns: - - - - - - - -@conn: -@status: -@data: - - - - - - - -@conn: -@callback: -@user_data: - - - - - - - -@conn: -@Returns: - - - - - - - -@conn: - - - - - - - -@conn: -@Returns: - - - - - - - -@conn: -@Returns: - - - - - - - -@conn: -@req: - - - - - - - -@conn: -@msg: -@auth_type: -@auth_realm: -@username: -@password: - - - - - - - -@conn: -@msg: -@auth_type: -@auth_realm: -@username: -@password: - - - - - - - -@conn: - - - - - - - -@conn: - - - - -An alias for the "origin-uri" property. - - - - - - -An alias for the "proxy-uri" property. - - - - - - -An alias for the "ssl-creds" property. - - - - - - -An alias for the "message-filter" property. - - - - - - -An alias for the "async-context" property. - - - - - - -An alias for the "timeout" property. - - - - diff --git a/docs/reference/tmpl/soup-dns.sgml b/docs/reference/tmpl/soup-dns.sgml deleted file mode 100644 index bc13b52..0000000 --- a/docs/reference/tmpl/soup-dns.sgml +++ /dev/null @@ -1,123 +0,0 @@ - -soup-dns - - -Low-level DNS routines - - - - - - - - - - - - - - - - - - - - - - - - - - -@sa: -@Returns: - - - - - - - - - - - - - -@name: -@Returns: - - - - - - - -@sockaddr: -@Returns: - - - - - - - -@lookup: -@Returns: - - - - - - - -@lookup: -@success: -@user_data: - - - - - - - -@lookup: -@async_context: -@callback: -@user_data: - - - - - - - -@lookup: - - - - - - - -@lookup: -@Returns: - - - - - - - -@lookup: -@Returns: - - - - - - - -@lookup: - - diff --git a/docs/reference/tmpl/soup-md5-utils.sgml b/docs/reference/tmpl/soup-md5-utils.sgml deleted file mode 100644 index afb42b2..0000000 --- a/docs/reference/tmpl/soup-md5-utils.sgml +++ /dev/null @@ -1,49 +0,0 @@ - -soup-md5-utils - - -MD5 hash utilities - - - - - - - - - - - - - - - - - - - - - - -@ctx: - - - - - - - -@ctx: -@buf: -@len: - - - - - - - -@ctx: -@digest: - - diff --git a/docs/reference/tmpl/soup-message-filter.sgml b/docs/reference/tmpl/soup-message-filter.sgml deleted file mode 100644 index f1c02af..0000000 --- a/docs/reference/tmpl/soup-message-filter.sgml +++ /dev/null @@ -1,28 +0,0 @@ - -SoupMessageFilter - - -Automatic message processing - - - - - - - - - - - - - - - - - - - -@filter: -@msg: - - diff --git a/docs/reference/tmpl/soup-message-private.sgml b/docs/reference/tmpl/soup-message-private.sgml deleted file mode 100644 index 581531f..0000000 --- a/docs/reference/tmpl/soup-message-private.sgml +++ /dev/null @@ -1,82 +0,0 @@ - -soup-message-private - - - - - - - - - - - - - - - - - - - -@msg: -@phase: - - - - - - - -@req: - - - - - - - -@msg: -@headers: -@encoding: -@user_data: - - - - - - - -@msg: -@headers: -@header_len: -@encoding: -@content_len: -@user_data: -@Returns: - - - - - - - -@msg: -@sock: -@get_headers_cb: -@parse_headers_cb: -@user_data: - - - - - - - -@msg: -@sock: -@get_headers_cb: -@parse_headers_cb: -@user_data: - - diff --git a/docs/reference/tmpl/soup-message-queue.sgml b/docs/reference/tmpl/soup-message-queue.sgml deleted file mode 100644 index 468f0d6..0000000 --- a/docs/reference/tmpl/soup-message-queue.sgml +++ /dev/null @@ -1,102 +0,0 @@ - -soup-message-queue - - -Message queue object - - - -#SoupMessageQueue maintains the queue of pending messages in a -#SoupSession. - - - - - - - - - - - - - - - - - - - - - - - - -@Returns: - - - - - - - -@queue: -@msg: - - - - - - - -@queue: -@iter: -@Returns: - - - - - - - -@queue: -@iter: -@Returns: - - - - - - - -@queue: -@iter: -@Returns: - - - - - - - -@queue: -@iter: - - - - - - - -@queue: - - - - - - - -@queue: -@msg: - - diff --git a/docs/reference/tmpl/soup-message.sgml b/docs/reference/tmpl/soup-message.sgml deleted file mode 100644 index 195a394..0000000 --- a/docs/reference/tmpl/soup-message.sgml +++ /dev/null @@ -1,522 +0,0 @@ - -SoupMessage - - -An HTTP request and response. - - - - - - - - - - - - - - - - - - - -@method: -@status_code: -@reason_phrase: -@request: -@request_headers: -@response: -@response_headers: -@status: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@msg: - - - - - - -@SOUP_MESSAGE_STATUS_IDLE: -@SOUP_MESSAGE_STATUS_QUEUED: -@SOUP_MESSAGE_STATUS_CONNECTING: -@SOUP_MESSAGE_STATUS_RUNNING: -@SOUP_MESSAGE_STATUS_FINISHED: - - - - - - - -@msg: -@Returns: - - - - - - - -@SOUP_TRANSFER_UNKNOWN: -@SOUP_TRANSFER_CHUNKED: -@SOUP_TRANSFER_CONTENT_LENGTH: -@SOUP_TRANSFER_BYTERANGES: -@SOUP_TRANSFER_NONE: -@SOUP_TRANSFER_EOF: - - - - - - -@SOUP_BUFFER_SYSTEM_OWNED: -@SOUP_BUFFER_USER_OWNED: -@SOUP_BUFFER_STATIC: - - - - - - -@owner: -@body: -@length: - - - - - - -@req: -@user_data: - - - - - - - -@method: -@uri_string: -@Returns: - - - - - - - -@method: -@uri: -@Returns: - - - - - - - -@msg: -@content_type: -@req_owner: -@req_body: -@req_length: - - - - - - - -@msg: -@content_type: -@resp_owner: -@resp_body: -@resp_length: - - - - - - - -@hash: -@name: -@value: - - - - - - - -@hash: -@name: -@Returns: - - - - - - - -@hash: -@name: -@Returns: - - - - - - - -@hash: -@func: -@user_data: - - - - - - - -@hash: -@name: - - - - - - - -@hash: - - - - - - - -@SOUP_HTTP_1_0: -@SOUP_HTTP_1_1: - - - - - - -@msg: -@version: - - - - - - - -@msg: -@Returns: - - - - - - - -@msg: -@Returns: - - - - - - - -@msg: -@uri: - - - - - - - -@SOUP_MESSAGE_NO_REDIRECT: -@SOUP_MESSAGE_OVERWRITE_CHUNKS: -@SOUP_MESSAGE_EXPECT_CONTINUE: - - - - - - -@msg: -@flags: - - - - - - - -@msg: -@Returns: - - - - - - - -@msg: -@status_code: - - - - - - - -@msg: -@status_code: -@reason_phrase: - - - - - - - -@msg: -@owner: -@body: -@length: - - - - - - - -@msg: - - - - - - - -@msg: -@Returns: - - - - - - - -@msg: -@Returns: - - - - - - - -@msg: -@content_length: -@Returns: - - - - - - - -@msg: -@content_length: -@Returns: - - - - - - - -@SOUP_HANDLER_POST_REQUEST: -@SOUP_HANDLER_PRE_BODY: -@SOUP_HANDLER_BODY_CHUNK: -@SOUP_HANDLER_POST_BODY: - - - - - - -@msg: -@phase: -@handler_cb: -@user_data: - - - - - - - -@msg: -@header: -@phase: -@handler_cb: -@user_data: - - - - - - - -@msg: -@status_code: -@phase: -@handler_cb: -@user_data: - - - - - - - -@msg: -@status_class: -@phase: -@handler_cb: -@user_data: - - - - - - - -@msg: -@phase: -@handler_cb: -@user_data: - - - - - - - -@req: -@sock: -@is_via_proxy: - - - - - - - -@req: -@sock: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: - - diff --git a/docs/reference/tmpl/soup-misc.sgml b/docs/reference/tmpl/soup-misc.sgml deleted file mode 100644 index 7f949d0..0000000 --- a/docs/reference/tmpl/soup-misc.sgml +++ /dev/null @@ -1,277 +0,0 @@ - -soup-misc - - -Miscellaneous functions - - - - - - - - - - - - - - - - - - - -@text: -@out_len: -@Returns: - - - - - - - -@text: -@len: -@Returns: - - - - - - - -@in: -@len: -@out: -@state: -@save: -@Returns: - - - - - - - -@in: -@len: -@break_lines: -@out: -@state: -@save: -@Returns: - - - - - - - -@in: -@inlen: -@break_lines: -@out: -@state: -@save: -@Returns: - - - - - - - -@async_context: -@function: -@data: -@Returns: - - - - - - - -@async_context: -@chan: -@condition: -@function: -@data: -@Returns: - - - - - - - -@async_context: -@interval: -@function: -@data: -@Returns: - - - - - - - -@instance: -@detailed_signal: -@c_handler: -@data: -@Returns: - - - - - - - -@timestamp: -@Returns: - - - - - - - -@timestamp: -@Returns: - - - - - - - -@when: -@Returns: - - - - - - - -@when: -@tm: - - - - - - - -@tm: -@Returns: - - - - - - - -@tokens: -@t: -@Returns: - - - - - - - -@in: -@Returns: - - - - - - - -@table: - - - - - - - -@header: -@Returns: - - - - - - - -@str: -@len: -@dest: -@req_method: -@req_path: -@ver: -@Returns: - - - - - - - -@str: -@len: -@dest: -@ver: -@status_code: -@reason_phrase: -@Returns: - - - - - - - -@status_line: -@ver: -@status_code: -@reason_phrase: -@Returns: - - - - - - - -@v1: -@v2: -@Returns: - - - - - - - -@key: -@Returns: - - - - - - - -@node: -@Returns: - - diff --git a/docs/reference/tmpl/soup-server-auth.sgml b/docs/reference/tmpl/soup-server-auth.sgml deleted file mode 100644 index cbe22e3..0000000 --- a/docs/reference/tmpl/soup-server-auth.sgml +++ /dev/null @@ -1,127 +0,0 @@ - -soup-server-auth - - -Server-side authentication structures - - - - - - - - - - - - - - - - -@types: -@callback: -@user_data: - - - - - - -@auth_ctx: -@auth: -@msg: -@data: -@Returns: - - - - - - - -@auth_ctx: -@msg: -@header_name: - - - - - - - -@SOUP_AUTH_TYPE_BASIC: -@SOUP_AUTH_TYPE_DIGEST: - - - - - - -@type: -@user: -@passwd: - - - - - - -@SOUP_ALGORITHM_MD5: -@SOUP_ALGORITHM_MD5_SESS: - - - - - - -@type: -@algorithm: -@integrity: -@realm: -@user: -@nonce: -@nonce_count: -@cnonce: -@digest_uri: -@digest_response: -@request_method: - - - - - - -@auth_ctx: -@auth_hdrs: -@msg: -@Returns: - - - - - - - -@auth: - - - - - - - -@auth: -@Returns: - - - - - - - -@auth: -@passwd: -@Returns: - - diff --git a/docs/reference/tmpl/soup-server-message.sgml b/docs/reference/tmpl/soup-server-message.sgml deleted file mode 100644 index d5ed721..0000000 --- a/docs/reference/tmpl/soup-server-message.sgml +++ /dev/null @@ -1,95 +0,0 @@ - -SoupServerMessage - - -Server-side #SoupMessage - - - - - - - - - - - - - - - - - - - - - - - - - -@server: -@Returns: - - - - - - - -@smsg: -@Returns: - - - - - - - -@smsg: -@encoding: - - - - - - - -@smsg: -@Returns: - - - - - - - -@smsg: - - - - - - - -@smsg: -@Returns: - - - - - - - -@smsg: - - - - - - - -@smsg: -@Returns: - - diff --git a/docs/reference/tmpl/soup-server.sgml b/docs/reference/tmpl/soup-server.sgml deleted file mode 100644 index 29babd8..0000000 --- a/docs/reference/tmpl/soup-server.sgml +++ /dev/null @@ -1,249 +0,0 @@ - -SoupServer - - -HTTP server - - - -#SoupServer implements a simple HTTP server. - - - -This API is less stable than the soup client API, and will most likely -change in the next release. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@msg: -@path: -@method_id: -@auth: -@server: -@handler: -@sock: - - - - - - -@context: -@msg: -@user_data: - - - - - - - -@server: -@handler: -@user_data: - - - - - - - -@optname1: -@Varargs: -@Returns: - - - - - - - -@serv: -@Returns: - - - - - - - -@serv: -@Returns: - - - - - - - -@serv: -@Returns: - - - - - - - -@serv: - - - - - - - -@serv: - - - - - - - -@serv: - - - - - - - - - - - - - -@serv: -@path: -@auth_ctx: -@callback: -@unreg: -@data: - - - - - - - -@serv: -@path: - - - - - - - -@serv: -@path: -@Returns: - - - - - - - -@serv: -@Returns: - - - - - - - -@ctx: -@Returns: - - - - - - - -@ctx: -@Returns: - - - - -An alias for the "port" property. - - - - - - -An alias for the "interface" property. - - - - - - -An alias for the "ssl-cert-file" property. - - - - - - -An alias for the "ssl-key-file" property. - - - - - - -An alias for the "async-context" property. - - - - diff --git a/docs/reference/tmpl/soup-session-async.sgml b/docs/reference/tmpl/soup-session-async.sgml deleted file mode 100644 index 5453324..0000000 --- a/docs/reference/tmpl/soup-session-async.sgml +++ /dev/null @@ -1,45 +0,0 @@ - -SoupSessionAsync - - -Soup session for asynchronous (main-loop-based) I/O. - - - -#SoupSessionASync is an implementation of #SoupSession that uses -non-blocking I/O via the glib main loop. It is intended for use in -single-threaded programs. - - - - - - - - - - - - - - - - - - - - - -@Returns: - - - - - - - -@optname1: -@Varargs: -@Returns: - - diff --git a/docs/reference/tmpl/soup-session-sync.sgml b/docs/reference/tmpl/soup-session-sync.sgml deleted file mode 100644 index 63e3093..0000000 --- a/docs/reference/tmpl/soup-session-sync.sgml +++ /dev/null @@ -1,49 +0,0 @@ - -SoupSessionSync - - -Soup session for blocking I/O in multithreaded programs. - - - -#SoupSessionSync is an implementation of #SoupSession that uses -synchronous I/O, intended for use in multi-threaded programs. - - - -Note that you cannot use soup_session_queue_message() with a -synchronous session. You can only use soup_session_send_message(). - - - - - - - - - - - - - - - - - - - - - -@Returns: - - - - - - - -@optname1: -@Varargs: -@Returns: - - diff --git a/docs/reference/tmpl/soup-session.sgml b/docs/reference/tmpl/soup-session.sgml deleted file mode 100644 index 4c5d80d..0000000 --- a/docs/reference/tmpl/soup-session.sgml +++ /dev/null @@ -1,219 +0,0 @@ - -SoupSession - - -Soup session state object - - - - - - - - - - - - - - - - - - - - - - - - - -@session: -@msg: -@auth_type: -@auth_realm: -@username: -@password: - - - - - - -@session: -@msg: -@auth_type: -@auth_realm: -@username: -@password: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@session: -@msg: -@callback: -@user_data: - - - - - - - -@session: -@msg: - - - - - - - -@session: -@msg: -@Returns: - - - - - - - -@session: - - - - - - - -@session: -@filter: - - - - - - - -@session: -@msg: - - - - - - - -@session: -@msg: -@try_pruning: -@is_new: -@Returns: - - - - - - - -@session: -@filter: - - - - - - - -@session: -@Returns: - - - - -An alias for the "proxy-uri" property. - - - - - - -An alias for the "max-conns" property. - - - - - - -An alias for the "max-conns-per-host" property. - - - - - - -An alias for the "use-ntlm" property. - - - - - - -An alias for the "ssl-ca-file" property. - - - - - - -An alias for the "async-context" property. - - - - - - -An alias for the "timeout" property. - - - - diff --git a/docs/reference/tmpl/soup-soap-message.sgml b/docs/reference/tmpl/soup-soap-message.sgml deleted file mode 100644 index 72c83ed..0000000 --- a/docs/reference/tmpl/soup-soap-message.sgml +++ /dev/null @@ -1,333 +0,0 @@ - -SoupSoapMessage - - -A SOAP request - - - - - - - - - - - - - - - - - - - - - - - - - -@method: -@uri_string: -@standalone: -@xml_encoding: -@env_prefix: -@env_uri: -@Returns: - - - - - - - -@method: -@uri: -@standalone: -@xml_encoding: -@env_prefix: -@env_uri: -@Returns: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: -@name: -@prefix: -@ns_uri: - - - - - - - -@msg: - - - - - - - -@msg: -@faultcode: -@faultstring: -@faultfactor: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: -@name: -@must_understand: -@actor_uri: -@prefix: -@ns_uri: - - - - - - - -@msg: - - - - - - - -@msg: -@i: - - - - - - - -@msg: -@d: - - - - - - - -@msg: -@string: -@len: - - - - - - - -@msg: -@timeval: - - - - - - - -@msg: -@string: - - - - - - - -@msg: -@buffer: -@len: - - - - - - - -@msg: -@xsi_type: - - - - - - - -@msg: - - - - - - - -@msg: -@name: -@value: -@prefix: -@ns_uri: - - - - - - - -@msg: -@prefix: -@ns_uri: - - - - - - - -@msg: -@ns_uri: - - - - - - - -@msg: -@enc_style: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: -@ns_uri: -@Returns: - - - - - - - -@msg: -@Returns: - - - - - - - -@msg: -@Returns: - - diff --git a/docs/reference/tmpl/soup-soap-response.sgml b/docs/reference/tmpl/soup-soap-response.sgml deleted file mode 100644 index 0bde3c0..0000000 --- a/docs/reference/tmpl/soup-soap-response.sgml +++ /dev/null @@ -1,200 +0,0 @@ - -SoupSoapResponse - - -A SOAP response - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@Returns: - - - - - - - -@xmlstr: -@Returns: - - - - - - - -@response: -@method_name: - - - - - - - -@param: -@Returns: - - - - - - - -@param: -@name: -@Returns: - - - - - - - -@param: -@Returns: - - - - - - - -@param: -@Returns: - - - - - - - -@param: -@Returns: - - - - - - - -@param: -@name: -@Returns: - - - - - - - -@param: -@prop_name: -@Returns: - - - - - - - -@param: -@Returns: - - - - - - - -@response: -@xmlstr: -@Returns: - - - - - - - -@response: -@Returns: - - - - - - - -@response: -@name: -@Returns: - - - - - - - -@response: -@Returns: - - - - - - - -@response: -@from: -@Returns: - - - - - - - -@response: -@from: -@name: -@Returns: - - - - - - - -@response: -@Returns: - - diff --git a/docs/reference/tmpl/soup-socket.sgml b/docs/reference/tmpl/soup-socket.sgml deleted file mode 100644 index 0a86e70..0000000 --- a/docs/reference/tmpl/soup-socket.sgml +++ /dev/null @@ -1,348 +0,0 @@ - -SoupSocket - - -a network socket - - - - - - - - - - - - - - - - - - - - - - - - - -@sock: -@status: - - - - - - -@sock: - - - - - - -@sock: -@new: - - - - - - -@sock: - - - - - - -@sock: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -@optname1: -@Varargs: -@Returns: - - - - - - - -@sock: -@remote_addr: -@Returns: - - - - - - - -@sock: -@local_addr: -@Returns: - - - - - - - -@sock: -@Returns: - - - - - - - -@sock: -@ssl_host: -@Returns: - - - - - - - -@sock: - - - - - - - -@sock: -@Returns: - - - - - - - -@sock: -@status: -@user_data: - - - - - - - -@listener: -@sock: -@user_data: - - - - - - - -@hostname: -@port: -@ssl_creds: -@callback: -@user_data: -@Returns: - - - - - - - -@hostname: -@port: -@ssl_creds: -@status_ret: -@Returns: - - - - - - - -@local_addr: -@ssl_creds: -@callback: -@user_data: -@Returns: - - - - - - - -@sock: -@Returns: - - - - - - - -@sock: -@Returns: - - - - - - - -@SOUP_SOCKET_OK: -@SOUP_SOCKET_WOULD_BLOCK: -@SOUP_SOCKET_EOF: -@SOUP_SOCKET_ERROR: - - - - - - -@sock: -@buffer: -@len: -@nread: -@Returns: - - - - - - - -@sock: -@buffer: -@len: -@boundary: -@boundary_len: -@nread: -@got_boundary: -@Returns: - - - - - - - -@sock: -@buffer: -@len: -@nwrote: -@Returns: - - - - -An alias for the "non-blocking" property. - - - - - - -An alias for the "nodelay" property. - - - - - - -An alias for the "reuseaddr" property. - - - - - - -An alias for the "cloexec" property. - - - - - - -An alias for the "timeout" property. - - - - - - -An alias for the "is-server" property. - - - - - - -An alias for the "ssl-creds" property. - - - - - - -An alias for the "async-context" property. - - - - diff --git a/docs/reference/tmpl/soup-ssl.sgml b/docs/reference/tmpl/soup-ssl.sgml deleted file mode 100644 index 714c88e..0000000 --- a/docs/reference/tmpl/soup-ssl.sgml +++ /dev/null @@ -1,110 +0,0 @@ - -soup-ssl - - -SSL/TLS handling - - - - - - - - - - - - - - - - - - - - - - - - - -@SOUP_SSL_TYPE_CLIENT: -@SOUP_SSL_TYPE_SERVER: - - - - - - - - - - - - -@ca_file: -@Returns: - - - - - - - -@creds: - - - - - - - -@cert_file: -@key_file: -@Returns: - - - - - - - -@creds: - - - - - - - -@sock: -@type: -@remote_host: -@creds: -@Returns: - - - - - - - - - - - - - - -@Returns: - - - - - - - -@SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ: -@SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE: -@SOUP_SSL_ERROR_CERTIFICATE: - diff --git a/docs/reference/tmpl/soup-status.sgml b/docs/reference/tmpl/soup-status.sgml deleted file mode 100644 index b5d8f17..0000000 --- a/docs/reference/tmpl/soup-status.sgml +++ /dev/null @@ -1,152 +0,0 @@ - -soup-status - - -HTTP and Soup status codes - - - - - - - - - - - - - - - - - - - -@SOUP_STATUS_CLASS_TRANSPORT_ERROR: -@SOUP_STATUS_CLASS_INFORMATIONAL: -@SOUP_STATUS_CLASS_SUCCESS: -@SOUP_STATUS_CLASS_REDIRECT: -@SOUP_STATUS_CLASS_CLIENT_ERROR: -@SOUP_STATUS_CLASS_SERVER_ERROR: - - - - - - -@status: -@Returns: - - - - - - - -@status: -@Returns: - - - - - - - -@status: -@Returns: - - - - - - - -@status: -@Returns: - - - - - - - -@status: -@Returns: - - - - - - - -@SOUP_STATUS_NONE: -@SOUP_STATUS_CANCELLED: -@SOUP_STATUS_CANT_RESOLVE: -@SOUP_STATUS_CANT_RESOLVE_PROXY: -@SOUP_STATUS_CANT_CONNECT: -@SOUP_STATUS_CANT_CONNECT_PROXY: -@SOUP_STATUS_SSL_FAILED: -@SOUP_STATUS_IO_ERROR: -@SOUP_STATUS_MALFORMED: -@SOUP_STATUS_TRY_AGAIN: -@SOUP_STATUS_CONTINUE: -@SOUP_STATUS_SWITCHING_PROTOCOLS: -@SOUP_STATUS_PROCESSING: -@SOUP_STATUS_OK: -@SOUP_STATUS_CREATED: -@SOUP_STATUS_ACCEPTED: -@SOUP_STATUS_NON_AUTHORITATIVE: -@SOUP_STATUS_NO_CONTENT: -@SOUP_STATUS_RESET_CONTENT: -@SOUP_STATUS_PARTIAL_CONTENT: -@SOUP_STATUS_MULTI_STATUS: -@SOUP_STATUS_MULTIPLE_CHOICES: -@SOUP_STATUS_MOVED_PERMANENTLY: -@SOUP_STATUS_FOUND: -@SOUP_STATUS_MOVED_TEMPORARILY: -@SOUP_STATUS_SEE_OTHER: -@SOUP_STATUS_NOT_MODIFIED: -@SOUP_STATUS_USE_PROXY: -@SOUP_STATUS_NOT_APPEARING_IN_THIS_PROTOCOL: -@SOUP_STATUS_TEMPORARY_REDIRECT: -@SOUP_STATUS_BAD_REQUEST: -@SOUP_STATUS_UNAUTHORIZED: -@SOUP_STATUS_PAYMENT_REQUIRED: -@SOUP_STATUS_FORBIDDEN: -@SOUP_STATUS_NOT_FOUND: -@SOUP_STATUS_METHOD_NOT_ALLOWED: -@SOUP_STATUS_NOT_ACCEPTABLE: -@SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED: -@SOUP_STATUS_PROXY_UNAUTHORIZED: -@SOUP_STATUS_REQUEST_TIMEOUT: -@SOUP_STATUS_CONFLICT: -@SOUP_STATUS_GONE: -@SOUP_STATUS_LENGTH_REQUIRED: -@SOUP_STATUS_PRECONDITION_FAILED: -@SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE: -@SOUP_STATUS_REQUEST_URI_TOO_LONG: -@SOUP_STATUS_UNSUPPORTED_MEDIA_TYPE: -@SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE: -@SOUP_STATUS_INVALID_RANGE: -@SOUP_STATUS_EXPECTATION_FAILED: -@SOUP_STATUS_UNPROCESSABLE_ENTITY: -@SOUP_STATUS_LOCKED: -@SOUP_STATUS_FAILED_DEPENDENCY: -@SOUP_STATUS_INTERNAL_SERVER_ERROR: -@SOUP_STATUS_NOT_IMPLEMENTED: -@SOUP_STATUS_BAD_GATEWAY: -@SOUP_STATUS_SERVICE_UNAVAILABLE: -@SOUP_STATUS_GATEWAY_TIMEOUT: -@SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED: -@SOUP_STATUS_INSUFFICIENT_STORAGE: -@SOUP_STATUS_NOT_EXTENDED: - - - - - - -@status_code: -@Returns: - - diff --git a/docs/reference/tmpl/soup-uri.sgml b/docs/reference/tmpl/soup-uri.sgml deleted file mode 100644 index a3814d7..0000000 --- a/docs/reference/tmpl/soup-uri.sgml +++ /dev/null @@ -1,146 +0,0 @@ - -soup-uri - - -URIs - - - - - - - - - - - - - - - - - - - -@protocol: -@user: -@passwd: -@host: -@port: -@path: -@query: -@fragment: -@broken_encoding: - - - - - - - - - - - - - - - - - - - - - - - - - - -@base: -@uri_string: -@Returns: - - - - - - - -@uri_string: -@Returns: - - - - - - - -@uri: -@just_path: -@Returns: - - - - - - - -@uri: -@Returns: - - - - - - - -@uri: -@Returns: - - - - - - - -@uri1: -@uri2: -@Returns: - - - - - - - -@uri: - - - - - - - -@part: -@escape_extra: -@Returns: - - - - - - - -@part: - - - - - - - -@uri: -@Returns: - - diff --git a/docs/reference/tmpl/soup-xmlrpc-message.sgml b/docs/reference/tmpl/soup-xmlrpc-message.sgml deleted file mode 100644 index 93e4ce6..0000000 --- a/docs/reference/tmpl/soup-xmlrpc-message.sgml +++ /dev/null @@ -1,216 +0,0 @@ - -SoupXmlrpcMessage - - -An XML-RPC Message - - - - - - - - - - - - - - - - - - - - - - - - - -@uri_string: -@Returns: - - - - - - - -@uri: -@Returns: - - - - - - - -@message: -@xmlstr: -@Returns: - - - - - - - -@msg: -@method_name: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: -@Param2: - - - - - - - -@msg: -@b: - - - - - - - -@msg: -@str: - - - - - - - -@msg: -@d: - - - - - - - -@msg: -@timeval: - - - - - - - -@msg: -@buf: -@len: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: -@name: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: - - - - - - - -@msg: -@Returns: - - - - - - - -@msg: - - - - - - - -@msg: -@Returns: - - diff --git a/docs/reference/tmpl/soup-xmlrpc-response.sgml b/docs/reference/tmpl/soup-xmlrpc-response.sgml deleted file mode 100644 index 2f678b2..0000000 --- a/docs/reference/tmpl/soup-xmlrpc-response.sgml +++ /dev/null @@ -1,231 +0,0 @@ - -SoupXmlrpcResponse - - -An XML-RPC response - - - - - - - - - - - - - - - - - - - - - - - - - -@Returns: - - - - - - - -@xmlstr: -@Returns: - - - - - - - -@response: -@xmlstr: -@Returns: - - - - - - - -@response: -@Returns: - - - - - - - - - - - - - -@SOUP_XMLRPC_VALUE_TYPE_BAD: -@SOUP_XMLRPC_VALUE_TYPE_INT: -@SOUP_XMLRPC_VALUE_TYPE_BOOLEAN: -@SOUP_XMLRPC_VALUE_TYPE_STRING: -@SOUP_XMLRPC_VALUE_TYPE_DOUBLE: -@SOUP_XMLRPC_VALUE_TYPE_DATETIME: -@SOUP_XMLRPC_VALUE_TYPE_BASE64: -@SOUP_XMLRPC_VALUE_TYPE_STRUCT: -@SOUP_XMLRPC_VALUE_TYPE_ARRAY: - - - - - - -@response: -@Returns: - - - - - - - -@response: -@Returns: - - - - - - - -@value: -@Returns: - - - - - - - -@value: -@i: -@Returns: - - - - - - - -@value: -@b: -@Returns: - - - - - - - -@value: -@b: -@Returns: - - - - - - - -@value: -@str: -@Returns: - - - - - - - -@value: -@timeval: -@Returns: - - - - - - - -@value: -@data: -@Returns: - - - - - - - -@value: -@table: -@Returns: - - - - - - - - - - - - - -@value: -@iter: -@Returns: - - - - - - - -@iter: -@Returns: - - - - - - - -@iter: -@Returns: - - - - - - - -@iter: -@value: -@Returns: - - - - - - - -@value: - - diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index d281079..2979eed 100644 --- a/libsoup/Makefile.am +++ b/libsoup/Makefile.am @@ -16,54 +16,73 @@ INCLUDES = \ MARSHAL_GENERATED = soup-marshal.c soup-marshal.h soup-marshal.h: soup-marshal.list - ( @GLIB_GENMARSHAL@ --prefix=soup_marshal $(srcdir)/soup-marshal.list --header > soup-marshal.tmp \ + ( $(GLIB_GENMARSHAL) --prefix=soup_marshal $(srcdir)/soup-marshal.list --header > soup-marshal.tmp \ && mv soup-marshal.tmp soup-marshal.h ) \ || ( rm -f soup-marshal.tmp && exit 1 ) soup-marshal.c: soup-marshal.h - ( (echo '#include "soup-marshal.h"'; @GLIB_GENMARSHAL@ --prefix=soup_marshal $(srcdir)/soup-marshal.list --body) > soup-marshal.tmp \ + ( (echo '#include "soup-marshal.h"'; $(GLIB_GENMARSHAL) --prefix=soup_marshal $(srcdir)/soup-marshal.list --body) > soup-marshal.tmp \ && mv soup-marshal.tmp soup-marshal.c ) \ || ( rm -f soup-marshal.tmp && exit 1 ) +soup-enum-types.h: $(soup_headers) + ( cd $(srcdir) && $(GLIB_MKENUMS) --template soup-enum-types.h.tmpl \ + $(soup_headers) ) > soup-enum-types.h.tmp \ + && mv soup-enum-types.h.tmp soup-enum-types.h \ + || rm -f soup-enum-type.h.tmp + +soup-enum-types.c: $(libsoupinclude_HEADERS) + ( cd $(srcdir) && $(GLIB_MKENUMS) --template soup-enum-types.c.tmpl \ + $(soup_headers) ) > soup-enum-types.c.tmp \ + && mv soup-enum-types.c.tmp soup-enum-types.c \ + || rm -f soup-enum-type.c.tmp + BUILT_SOURCES = $(MARSHAL_GENERATED) CLEANFILES = $(MARSHAL_GENERATED) -libsoupincludedir = $(includedir)/libsoup-2.2/libsoup +DISTCLEANFILES = soup-enum-types.h soup-enum-types.c -libsoupinclude_HEADERS = \ +libsoupincludedir = $(includedir)/libsoup-$(SOUP_API_VERSION)/libsoup + +soup_headers = \ soup.h \ soup-address.h \ - soup-connection.h \ + soup-auth.h \ + soup-auth-domain.h \ + soup-auth-domain-basic.h \ + soup-auth-domain-digest.h \ soup-date.h \ + soup-form.h \ soup-headers.h \ + soup-logger.h \ soup-message.h \ - soup-message-filter.h \ - soup-message-queue.h \ + soup-message-body.h \ + soup-message-headers.h \ soup-method.h \ soup-misc.h \ soup-portability.h \ - soup-server-auth.h \ - soup-server-message.h \ soup-server.h \ soup-session.h \ soup-session-async.h \ soup-session-sync.h \ - soup-soap-message.h \ - soup-soap-response.h \ soup-socket.h \ soup-status.h \ soup-types.h \ soup-uri.h \ - soup-xmlrpc-message.h \ - soup-xmlrpc-response.h + soup-value-utils.h \ + soup-xmlrpc.h + +libsoupinclude_HEADERS = \ + $(soup_headers) \ + soup-enum-types.h -lib_LTLIBRARIES = libsoup-2.2.la +lib_LTLIBRARIES = libsoup-2.4.la -libsoup_2_2_la_LDFLAGS = \ +libsoup_2_4_la_LDFLAGS = \ -version-info $(SOUP_CURRENT):$(SOUP_REVISION):$(SOUP_AGE) -no-undefined -libsoup_2_2_la_LIBADD = \ +libsoup_2_4_la_LIBADD = \ $(GLIB_LIBS) \ $(XML_LIBS) \ $(LIBGNUTLS_LIBS_STATIC) \ @@ -71,49 +90,56 @@ libsoup_2_2_la_LIBADD = \ $(LIBGCRYPT_LIBS) \ $(LIBWS2_32) -libsoup_2_2_la_SOURCES = \ +libsoup_2_4_la_SOURCES = \ $(MARSHAL_GENERATED) \ soup-address.c \ - soup-auth.h \ soup-auth.c \ soup-auth-basic.h \ soup-auth-basic.c \ soup-auth-digest.h \ soup-auth-digest.c \ + soup-auth-ntlm.h \ + soup-auth-ntlm.c \ + soup-auth-domain.c \ + soup-auth-domain-basic.c \ + soup-auth-domain-digest.c \ + soup-auth-manager.h \ + soup-auth-manager.c \ + soup-connection.h \ soup-connection.c \ soup-connection-ntlm.h \ soup-connection-ntlm.c \ soup-date.c \ + soup-enum-types.c \ soup-dns.h \ soup-dns.c \ + soup-form.c \ soup-gnutls.c \ soup-headers.c \ - soup-md5-utils.h \ - soup-md5-utils.c \ + soup-logger.c \ soup-message.c \ + soup-message-body.c \ soup-message-client-io.c \ - soup-message-filter.c \ - soup-message-handlers.c \ + soup-message-headers.c \ soup-message-io.c \ soup-message-private.h \ + soup-message-queue.h \ soup-message-queue.c \ soup-message-server-io.c \ - soup-method.c \ soup-misc.c \ soup-nossl.c \ + soup-path-map.h \ + soup-path-map.c \ soup-server.c \ - soup-server-auth.c \ - soup-server-message.c \ soup-session.c \ soup-session-async.c \ + soup-session-private.h \ soup-session-sync.c \ - soup-soap-message.c \ - soup-soap-response.c \ soup-socket.c \ soup-ssl.h \ soup-status.c \ soup-uri.c \ - soup-xmlrpc-message.c \ - soup-xmlrpc-response.c + soup-value-utils.c \ + soup-xmlrpc.c EXTRA_DIST= soup-marshal.list diff --git a/libsoup/soup-address.c b/libsoup/soup-address.c index 20f54cb..628f4ba 100644 --- a/libsoup/soup-address.c +++ b/libsoup/soup-address.c @@ -15,7 +15,6 @@ #include #include #include -#include #include "soup-address.h" #include "soup-dns.h" @@ -31,6 +30,18 @@ #define INADDR_NONE -1 #endif +/** + * SECTION:soup-address + * @short_description: DNS support + * + * #SoupAddress represents the address of a TCP connection endpoint; + * both the IP address and the port. (It is somewhat like an + * object-oriented version of struct sockaddr.) + * + * If libsoup was built with IPv6 support, #SoupAddress will allow + * both IPv4 and IPv6 addresses. + **/ + typedef struct { struct sockaddr *sockaddr; @@ -99,13 +110,6 @@ typedef struct { memcpy (SOUP_ADDRESS_GET_DATA (priv), data, length) -enum { - DNS_RESULT, - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL] = { 0 }; - G_DEFINE_TYPE (SoupAddress, soup_address, G_TYPE_OBJECT) static void @@ -146,26 +150,6 @@ soup_address_class_init (SoupAddressClass *address_class) /* virtual method override */ object_class->finalize = finalize; - /* signals */ - - /** - * SoupAddress::dns-result: - * @addr: the #SoupAddress - * @status: the DNS status code - * - * Emitted when an address resolution completes. (This is used - * internally by soup_address_resolve_async() itself.) - **/ - signals[DNS_RESULT] = - g_signal_new ("dns_result", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (SoupAddressClass, dns_result), - NULL, NULL, - soup_marshal_NONE__INT, - G_TYPE_NONE, 1, - G_TYPE_INT); - #ifdef G_OS_WIN32 /* This hopefully is a good place to call WSAStartup */ { @@ -248,6 +232,13 @@ soup_address_new_from_sockaddr (struct sockaddr *sa, int len) **/ /** + * SOUP_ADDRESS_ANY_PORT: + * + * This can be passed to any #SoupAddress method that expects a port, + * to indicate that you don't care what port is used. + **/ + +/** * soup_address_new_any: * @family: the address family * @port: the port number (usually %SOUP_ADDRESS_ANY_PORT) @@ -363,91 +354,124 @@ soup_address_get_port (SoupAddress *addr) static void -update_address (SoupDNSLookup *lookup, gboolean success, gpointer user_data) +update_address (SoupAddress *addr, SoupDNSLookup *lookup) { - SoupAddress *addr = user_data; SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr); - if (success) { - if (!priv->name) - priv->name = soup_dns_lookup_get_hostname (lookup); + if (!priv->name) + priv->name = soup_dns_lookup_get_hostname (lookup); - if (!priv->sockaddr) { - priv->sockaddr = soup_dns_lookup_get_address (lookup); - SOUP_ADDRESS_SET_PORT (priv, htons (priv->port)); - } + if (!priv->sockaddr) { + priv->sockaddr = soup_dns_lookup_get_address (lookup); + SOUP_ADDRESS_SET_PORT (priv, htons (priv->port)); } +} + +typedef struct { + SoupAddress *addr; + SoupAddressCallback callback; + gpointer callback_data; +} SoupAddressResolveAsyncData; + +static void +lookup_resolved (SoupDNSLookup *lookup, guint status, gpointer user_data) +{ + SoupAddressResolveAsyncData *res_data = user_data; + SoupAddress *addr; + SoupAddressCallback callback; + gpointer callback_data; + + addr = res_data->addr; + callback = res_data->callback; + callback_data = res_data->callback_data; + g_free (res_data); - g_signal_emit (addr, signals[DNS_RESULT], 0, success ? SOUP_STATUS_OK : SOUP_STATUS_CANT_RESOLVE); + if (status == SOUP_STATUS_OK) + update_address (addr, lookup); + + if (callback) + callback (addr, status, callback_data); + + g_object_unref (addr); } /** - * soup_address_resolve_async: - * @addr: a #SoupAddress - * @callback: callback to call with the result - * @user_data: data for @callback + * SoupAddressCallback: + * @addr: the #SoupAddress that was resolved + * @status: %SOUP_STATUS_OK, %SOUP_STATUS_CANT_RESOLVE, or + * %SOUP_STATUS_CANCELLED + * @data: the user data that was passed to + * soup_address_resolve_async() * - * Asynchronously resolves the missing half of @addr. (Its IP address - * if it was created with soup_address_new(), or its hostname if it - * was created with soup_address_new_from_sockaddr() or - * soup_address_new_any().) @callback will be called when the - * resolution finishes (successfully or not). + * The callback function passed to soup_address_resolve_async(). **/ -void -soup_address_resolve_async (SoupAddress *addr, - SoupAddressCallback callback, - gpointer user_data) -{ - soup_address_resolve_async_full (addr, NULL, callback, user_data); -} /** - * soup_address_resolve_async_full: + * soup_address_resolve_async: * @addr: a #SoupAddress * @async_context: the #GMainContext to call @callback from + * @cancellable: a #GCancellable object, or %NULL * @callback: callback to call with the result * @user_data: data for @callback * - * Like soup_address_resolve_async(), but calls @callback from - * the given @async_context. + * Asynchronously resolves the missing half of @addr. (Its IP address + * if it was created with soup_address_new(), or its hostname if it + * was created with soup_address_new_from_sockaddr() or + * soup_address_new_any().) + * + * If @cancellable is non-%NULL, it can be used to cancel the + * resolution. @callback will still be invoked in this case, with a + * status of %SOUP_STATUS_CANCELLED. **/ void -soup_address_resolve_async_full (SoupAddress *addr, GMainContext *async_context, - SoupAddressCallback callback, - gpointer user_data) +soup_address_resolve_async (SoupAddress *addr, GMainContext *async_context, + GCancellable *cancellable, + SoupAddressCallback callback, gpointer user_data) { SoupAddressPrivate *priv; + SoupAddressResolveAsyncData *res_data; g_return_if_fail (SOUP_IS_ADDRESS (addr)); priv = SOUP_ADDRESS_GET_PRIVATE (addr); - if (callback) { - soup_signal_connect_once (addr, "dns_result", - G_CALLBACK (callback), user_data); - } + res_data = g_new (SoupAddressResolveAsyncData, 1); + res_data->addr = addr; + res_data->callback = callback; + res_data->callback_data = user_data; - soup_dns_lookup_resolve_async (priv->lookup, async_context, update_address, addr); + g_object_ref (addr); + soup_dns_lookup_resolve_async (priv->lookup, async_context, cancellable, + lookup_resolved, res_data); } /** * soup_address_resolve_sync: * @addr: a #SoupAddress + * @cancellable: a #GCancellable object, or %NULL * * Synchronously resolves the missing half of @addr, as with * soup_address_resolve_async(). * - * Return value: %SOUP_STATUS_OK or %SOUP_STATUS_CANT_RESOLVE + * If @cancellable is non-%NULL, it can be used to cancel the + * resolution. soup_address_resolve_sync() will then return a status + * of %SOUP_STATUS_CANCELLED. + * + * Return value: %SOUP_STATUS_OK, %SOUP_STATUS_CANT_RESOLVE, or + * %SOUP_STATUS_CANCELLED. **/ guint -soup_address_resolve_sync (SoupAddress *addr) +soup_address_resolve_sync (SoupAddress *addr, GCancellable *cancellable) { SoupAddressPrivate *priv; - gboolean success; + guint status; g_return_val_if_fail (SOUP_IS_ADDRESS (addr), SOUP_STATUS_MALFORMED); priv = SOUP_ADDRESS_GET_PRIVATE (addr); - success = soup_dns_lookup_resolve (priv->lookup); - update_address (priv->lookup, success, addr); - return success ? SOUP_STATUS_OK : SOUP_STATUS_CANT_RESOLVE; + g_object_ref (addr); + status = soup_dns_lookup_resolve (priv->lookup, cancellable); + if (status == SOUP_STATUS_OK) + update_address (addr, priv->lookup); + g_object_unref (addr); + return status; } diff --git a/libsoup/soup-address.h b/libsoup/soup-address.h index 68357b0..92e71fe 100644 --- a/libsoup/soup-address.h +++ b/libsoup/soup-address.h @@ -8,6 +8,8 @@ #include +#include + #include #include @@ -28,42 +30,29 @@ struct SoupAddress { typedef struct { GObjectClass parent_class; - /* signals */ - void (*dns_result) (SoupAddress *addr, guint status); + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); } SoupAddressClass; -/* This is messy, but gtk-doc doesn't understand if the #if occurs - * inside the typedef. - */ -#ifdef AF_INET6 +/* gtk-doc gets confused if there's an #ifdef inside the typedef */ +#ifndef AF_INET6 +#define AF_INET6 -1 +#endif + typedef enum { SOUP_ADDRESS_FAMILY_IPV4 = AF_INET, SOUP_ADDRESS_FAMILY_IPV6 = AF_INET6 } SoupAddressFamily; -#else -typedef enum { - SOUP_ADDRESS_FAMILY_IPV4 = AF_INET, - SOUP_ADDRESS_FAMILY_IPV6 = -1 -} SoupAddressFamily; + +#if AF_INET6 == -1 +#undef AF_INET6 #endif -/** - * SOUP_ADDRESS_ANY_PORT: - * - * This can be passed to any #SoupAddress method that expects a port, - * to indicate that you don't care what port is used. - **/ #define SOUP_ADDRESS_ANY_PORT 0 -/** - * SoupAddressCallback: - * @addr: the #SoupAddress that was resolved - * @status: %SOUP_STATUS_OK or %SOUP_STATUS_CANT_RESOLVE - * @data: the user data that was passed to - * soup_address_resolve_async() - * - * The callback function passed to soup_address_resolve_async(). - **/ typedef void (*SoupAddressCallback) (SoupAddress *addr, guint status, gpointer data); @@ -78,13 +67,12 @@ SoupAddress *soup_address_new_any (SoupAddressFamily family, guint port); void soup_address_resolve_async (SoupAddress *addr, - SoupAddressCallback callback, - gpointer user_data); -void soup_address_resolve_async_full (SoupAddress *addr, GMainContext *async_context, + GCancellable *cancellable, SoupAddressCallback callback, gpointer user_data); -guint soup_address_resolve_sync (SoupAddress *addr); +guint soup_address_resolve_sync (SoupAddress *addr, + GCancellable *cancellable); const char *soup_address_get_name (SoupAddress *addr); const char *soup_address_get_physical (SoupAddress *addr); diff --git a/libsoup/soup-auth-basic.c b/libsoup/soup-auth-basic.c index 003bda6..35f4708 100644 --- a/libsoup/soup-auth-basic.c +++ b/libsoup/soup-auth-basic.c @@ -17,8 +17,8 @@ #include "soup-misc.h" #include "soup-uri.h" -static void construct (SoupAuth *auth, GHashTable *auth_params); -static GSList *get_protection_space (SoupAuth *auth, const SoupUri *source_uri); +static gboolean update (SoupAuth *auth, SoupMessage *msg, GHashTable *auth_params); +static GSList *get_protection_space (SoupAuth *auth, SoupURI *source_uri); static void authenticate (SoupAuth *auth, const char *username, const char *password); static gboolean is_authenticated (SoupAuth *auth); static char *get_authorization (SoupAuth *auth, SoupMessage *msg); @@ -54,8 +54,9 @@ soup_auth_basic_class_init (SoupAuthBasicClass *auth_basic_class) g_type_class_add_private (auth_basic_class, sizeof (SoupAuthBasicPrivate)); auth_class->scheme_name = "Basic"; + auth_class->strength = 1; - auth_class->construct = construct; + auth_class->update = update; auth_class->get_protection_space = get_protection_space; auth_class->authenticate = authenticate; auth_class->is_authenticated = is_authenticated; @@ -65,14 +66,26 @@ soup_auth_basic_class_init (SoupAuthBasicClass *auth_basic_class) } -static void -construct (SoupAuth *auth, GHashTable *auth_params) +static gboolean +update (SoupAuth *auth, SoupMessage *msg, GHashTable *auth_params) { - ; + SoupAuthBasicPrivate *priv = SOUP_AUTH_BASIC_GET_PRIVATE (auth); + + /* If we're updating a pre-existing auth, the + * username/password must be bad now, so forget it. + * Other than that, there's nothing to do here. + */ + if (priv->token) { + memset (priv->token, 0, strlen (priv->token)); + g_free (priv->token); + priv->token = NULL; + } + + return TRUE; } static GSList * -get_protection_space (SoupAuth *auth, const SoupUri *source_uri) +get_protection_space (SoupAuth *auth, SoupURI *source_uri) { char *space, *p; diff --git a/libsoup/soup-auth-digest.c b/libsoup/soup-auth-digest.c index 3841e98..8ad5f85 100644 --- a/libsoup/soup-auth-digest.c +++ b/libsoup/soup-auth-digest.c @@ -16,45 +16,38 @@ #include "soup-auth-digest.h" #include "soup-headers.h" -#include "soup-md5-utils.h" #include "soup-message.h" +#include "soup-message-private.h" #include "soup-misc.h" #include "soup-uri.h" -static void construct (SoupAuth *auth, GHashTable *auth_params); -static GSList *get_protection_space (SoupAuth *auth, const SoupUri *source_uri); +static gboolean update (SoupAuth *auth, SoupMessage *msg, GHashTable *auth_params); +static GSList *get_protection_space (SoupAuth *auth, SoupURI *source_uri); static void authenticate (SoupAuth *auth, const char *username, const char *password); static gboolean is_authenticated (SoupAuth *auth); static char *get_authorization (SoupAuth *auth, SoupMessage *msg); -typedef enum { - QOP_NONE = 0, - QOP_AUTH = 1 << 0, - QOP_AUTH_INT = 1 << 1 -} QOPType; - -typedef enum { - ALGORITHM_MD5 = 1 << 0, - ALGORITHM_MD5_SESS = 1 << 1 -} AlgorithmType; - typedef struct { - char *user; - char hex_a1[33]; + char *user; + char hex_urp[33]; + char hex_a1[33]; /* These are provided by the server */ - char *nonce; - QOPType qop_options; - AlgorithmType algorithm; - char *domain; + char *nonce; + char *opaque; + SoupAuthDigestQop qop_options; + SoupAuthDigestAlgorithm algorithm; + char *domain; /* These are generated by the client */ - char *cnonce; - int nc; - QOPType qop; + char *cnonce; + int nc; + SoupAuthDigestQop qop; } SoupAuthDigestPrivate; #define SOUP_AUTH_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DIGEST, SoupAuthDigestPrivate)) +static void recompute_hex_a1 (SoupAuthDigestPrivate *priv); + G_DEFINE_TYPE (SoupAuthDigest, soup_auth_digest, SOUP_TYPE_AUTH) static void @@ -76,6 +69,9 @@ finalize (GObject *object) if (priv->cnonce) g_free (priv->cnonce); + memset (priv->hex_urp, 0, sizeof (priv->hex_urp)); + memset (priv->hex_a1, 0, sizeof (priv->hex_a1)); + G_OBJECT_CLASS (soup_auth_digest_parent_class)->finalize (object); } @@ -88,9 +84,10 @@ soup_auth_digest_class_init (SoupAuthDigestClass *auth_digest_class) g_type_class_add_private (auth_digest_class, sizeof (SoupAuthDigestPrivate)); auth_class->scheme_name = "Digest"; + auth_class->strength = 5; auth_class->get_protection_space = get_protection_space; - auth_class->construct = construct; + auth_class->update = update; auth_class->authenticate = authenticate; auth_class->is_authenticated = is_authenticated; auth_class->get_authorization = get_authorization; @@ -98,91 +95,118 @@ soup_auth_digest_class_init (SoupAuthDigestClass *auth_digest_class) object_class->finalize = finalize; } -typedef struct { - const char *name; - guint type; -} DataType; - -static DataType qop_types[] = { - { "auth", QOP_AUTH }, - { "auth-int", QOP_AUTH_INT }, - { NULL, 0 } -}; - -static DataType algorithm_types[] = { - { "MD5", ALGORITHM_MD5 }, - { "MD5-sess", ALGORITHM_MD5_SESS }, - { NULL, 0 } -}; - -static guint -decode_data_type (DataType *dtype, const char *name) +SoupAuthDigestAlgorithm +soup_auth_digest_parse_algorithm (const char *algorithm) { - int i; - - if (!name) - return 0; - - for (i = 0; dtype[i].name; i++) { - if (!g_ascii_strcasecmp (dtype[i].name, name)) - return dtype[i].type; - } - - return 0; + if (!algorithm) + return SOUP_AUTH_DIGEST_ALGORITHM_NONE; + else if (!g_ascii_strcasecmp (algorithm, "MD5")) + return SOUP_AUTH_DIGEST_ALGORITHM_MD5; + else if (!g_ascii_strcasecmp (algorithm, "MD5-sess")) + return SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS; + else + return -1; } -static inline guint -decode_qop (const char *name) +char * +soup_auth_digest_get_algorithm (SoupAuthDigestAlgorithm algorithm) { - return decode_data_type (qop_types, name); + if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5) + return g_strdup ("MD5"); + else if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS) + return g_strdup ("MD5-sess"); + else + return NULL; } -static inline guint -decode_algorithm (const char *name) +SoupAuthDigestQop +soup_auth_digest_parse_qop (const char *qop) { - return decode_data_type (algorithm_types, name); + GSList *qop_values, *iter; + SoupAuthDigestQop out = 0; + + if (qop) { + qop_values = soup_header_parse_list (qop); + for (iter = qop_values; iter; iter = iter->next) { + if (!g_ascii_strcasecmp (iter->data, "auth")) + out |= SOUP_AUTH_DIGEST_QOP_AUTH; + else if (!g_ascii_strcasecmp (iter->data, "auth-int")) + out |= SOUP_AUTH_DIGEST_QOP_AUTH_INT; + else + out = -1; + } + soup_header_free_list (qop_values); + } + + return out; } -static void -construct (SoupAuth *auth, GHashTable *auth_params) +char * +soup_auth_digest_get_qop (SoupAuthDigestQop qop) { - SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth); - char *tmp, *ptr; + GString *out; + + out = g_string_new (NULL); + if (qop & SOUP_AUTH_DIGEST_QOP_AUTH) + g_string_append (out, "auth"); + if (qop & SOUP_AUTH_DIGEST_QOP_AUTH_INT) { + if (qop & SOUP_AUTH_DIGEST_QOP_AUTH) + g_string_append (out, ","); + g_string_append (out, "auth-int"); + } - priv->nc = 1; - /* We're just going to do qop=auth for now */ - priv->qop = QOP_AUTH; + return g_string_free (out, FALSE); +} - priv->domain = soup_header_param_copy_token (auth_params, "domain"); - priv->nonce = soup_header_param_copy_token (auth_params, "nonce"); +static gboolean +update (SoupAuth *auth, SoupMessage *msg, GHashTable *auth_params) +{ + SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth); + const char *stale; + guint qop_options; + gboolean ok = TRUE; - tmp = soup_header_param_copy_token (auth_params, "qop"); - ptr = tmp; + g_free (priv->domain); + g_free (priv->nonce); + g_free (priv->opaque); - while (ptr && *ptr) { - char *token; + priv->nc = 1; - token = soup_header_param_decode_token ((char **)&ptr); - if (token) - priv->qop_options |= decode_qop (token); - g_free (token); + priv->domain = g_strdup (g_hash_table_lookup (auth_params, "domain")); + priv->nonce = g_strdup (g_hash_table_lookup (auth_params, "nonce")); + priv->opaque = g_strdup (g_hash_table_lookup (auth_params, "opaque")); - if (*ptr == ',') - ptr++; - } - g_free (tmp); + qop_options = soup_auth_digest_parse_qop (g_hash_table_lookup (auth_params, "qop")); + /* We're just going to do qop=auth for now */ + if (qop_options == -1 || !(qop_options & SOUP_AUTH_DIGEST_QOP_AUTH)) + ok = FALSE; + priv->qop = SOUP_AUTH_DIGEST_QOP_AUTH; + + priv->algorithm = soup_auth_digest_parse_algorithm (g_hash_table_lookup (auth_params, "algorithm")); + if (priv->algorithm == -1) + ok = FALSE; + + stale = g_hash_table_lookup (auth_params, "stale"); + if (stale && !g_ascii_strcasecmp (stale, "TRUE") && *priv->hex_urp) + recompute_hex_a1 (priv); + else { + g_free (priv->user); + priv->user = NULL; + g_free (priv->cnonce); + priv->cnonce = NULL; + memset (priv->hex_urp, 0, sizeof (priv->hex_urp)); + memset (priv->hex_a1, 0, sizeof (priv->hex_a1)); + } - tmp = soup_header_param_copy_token (auth_params, "algorithm"); - priv->algorithm = decode_algorithm (tmp); - g_free (tmp); + return ok; } static GSList * -get_protection_space (SoupAuth *auth, const SoupUri *source_uri) +get_protection_space (SoupAuth *auth, SoupURI *source_uri) { SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth); GSList *space = NULL; - SoupUri *uri; + SoupURI *uri; char **dvec, *d, *dir, *slash; int dix; @@ -200,7 +224,7 @@ get_protection_space (SoupAuth *auth, const SoupUri *source_uri) dir = g_strdup (d); else { uri = soup_uri_new (d); - if (uri && uri->protocol == source_uri->protocol && + if (uri && uri->scheme == source_uri->scheme && uri->port == source_uri->port && !strcmp (uri->host, source_uri->host)) dir = g_strdup (uri->path); @@ -223,16 +247,72 @@ get_protection_space (SoupAuth *auth, const SoupUri *source_uri) return space; } +void +soup_auth_digest_compute_hex_urp (const char *username, + const char *realm, + const char *password, + char hex_urp[33]) +{ + GChecksum *checksum; + + checksum = g_checksum_new (G_CHECKSUM_MD5); + g_checksum_update (checksum, username, strlen (username)); + g_checksum_update (checksum, ":", 1); + g_checksum_update (checksum, realm, strlen (realm)); + g_checksum_update (checksum, ":", 1); + g_checksum_update (checksum, password, strlen (password)); + strncpy (hex_urp, g_checksum_get_string (checksum), 33); + g_checksum_free (checksum); +} + +void +soup_auth_digest_compute_hex_a1 (const char *hex_urp, + SoupAuthDigestAlgorithm algorithm, + const char *nonce, + const char *cnonce, + char hex_a1[33]) +{ + if (algorithm == SOUP_AUTH_DIGEST_ALGORITHM_MD5) { + /* In MD5, A1 is just user:realm:password, so hex_A1 + * is just hex_urp. + */ + /* You'd think you could say "sizeof (hex_a1)" here, + * but you'd be wrong. + */ + memcpy (hex_a1, hex_urp, 33); + } else { + GChecksum *checksum; + + /* In MD5-sess, A1 is hex_urp:nonce:cnonce */ + + checksum = g_checksum_new (G_CHECKSUM_MD5); + g_checksum_update (checksum, hex_urp, strlen (hex_urp)); + g_checksum_update (checksum, ":", 1); + g_checksum_update (checksum, nonce, strlen (nonce)); + g_checksum_update (checksum, ":", 1); + g_checksum_update (checksum, cnonce, strlen (cnonce)); + strncpy (hex_a1, g_checksum_get_string (checksum), 33); + g_checksum_free (checksum); + } +} + +static void +recompute_hex_a1 (SoupAuthDigestPrivate *priv) +{ + soup_auth_digest_compute_hex_a1 (priv->hex_urp, + priv->algorithm, + priv->nonce, + priv->cnonce, + priv->hex_a1); +} + static void authenticate (SoupAuth *auth, const char *username, const char *password) { SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth); - SoupMD5Context ctx; - guchar d[16]; char *bgen; - g_return_if_fail (username != NULL); - + /* Create client nonce */ bgen = g_strdup_printf ("%p:%lu:%lu", auth, (unsigned long) getpid (), @@ -242,32 +322,13 @@ authenticate (SoupAuth *auth, const char *username, const char *password) priv->user = g_strdup (username); - /* compute A1 */ - soup_md5_init (&ctx); - - soup_md5_update (&ctx, username, strlen (username)); - - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, auth->realm, strlen (auth->realm)); - soup_md5_update (&ctx, ":", 1); - if (password) - soup_md5_update (&ctx, password, strlen (password)); - - if (priv->algorithm == ALGORITHM_MD5_SESS) { - soup_md5_final (&ctx, d); - - soup_md5_init (&ctx); - soup_md5_update (&ctx, d, 16); - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, priv->nonce, - strlen (priv->nonce)); - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, priv->cnonce, - strlen (priv->cnonce)); - } + /* compute "URP" (user:realm:password) */ + soup_auth_digest_compute_hex_urp (username, auth->realm, + password ? password : "", + priv->hex_urp); - /* hexify A1 */ - soup_md5_final_hex (&ctx, priv->hex_a1); + /* And compute A1 from that */ + recompute_hex_a1 (priv); } static gboolean @@ -276,125 +337,134 @@ is_authenticated (SoupAuth *auth) return SOUP_AUTH_DIGEST_GET_PRIVATE (auth)->cnonce != NULL; } -static char * -compute_response (SoupAuthDigestPrivate *priv, SoupMessage *msg) +void +soup_auth_digest_compute_response (const char *method, + const char *uri, + const char *hex_a1, + SoupAuthDigestQop qop, + const char *nonce, + const char *cnonce, + int nc, + char response[33]) { - char hex_a2[33], o[33]; - SoupMD5Context md5; - char *url; - const SoupUri *uri; - - uri = soup_message_get_uri (msg); - g_return_val_if_fail (uri != NULL, NULL); - url = soup_uri_to_string (uri, TRUE); + char hex_a2[33]; + GChecksum *checksum; /* compute A2 */ - soup_md5_init (&md5); - soup_md5_update (&md5, msg->method, strlen (msg->method)); - soup_md5_update (&md5, ":", 1); - soup_md5_update (&md5, url, strlen (url)); + checksum = g_checksum_new (G_CHECKSUM_MD5); + g_checksum_update (checksum, method, strlen (method)); + g_checksum_update (checksum, ":", 1); + g_checksum_update (checksum, uri, strlen (uri)); + strncpy (hex_a2, g_checksum_get_string (checksum), 33); + g_checksum_free (checksum); - g_free (url); - - if (priv->qop == QOP_AUTH_INT) { - /* FIXME: Actually implement. Ugh. */ - soup_md5_update (&md5, ":", 1); - soup_md5_update (&md5, "00000000000000000000000000000000", 32); + /* compute KD */ + checksum = g_checksum_new (G_CHECKSUM_MD5); + g_checksum_update (checksum, hex_a1, strlen (hex_a1)); + g_checksum_update (checksum, ":", 1); + g_checksum_update (checksum, nonce, strlen (nonce)); + g_checksum_update (checksum, ":", 1); + + if (qop) { + char tmp[9]; + + snprintf (tmp, 9, "%.8x", nc); + g_checksum_update (checksum, tmp, strlen (tmp)); + g_checksum_update (checksum, ":", 1); + g_checksum_update (checksum, cnonce, strlen (cnonce)); + g_checksum_update (checksum, ":", 1); + + if (qop != SOUP_AUTH_DIGEST_QOP_AUTH) + g_assert_not_reached (); + g_checksum_update (checksum, "auth", strlen ("auth")); + g_checksum_update (checksum, ":", 1); } - /* now hexify A2 */ - soup_md5_final_hex (&md5, hex_a2); + g_checksum_update (checksum, hex_a2, 32); + strncpy (response, g_checksum_get_string (checksum), 33); + g_checksum_free (checksum); +} - /* compute KD */ - soup_md5_init (&md5); - soup_md5_update (&md5, priv->hex_a1, 32); - soup_md5_update (&md5, ":", 1); - soup_md5_update (&md5, priv->nonce, - strlen (priv->nonce)); - soup_md5_update (&md5, ":", 1); +static void +authentication_info_cb (SoupMessage *msg, gpointer data) +{ + SoupAuth *auth = data; + SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth); + const char *header; + GHashTable *auth_params; + char *nextnonce; - if (priv->qop) { - char *tmp; - - tmp = g_strdup_printf ("%.8x", priv->nc); - - soup_md5_update (&md5, tmp, strlen (tmp)); - g_free (tmp); - soup_md5_update (&md5, ":", 1); - soup_md5_update (&md5, priv->cnonce, - strlen (priv->cnonce)); - soup_md5_update (&md5, ":", 1); - - if (priv->qop == QOP_AUTH) - tmp = "auth"; - else if (priv->qop == QOP_AUTH_INT) - tmp = "auth-int"; - else - g_assert_not_reached (); + if (auth != soup_message_get_auth (msg)) + return; - soup_md5_update (&md5, tmp, strlen (tmp)); - soup_md5_update (&md5, ":", 1); - } + header = soup_message_headers_get (msg->response_headers, + soup_auth_is_for_proxy (auth) ? + "Proxy-Authentication-Info" : + "Authentication-Info"); + g_return_if_fail (header != NULL); - soup_md5_update (&md5, hex_a2, 32); - soup_md5_final_hex (&md5, o); + auth_params = soup_header_parse_param_list (header); + if (!auth_params) + return; - return g_strdup (o); + nextnonce = g_strdup (g_hash_table_lookup (auth_params, "nextnonce")); + if (nextnonce) { + g_free (priv->nonce); + priv->nonce = nextnonce; + } + + soup_header_free_param_list (auth_params); } static char * get_authorization (SoupAuth *auth, SoupMessage *msg) { SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth); - char *response; - char *qop = NULL; - char *nc; + char response[33], *token; char *url; - char *out; - const SoupUri *uri; + GString *out; + SoupURI *uri; uri = soup_message_get_uri (msg); g_return_val_if_fail (uri != NULL, NULL); url = soup_uri_to_string (uri, TRUE); - response = compute_response (priv, msg); - - if (priv->qop == QOP_AUTH) - qop = "auth"; - else if (priv->qop == QOP_AUTH_INT) - qop = "auth-int"; - else - g_assert_not_reached (); - - nc = g_strdup_printf ("%.8x", priv->nc); + soup_auth_digest_compute_response (msg->method, url, priv->hex_a1, + priv->qop, priv->nonce, + priv->cnonce, priv->nc, + response); - out = g_strdup_printf ( - "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", %s%s%s " - "%s%s%s %s%s%s uri=\"%s\", response=\"%s\"", - priv->user, - auth->realm, - priv->nonce, + out = g_string_new ("Digest "); - priv->qop ? "cnonce=\"" : "", - priv->qop ? priv->cnonce : "", - priv->qop ? "\"," : "", + /* FIXME: doesn't deal with quotes in the %s strings */ + g_string_append_printf (out, "username=\"%s\", realm=\"%s\", " + "nonce=\"%s\", uri=\"%s\", response=\"%s\"", + priv->user, auth->realm, priv->nonce, + url, response); - priv->qop ? "nc=" : "", - priv->qop ? nc : "", - priv->qop ? "," : "", + if (priv->opaque) + g_string_append_printf (out, ", opaque=\"%s\"", priv->opaque); - priv->qop ? "qop=" : "", - priv->qop ? qop : "", - priv->qop ? "," : "", + if (priv->qop) { + char *qop = soup_auth_digest_get_qop (priv->qop); - url, - response); + g_string_append_printf (out, ", cnonce=\"%s\", nc=\"%.8x\", qop=\"%s\"", + priv->cnonce, priv->nc, qop); + g_free (qop); + } - g_free (response); g_free (url); - g_free (nc); priv->nc++; - return out; + token = g_string_free (out, FALSE); + + soup_message_add_header_handler (msg, + "got_headers", + soup_auth_is_for_proxy (auth) ? + "Proxy-Authentication-Info" : + "Authentication-Info", + G_CALLBACK (authentication_info_cb), + auth); + return token; } diff --git a/libsoup/soup-auth-digest.h b/libsoup/soup-auth-digest.h index b8bf8fa..453c40c 100644 --- a/libsoup/soup-auth-digest.h +++ b/libsoup/soup-auth-digest.h @@ -27,4 +27,41 @@ typedef struct { GType soup_auth_digest_get_type (void); +/* Utility routines (also used by SoupAuthDomainDigest) */ + +typedef enum { + SOUP_AUTH_DIGEST_ALGORITHM_NONE, + SOUP_AUTH_DIGEST_ALGORITHM_MD5, + SOUP_AUTH_DIGEST_ALGORITHM_MD5_SESS +} SoupAuthDigestAlgorithm; + +typedef enum { + SOUP_AUTH_DIGEST_QOP_AUTH = 1 << 0, + SOUP_AUTH_DIGEST_QOP_AUTH_INT = 1 << 1 +} SoupAuthDigestQop; + +SoupAuthDigestAlgorithm soup_auth_digest_parse_algorithm (const char *algorithm); +char *soup_auth_digest_get_algorithm (SoupAuthDigestAlgorithm algorithm); + +SoupAuthDigestQop soup_auth_digest_parse_qop (const char *qop); +char *soup_auth_digest_get_qop (SoupAuthDigestQop qop); + +void soup_auth_digest_compute_hex_urp (const char *username, + const char *realm, + const char *password, + char hex_urp[33]); +void soup_auth_digest_compute_hex_a1 (const char *hex_urp, + SoupAuthDigestAlgorithm algorithm, + const char *nonce, + const char *cnonce, + char hex_a1[33]); +void soup_auth_digest_compute_response (const char *method, + const char *uri, + const char *hex_a1, + SoupAuthDigestQop qop, + const char *nonce, + const char *cnonce, + int nc, + char response[33]); + #endif /*SOUP_AUTH_DIGEST_H*/ diff --git a/libsoup/soup-auth-domain-basic.c b/libsoup/soup-auth-domain-basic.c new file mode 100644 index 0000000..c3c64e2 --- /dev/null +++ b/libsoup/soup-auth-domain-basic.c @@ -0,0 +1,294 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-auth-domain-basic.c: HTTP Basic Authentication (server-side) + * + * Copyright (C) 2007 Novell, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "soup-auth-domain-basic.h" +#include "soup-marshal.h" +#include "soup-message.h" + +/** + * SECTION:soup-auth-domain-basic + * @short_description: Server-side "Basic" authentication + * + * #SoupAuthDomainBasic handles the server side of HTTP "Basic" (ie, + * cleartext password) authentication. + **/ + +enum { + PROP_0, + + PROP_AUTH_CALLBACK, + PROP_AUTH_DATA, + + LAST_PROP +}; + +typedef struct { + SoupAuthDomainBasicAuthCallback auth_callback; + gpointer auth_data; + GDestroyNotify auth_dnotify; +} SoupAuthDomainBasicPrivate; + +#define SOUP_AUTH_DOMAIN_BASIC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DOMAIN_BASIC, SoupAuthDomainBasicPrivate)) + +G_DEFINE_TYPE (SoupAuthDomainBasic, soup_auth_domain_basic, SOUP_TYPE_AUTH_DOMAIN) + +static char *accepts (SoupAuthDomain *domain, + SoupMessage *msg, + const char *header); +static char *challenge (SoupAuthDomain *domain, + SoupMessage *msg); + +static void set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static void get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); + +static void +soup_auth_domain_basic_init (SoupAuthDomainBasic *basic) +{ +} + +static void +finalize (GObject *object) +{ + SoupAuthDomainBasicPrivate *priv = + SOUP_AUTH_DOMAIN_BASIC_GET_PRIVATE (object); + + if (priv->auth_dnotify) + priv->auth_dnotify (priv->auth_data); + + G_OBJECT_CLASS (soup_auth_domain_basic_parent_class)->finalize (object); +} + +static void +soup_auth_domain_basic_class_init (SoupAuthDomainBasicClass *basic_class) +{ + SoupAuthDomainClass *auth_domain_class = + SOUP_AUTH_DOMAIN_CLASS (basic_class); + GObjectClass *object_class = G_OBJECT_CLASS (basic_class); + + g_type_class_add_private (basic_class, sizeof (SoupAuthDomainBasicPrivate)); + + auth_domain_class->accepts = accepts; + auth_domain_class->challenge = challenge; + + object_class->finalize = finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; + + g_object_class_install_property ( + object_class, PROP_AUTH_CALLBACK, + g_param_spec_pointer (SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, + "Authentication callback", + "Password-checking callback", + G_PARAM_READWRITE)); + g_object_class_install_property ( + object_class, PROP_AUTH_DATA, + g_param_spec_pointer (SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA, + "Authentication callback data", + "Data to pass to authentication callback", + G_PARAM_READWRITE)); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + SoupAuthDomainBasicPrivate *priv = + SOUP_AUTH_DOMAIN_BASIC_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_AUTH_CALLBACK: + priv->auth_callback = g_value_get_pointer (value); + break; + case PROP_AUTH_DATA: + if (priv->auth_dnotify) { + priv->auth_dnotify (priv->auth_data); + priv->auth_dnotify = NULL; + } + priv->auth_data = g_value_get_pointer (value); + break; + default: + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + SoupAuthDomainBasicPrivate *priv = + SOUP_AUTH_DOMAIN_BASIC_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_AUTH_CALLBACK: + g_value_set_pointer (value, priv->auth_callback); + break; + case PROP_AUTH_DATA: + g_value_set_pointer (value, priv->auth_data); + break; + default: + break; + } +} + +/** + * soup_auth_domain_basic_new: + * @optname1: name of first option, or %NULL + * @...: option name/value pairs + * + * Creates a #SoupAuthDomainBasic. You must set the + * %SOUP_AUTH_DOMAIN_REALM parameter, to indicate the realm name to be + * returned with the authentication challenge to the client. Other + * parameters are optional. + * + * Return value: the new #SoupAuthDomain + **/ +SoupAuthDomain * +soup_auth_domain_basic_new (const char *optname1, ...) +{ + SoupAuthDomain *domain; + va_list ap; + + va_start (ap, optname1); + domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_BASIC, + optname1, ap); + va_end (ap); + + g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL); + + return domain; +} + +/** + * SoupAuthDomainBasicAuthCallback: + * @domain: the domain + * @msg: the message being authenticated + * @username: the username provided by the client + * @password: the password provided by the client + * @user_data: the data passed to soup_auth_domain_basic_set_auth_callback() + * + * Callback used by #SoupAuthDomainBasic for authentication purposes. + * The application should verify that @username and @password and valid + * and return %TRUE or %FALSE. + * + * If you are maintaining your own password database (rather than + * using the password to authenticate against some other system like + * PAM or a remote server), you should make sure you know what you are + * doing. In particular, don't store cleartext passwords, or + * easily-computed hashes of cleartext passwords, even if you don't + * care that much about the security of your server, because users + * will frequently use the same password for multiple sites, and so + * compromising any site with a cleartext (or easily-cracked) password + * database may give attackers access to other more-interesting sites + * as well. + * + * Return value: %TRUE if @username and @password are valid + **/ + +/** + * soup_auth_domain_basic_set_auth_callback: + * @domain: the domain + * @callback: the callback + * @user_data: data to pass to @auth_callback + * @dnotify: destroy notifier to free @user_data when @domain + * is destroyed + * + * Sets the callback that @domain will use to authenticate incoming + * requests. For each request containing authorization, @domain will + * invoke the callback, and then either accept or reject the request + * based on @callback's return value. + * + * You can also set the auth callback by setting the + * %SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK and + * %SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA properties, which can also be + * used to set the callback at construct time. + **/ +void +soup_auth_domain_basic_set_auth_callback (SoupAuthDomain *domain, + SoupAuthDomainBasicAuthCallback callback, + gpointer user_data, + GDestroyNotify dnotify) +{ + SoupAuthDomainBasicPrivate *priv = + SOUP_AUTH_DOMAIN_BASIC_GET_PRIVATE (domain); + + if (priv->auth_dnotify) + priv->auth_dnotify (priv->auth_data); + + priv->auth_callback = callback; + priv->auth_data = user_data; + priv->auth_dnotify = dnotify; + + g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK); + g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA); +} + +static void +pw_free (char *pw) +{ + memset (pw, 0, strlen (pw)); + g_free (pw); +} + +static char * +accepts (SoupAuthDomain *domain, SoupMessage *msg, const char *header) +{ + SoupAuthDomainBasicPrivate *priv = + SOUP_AUTH_DOMAIN_BASIC_GET_PRIVATE (domain); + char *decoded, *colon; + gsize len, plen; + char *username, *password; + gboolean ok = FALSE; + + if (!priv->auth_callback) + return NULL; + + if (strncmp (header, "Basic ", 6) != 0) + return NULL; + + decoded = (char *)g_base64_decode (header + 6, &len); + if (!decoded) + return NULL; + + colon = memchr (decoded, ':', len); + if (!colon) { + pw_free (decoded); + return NULL; + } + *colon = '\0'; + plen = len - (colon - decoded) - 1; + + password = g_strndup (colon + 1, plen); + memset (colon + 1, 0, plen); + username = decoded; + + ok = priv->auth_callback (domain, msg, username, password, + priv->auth_data); + pw_free (password); + + if (ok) + return username; + else { + g_free (username); + return NULL; + } +} + +static char * +challenge (SoupAuthDomain *domain, SoupMessage *msg) +{ + /* FIXME: if realm has '"'s or '\'s in it, need to escape them */ + return g_strdup_printf ("Basic realm=\"%s\"", + soup_auth_domain_get_realm (domain)); +} diff --git a/libsoup/soup-auth-domain-basic.h b/libsoup/soup-auth-domain-basic.h new file mode 100644 index 0000000..66ccb29 --- /dev/null +++ b/libsoup/soup-auth-domain-basic.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Novell, Inc. + */ + +#ifndef SOUP_AUTH_DOMAIN_BASIC_H +#define SOUP_AUTH_DOMAIN_BASIC_H 1 + +#include + +#define SOUP_TYPE_AUTH_DOMAIN_BASIC (soup_auth_domain_basic_get_type ()) +#define SOUP_AUTH_DOMAIN_BASIC(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_AUTH_DOMAIN_BASIC, SoupAuthDomainBasic)) +#define SOUP_AUTH_DOMAIN_BASIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_DOMAIN_BASIC, SoupAuthDomainBasicClass)) +#define SOUP_IS_AUTH_DOMAIN_BASIC(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN_BASIC)) +#define SOUP_IS_AUTH_DOMAIN_BASIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN_BASIC)) +#define SOUP_AUTH_DOMAIN_BASIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_DOMAIN_BASIC, SoupAuthDomainBasicClass)) + +typedef struct { + SoupAuthDomain parent; + +} SoupAuthDomainBasic; + +typedef struct { + SoupAuthDomainClass parent_class; + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); +} SoupAuthDomainBasicClass; + +#define SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK "auth-callback" +#define SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA "auth-data" + +GType soup_auth_domain_basic_get_type (void); + +SoupAuthDomain *soup_auth_domain_basic_new (const char *optname1, + ...) G_GNUC_NULL_TERMINATED; + +typedef gboolean (*SoupAuthDomainBasicAuthCallback) (SoupAuthDomain *domain, + SoupMessage *msg, + const char *username, + const char *password, + gpointer user_data); + +void soup_auth_domain_basic_set_auth_callback (SoupAuthDomain *domain, + SoupAuthDomainBasicAuthCallback callback, + gpointer user_data, + GDestroyNotify dnotify); + +#endif /* SOUP_AUTH_DOMAIN_BASIC_H */ diff --git a/libsoup/soup-auth-domain-digest.c b/libsoup/soup-auth-domain-digest.c new file mode 100644 index 0000000..b303ce8 --- /dev/null +++ b/libsoup/soup-auth-domain-digest.c @@ -0,0 +1,454 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-auth-domain-digest.c: HTTP Digest Authentication (server-side) + * + * Copyright (C) 2007 Novell, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "soup-auth-domain-digest.h" +#include "soup-auth-digest.h" +#include "soup-headers.h" +#include "soup-marshal.h" +#include "soup-message.h" +#include "soup-uri.h" + +/** + * SECTION:soup-auth-domain-digest + * @short_description: Server-side "Digest" authentication + * + * #SoupAuthDomainBasic handles the server side of HTTP "Digest" + * authentication. + **/ + +enum { + PROP_0, + + PROP_AUTH_CALLBACK, + PROP_AUTH_DATA, + + LAST_PROP +}; + +typedef struct { + SoupAuthDomainDigestAuthCallback auth_callback; + gpointer auth_data; + GDestroyNotify auth_dnotify; + +} SoupAuthDomainDigestPrivate; + +#define SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigestPrivate)) + +G_DEFINE_TYPE (SoupAuthDomainDigest, soup_auth_domain_digest, SOUP_TYPE_AUTH_DOMAIN) + +static char *accepts (SoupAuthDomain *domain, + SoupMessage *msg, + const char *header); +static char *challenge (SoupAuthDomain *domain, + SoupMessage *msg); + +static void set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static void get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); + +static void +soup_auth_domain_digest_init (SoupAuthDomainDigest *digest) +{ +} + +static void +finalize (GObject *object) +{ + SoupAuthDomainDigestPrivate *priv = + SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object); + + if (priv->auth_dnotify) + priv->auth_dnotify (priv->auth_data); + + G_OBJECT_CLASS (soup_auth_domain_digest_parent_class)->finalize (object); +} + +static void +soup_auth_domain_digest_class_init (SoupAuthDomainDigestClass *digest_class) +{ + SoupAuthDomainClass *auth_domain_class = + SOUP_AUTH_DOMAIN_CLASS (digest_class); + GObjectClass *object_class = G_OBJECT_CLASS (digest_class); + + g_type_class_add_private (digest_class, sizeof (SoupAuthDomainDigestPrivate)); + + auth_domain_class->accepts = accepts; + auth_domain_class->challenge = challenge; + + object_class->finalize = finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; + + g_object_class_install_property ( + object_class, PROP_AUTH_CALLBACK, + g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, + "Authentication callback", + "Password-finding callback", + G_PARAM_READWRITE)); + g_object_class_install_property ( + object_class, PROP_AUTH_DATA, + g_param_spec_pointer (SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA, + "Authentication callback data", + "Data to pass to authentication callback", + G_PARAM_READWRITE)); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + SoupAuthDomainDigestPrivate *priv = + SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_AUTH_CALLBACK: + priv->auth_callback = g_value_get_pointer (value); + break; + case PROP_AUTH_DATA: + if (priv->auth_dnotify) { + priv->auth_dnotify (priv->auth_data); + priv->auth_dnotify = NULL; + } + priv->auth_data = g_value_get_pointer (value); + break; + default: + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + SoupAuthDomainDigestPrivate *priv = + SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_AUTH_CALLBACK: + g_value_set_pointer (value, priv->auth_callback); + break; + case PROP_AUTH_DATA: + g_value_set_pointer (value, priv->auth_data); + break; + default: + break; + } +} + +/** + * soup_auth_domain_digest_new: + * @optname1: name of first option, or %NULL + * @...: option name/value pairs + * + * Creates a #SoupAuthDomainDigest. You must set the + * %SOUP_AUTH_DOMAIN_REALM parameter, to indicate the realm name to be + * returned with the authentication challenge to the client. Other + * parameters are optional. + * + * Return value: the new #SoupAuthDomain + **/ +SoupAuthDomain * +soup_auth_domain_digest_new (const char *optname1, ...) +{ + SoupAuthDomain *domain; + va_list ap; + + va_start (ap, optname1); + domain = (SoupAuthDomain *)g_object_new_valist (SOUP_TYPE_AUTH_DOMAIN_DIGEST, + optname1, ap); + va_end (ap); + + g_return_val_if_fail (soup_auth_domain_get_realm (domain) != NULL, NULL); + + return domain; +} + +/** + * SoupAuthDomainDigestAuthCallback: + * @domain: the domain + * @msg: the message being authenticated + * @username: the username provided by the client + * @user_data: the data passed to soup_auth_domain_digest_set_auth_callback() + * + * Callback used by #SoupAuthDomainDigest for authentication purposes. + * The application should look up @username in its password database, + * and return the corresponding encoded password (see + * soup_auth_domain_digest_encode_password()). + * + * Return value: the encoded password, or %NULL if @username is not a + * valid user. @domain will free the password when it is done with it. + **/ + +/** + * soup_auth_domain_digest_set_auth_callback: + * @domain: the domain + * @callback: the callback + * @user_data: data to pass to @auth_callback + * @dnotify: destroy notifier to free @user_data when @domain + * is destroyed + * + * Sets the callback that @domain will use to authenticate incoming + * requests. For each request containing authorization, @domain will + * invoke the callback, and then either accept or reject the request + * based on @callback's return value. + * + * You can also set the auth callback by setting the + * %SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK and + * %SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA properties, which can also be + * used to set the callback at construct time. + **/ +void +soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain, + SoupAuthDomainDigestAuthCallback callback, + gpointer user_data, + GDestroyNotify dnotify) +{ + SoupAuthDomainDigestPrivate *priv = + SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (domain); + + if (priv->auth_dnotify) + priv->auth_dnotify (priv->auth_data); + + priv->auth_callback = callback; + priv->auth_data = user_data; + priv->auth_dnotify = dnotify; + + g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK); + g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA); +} + +static char * +accepts (SoupAuthDomain *domain, SoupMessage *msg, const char *header) +{ + SoupAuthDomainDigestPrivate *priv = + SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (domain); + GHashTable *params; + const char *uri, *qop, *realm, *username; + const char *nonce, *nc, *cnonce, *response; + char *hex_urp, hex_a1[33], computed_response[33], *ret_user; + int nonce_count; + SoupURI *dig_uri, *req_uri; + gboolean accept = FALSE; + + if (!priv->auth_callback) + return NULL; + + if (strncmp (header, "Digest ", 7) != 0) + return NULL; + + params = soup_header_parse_param_list (header + 7); + if (!params) + return NULL; + + /* Check uri */ + uri = g_hash_table_lookup (params, "uri"); + if (!uri) + goto DONE; + + req_uri = soup_message_get_uri (msg); + dig_uri = soup_uri_new (uri); + if (dig_uri) { + if (!soup_uri_equal (dig_uri, req_uri)) { + soup_uri_free (dig_uri); + goto DONE; + } + soup_uri_free (dig_uri); + } else { + char *req_path; + + req_path = soup_uri_to_string (req_uri, TRUE); + if (strcmp (uri, req_path) != 0) { + g_free (req_path); + goto DONE; + } + g_free (req_path); + } + + /* Check qop; we only support "auth" for now */ + qop = g_hash_table_lookup (params, "qop"); + if (!qop || strcmp (qop, "auth") != 0) + goto DONE; + + /* Check realm */ + realm = g_hash_table_lookup (params, "realm"); + if (!realm || strcmp (realm, soup_auth_domain_get_realm (domain)) != 0) + goto DONE; + + username = g_hash_table_lookup (params, "username"); + if (!username) + goto DONE; + nonce = g_hash_table_lookup (params, "nonce"); + if (!nonce) + goto DONE; + nc = g_hash_table_lookup (params, "nc"); + if (!nc) + goto DONE; + nonce_count = atoi (nc); + if (nonce_count <= 0) + goto DONE; + cnonce = g_hash_table_lookup (params, "cnonce"); + if (!cnonce) + goto DONE; + response = g_hash_table_lookup (params, "response"); + if (!response) + goto DONE; + + hex_urp = priv->auth_callback (domain, msg, username, priv->auth_data); + if (hex_urp) { + soup_auth_digest_compute_hex_a1 (hex_urp, + SOUP_AUTH_DIGEST_ALGORITHM_MD5, + nonce, cnonce, hex_a1); + g_free (hex_urp); + soup_auth_digest_compute_response (msg->method, uri, hex_a1, + SOUP_AUTH_DIGEST_QOP_AUTH, + nonce, cnonce, nonce_count, + computed_response); + accept = (strcmp (response, computed_response) == 0); + } + + DONE: + ret_user = accept ? g_strdup (username) : NULL; + soup_header_free_param_list (params); + return ret_user; +} + +static char * +challenge (SoupAuthDomain *domain, SoupMessage *msg) +{ + GString *str; + + str = g_string_new ("Digest "); + + /* FIXME: escape realm */ + g_string_append_printf (str, "realm=\"%s\", ", + soup_auth_domain_get_realm (domain)); + + g_string_append_printf (str, "nonce=\"%lu%lu\", ", + (unsigned long) msg, + (unsigned long) time (0)); + + g_string_append_printf (str, "qop=\"auth\", "); + g_string_append_printf (str, "algorithm=\"MD5\""); + + return g_string_free (str, FALSE); +} + +/** + * soup_auth_domain_digest_encode_password: + * @username: a username + * @realm: an auth realm name + * @password: the password for @username in @realm + * + * Encodes the username/realm/password triplet for Digest + * authentication. (That is, it returns a stringified MD5 hash of + * @username, @realm, and @password concatenated together). This is + * the form that is needed as the return value of + * #SoupAuthDomainDigest's auth handler. + * + * For security reasons, you should store the encoded hash, rather + * than storing the cleartext password itself and calling this method + * only when you need to verify it. This way, if your server is + * compromised, the attackers will not gain access to cleartext + * passwords which might also be usable at other sites. (Note also + * that the encoded password returned by this method is identical to + * the encoded password stored in an Apache .htdigest file.) + * + * Return value: the encoded password + **/ +char * +soup_auth_domain_digest_encode_password (const char *username, + const char *realm, + const char *password) +{ + char hex_urp[33]; + + soup_auth_digest_compute_hex_urp (username, realm, password, hex_urp); + return g_strdup (hex_urp); +} + +static char * +evil_auth_callback (SoupAuthDomain *domain, SoupMessage *msg, + const char *username, gpointer encoded_password) +{ + return g_strdup (encoded_password); +} + +/** + * soup_auth_domain_digest_evil_check_password: + * @domain: the auth domain + * @msg: the possibly-authenticated request + * @username: the username to check @msg against + * @password: the password to check @msg against + * + * Checks if @msg correctly authenticates @username via @password in + * @domain. + * + * Don't use this method; it's evil. It requires you to have a + * cleartext password database, which means that if your server is + * compromised, the attackers will have access to all of your users' + * passwords, which may also be their passwords on other servers. It + * is much better to store the passwords encoded in some format (eg, + * via soup_auth_domain_digest_encode_password() when using Digest + * authentication), so that if the server is compromised, the + * attackers won't be able to use the encoded passwords elsewhere. + * + * At any rate, even if you do have a cleartext password database, you + * still don't need to use this method, as you can just call + * soup_auth_domain_digest_encode_password() on the cleartext password + * from the #SoupAuthDomainDigestAuthCallback anyway. This method + * really only exists so as not to break certain libraries written + * against libsoup 2.2 whose public APIs depend on the existence of a + * "check this password against this request" method. + * + * Return value: %TRUE if @msg matches @username and @password, + * %FALSE if not. + **/ +gboolean +soup_auth_domain_digest_evil_check_password (SoupAuthDomain *domain, + SoupMessage *msg, + const char *username, + const char *password) +{ + SoupAuthDomainDigestPrivate *priv = + SOUP_AUTH_DOMAIN_DIGEST_GET_PRIVATE (domain); + char *encoded_password; + const char *header; + SoupAuthDomainDigestAuthCallback old_callback; + gpointer old_data; + char *matched_username; + + encoded_password = soup_auth_domain_digest_encode_password ( + username, soup_auth_domain_get_realm (domain), password); + + old_callback = priv->auth_callback; + old_data = priv->auth_data; + + priv->auth_callback = evil_auth_callback; + priv->auth_data = encoded_password; + + header = soup_message_headers_get (msg->request_headers, "Authorization"); + matched_username = accepts (domain, msg, header); + + priv->auth_callback = old_callback; + priv->auth_data = old_data; + + g_free (encoded_password); + + if (matched_username) { + g_free (matched_username); + return TRUE; + } else + return FALSE; +} diff --git a/libsoup/soup-auth-domain-digest.h b/libsoup/soup-auth-domain-digest.h new file mode 100644 index 0000000..8f8ae10 --- /dev/null +++ b/libsoup/soup-auth-domain-digest.h @@ -0,0 +1,62 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Novell, Inc. + */ + +#ifndef SOUP_AUTH_DOMAIN_DIGEST_H +#define SOUP_AUTH_DOMAIN_DIGEST_H 1 + +#include + +#define SOUP_TYPE_AUTH_DOMAIN_DIGEST (soup_auth_domain_digest_get_type ()) +#define SOUP_AUTH_DOMAIN_DIGEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigest)) +#define SOUP_AUTH_DOMAIN_DIGEST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigestClass)) +#define SOUP_IS_AUTH_DOMAIN_DIGEST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN_DIGEST)) +#define SOUP_IS_AUTH_DOMAIN_DIGEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN_DIGEST)) +#define SOUP_AUTH_DOMAIN_DIGEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_DOMAIN_DIGEST, SoupAuthDomainDigestClass)) + +typedef struct { + SoupAuthDomain parent; + +} SoupAuthDomainDigest; + +typedef struct { + SoupAuthDomainClass parent_class; + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); +} SoupAuthDomainDigestClass; + +#define SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK "auth-callback" +#define SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA "auth-data" + +GType soup_auth_domain_digest_get_type (void); + +SoupAuthDomain *soup_auth_domain_digest_new (const char *optname1, + ...) G_GNUC_NULL_TERMINATED; + +typedef char * (*SoupAuthDomainDigestAuthCallback) (SoupAuthDomain *domain, + SoupMessage *msg, + const char *username, + gpointer user_data); + +void soup_auth_domain_digest_set_auth_callback (SoupAuthDomain *domain, + SoupAuthDomainDigestAuthCallback callback, + gpointer user_data, + GDestroyNotify dnotify); + +char *soup_auth_domain_digest_encode_password (const char *username, + const char *realm, + const char *password); + + +gboolean soup_auth_domain_digest_evil_check_password (SoupAuthDomain *domain, + SoupMessage *msg, + const char *username, + const char *password); + + +#endif /* SOUP_AUTH_DOMAIN_DIGEST_H */ diff --git a/libsoup/soup-auth-domain.c b/libsoup/soup-auth-domain.c new file mode 100644 index 0000000..8ff77bb --- /dev/null +++ b/libsoup/soup-auth-domain.c @@ -0,0 +1,415 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-auth-domain.c: HTTP Authentication Domain (server-side) + * + * Copyright (C) 2007 Novell, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "soup-auth-domain.h" +#include "soup-message.h" +#include "soup-path-map.h" +#include "soup-uri.h" + +/** + * SECTION:soup-auth-domain + * @short_description: Server-side authentication + * @see_also: #SoupServer + * + * A #SoupAuthDomain manages authentication for all or part of a + * #SoupServer. To make a server require authentication, first create + * an appropriate subclass of #SoupAuthDomain, and then add it to the + * server with soup_server_add_auth_domain(). + * + * In order for an auth domain to have any effect, you must add one or + * more paths to it (via soup_auth_domain_add_path() or the + * %SOUP_AUTH_DOMAIN_ADD_PATH property). To require authentication for + * all requests, add the path "/". + * + * If you need greater control over which requests should and + * shouldn't be authenticated, add paths covering everything you + * might want authenticated, and then use a + * filter (soup_auth_domain_set_filter()) to bypass authentication for + * those requests that don't need it. + **/ + +enum { + PROP_0, + + PROP_REALM, + PROP_PROXY, + PROP_ADD_PATH, + PROP_REMOVE_PATH, + PROP_FILTER, + PROP_FILTER_DATA, + + LAST_PROP +}; + +typedef struct { + char *realm; + gboolean proxy; + SoupAuthDomainFilter filter; + gpointer filter_data; + GDestroyNotify filter_dnotify; + SoupPathMap *paths; +} SoupAuthDomainPrivate; + +#define SOUP_AUTH_DOMAIN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomainPrivate)) + +G_DEFINE_TYPE (SoupAuthDomain, soup_auth_domain, G_TYPE_OBJECT) + +static void set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static void get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); + +static void +soup_auth_domain_init (SoupAuthDomain *domain) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain); + + priv->paths = soup_path_map_new (NULL); +} + +static void +finalize (GObject *object) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object); + + g_free (priv->realm); + soup_path_map_free (priv->paths); + + if (priv->filter_dnotify) + priv->filter_dnotify (priv->filter_data); + + G_OBJECT_CLASS (soup_auth_domain_parent_class)->finalize (object); +} + +static void +soup_auth_domain_class_init (SoupAuthDomainClass *auth_domain_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (auth_domain_class); + + g_type_class_add_private (auth_domain_class, sizeof (SoupAuthDomainPrivate)); + + object_class->finalize = finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; + + g_object_class_install_property ( + object_class, PROP_REALM, + g_param_spec_string (SOUP_AUTH_DOMAIN_REALM, + "Realm", + "The realm of this auth domain", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, PROP_PROXY, + g_param_spec_boolean (SOUP_AUTH_DOMAIN_PROXY, + "Proxy", + "Whether or not this is a proxy auth domain", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, PROP_ADD_PATH, + g_param_spec_string (SOUP_AUTH_DOMAIN_ADD_PATH, + "Add a path", + "Add a path covered by this auth domain", + NULL, + G_PARAM_WRITABLE)); + g_object_class_install_property ( + object_class, PROP_REMOVE_PATH, + g_param_spec_string (SOUP_AUTH_DOMAIN_REMOVE_PATH, + "Remove a path", + "Remove a path covered by this auth domain", + NULL, + G_PARAM_WRITABLE)); + g_object_class_install_property ( + object_class, PROP_FILTER, + g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER, + "Filter", + "A filter for deciding whether or not to require authentication", + G_PARAM_READWRITE)); + g_object_class_install_property ( + object_class, PROP_FILTER_DATA, + g_param_spec_pointer (SOUP_AUTH_DOMAIN_FILTER_DATA, + "Filter data", + "Data to pass to filter", + G_PARAM_READWRITE)); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + SoupAuthDomain *auth_domain = SOUP_AUTH_DOMAIN (object); + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_REALM: + g_free (priv->realm); + priv->realm = g_value_dup_string (value); + break; + case PROP_PROXY: + priv->proxy = g_value_get_boolean (value); + break; + case PROP_ADD_PATH: + soup_auth_domain_add_path (auth_domain, + g_value_get_string (value)); + break; + case PROP_REMOVE_PATH: + soup_auth_domain_remove_path (auth_domain, + g_value_get_string (value)); + break; + case PROP_FILTER: + priv->filter = g_value_get_pointer (value); + break; + case PROP_FILTER_DATA: + if (priv->filter_dnotify) { + priv->filter_dnotify (priv->filter_data); + priv->filter_dnotify = NULL; + } + priv->filter_data = g_value_get_pointer (value); + break; + default: + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_REALM: + g_value_set_string (value, priv->realm); + break; + case PROP_PROXY: + g_value_set_boolean (value, priv->proxy); + break; + case PROP_FILTER: + g_value_set_pointer (value, priv->filter); + break; + case PROP_FILTER_DATA: + g_value_set_pointer (value, priv->filter_data); + break; + default: + break; + } +} + +/** + * soup_auth_domain_add_path: + * @domain: a #SoupAuthDomain + * @path: the path to add to @domain + * + * Adds @path to @domain, such that requests under @path on @domain's + * server will require authentication (unless overridden by + * soup_auth_domain_remove_path() or soup_auth_domain_set_filter()). + * + * You can also add paths by setting the %SOUP_AUTH_DOMAIN_ADD_PATH + * property, which can also be used to add one or more paths at + * construct time. + **/ +void +soup_auth_domain_add_path (SoupAuthDomain *domain, const char *path) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain); + + soup_path_map_add (priv->paths, path, GINT_TO_POINTER (TRUE)); +} + +/** + * soup_auth_domain_remove_path: + * @domain: a #SoupAuthDomain + * @path: the path to remove from @domain + * + * Removes @path from @domain, such that requests under @path on + * @domain's server will NOT require authentication. + * + * This is not simply an undo-er for soup_auth_domain_add_path(); it + * can be used to "carve out" a subtree that does not require + * authentication inside a hierarchy that does. Note also that unlike + * with soup_auth_domain_add_path(), this cannot be overridden by + * adding a filter, as filters can only bypass authentication that + * would otherwise be required, not require it where it would + * otherwise be unnecessary. + * + * You can also remove paths by setting the + * %SOUP_AUTH_DOMAIN_REMOVE_PATH property, which can also be used to + * remove one or more paths at construct time. + **/ +void +soup_auth_domain_remove_path (SoupAuthDomain *domain, const char *path) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain); + + soup_path_map_add (priv->paths, path, GINT_TO_POINTER (FALSE)); +} + +/** + * soup_auth_domain_set_filter: + * @domain: a #SoupAuthDomain + * @filter: the auth filter for @domain + * @filter_data: data to pass to @filter + * @dnotify: destroy notifier to free @filter_data when @domain + * is destroyed + * + * Adds @filter as an authentication filter to @domain. The filter + * gets a chance to bypass authentication for certain requests that + * would otherwise require it. Eg, it might check the message's path + * in some way that is too complicated to do via the other methods, or + * it might check the message's method, and allow GETs but not PUTs. + * + * The filter function returns %TRUE if the request should still + * require authentication, or %FALSE if authentication is unnecessary + * for this request. + * + * To help prevent security holes, your filter should return %TRUE by + * default, and only return %FALSE under specifically-tested + * circumstances, rather than the other way around. Eg, in the example + * above, where you want to authenticate PUTs but not GETs, you should + * check if the method is GET and return %FALSE in that case, and then + * return %TRUE for all other methods (rather than returning %TRUE for + * PUT and %FALSE for all other methods). This way if it turned out + * (now or later) that some paths supported additional methods besides + * GET and PUT, those methods would default to being NOT allowed for + * unauthenticated users. + * + * You can also set the filter by setting the %SOUP_AUTH_DOMAIN_FILTER + * and %SOUP_AUTH_DOMAIN_FILTER_DATA properties, which can also be + * used to set the filter at construct time. + **/ +void +soup_auth_domain_set_filter (SoupAuthDomain *domain, + SoupAuthDomainFilter filter, + gpointer filter_data, + GDestroyNotify dnotify) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain); + + if (priv->filter_dnotify) + priv->filter_dnotify (priv->filter_data); + + priv->filter = filter; + priv->filter_data = filter_data; + priv->filter_dnotify = dnotify; + + g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER); + g_object_notify (G_OBJECT (domain), SOUP_AUTH_DOMAIN_FILTER_DATA); +} + +/** + * soup_auth_domain_get_realm: + * @domain: a #SoupAuthDomain + * + * Gets the realm name associated with @domain + * + * Return value: @domain's realm + **/ +const char * +soup_auth_domain_get_realm (SoupAuthDomain *domain) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain); + + return priv->realm; +} + +/** + * soup_auth_domain_covers: + * @domain: a #SoupAuthDomain + * @msg: a #SoupMessage + * + * Checks if @domain requires @msg to be authenticated (according to + * its paths and filter function). This does not actually look at + * whether @msg *is* authenticated, merely whether or not is needs to + * be. + * + * This is used by #SoupServer internally and is probably of no use to + * anyone else. + * + * Return value: %TRUE if @domain requires @msg to be authenticated + **/ +gboolean +soup_auth_domain_covers (SoupAuthDomain *domain, SoupMessage *msg) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain); + const char *path; + + path = soup_message_get_uri (msg)->path; + if (!soup_path_map_lookup (priv->paths, path)) + return FALSE; + + if (priv->filter && !priv->filter (domain, msg, priv->filter_data)) + return FALSE; + else + return TRUE; +} + +/** + * soup_auth_domain_accepts: + * @domain: a #SoupAuthDomain + * @msg: a #SoupMessage + * + * Checks if @msg contains appropriate authorization for @domain to + * accept it. Mirroring soup_auth_domain_covers(), this does not check + * whether or not @domain *cares* if @msg is authorized. + * + * This is used by #SoupServer internally and is probably of no use to + * anyone else. + * + * Return value: the username that @msg has authenticated as, if in + * fact it has authenticated. %NULL otherwise. + **/ +char * +soup_auth_domain_accepts (SoupAuthDomain *domain, SoupMessage *msg) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain); + const char *header; + + header = soup_message_headers_get (msg->request_headers, + priv->proxy ? + "Proxy-Authorization" : + "Authorization"); + if (!header) + return NULL; + return SOUP_AUTH_DOMAIN_GET_CLASS (domain)->accepts (domain, msg, header); +} + +/** + * soup_auth_domain_challenge: + * @domain: a #SoupAuthDomain + * @msg: a #SoupMessage + * + * Adds a "WWW-Authenticate" or "Proxy-Authenticate" header to @msg, + * requesting that the client authenticate, and sets @msg's status + * accordingly. + * + * This is used by #SoupServer internally and is probably of no use to + * anyone else. + **/ +void +soup_auth_domain_challenge (SoupAuthDomain *domain, SoupMessage *msg) +{ + SoupAuthDomainPrivate *priv = SOUP_AUTH_DOMAIN_GET_PRIVATE (domain); + char *challenge; + + challenge = SOUP_AUTH_DOMAIN_GET_CLASS (domain)->challenge (domain, msg); + soup_message_set_status (msg, priv->proxy ? + SOUP_STATUS_PROXY_UNAUTHORIZED : + SOUP_STATUS_UNAUTHORIZED); + soup_message_headers_append (msg->response_headers, + priv->proxy ? + "Proxy-Authenticate" : + "WWW-Authenticate", + challenge); + g_free (challenge); +} diff --git a/libsoup/soup-auth-domain.h b/libsoup/soup-auth-domain.h new file mode 100644 index 0000000..8b903f7 --- /dev/null +++ b/libsoup/soup-auth-domain.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Novell, Inc. + */ + +#ifndef SOUP_AUTH_DOMAIN_H +#define SOUP_AUTH_DOMAIN_H 1 + +#include + +#define SOUP_TYPE_AUTH_DOMAIN (soup_auth_domain_get_type ()) +#define SOUP_AUTH_DOMAIN(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomain)) +#define SOUP_AUTH_DOMAIN_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomainClass)) +#define SOUP_IS_AUTH_DOMAIN(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN)) +#define SOUP_IS_AUTH_DOMAIN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_AUTH_DOMAIN)) +#define SOUP_AUTH_DOMAIN_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_DOMAIN, SoupAuthDomainClass)) + +struct SoupAuthDomain { + GObject parent; + +}; + +typedef struct { + GObjectClass parent_class; + + char * (*accepts) (SoupAuthDomain *domain, + SoupMessage *msg, + const char *header); + char * (*challenge) (SoupAuthDomain *domain, + SoupMessage *msg); + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); +} SoupAuthDomainClass; + +#define SOUP_AUTH_DOMAIN_REALM "realm" +#define SOUP_AUTH_DOMAIN_PROXY "proxy" +#define SOUP_AUTH_DOMAIN_ADD_PATH "add-path" +#define SOUP_AUTH_DOMAIN_REMOVE_PATH "remove-path" +#define SOUP_AUTH_DOMAIN_FILTER "filter" +#define SOUP_AUTH_DOMAIN_FILTER_DATA "filter-data" + +typedef gboolean (*SoupAuthDomainFilter) (SoupAuthDomain *, SoupMessage *, gpointer); + +GType soup_auth_domain_get_type (void); + +void soup_auth_domain_add_path (SoupAuthDomain *domain, + const char *path); +void soup_auth_domain_remove_path (SoupAuthDomain *domain, + const char *path); + +void soup_auth_domain_set_filter (SoupAuthDomain *domain, + SoupAuthDomainFilter filter, + gpointer filter_data, + GDestroyNotify dnotify); + +const char *soup_auth_domain_get_realm (SoupAuthDomain *domain); + +gboolean soup_auth_domain_covers (SoupAuthDomain *domain, + SoupMessage *msg); +char *soup_auth_domain_accepts (SoupAuthDomain *domain, + SoupMessage *msg); +void soup_auth_domain_challenge (SoupAuthDomain *domain, + SoupMessage *msg); + +#endif /* SOUP_AUTH_DOMAIN_H */ diff --git a/libsoup/soup-auth-manager.c b/libsoup/soup-auth-manager.c new file mode 100644 index 0000000..6417197 --- /dev/null +++ b/libsoup/soup-auth-manager.c @@ -0,0 +1,451 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-auth-manager.c: SoupAuth manager for SoupSession + * + * Copyright (C) 2007 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "soup-auth-manager.h" +#include "soup-headers.h" +#include "soup-message-private.h" +#include "soup-path-map.h" +#include "soup-session.h" +#include "soup-session-private.h" +#include "soup-uri.h" + +static void session_request_started (SoupSession *session, SoupMessage *msg, + SoupSocket *socket, gpointer data); + +struct SoupAuthManager { + SoupSession *session; + GPtrArray *auth_types; + + SoupAuth *proxy_auth; + GHashTable *auth_hosts; +}; + +typedef struct { + SoupURI *root_uri; + SoupPathMap *auth_realms; /* path -> scheme:realm */ + GHashTable *auths; /* scheme:realm -> SoupAuth */ +} SoupAuthHost; + +/* temporary until we fix this to index hosts by SoupAddress */ +extern guint soup_uri_host_hash (gconstpointer key); +extern gboolean soup_uri_host_equal (gconstpointer v1, + gconstpointer v2); +extern SoupURI *soup_uri_copy_root (SoupURI *uri); + +SoupAuthManager * +soup_auth_manager_new (SoupSession *session) +{ + SoupAuthManager *manager; + + manager = g_slice_new0 (SoupAuthManager); + manager->session = session; + manager->auth_types = g_ptr_array_new (); + manager->auth_hosts = g_hash_table_new (soup_uri_host_hash, + soup_uri_host_equal); + + g_signal_connect (session, "request_started", + G_CALLBACK (session_request_started), manager); + return manager; +} + +static gboolean +foreach_free_host (gpointer key, gpointer value, gpointer data) +{ + SoupAuthHost *host = value; + + if (host->auth_realms) + soup_path_map_free (host->auth_realms); + if (host->auths) + g_hash_table_destroy (host->auths); + + soup_uri_free (host->root_uri); + g_slice_free (SoupAuthHost, host); + + return TRUE; +} + +void +soup_auth_manager_free (SoupAuthManager *manager) +{ + int i; + + g_signal_handlers_disconnect_by_func ( + manager->session, + G_CALLBACK (session_request_started), manager); + + for (i = 0; i < manager->auth_types->len; i++) + g_type_class_unref (manager->auth_types->pdata[i]); + g_ptr_array_free (manager->auth_types, TRUE); + + g_hash_table_foreach_remove (manager->auth_hosts, foreach_free_host, NULL); + g_hash_table_destroy (manager->auth_hosts); + + if (manager->proxy_auth) + g_object_unref (manager->proxy_auth); + + g_slice_free (SoupAuthManager, manager); +} + +static int +auth_type_compare_func (gconstpointer a, gconstpointer b) +{ + SoupAuthClass *auth1 = (SoupAuthClass *)a; + SoupAuthClass *auth2 = (SoupAuthClass *)b; + + return auth2->strength - auth1->strength; +} + +void +soup_auth_manager_add_type (SoupAuthManager *manager, GType type) +{ + SoupAuthClass *auth_class; + + g_return_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH)); + + auth_class = g_type_class_ref (type); + g_ptr_array_add (manager->auth_types, auth_class); + g_ptr_array_sort (manager->auth_types, auth_type_compare_func); +} + +void +soup_auth_manager_remove_type (SoupAuthManager *manager, GType type) +{ + SoupAuthClass *auth_class; + int i; + + g_return_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH)); + + auth_class = g_type_class_peek (type); + for (i = 0; i < manager->auth_types->len; i++) { + if (manager->auth_types->pdata[i] == (gpointer)auth_class) { + g_ptr_array_remove_index (manager->auth_types, i); + g_type_class_unref (auth_class); + return; + } + } +} + +static inline const char * +auth_header_for_message (SoupMessage *msg) +{ + if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) { + return soup_message_headers_get (msg->response_headers, + "Proxy-Authenticate"); + } else { + return soup_message_headers_get (msg->response_headers, + "WWW-Authenticate"); + } +} + +static char * +extract_challenge (const char *challenges, const char *scheme) +{ + GSList *items, *i; + int schemelen = strlen (scheme); + char *item, *space, *equals; + GString *challenge; + + /* The relevant grammar: + * + * WWW-Authenticate = 1#challenge + * Proxy-Authenticate = 1#challenge + * challenge = auth-scheme 1#auth-param + * auth-scheme = token + * auth-param = token "=" ( token | quoted-string ) + * + * The fact that quoted-strings can contain commas, equals + * signs, and auth scheme names makes it tricky to "cheat" on + * the parsing. We just use soup_header_parse_list(), and then + * reassemble the pieces after we find the one we want. + */ + + items = soup_header_parse_list (challenges); + + /* First item will start with the scheme name, followed by a + * space and then the first auth-param. + */ + for (i = items; i; i = i->next) { + item = i->data; + if (!g_ascii_strncasecmp (item, scheme, schemelen) && + g_ascii_isspace (item[schemelen])) + break; + } + if (!i) + return NULL; + + /* The challenge extends from this item until the end, or until + * the next item that has a space before an equals sign. + */ + challenge = g_string_new (item); + for (i = i->next; i; i = i->next) { + item = i->data; + space = strpbrk (item, " \t"); + equals = strchr (item, '='); + if (!equals || (space && equals > space)) + break; + + g_string_append (challenge, ", "); + g_string_append (challenge, item); + } + + soup_header_free_list (items); + return g_string_free (challenge, FALSE); +} + +static SoupAuth * +create_auth (SoupAuthManager *manager, SoupMessage *msg) +{ + const char *header; + SoupAuthClass *auth_class; + char *challenge = NULL; + SoupAuth *auth; + int i; + + header = auth_header_for_message (msg); + if (!header) + return NULL; + + for (i = manager->auth_types->len - 1; i >= 0; i--) { + auth_class = manager->auth_types->pdata[i]; + challenge = extract_challenge (header, auth_class->scheme_name); + if (challenge) + break; + } + if (!challenge) + return NULL; + + auth = soup_auth_new (G_TYPE_FROM_CLASS (auth_class), msg, challenge); + g_free (challenge); + return auth; +} + +static gboolean +check_auth (SoupAuthManager *manager, SoupMessage *msg, SoupAuth *auth) +{ + const char *header; + char *challenge; + gboolean ok; + + header = auth_header_for_message (msg); + if (!header) + return FALSE; + + challenge = extract_challenge (header, soup_auth_get_scheme_name (auth)); + if (!challenge) + return FALSE; + + ok = soup_auth_update (auth, msg, challenge); + g_free (challenge); + return ok; +} + +static SoupAuthHost * +get_auth_host_for_message (SoupAuthManager *manager, SoupMessage *msg) +{ + SoupAuthHost *host; + SoupURI *source = soup_message_get_uri (msg); + + host = g_hash_table_lookup (manager->auth_hosts, source); + if (host) + return host; + + host = g_slice_new0 (SoupAuthHost); + host->root_uri = soup_uri_copy_root (source); + g_hash_table_insert (manager->auth_hosts, host->root_uri, host); + + return host; +} + +static SoupAuth * +lookup_auth (SoupAuthManager *manager, SoupMessage *msg) +{ + SoupAuthHost *host; + const char *path, *realm; + + host = get_auth_host_for_message (manager, msg); + if (!host->auth_realms) + return NULL; + + path = soup_message_get_uri (msg)->path; + if (!path) + path = "/"; + realm = soup_path_map_lookup (host->auth_realms, path); + if (realm) + return g_hash_table_lookup (host->auths, realm); + else + return NULL; +} + +static gboolean +authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, + SoupMessage *msg, gboolean prior_auth_failed, + gboolean proxy) +{ + SoupURI *uri; + + if (soup_auth_is_authenticated (auth)) + return TRUE; + + if (proxy) { + g_object_get (G_OBJECT (manager->session), + SOUP_SESSION_PROXY_URI, &uri, + NULL); + } else + uri = soup_uri_copy (soup_message_get_uri (msg)); + + if (uri->password && !prior_auth_failed) { + soup_auth_authenticate (auth, uri->user, uri->password); + soup_uri_free (uri); + return TRUE; + } + soup_uri_free (uri); + + soup_session_emit_authenticate (manager->session, + msg, auth, prior_auth_failed); + return soup_auth_is_authenticated (auth); +} + +static gboolean +update_auth (SoupAuthManager *manager, SoupMessage *msg) +{ + SoupAuthHost *host; + SoupAuth *auth, *prior_auth, *old_auth; + const char *path; + char *auth_info, *old_auth_info; + GSList *pspace, *p; + gboolean prior_auth_failed = FALSE; + + host = get_auth_host_for_message (manager, msg); + + /* See if we used auth last time */ + prior_auth = soup_message_get_auth (msg); + if (prior_auth && check_auth (manager, msg, prior_auth)) { + auth = prior_auth; + if (!soup_auth_is_authenticated (auth)) + prior_auth_failed = TRUE; + } else { + auth = create_auth (manager, msg); + if (!auth) + return FALSE; + } + auth_info = soup_auth_get_info (auth); + + if (!host->auth_realms) { + host->auth_realms = soup_path_map_new (g_free); + host->auths = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); + } + + /* Record where this auth realm is used. */ + pspace = soup_auth_get_protection_space (auth, soup_message_get_uri (msg)); + for (p = pspace; p; p = p->next) { + path = p->data; + old_auth_info = soup_path_map_lookup (host->auth_realms, path); + if (old_auth_info) { + if (!strcmp (old_auth_info, auth_info)) + continue; + soup_path_map_remove (host->auth_realms, path); + } + + soup_path_map_add (host->auth_realms, path, + g_strdup (auth_info)); + } + soup_auth_free_protection_space (auth, pspace); + + /* Now, make sure the auth is recorded. (If there's a + * pre-existing auth, we keep that rather than the new one, + * since the old one might already be authenticated.) + */ + old_auth = g_hash_table_lookup (host->auths, auth_info); + if (old_auth) { + g_free (auth_info); + if (auth != old_auth && auth != prior_auth) { + g_object_unref (auth); + auth = old_auth; + } + } else { + g_hash_table_insert (host->auths, auth_info, auth); + } + + /* If we need to authenticate, try to do it. */ + return authenticate_auth (manager, auth, msg, + prior_auth_failed, FALSE); +} + +static gboolean +update_proxy_auth (SoupAuthManager *manager, SoupMessage *msg) +{ + SoupAuth *prior_auth; + gboolean prior_auth_failed = FALSE; + + /* See if we used auth last time */ + prior_auth = soup_message_get_proxy_auth (msg); + if (prior_auth && check_auth (manager, msg, prior_auth)) { + if (!soup_auth_is_authenticated (prior_auth)) + prior_auth_failed = TRUE; + } + + if (!manager->proxy_auth) { + manager->proxy_auth = create_auth (manager, msg); + if (!manager->proxy_auth) + return FALSE; + } + + /* If we need to authenticate, try to do it. */ + return authenticate_auth (manager, manager->proxy_auth, msg, + prior_auth_failed, TRUE); +} + +static void +authorize_handler (SoupMessage *msg, gpointer user_data) +{ + SoupAuthManager *manager = user_data; + + if (update_auth (manager, msg)) + soup_session_requeue_message (manager->session, msg); +} + +static void +proxy_authorize_handler (SoupMessage *msg, gpointer user_data) +{ + SoupAuthManager *manager = user_data; + + if (update_proxy_auth (manager, msg)) + soup_session_requeue_message (manager->session, msg); +} + +static void +session_request_started (SoupSession *session, SoupMessage *msg, + SoupSocket *socket, gpointer data) +{ + SoupAuthManager *manager = data; + SoupAuth *auth; + + auth = lookup_auth (manager, msg); + if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE)) + auth = NULL; + soup_message_set_auth (msg, auth); + soup_message_add_status_code_handler ( + msg, "got_body", SOUP_STATUS_UNAUTHORIZED, + G_CALLBACK (authorize_handler), manager); + + auth = manager->proxy_auth; + if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE)) + auth = NULL; + soup_message_set_proxy_auth (msg, auth); + soup_message_add_status_code_handler ( + msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED, + G_CALLBACK (proxy_authorize_handler), manager); +} + + diff --git a/libsoup/soup-auth-manager.h b/libsoup/soup-auth-manager.h new file mode 100644 index 0000000..89b7bed --- /dev/null +++ b/libsoup/soup-auth-manager.h @@ -0,0 +1,27 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Red Hat, Inc. + */ + +#ifndef SOUP_AUTH_MANAGER_H +#define SOUP_AUTH_MANAGER_H 1 + +#include "soup-types.h" +#include "soup-auth.h" + +G_BEGIN_DECLS + +typedef struct SoupAuthManager SoupAuthManager; + +SoupAuthManager *soup_auth_manager_new (SoupSession *session); + +void soup_auth_manager_add_type (SoupAuthManager *manager, + GType type); +void soup_auth_manager_remove_type (SoupAuthManager *manager, + GType type); + +void soup_auth_manager_free (SoupAuthManager *manager); + +G_END_DECLS + +#endif /* SOUP_AUTH_MANAGER_H */ diff --git a/libsoup/soup-auth-ntlm.c b/libsoup/soup-auth-ntlm.c new file mode 100644 index 0000000..6383311 --- /dev/null +++ b/libsoup/soup-auth-ntlm.c @@ -0,0 +1,134 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-auth-ntlm.c: HTTP NTLM Authentication helper + * + * Copyright (C) 2007 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "soup-auth-ntlm.h" +#include "soup-headers.h" +#include "soup-message.h" +#include "soup-misc.h" +#include "soup-uri.h" + +static gboolean update (SoupAuth *auth, SoupMessage *msg, GHashTable *auth_params); +static GSList *get_protection_space (SoupAuth *auth, SoupURI *source_uri); +static void authenticate (SoupAuth *auth, const char *username, const char *password); +static gboolean is_authenticated (SoupAuth *auth); +static char *get_authorization (SoupAuth *auth, SoupMessage *msg); + +typedef struct { + char *username, *password; +} SoupAuthNTLMPrivate; +#define SOUP_AUTH_NTLM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMPrivate)) + +G_DEFINE_TYPE (SoupAuthNTLM, soup_auth_ntlm, SOUP_TYPE_AUTH) + +static void +soup_auth_ntlm_init (SoupAuthNTLM *ntlm) +{ +} + +static void +finalize (GObject *object) +{ + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (object); + + g_free (priv->username); + if (priv->password) { + memset (priv->password, 0, strlen (priv->password)); + g_free (priv->password); + } + + G_OBJECT_CLASS (soup_auth_ntlm_parent_class)->finalize (object); +} + +static void +soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class) +{ + SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_ntlm_class); + GObjectClass *object_class = G_OBJECT_CLASS (auth_ntlm_class); + + g_type_class_add_private (auth_ntlm_class, sizeof (SoupAuthNTLMPrivate)); + + auth_class->scheme_name = "NTLM"; + auth_class->strength = 3; + + auth_class->update = update; + auth_class->get_protection_space = get_protection_space; + auth_class->authenticate = authenticate; + auth_class->is_authenticated = is_authenticated; + auth_class->get_authorization = get_authorization; + + object_class->finalize = finalize; +} + +SoupAuth * +soup_auth_ntlm_new (const char *realm, const char *host) +{ + SoupAuth *auth; + + auth = g_object_new (SOUP_TYPE_AUTH_NTLM, + SOUP_AUTH_REALM, realm, + SOUP_AUTH_HOST, host, + NULL); + return auth; +} + +static gboolean +update (SoupAuth *auth, SoupMessage *msg, GHashTable *auth_params) +{ + g_return_val_if_reached (FALSE); +} + +static GSList * +get_protection_space (SoupAuth *auth, SoupURI *source_uri) +{ + g_return_val_if_reached (NULL); +} + +static void +authenticate (SoupAuth *auth, const char *username, const char *password) +{ + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + + g_return_if_fail (username != NULL); + g_return_if_fail (password != NULL); + + priv->username = g_strdup (username); + priv->password = g_strdup (password); +} + +static gboolean +is_authenticated (SoupAuth *auth) +{ + return SOUP_AUTH_NTLM_GET_PRIVATE (auth)->password != NULL; +} + +static char * +get_authorization (SoupAuth *auth, SoupMessage *msg) +{ + g_return_val_if_reached (NULL); +} + +const char * +soup_auth_ntlm_get_username (SoupAuth *auth) +{ + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + + return priv->username; +} + +const char * +soup_auth_ntlm_get_password (SoupAuth *auth) +{ + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + + return priv->password; +} diff --git a/libsoup/soup-auth-ntlm.h b/libsoup/soup-auth-ntlm.h new file mode 100644 index 0000000..f494c02 --- /dev/null +++ b/libsoup/soup-auth-ntlm.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Red Hat, Inc. + */ + +#ifndef SOUP_AUTH_NTLM_H +#define SOUP_AUTH_NTLM_H 1 + +#include "soup-auth.h" + +#define SOUP_TYPE_AUTH_NTLM (soup_auth_ntlm_get_type ()) +#define SOUP_AUTH_NTLM(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLM)) +#define SOUP_AUTH_NTLM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMClass)) +#define SOUP_IS_AUTH_NTLM(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_AUTH_NTLM)) +#define SOUP_IS_AUTH_NTLM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_NTLM)) +#define SOUP_AUTH_NTLM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMClass)) + +typedef struct { + SoupAuth parent; + +} SoupAuthNTLM; + +typedef struct { + SoupAuthClass parent_class; + +} SoupAuthNTLMClass; + +GType soup_auth_ntlm_get_type (void); + +SoupAuth *soup_auth_ntlm_new (const char *realm, + const char *host); +const char *soup_auth_ntlm_get_username (SoupAuth *auth); +const char *soup_auth_ntlm_get_password (SoupAuth *auth); + +#endif /* SOUP_AUTH_NTLM_H */ diff --git a/libsoup/soup-auth.c b/libsoup/soup-auth.c index 51ad491..3079281 100644 --- a/libsoup/soup-auth.c +++ b/libsoup/soup-auth.c @@ -15,15 +15,61 @@ #include "soup-auth-basic.h" #include "soup-auth-digest.h" #include "soup-headers.h" +#include "soup-uri.h" + +/** + * SECTION:soup-auth + * @short_description: HTTP client-side authentication support + * @see_also: #SoupSession + * + * #SoupAuth objects store the authentication data associated with a + * given bit of web space. They are created automatically by + * #SoupSession. + **/ + +/** + * SoupAuth: + * + * The abstract base class for handling authentication. Specific HTTP + * Authentication mechanisms are implemented by its subclasses, but + * applications never need to be aware of the specific subclasses + * being used. + **/ + +typedef struct { + gboolean proxy; + char *host; + +} SoupAuthPrivate; +#define SOUP_AUTH_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH, SoupAuthPrivate)) G_DEFINE_TYPE (SoupAuth, soup_auth, G_TYPE_OBJECT) +enum { + PROP_0, + + PROP_SCHEME_NAME, + PROP_REALM, + PROP_HOST, + PROP_IS_FOR_PROXY, + PROP_IS_AUTHENTICATED, + + LAST_PROP +}; + +static void set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static void get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); + static void finalize (GObject *object) { SoupAuth *auth = SOUP_AUTH (object); + SoupAuthPrivate *priv = SOUP_AUTH_GET_PRIVATE (auth); g_free (auth->realm); + g_free (priv->host); G_OBJECT_CLASS (soup_auth_parent_class)->finalize (object); } @@ -33,7 +79,48 @@ soup_auth_class_init (SoupAuthClass *auth_class) { GObjectClass *object_class = G_OBJECT_CLASS (auth_class); + g_type_class_add_private (auth_class, sizeof (SoupAuthPrivate)); + object_class->finalize = finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; + + /* properties */ + g_object_class_install_property ( + object_class, PROP_SCHEME_NAME, + g_param_spec_string (SOUP_AUTH_SCHEME_NAME, + "Scheme name", + "Authentication scheme name", + NULL, + G_PARAM_READABLE)); + g_object_class_install_property ( + object_class, PROP_REALM, + g_param_spec_string (SOUP_AUTH_REALM, + "Realm", + "Authentication realm", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, PROP_HOST, + g_param_spec_string (SOUP_AUTH_HOST, + "Host", + "Authentication host", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, PROP_IS_FOR_PROXY, + g_param_spec_boolean (SOUP_AUTH_IS_FOR_PROXY, + "For Proxy", + "Whether or not the auth is for a proxy server", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, PROP_IS_AUTHENTICATED, + g_param_spec_boolean (SOUP_AUTH_IS_AUTHENTICATED, + "Authenticated", + "Whether or not the auth is authenticated", + FALSE, + G_PARAM_READABLE)); } static void @@ -41,108 +128,208 @@ soup_auth_init (SoupAuth *auth) { } -typedef GType (*GTypeFunc) (void); - -typedef struct { - const char *name; - int len; - GTypeFunc type_func; - int strength; -} AuthScheme; - -static AuthScheme known_auth_schemes [] = { - { "Basic", sizeof ("Basic") - 1, soup_auth_basic_get_type, 0 }, - { "Digest", sizeof ("Digest") - 1, soup_auth_digest_get_type, 3 }, - { NULL } -}; +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + SoupAuth *auth = SOUP_AUTH (object); + SoupAuthPrivate *priv = SOUP_AUTH_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_REALM: + auth->realm = g_value_dup_string (value); + break; + case PROP_HOST: + priv->host = g_value_dup_string (value); + break; + case PROP_IS_FOR_PROXY: + priv->proxy = g_value_get_boolean (value); + break; + default: + break; + } +} -/* FIXME: it should be possible to register new auth schemes! */ +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + SoupAuth *auth = SOUP_AUTH (object); + SoupAuthPrivate *priv = SOUP_AUTH_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_SCHEME_NAME: + g_value_set_string (value, soup_auth_get_scheme_name (auth)); + break; + case PROP_REALM: + g_value_set_string (value, soup_auth_get_realm (auth)); + break; + case PROP_HOST: + g_value_set_string (value, soup_auth_get_host (auth)); + break; + case PROP_IS_FOR_PROXY: + g_value_set_boolean (value, priv->proxy); + break; + case PROP_IS_AUTHENTICATED: + g_value_set_boolean (value, soup_auth_is_authenticated (auth)); + break; + default: + break; + } +} /** - * soup_auth_new_from_header_list: - * @vals: a list of WWW-Authenticate headers from a server response + * soup_auth_new: + * @type: the type of auth to create (a subtype of #SoupAuth) + * @msg: the #SoupMessage the auth is being created for + * @auth_header: the WWW-Authenticate/Proxy-Authenticate header * - * Creates a #SoupAuth value based on the strongest available - * supported auth type in @vals. + * Creates a new #SoupAuth of type @type with the information from + * @msg and @auth_header. * - * Return value: the new #SoupAuth, or %NULL if none could be created. + * This is called by #SoupSession; you will normally not create auths + * yourself. + * + * Return value: the new #SoupAuth, or %NULL if it could not be + * created **/ SoupAuth * -soup_auth_new_from_header_list (const GSList *vals) +soup_auth_new (GType type, SoupMessage *msg, const char *auth_header) { - char *header = NULL, *realm; - AuthScheme *scheme = NULL, *iter; - SoupAuth *auth = NULL; + SoupAuth *auth; GHashTable *params; + const char *scheme, *realm; - g_return_val_if_fail (vals != NULL, NULL); - - while (vals) { - char *tryheader = vals->data; - - for (iter = known_auth_schemes; iter->name; iter++) { - if (!g_ascii_strncasecmp (tryheader, iter->name, - iter->len) && - (!tryheader[iter->len] || - g_ascii_isspace (tryheader[iter->len]))) { - if (!scheme || - scheme->strength < iter->strength) { - header = tryheader; - scheme = iter; - } + g_return_val_if_fail (g_type_is_a (type, SOUP_TYPE_AUTH), NULL); + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL); + g_return_val_if_fail (auth_header != NULL, NULL); - break; - } - } + auth = g_object_new (type, + SOUP_AUTH_IS_FOR_PROXY, (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED), + SOUP_AUTH_HOST, soup_message_get_uri (msg)->host, + NULL); - vals = vals->next; + scheme = soup_auth_get_scheme_name (auth); + if (strncmp (auth_header, scheme, strlen (scheme)) != 0) { + g_object_unref (auth); + return NULL; } - if (!scheme) + params = soup_header_parse_param_list (auth_header + strlen (scheme)); + if (!params) { + g_object_unref (auth); return NULL; + } - params = soup_header_param_parse_list (header + scheme->len); - if (!params) - return NULL; - realm = soup_header_param_copy_token (params, "realm"); + realm = g_hash_table_lookup (params, "realm"); if (!realm) { - soup_header_param_destroy_hash (params); + soup_header_free_param_list (params); + g_object_unref (auth); return NULL; } - auth = g_object_new (scheme->type_func (), NULL); - auth->realm = realm; + auth->realm = g_strdup (realm); - SOUP_AUTH_GET_CLASS (auth)->construct (auth, params); - soup_header_param_destroy_hash (params); + if (!SOUP_AUTH_GET_CLASS (auth)->update (auth, msg, params)) { + g_object_unref (auth); + auth = NULL; + } + soup_header_free_param_list (params); return auth; } /** + * soup_auth_update: + * @auth: a #SoupAuth + * @msg: the #SoupMessage @auth is being updated for + * @auth_header: the WWW-Authenticate/Proxy-Authenticate header + * + * Updates @auth with the information from @msg and @auth_header, + * possibly un-authenticating it. As with soup_auth_new(), this is + * normally only used by #SoupSession. + * + * Return value: %TRUE if @auth is still a valid (but potentially + * unauthenticated) #SoupAuth. %FALSE if something about @auth_params + * could not be parsed or incorporated into @auth at all. + **/ +gboolean +soup_auth_update (SoupAuth *auth, SoupMessage *msg, const char *auth_header) +{ + GHashTable *params; + const char *scheme, *realm; + gboolean was_authenticated, success; + + g_return_val_if_fail (SOUP_IS_AUTH (auth), FALSE); + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), FALSE); + g_return_val_if_fail (auth_header != NULL, FALSE); + + scheme = soup_auth_get_scheme_name (auth); + if (strncmp (auth_header, scheme, strlen (scheme)) != 0) + return FALSE; + + params = soup_header_parse_param_list (auth_header + strlen (scheme)); + if (!params) + return FALSE; + + realm = g_hash_table_lookup (params, "realm"); + if (!realm || strcmp (realm, auth->realm) != 0) { + soup_header_free_param_list (params); + return FALSE; + } + + was_authenticated = soup_auth_is_authenticated (auth); + success = SOUP_AUTH_GET_CLASS (auth)->update (auth, msg, params); + if (was_authenticated != soup_auth_is_authenticated (auth)) + g_object_notify (G_OBJECT (auth), SOUP_AUTH_IS_AUTHENTICATED); + soup_header_free_param_list (params); + return success; +} + +/** * soup_auth_authenticate: * @auth: a #SoupAuth * @username: the username provided by the user or client * @password: the password provided by the user or client * - * This is called by the session after requesting a username and - * password from the application. @auth will take the information - * and do whatever scheme-specific processing is needed. + * Call this on an auth to authenticate it; normally this will cause + * the auth's message to be requeued with the new authentication info. **/ void soup_auth_authenticate (SoupAuth *auth, const char *username, const char *password) { + gboolean was_authenticated; + g_return_if_fail (SOUP_IS_AUTH (auth)); - g_return_if_fail (username != NULL); - g_return_if_fail (password != NULL); + g_return_if_fail (username != NULL || password == NULL); + was_authenticated = soup_auth_is_authenticated (auth); SOUP_AUTH_GET_CLASS (auth)->authenticate (auth, username, password); + if (was_authenticated != soup_auth_is_authenticated (auth)) + g_object_notify (G_OBJECT (auth), SOUP_AUTH_IS_AUTHENTICATED); +} + +/** + * soup_auth_is_for_proxy: + * @auth: a #SoupAuth + * + * Tests whether or not @auth is associated with a proxy server rather + * than an "origin" server. + * + * Return value: %TRUE or %FALSE + **/ +gboolean +soup_auth_is_for_proxy (SoupAuth *auth) +{ + g_return_val_if_fail (SOUP_IS_AUTH (auth), FALSE); + + return SOUP_AUTH_GET_PRIVATE (auth)->proxy; } /** * soup_auth_get_scheme_name: * @auth: a #SoupAuth * - * Returns @auth's scheme name. (Eg, "Basic") + * Returns @auth's scheme name. (Eg, "Basic", "Digest", or "NTLM") * * Return value: the scheme name **/ @@ -155,10 +342,30 @@ soup_auth_get_scheme_name (SoupAuth *auth) } /** + * soup_auth_get_host: + * @auth: a #SoupAuth + * + * Returns the host that @auth is associated with. + * + * Return value: the hostname + **/ +const char * +soup_auth_get_host (SoupAuth *auth) +{ + g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL); + + return SOUP_AUTH_GET_PRIVATE (auth)->host; +} + + +/** * soup_auth_get_realm: * @auth: a #SoupAuth * - * Returns @auth's realm. + * Returns @auth's realm. This is an identifier that distinguishes + * separate authentication spaces on a given server, and may be some + * string that is meaningful to the user. (Although it is probably not + * localized.) * * Return value: the realm name **/ @@ -174,10 +381,10 @@ soup_auth_get_realm (SoupAuth *auth) * soup_auth_get_info: * @auth: a #SoupAuth * - * Gets an identifier for @auth. #SoupAuth objects from the same - * server with the same identifier refer to the same authentication - * domain (eg, the URLs associated with them take the same usernames - * and passwords). + * Gets an opaque identifier for @auth, for use as a hash key or the + * like. #SoupAuth objects from the same server with the same + * identifier refer to the same authentication domain (eg, the URLs + * associated with them take the same usernames and passwords). * * Return value: the identifier **/ @@ -242,7 +449,7 @@ soup_auth_get_authorization (SoupAuth *auth, SoupMessage *msg) * soup_auth_free_protection_space(). **/ GSList * -soup_auth_get_protection_space (SoupAuth *auth, const SoupUri *source_uri) +soup_auth_get_protection_space (SoupAuth *auth, SoupURI *source_uri) { g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL); g_return_val_if_fail (source_uri != NULL, NULL); diff --git a/libsoup/soup-auth.h b/libsoup/soup-auth.h index 0feda0c..50e3849 100644 --- a/libsoup/soup-auth.h +++ b/libsoup/soup-auth.h @@ -7,6 +7,7 @@ #define SOUP_AUTH_H 1 #include +#include #define SOUP_TYPE_AUTH (soup_auth_get_type ()) #define SOUP_AUTH(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_AUTH, SoupAuth)) @@ -15,22 +16,24 @@ #define SOUP_IS_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_AUTH)) #define SOUP_AUTH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH, SoupAuthClass)) -typedef struct { +struct SoupAuth { GObject parent; char *realm; -} SoupAuth; +}; typedef struct { GObjectClass parent_class; - const char *scheme_name; + const char *scheme_name; + guint strength; - void (*construct) (SoupAuth *auth, + gboolean (*update) (SoupAuth *auth, + SoupMessage *msg, GHashTable *auth_params); GSList * (*get_protection_space) (SoupAuth *auth, - const SoupUri *source_uri); + SoupURI *source_uri); void (*authenticate) (SoupAuth *auth, const char *username, @@ -39,14 +42,31 @@ typedef struct { char * (*get_authorization) (SoupAuth *auth, SoupMessage *msg); + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); } SoupAuthClass; -GType soup_auth_get_type (void); +#define SOUP_AUTH_SCHEME_NAME "scheme-name" +#define SOUP_AUTH_REALM "realm" +#define SOUP_AUTH_HOST "host" +#define SOUP_AUTH_IS_FOR_PROXY "is-for-proxy" +#define SOUP_AUTH_IS_AUTHENTICATED "is-authenticated" +GType soup_auth_get_type (void); -SoupAuth *soup_auth_new_from_header_list (const GSList *vals); +SoupAuth *soup_auth_new (GType type, + SoupMessage *msg, + const char *auth_header); +gboolean soup_auth_update (SoupAuth *auth, + SoupMessage *msg, + const char *auth_header); +gboolean soup_auth_is_for_proxy (SoupAuth *auth); const char *soup_auth_get_scheme_name (SoupAuth *auth); +const char *soup_auth_get_host (SoupAuth *auth); const char *soup_auth_get_realm (SoupAuth *auth); char *soup_auth_get_info (SoupAuth *auth); @@ -59,7 +79,7 @@ char *soup_auth_get_authorization (SoupAuth *auth, SoupMessage *msg); GSList *soup_auth_get_protection_space (SoupAuth *auth, - const SoupUri *source_uri); + SoupURI *source_uri); void soup_auth_free_protection_space (SoupAuth *auth, GSList *space); diff --git a/libsoup/soup-connection-ntlm.c b/libsoup/soup-connection-ntlm.c index 1ffb66b..efd1570 100644 --- a/libsoup/soup-connection-ntlm.c +++ b/libsoup/soup-connection-ntlm.c @@ -13,7 +13,9 @@ #include #include "soup-connection-ntlm.h" +#include "soup-auth-ntlm.h" #include "soup-message.h" +#include "soup-message-private.h" #include "soup-misc.h" #include "soup-uri.h" @@ -79,10 +81,10 @@ ntlm_authorize_pre (SoupMessage *msg, gpointer user_data) { SoupConnectionNTLM *ntlm = user_data; SoupConnectionNTLMPrivate *priv = SOUP_CONNECTION_NTLM_GET_PRIVATE (ntlm); - const GSList *headers; + SoupAuth *auth; const char *val; char *nonce, *header; - char *username, *domain_username = NULL, *password = NULL; + const char *username = NULL, *password = NULL; char *slash, *domain; if (priv->state > SOUP_CONNECTION_NTLM_SENT_REQUEST) { @@ -94,15 +96,11 @@ ntlm_authorize_pre (SoupMessage *msg, gpointer user_data) goto done; } - headers = soup_message_get_header_list (msg->response_headers, - "WWW-Authenticate"); - while (headers) { - val = headers->data; - if (!strncmp (val, "NTLM ", 5)) - break; - headers = headers->next; - } - if (!headers) { + val = soup_message_headers_get (msg->response_headers, + "WWW-Authenticate"); + if (val) + val = strstr (val, "NTLM "); + if (!val) { priv->state = SOUP_CONNECTION_NTLM_FAILED; goto done; } @@ -112,36 +110,32 @@ ntlm_authorize_pre (SoupMessage *msg, gpointer user_data) goto done; } - soup_connection_authenticate (SOUP_CONNECTION (ntlm), msg, - "NTLM", domain, - &domain_username, &password); - if (!domain_username || !password) { + auth = soup_auth_ntlm_new (domain, soup_message_get_uri (msg)->host); + soup_connection_authenticate (SOUP_CONNECTION (ntlm), msg, auth, FALSE); + username = soup_auth_ntlm_get_username (auth); + password = soup_auth_ntlm_get_password (auth); + if (!username || !password) { g_free (nonce); g_free (domain); - g_free (domain_username); - g_free (password); + g_object_unref (auth); goto done; } - slash = strpbrk (domain_username, "\\/"); + slash = strpbrk (username, "\\/"); if (slash) { g_free (domain); + domain = g_strdup (username); + slash = strpbrk (domain, "\\/"); *slash = '\0'; - domain = domain_username; username = slash + 1; - domain_username = NULL; - } else - username = domain_username; + } header = soup_ntlm_response (nonce, username, password, NULL, domain); - g_free (domain_username); - g_free (password); g_free (domain); g_free (nonce); + g_object_unref (auth); - soup_message_remove_header (msg->request_headers, "Authorization"); - soup_message_add_header (msg->request_headers, - "Authorization", header); + soup_message_headers_replace (msg->request_headers, "Authorization", header); g_free (header); priv->state = SOUP_CONNECTION_NTLM_RECEIVED_CHALLENGE; @@ -149,7 +143,7 @@ ntlm_authorize_pre (SoupMessage *msg, gpointer user_data) /* Remove the WWW-Authenticate headers so the session won't try * to do Basic auth too. */ - soup_message_remove_header (msg->response_headers, "WWW-Authenticate"); + soup_message_headers_remove (msg->response_headers, "WWW-Authenticate"); } static void @@ -158,7 +152,7 @@ ntlm_authorize_post (SoupMessage *msg, gpointer conn) SoupConnectionNTLMPrivate *priv = SOUP_CONNECTION_NTLM_GET_PRIVATE (conn); if (priv->state == SOUP_CONNECTION_NTLM_RECEIVED_CHALLENGE && - soup_message_get_header (msg->request_headers, "Authorization")) { + soup_message_headers_get (msg->request_headers, "Authorization")) { /* We just added the last Auth header, so restart it. */ priv->state = SOUP_CONNECTION_NTLM_SENT_RESPONSE; @@ -182,10 +176,8 @@ ntlm_cleanup_msg (SoupMessage *msg, gpointer conn) /* Do this when the message is restarted, in case it's * restarted on a different connection. */ - soup_message_remove_handler (msg, SOUP_HANDLER_PRE_BODY, - ntlm_authorize_pre, conn); - soup_message_remove_handler (msg, SOUP_HANDLER_POST_BODY, - ntlm_authorize_post, conn); + g_signal_handlers_disconnect_by_func (msg, ntlm_authorize_pre, conn); + g_signal_handlers_disconnect_by_func (msg, ntlm_authorize_post, conn); } static void @@ -196,21 +188,21 @@ send_request (SoupConnection *conn, SoupMessage *req) if (priv->state == SOUP_CONNECTION_NTLM_NEW) { char *header = soup_ntlm_request (); - soup_message_remove_header (req->request_headers, - "Authorization"); - soup_message_add_header (req->request_headers, - "Authorization", header); + soup_message_headers_replace (req->request_headers, + "Authorization", header); g_free (header); priv->state = SOUP_CONNECTION_NTLM_SENT_REQUEST; } - soup_message_add_status_code_handler (req, SOUP_STATUS_UNAUTHORIZED, - SOUP_HANDLER_PRE_BODY, - ntlm_authorize_pre, conn); + soup_message_add_status_code_handler (req, "got_headers", + SOUP_STATUS_UNAUTHORIZED, + G_CALLBACK (ntlm_authorize_pre), + conn); - soup_message_add_status_code_handler (req, SOUP_STATUS_UNAUTHORIZED, - SOUP_HANDLER_POST_BODY, - ntlm_authorize_post, conn); + soup_message_add_status_code_handler (req, "got_body", + SOUP_STATUS_UNAUTHORIZED, + G_CALLBACK (ntlm_authorize_post), + conn); g_signal_connect (req, "restarted", G_CALLBACK (ntlm_cleanup_msg), conn); diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c index f3c3270..4dc6d42 100644 --- a/libsoup/soup-connection.c +++ b/libsoup/soup-connection.c @@ -22,7 +22,6 @@ #include "soup-marshal.h" #include "soup-message.h" #include "soup-message-private.h" -#include "soup-message-filter.h" #include "soup-misc.h" #include "soup-socket.h" #include "soup-ssl.h" @@ -43,12 +42,11 @@ typedef struct { * connected to, which will be proxy_uri if there's a proxy * and origin_uri if not. */ - SoupUri *proxy_uri, *origin_uri, *conn_uri; + SoupURI *proxy_uri, *origin_uri, *conn_uri; gpointer ssl_creds; SoupConnectionMode mode; - SoupMessageFilter *filter; GMainContext *async_context; SoupMessage *cur_req; @@ -63,8 +61,8 @@ G_DEFINE_TYPE (SoupConnection, soup_connection, G_TYPE_OBJECT) enum { CONNECT_RESULT, DISCONNECTED, + REQUEST_STARTED, AUTHENTICATE, - REAUTHENTICATE, LAST_SIGNAL }; @@ -76,7 +74,6 @@ enum { PROP_ORIGIN_URI, PROP_PROXY_URI, PROP_SSL_CREDS, - PROP_MESSAGE_FILTER, PROP_ASYNC_CONTEXT, PROP_TIMEOUT, @@ -108,8 +105,6 @@ finalize (GObject *object) if (priv->origin_uri) soup_uri_free (priv->origin_uri); - if (priv->filter) - g_object_unref (priv->filter); if (priv->async_context) g_main_context_unref (priv->async_context); @@ -145,14 +140,6 @@ soup_connection_class_init (SoupConnectionClass *connection_class) /* signals */ - /** - * SoupConnection::connect-result: - * @conn: the connection - * @status: the status - * - * Emitted when a connection attempt succeeds or fails. This - * is used internally by soup_connection_connect_async(). - **/ signals[CONNECT_RESULT] = g_signal_new ("connect_result", G_OBJECT_CLASS_TYPE (object_class), @@ -162,14 +149,6 @@ soup_connection_class_init (SoupConnectionClass *connection_class) soup_marshal_NONE__INT, G_TYPE_NONE, 1, G_TYPE_INT); - - /** - * SoupConnection::disconnected: - * @conn: the connection - * - * Emitted when the connection's socket is disconnected, for - * whatever reason. - **/ signals[DISCONNECTED] = g_signal_new ("disconnected", G_OBJECT_CLASS_TYPE (object_class), @@ -178,62 +157,26 @@ soup_connection_class_init (SoupConnectionClass *connection_class) NULL, NULL, soup_marshal_NONE__NONE, G_TYPE_NONE, 0); - - /** - * SoupConnection::authenticate: - * @conn: the connection - * @msg: the #SoupMessage being sent - * @auth_type: the authentication type - * @auth_realm: the realm being authenticated to - * @username: the signal handler should set this to point to - * the provided username - * @password: the signal handler should set this to point to - * the provided password - * - * Emitted when the connection requires authentication. - * (#SoupConnectionNTLM makes use of this.) - **/ - signals[AUTHENTICATE] = - g_signal_new ("authenticate", + signals[REQUEST_STARTED] = + g_signal_new ("request-started", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (SoupConnectionClass, authenticate), + G_STRUCT_OFFSET (SoupConnectionClass, request_started), NULL, NULL, - soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER, - G_TYPE_NONE, 5, - SOUP_TYPE_MESSAGE, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_POINTER, - G_TYPE_POINTER); - - /** - * SoupConnection::reauthenticate: - * @conn: the connection - * @msg: the #SoupMessage being sent - * @auth_type: the authentication type - * @auth_realm: the realm being authenticated to - * @username: the signal handler should set this to point to - * the provided username - * @password: the signal handler should set this to point to - * the provided password - * - * Emitted when the authentication data acquired by a previous - * %authenticate or %reauthenticate signal fails. - **/ - signals[REAUTHENTICATE] = - g_signal_new ("reauthenticate", + soup_marshal_NONE__OBJECT, + G_TYPE_NONE, 1, + SOUP_TYPE_MESSAGE); + signals[AUTHENTICATE] = + g_signal_new ("authenticate", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (SoupConnectionClass, reauthenticate), + G_STRUCT_OFFSET (SoupConnectionClass, authenticate), NULL, NULL, - soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER, - G_TYPE_NONE, 5, + soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN, + G_TYPE_NONE, 3, SOUP_TYPE_MESSAGE, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_POINTER, - G_TYPE_POINTER); + SOUP_TYPE_AUTH, + G_TYPE_BOOLEAN); /* properties */ g_object_class_install_property ( @@ -255,12 +198,6 @@ soup_connection_class_init (SoupConnectionClass *connection_class) "Opaque SSL credentials for this connection", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( - object_class, PROP_MESSAGE_FILTER, - g_param_spec_pointer (SOUP_CONNECTION_MESSAGE_FILTER, - "Message filter", - "Message filter object for this connection", - G_PARAM_READWRITE)); - g_object_class_install_property ( object_class, PROP_ASYNC_CONTEXT, g_param_spec_pointer (SOUP_CONNECTION_ASYNC_CONTEXT, "Async GMainContext", @@ -328,7 +265,7 @@ set_property (GObject *object, guint prop_id, if (priv->proxy_uri) { priv->conn_uri = priv->proxy_uri; if (priv->origin_uri && - priv->origin_uri->protocol == SOUP_PROTOCOL_HTTPS) + priv->origin_uri->scheme == SOUP_URI_SCHEME_HTTPS) priv->mode = SOUP_CONNECTION_MODE_TUNNEL; else priv->mode = SOUP_CONNECTION_MODE_PROXY; @@ -341,13 +278,6 @@ set_property (GObject *object, guint prop_id, case PROP_SSL_CREDS: priv->ssl_creds = g_value_get_pointer (value); break; - case PROP_MESSAGE_FILTER: - if (priv->filter) - g_object_unref (priv->filter); - priv->filter = g_value_get_pointer (value); - if (priv->filter) - g_object_ref (priv->filter); - break; case PROP_ASYNC_CONTEXT: priv->async_context = g_value_get_pointer (value); if (priv->async_context) @@ -380,9 +310,6 @@ get_property (GObject *object, guint prop_id, case PROP_SSL_CREDS: g_value_set_pointer (value, priv->ssl_creds); break; - case PROP_MESSAGE_FILTER: - g_value_set_pointer (value, priv->filter ? g_object_ref (priv->filter) : NULL); - break; case PROP_ASYNC_CONTEXT: g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL); break; @@ -399,7 +326,7 @@ set_current_request (SoupConnectionPrivate *priv, SoupMessage *req) { g_return_if_fail (priv->cur_req == NULL); - req->status = SOUP_MESSAGE_STATUS_RUNNING; + soup_message_set_io_status (req, SOUP_MESSAGE_IO_STATUS_RUNNING); priv->cur_req = req; priv->in_use = TRUE; g_object_add_weak_pointer (G_OBJECT (req), (gpointer)&priv->cur_req); @@ -458,7 +385,8 @@ tunnel_connect_finished (SoupMessage *msg, gpointer user_data) if (SOUP_STATUS_IS_SUCCESSFUL (status)) { if (soup_socket_start_proxy_ssl (priv->socket, - priv->origin_uri->host)) + priv->origin_uri->host, + NULL)) priv->connected = TRUE; else status = SOUP_STATUS_SSL_FAILED; @@ -512,8 +440,8 @@ socket_connect_result (SoupSocket *sock, guint status, gpointer user_data) if (!SOUP_STATUS_IS_SUCCESSFUL (status)) goto done; - if (priv->conn_uri->protocol == SOUP_PROTOCOL_HTTPS) { - if (!soup_socket_start_ssl (sock)) { + if (priv->conn_uri->scheme == SOUP_URI_SCHEME_HTTPS) { + if (!soup_socket_start_ssl (sock, NULL)) { status = SOUP_STATUS_SSL_FAILED; goto done; } @@ -567,14 +495,13 @@ soup_connection_connect_async (SoupConnection *conn, } addr = soup_address_new (priv->conn_uri->host, priv->conn_uri->port); - priv->socket = - soup_socket_new (SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds, + soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, addr, + SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds, SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context, NULL); - soup_socket_connect (priv->socket, addr); - soup_signal_connect_once (priv->socket, "connect_result", - G_CALLBACK (socket_connect_result), conn); + soup_socket_connect_async (priv->socket, NULL, + socket_connect_result, conn); g_signal_connect (priv->socket, "disconnected", G_CALLBACK (socket_disconnected), conn); @@ -600,27 +527,26 @@ soup_connection_connect_sync (SoupConnection *conn) priv = SOUP_CONNECTION_GET_PRIVATE (conn); g_return_val_if_fail (priv->socket == NULL, SOUP_STATUS_MALFORMED); + addr = soup_address_new (priv->conn_uri->host, + priv->conn_uri->port); priv->socket = - soup_socket_new (SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds, + soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, addr, + SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds, SOUP_SOCKET_FLAG_NONBLOCKING, FALSE, SOUP_SOCKET_TIMEOUT, priv->timeout, NULL); - addr = soup_address_new (priv->conn_uri->host, - priv->conn_uri->port); - - status = soup_socket_connect (priv->socket, addr); + status = soup_socket_connect_sync (priv->socket, NULL); g_object_unref (addr); if (!SOUP_STATUS_IS_SUCCESSFUL (status)) goto fail; - g_signal_connect (priv->socket, "disconnected", G_CALLBACK (socket_disconnected), conn); - if (priv->conn_uri->protocol == SOUP_PROTOCOL_HTTPS) { - if (!soup_socket_start_ssl (priv->socket)) { + if (priv->conn_uri->scheme == SOUP_URI_SCHEME_HTTPS) { + if (!soup_socket_start_ssl (priv->socket, NULL)) { status = SOUP_STATUS_SSL_FAILED; goto fail; } @@ -648,7 +574,8 @@ soup_connection_connect_sync (SoupConnection *conn) if (SOUP_STATUS_IS_SUCCESSFUL (status)) { if (!soup_socket_start_proxy_ssl (priv->socket, - priv->origin_uri->host)) + priv->origin_uri->host, + NULL)) status = SOUP_STATUS_SSL_FAILED; } } @@ -722,7 +649,8 @@ soup_connection_disconnect (SoupConnection *conn) * all we need to do to get the message requeued in * this case is to change its status. */ - priv->cur_req->status = SOUP_MESSAGE_STATUS_QUEUED; + soup_message_set_io_status (priv->cur_req, + SOUP_MESSAGE_IO_STATUS_QUEUED); return; } @@ -742,6 +670,14 @@ soup_connection_disconnect (SoupConnection *conn) */ } +SoupSocket * +soup_connection_get_socket (SoupConnection *conn) +{ + g_return_val_if_fail (SOUP_IS_CONNECTION (conn), NULL); + + return SOUP_CONNECTION_GET_PRIVATE (conn)->socket; +} + /** * soup_connection_is_in_use: * @conn: a connection @@ -783,12 +719,11 @@ send_request (SoupConnection *conn, SoupMessage *req) if (req != priv->cur_req) { set_current_request (priv, req); - if (priv->filter) - soup_message_filter_setup_message (priv->filter, req); + g_signal_emit (conn, signals[REQUEST_STARTED], 0, req); } - soup_message_send_request_internal (req, priv->socket, conn, - priv->mode == SOUP_CONNECTION_MODE_PROXY); + soup_message_send_request (req, priv->socket, conn, + priv->mode == SOUP_CONNECTION_MODE_PROXY); } /** @@ -846,44 +781,15 @@ soup_connection_release (SoupConnection *conn) * soup_connection_authenticate: * @conn: a #SoupConnection * @msg: the message to authenticate - * @auth_type: type of authentication to use - * @auth_realm: authentication realm - * @username: on successful return, will contain the username to - * authenticate with - * @password: on successful return, will contain the password to - * authenticate with + * @auth: the #SoupAuth to authenticate + * @retrying: %TRUE if this is the second or later try * * Emits the %authenticate signal on @conn. For use by #SoupConnection * subclasses. **/ void soup_connection_authenticate (SoupConnection *conn, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password) -{ - g_signal_emit (conn, signals[AUTHENTICATE], 0, - msg, auth_type, auth_realm, username, password); -} - -/** - * soup_connection_reauthenticate: - * @conn: a #SoupConnection - * @msg: the message to authenticate - * @auth_type: type of authentication to use - * @auth_realm: authentication realm - * @username: on successful return, will contain the username to - * authenticate with - * @password: on successful return, will contain the password to - * authenticate with - * - * Emits the %reauthenticate signal on @conn. For use by - * #SoupConnection subclasses. - **/ -void -soup_connection_reauthenticate (SoupConnection *conn, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password) + SoupAuth *auth, gboolean retrying) { - g_signal_emit (conn, signals[REAUTHENTICATE], 0, - msg, auth_type, auth_realm, username, password); + g_signal_emit (conn, signals[AUTHENTICATE], 0, msg, auth, retrying); } diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h index b36d5fa..98f20d6 100644 --- a/libsoup/soup-connection.h +++ b/libsoup/soup-connection.h @@ -8,7 +8,7 @@ #include -#include +#include "soup-types.h" G_BEGIN_DECLS @@ -19,24 +19,22 @@ G_BEGIN_DECLS #define SOUP_IS_CONNECTION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_CONNECTION)) #define SOUP_CONNECTION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CONNECTION, SoupConnectionClass)) -struct SoupConnection { +typedef struct { GObject parent; -}; +} SoupConnection; typedef struct { GObjectClass parent_class; /* signals */ - void (*connect_result) (SoupConnection *, guint); - void (*disconnected) (SoupConnection *); + void (*connect_result) (SoupConnection *, guint); + void (*disconnected) (SoupConnection *); + + void (*request_started) (SoupConnection *, SoupMessage *); - void (*authenticate) (SoupConnection *, SoupMessage *, - const char *auth_type, const char *auth_realm, - char **username, char **password); - void (*reauthenticate) (SoupConnection *, SoupMessage *, - const char *auth_type, const char *auth_realm, - char **username, char **password); + void (*authenticate) (SoupConnection *, SoupMessage *, + SoupAuth *, gboolean); /* methods */ void (*send_request) (SoupConnection *, SoupMessage *); @@ -45,14 +43,6 @@ typedef struct { GType soup_connection_get_type (void); -/** - * SoupConnectionCallback: - * @conn: the #SoupConnection - * @status: an HTTP status code indicating success or failure - * @data: the data passed to soup_connection_connect_async() - * - * The callback function passed to soup_connection_connect_async(). - **/ typedef void (*SoupConnectionCallback) (SoupConnection *conn, guint status, gpointer data); @@ -61,7 +51,6 @@ typedef void (*SoupConnectionCallback) (SoupConnection *conn, #define SOUP_CONNECTION_ORIGIN_URI "origin-uri" #define SOUP_CONNECTION_PROXY_URI "proxy-uri" #define SOUP_CONNECTION_SSL_CREDENTIALS "ssl-creds" -#define SOUP_CONNECTION_MESSAGE_FILTER "message-filter" #define SOUP_CONNECTION_ASYNC_CONTEXT "async-context" #define SOUP_CONNECTION_TIMEOUT "timeout" @@ -75,6 +64,8 @@ guint soup_connection_connect_sync (SoupConnection *conn); void soup_connection_disconnect (SoupConnection *conn); +SoupSocket *soup_connection_get_socket (SoupConnection *conn); + gboolean soup_connection_is_in_use (SoupConnection *conn); time_t soup_connection_last_used (SoupConnection *conn); @@ -87,16 +78,8 @@ void soup_connection_release (SoupConnection *conn); /* protected */ void soup_connection_authenticate (SoupConnection *conn, SoupMessage *msg, - const char *auth_type, - const char *auth_realm, - char **username, - char **password); -void soup_connection_reauthenticate (SoupConnection *conn, - SoupMessage *msg, - const char *auth_type, - const char *auth_realm, - char **username, - char **password); + SoupAuth *auth, + gboolean retrying); G_END_DECLS diff --git a/libsoup/soup-date.c b/libsoup/soup-date.c index 6f92e74..563849e 100644 --- a/libsoup/soup-date.c +++ b/libsoup/soup-date.c @@ -1,8 +1,9 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* - * soup-date.c: Date/time functions + * soup-date.c: Date/time handling * * Copyright (C) 2005, Novell, Inc. + * Copyright (C) 2007, Red Hat, Inc. */ #ifdef HAVE_CONFIG_H @@ -15,6 +16,26 @@ #include "soup-date.h" +/** + * SoupDate: + * @year: the year, 1 to 9999 + * @month: the month, 1 to 12 + * @day: day of the month, 1 to 31 + * @hour: hour of the day, 0 to 23 + * @minute: minute, 0 to 59 + * @second: second, 0 to 59 (or up to 61 in the case of leap seconds) + * @utc: %TRUE if the date is in UTC + * @offset: offset from UTC + + * A date and time. The date is assumed to be in the (proleptic) + * Gregorian calendar. The time is in UTC if @utc is %TRUE. Otherwise, + * the time is a local time, and @offset gives the offset from UTC in + * minutes (such that adding @offset to the time would give the + * correct UTC time). If @utc is %FALSE and @offset is 0, then the + * %SoupDate represents a "floating" time with no associated timezone + * information. + **/ + /* Do not internationalize */ static const char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", @@ -26,197 +47,478 @@ static const char *days[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; -static int -parse_month (const char *month) +static const int days_before[] = { + 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 +}; + +GType +soup_date_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static ( + g_intern_static_string ("SoupDate"), + (GBoxedCopyFunc)soup_date_copy, + (GBoxedFreeFunc)soup_date_free); + } + return type; +} + +/** + * soup_date_new: + * @year: the year (1-9999) + * @month: the month (1-12) + * @day: the day of the month (1-31, as appropriate for @month) + * @hour: the hour (0-23) + * @minute: the minute (0-59) + * @second: the second (0-59) + * + * Creates a #SoupDate representing the indicated time, UTC. + * + * Return value: a new #SoupDate + **/ +SoupDate * +soup_date_new (int year, int month, int day, + int hour, int minute, int second) +{ + SoupDate *date = g_slice_new (SoupDate); + + date->year = year; + date->month = month; + date->day = day; + date->hour = hour; + date->minute = minute; + date->second = second; + date->utc = TRUE; + date->offset = 0; + + return date; +} + +/** + * soup_date_new_from_now: + * @offset_seconds: offset from current time + * + * Creates a #SoupDate representing a time @offset_seconds after the + * current time (or before it, if @offset_seconds is negative). If + * offset_seconds is 0, returns the current time. + * + * Return value: a new #SoupDate + **/ +SoupDate * +soup_date_new_from_now (int offset_seconds) +{ + return soup_date_new_from_time_t (time (NULL) + offset_seconds); +} + +static gboolean +parse_iso8601_date (SoupDate *date, const char *date_string) +{ + gulong val; + + if (strlen (date_string) < 15) + return FALSE; + if (date_string[4] == '-' && + date_string[7] == '-' && + date_string[10] == 'T') { + /* YYYY-MM-DD */ + date->year = atoi (date_string); + date->month = atoi (date_string + 5); + date->day = atoi (date_string + 8); + date_string += 11; + } else if (date_string[8] == 'T') { + /* YYYYMMDD */ + val = atoi (date_string); + date->year = val / 10000; + date->month = (val % 10000) / 100; + date->day = val % 100; + date_string += 9; + } else + return FALSE; + + if (strlen (date_string) >= 8 && + date_string[2] == ':' && date_string[5] == ':') { + /* HH:MM:SS */ + date->hour = atoi (date_string); + date->minute = atoi (date_string + 3); + date->second = atoi (date_string + 6); + date_string += 8; + } else if (strlen (date_string) >= 6) { + /* HHMMSS */ + val = strtoul (date_string, (char **)&date_string, 10); + date->hour = val / 10000; + date->minute = (val % 10000) / 100; + date->second = val % 100; + } else + return FALSE; + + if (*date_string == '.') + strtoul (date_string + 1, (char **)&date_string, 10); + + if (*date_string == 'Z') { + date_string++; + date->utc = TRUE; + date->offset = 0; + } else if (*date_string == '+' || *date_string == '-') { + int sign = (*date_string == '+') ? -1 : 1; + val = strtoul (date_string + 1, (char **)&date_string, 10); + if (*date_string == ':') + val = 60 * val + strtoul (date_string + 1, (char **)&date_string, 10); + else + val = 60 * (val / 100) + (val % 100); + date->offset = sign * val; + date->utc = sign && !val; + } + + return !*date_string; +} + +static inline gboolean +parse_day (SoupDate *date, const char **date_string) +{ + char *end; + + date->day = strtoul (*date_string, &end, 10); + if (end == (char *)date_string) + return FALSE; + + while (*end == ' ' || *end == '-') + end++; + *date_string = end; + return TRUE; +} + +static inline gboolean +parse_month (SoupDate *date, const char **date_string) { int i; for (i = 0; i < G_N_ELEMENTS (months); i++) { - if (!strncmp (month, months[i], 3)) - return i; + if (!strncmp (*date_string, months[i], 3)) { + date->month = i + 1; + *date_string += 3; + while (**date_string == ' ' || **date_string == '-') + (*date_string)++; + return TRUE; + } } - return -1; + return FALSE; +} + +static inline gboolean +parse_year (SoupDate *date, const char **date_string) +{ + char *end; + + date->year = strtoul (*date_string, &end, 10); + if (end == (char *)date_string) + return FALSE; + + if (end == (char *)*date_string + 2) { + if (date->year < 70) + date->year += 2000; + else + date->year += 1900; + } else if (end == (char *)*date_string + 3) + date->year += 1900; + + while (*end == ' ' || *end == '-') + end++; + *date_string = end; + return TRUE; +} + +static inline gboolean +parse_time (SoupDate *date, const char **date_string) +{ + char *p; + + date->hour = strtoul (*date_string, &p, 10); + if (*p++ != ':') + return FALSE; + date->minute = strtoul (p, &p, 10); + if (*p++ != ':') + return FALSE; + date->second = strtoul (p, &p, 10); + + while (*p == ' ') + p++; + *date_string = p; + return TRUE; +} + +static inline gboolean +parse_timezone (SoupDate *date, const char **date_string) +{ + if (**date_string == '+' || **date_string == '-') { + gulong val; + int sign = (**date_string == '+') ? -1 : 1; + val = strtoul (*date_string + 1, (char **)date_string, 10); + if (**date_string != ':') + return FALSE; + val = 60 * val + strtoul (*date_string + 1, (char **)date_string, 10); + date->offset = sign * val; + date->utc = sign && !val; + } else if (**date_string == 'Z') { + date->offset = 0; + date->utc = TRUE; + (*date_string)++; + } else if (!strcmp (*date_string, "GMT") || + !strcmp (*date_string, "UTC")) { + date->offset = 0; + date->utc = TRUE; + (*date_string) += 3; + } else if (strchr ("ECMP", **date_string) && + ((*date_string)[1] == 'D' || (*date_string)[1] == 'S') && + (*date_string)[2] == 'T') { + date->offset = -60 * (5 * strcspn ("ECMP", *date_string)); + if ((*date_string)[1] == 'D') + date->offset += 60; + date->utc = FALSE; + } else if (!**date_string) { + date->utc = FALSE; + date->offset = 0; + } else + return FALSE; + return TRUE; +} + +static gboolean +parse_textual_date (SoupDate *date, const char *date_string) +{ + /* If it starts with a word, it must be a weekday, which we skip */ + while (g_ascii_isalpha (*date_string)) + date_string++; + if (*date_string == ',') + date_string++; + while (g_ascii_isspace (*date_string)) + date_string++; + + /* If there's now another word, this must be an asctime-date */ + if (g_ascii_isalpha (*date_string)) { + /* (Sun) Nov 6 08:49:37 1994 */ + if (!parse_month (date, &date_string) || + !parse_day (date, &date_string) || + !parse_time (date, &date_string) || + !parse_year (date, &date_string)) + return FALSE; + + /* There shouldn't be a timezone, but check anyway */ + parse_timezone (date, &date_string); + } else { + /* Non-asctime date, so some variation of + * (Sun,) 06 Nov 1994 08:49:37 GMT + */ + if (!parse_day (date, &date_string) || + !parse_month (date, &date_string) || + !parse_year (date, &date_string) || + !parse_time (date, &date_string)) + return FALSE; + + /* This time there *should* be a timezone, but we + * survive if there isn't. + */ + parse_timezone (date, &date_string); + } + return TRUE; +} + +static int +days_in_month (int month, int year) +{ + return days_before[month + 1] - days_before[month] + + (((year % 4 == 0) && month == 2) ? 1 : 0); } /** - * soup_mktime_utc: - * @tm: the UTC time + * SoupDateFormat: + * @SOUP_DATE_HTTP: RFC 1123 format, used by the HTTP "Date" header. Eg + * "Sun, 06 Nov 1994 08:49:37 GMT" + * @SOUP_DATE_COOKIE: The format for the "Expires" timestamp in the + * Netscape cookie specification. Eg, "Sun, 06-Nov-1994 08:49:37 GMT". + * @SOUP_DATE_RFC2822: RFC 2822 format, eg "Sun, 6 Nov 1994 09:49:37 -0100" + * @SOUP_DATE_ISO8601_COMPACT: ISO 8601 date/time with no optional + * punctuation. Eg, "19941106T094937-0100". + * @SOUP_DATE_ISO8601_FULL: ISO 8601 date/time with all optional + * punctuation. Eg, "1994-11-06T09:49:37-01:00". + * @SOUP_DATE_ISO8601_XMLRPC: ISO 8601 date/time as used by XML-RPC. + * Eg, "19941106T09:49:37". + * @SOUP_DATE_ISO8601: An alias for @SOUP_DATE_ISO8601_FULL. * - * Converts @tm to a #time_t. Unlike with mktime(), @tm is interpreted - * as being a UTC time. + * Date formats that soup_date_to_string() can use. * - * Return value: @tm as a #time_t + * @SOUP_DATE_HTTP and @SOUP_DATE_COOKIE always coerce the time to + * UTC. @SOUP_DATE_ISO8601_XMLRPC uses the time as given, ignoring the + * offset completely. @SOUP_DATE_RFC2822 and the other ISO 8601 + * variants use the local time, appending the offset information if + * available. + * + * This enum may be extended with more values in future releases. **/ -time_t -soup_mktime_utc (struct tm *tm) + +/** + * soup_date_new_from_string: + * @date_string: the date in some plausible format + * + * Parses @date_string and tries to extract a date from it. This + * recognizes all of the "HTTP-date" formats from RFC 2616, all ISO + * 8601 formats containing both a time and a date, RFC 2822 dates, + * and reasonable approximations thereof. (Eg, it is lenient about + * whitespace, leading "0"s, etc.) + * + * Return value: a new #SoupDate + **/ +SoupDate * +soup_date_new_from_string (const char *date_string) { -#if HAVE_TIMEGM - return timegm (tm); -#else - time_t tt; - static const int days_before[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 - }; - - /* We check the month because (a) if we don't, the - * days_before[] part below may access random memory, and (b) - * soup_date_parse() doesn't check the return value of - * parse_month(). The caller is responsible for ensuring the - * sanity of everything else. + SoupDate *date = g_slice_new (SoupDate); + gboolean success; + + while (g_ascii_isspace (*date_string)) + date_string++; + + /* If it starts with a digit, it's either an ISO 8601 date, or + * an RFC2822 date without the optional weekday; in the later + * case, there will be a month name later on, so look for one + * of the month-start letters. */ - if (tm->tm_mon < 0 || tm->tm_mon > 11) - return (time_t)-1; - - tt = (tm->tm_year - 70) * 365; - tt += (tm->tm_year - 68) / 4; - tt += days_before[tm->tm_mon] + tm->tm_mday - 1; - if (tm->tm_year % 4 == 0 && tm->tm_mon < 2) - tt--; - tt = ((((tt * 24) + tm->tm_hour) * 60) + tm->tm_min) * 60 + tm->tm_sec; - - return tt; -#endif + if (g_ascii_isdigit (*date_string) && + !strpbrk (date_string, "JFMASOND")) + success = parse_iso8601_date (date, date_string); + else + success = parse_textual_date (date, date_string); + + if (!success) { + g_slice_free (SoupDate, date); + return NULL; + } + + if (date->year < 1 || date->year > 9999 || + date->month < 1 || date->month > 12 || + date->day < 1 || + date->day > days_in_month (date->month, date->year) || + date->hour < 0 || date->hour > 23 || + date->minute < 0 || date->minute > 59 || + date->second < 0 || date->second > 59) { + g_slice_free (SoupDate, date); + return NULL; + } else + return date; } /** - * soup_gmtime: + * soup_date_new_from_time_t: * @when: a #time_t - * @tm: a struct tm to be filled in with the expansion of @when * - * Expands @when into @tm (as a UTC time). This is just a wrapper - * around gmtime_r() (or gmtime() on lame platforms). (The Microsoft C - * library on Windows doesn't have gmtime_r(), but its gmtime() is in - * fact thread-safe as it uses a per-thread buffer, so it's not - * totally lame ;-) + * Creates a #SoupDate corresponding to @when + * + * Return value: a new #SoupDate **/ -void -soup_gmtime (const time_t *when, struct tm *tm) +SoupDate * +soup_date_new_from_time_t (time_t when) { + struct tm tm; + #ifdef HAVE_GMTIME_R - gmtime_r (when, tm); + gmtime_r (&when, &tm); #else - *tm = *gmtime (when); + tm = *gmtime (when); #endif + + return soup_date_new (tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +static const char * +soup_date_weekday (SoupDate *date) +{ + int day; + + /* Proleptic Gregorian 0001-01-01 was a Monday, which + * corresponds to 1 in the days[] array. So we take the + * number of days since 0000-12-31, modulo 7. + */ + day = (date->year - 1) * 365 + ((date->year - 1) / 4); + day += days_before[date->month] + date->day; + if (date->year % 4 == 0 && date->month > 2) + day++; + + return days[day % 7]; } /** - * soup_date_parse: - * @timestamp: a timestamp, in any of the allowed HTTP 1.1 formats + * soup_date_to_string: + * @date: a #SoupDate + * @format: the format to generate the date in * - * Parses @timestamp and returns its value as a #time_t. + * Converts @date to a string in the format described by @format. * - * Return value: the #time_t corresponding to @timestamp, or -1 if - * @timestamp couldn't be parsed. + * Return value: @date as a string **/ -time_t -soup_date_parse (const char *timestamp) +char * +soup_date_to_string (SoupDate *date, SoupDateFormat format) { - struct tm tm; - int len = strlen (timestamp); - - if (len < 4) - return (time_t)-1; - - switch (timestamp[3]) { - case ',': - /* rfc1123-date = wkday "," SP date1 SP time SP "GMT" - * date1 = 2DIGIT SP month SP 4DIGIT - * time = 2DIGIT ":" 2DIGIT ":" 2DIGIT - * - * eg, "Sun, 06 Nov 1994 08:49:37 GMT" - */ - if (len != 29 || strcmp (timestamp + 25, " GMT") != 0) - return (time_t)-1; - - tm.tm_mday = atoi (timestamp + 5); - tm.tm_mon = parse_month (timestamp + 8); - tm.tm_year = atoi (timestamp + 12) - 1900; - tm.tm_hour = atoi (timestamp + 17); - tm.tm_min = atoi (timestamp + 20); - tm.tm_sec = atoi (timestamp + 23); - break; - - case ' ': - /* asctime-date = wkday SP date3 SP time SP 4DIGIT - * date3 = month SP ( 2DIGIT | ( SP 1DIGIT )) - * time = 2DIGIT ":" 2DIGIT ":" 2DIGIT - * - * eg, "Sun Nov 6 08:49:37 1994" - */ - if (len != 24) - return (time_t)-1; + /* FIXME: offset, 8601 zones, etc */ + + switch (format) { + case SOUP_DATE_HTTP: + /* "Sun, 06 Nov 1994 08:49:37 GMT" */ + return g_strdup_printf ("%s, %02d %s %04d %02d:%02d:%02d GMT", + soup_date_weekday (date), date->day, + months[date->month - 1], + date->year, date->hour, date->minute, + date->second); - tm.tm_mon = parse_month (timestamp + 4); - tm.tm_mday = atoi (timestamp + 8); - tm.tm_hour = atoi (timestamp + 11); - tm.tm_min = atoi (timestamp + 14); - tm.tm_sec = atoi (timestamp + 17); - tm.tm_year = atoi (timestamp + 20) - 1900; - break; + case SOUP_DATE_COOKIE: + /* "Sun, 06-Nov-1994 08:49:37 GMT" */ + return g_strdup_printf ("%s, %02d-%s-%04d %02d:%02d:%02d GMT", + soup_date_weekday (date), date->day, + months[date->month - 1], + date->year, date->hour, date->minute, + date->second); + + case SOUP_DATE_ISO8601_COMPACT: + return g_strdup_printf ("%04d%02d%02dT%02d%02d%02d", + date->year, date->month, date->day, + date->hour, date->minute, date->second); + case SOUP_DATE_ISO8601_FULL: + return g_strdup_printf ("%04d-%02d-%02dT%02d:%02d:%02d", + date->year, date->month, date->day, + date->hour, date->minute, date->second); + case SOUP_DATE_ISO8601_XMLRPC: + return g_strdup_printf ("%04d%02d%02dT%02d:%02d:%02d", + date->year, date->month, date->day, + date->hour, date->minute, date->second); default: - /* rfc850-date = weekday "," SP date2 SP time SP "GMT" - * date2 = 2DIGIT "-" month "-" 2DIGIT - * time = 2DIGIT ":" 2DIGIT ":" 2DIGIT - * - * eg, "Sunday, 06-Nov-94 08:49:37 GMT" - */ - timestamp = strchr (timestamp, ','); - if (timestamp == NULL || strlen (timestamp) != 24 || strcmp (timestamp + 20, " GMT") != 0) - return (time_t)-1; - - tm.tm_mday = atoi (timestamp + 2); - tm.tm_mon = parse_month (timestamp + 5); - tm.tm_year = atoi (timestamp + 9); - if (tm.tm_year < 70) - tm.tm_year += 100; - tm.tm_hour = atoi (timestamp + 12); - tm.tm_min = atoi (timestamp + 15); - tm.tm_sec = atoi (timestamp + 18); - break; + return NULL; } - - return soup_mktime_utc (&tm); } /** - * soup_date_generate: - * @when: the time to generate a timestamp for - * - * Generates an HTTP 1.1 Date header corresponding to @when. + * soup_date_copy: + * @date: a #SoupDate * - * Return value: the timestamp, which the caller must free. + * Copies @date. **/ -char * -soup_date_generate (time_t when) +SoupDate * +soup_date_copy (SoupDate *date) { - struct tm tm; - - soup_gmtime (&when, &tm); + SoupDate *copy = g_slice_new (SoupDate); - /* RFC1123 format, eg, "Sun, 06 Nov 1994 08:49:37 GMT" */ - return g_strdup_printf ("%s, %02d %s %04d %02d:%02d:%02d GMT", - days[tm.tm_wday], tm.tm_mday, - months[tm.tm_mon], tm.tm_year + 1900, - tm.tm_hour, tm.tm_min, tm.tm_sec); + memcpy (copy, date, sizeof (SoupDate)); + return copy; } /** - * soup_date_iso8601_parse: - * @timestamp: an ISO8601 timestamp - * - * Converts @timestamp to a %time_t value. @timestamp can be in any of the - * iso8601 formats that specify both a date and a time. + * soup_date_free: + * @date: a #SoupDate * - * Return value: the %time_t corresponding to @timestamp, or -1 on error. + * Frees @date. **/ -time_t -soup_date_iso8601_parse (const char *timestamp) +void +soup_date_free (SoupDate *date) { - GTimeVal timeval; - - if (!g_time_val_from_iso8601 (timestamp, &timeval)) - return (time_t) -1; - - return (time_t) timeval.tv_sec; + g_slice_free (SoupDate, date); } diff --git a/libsoup/soup-date.h b/libsoup/soup-date.h index 29bdf48..106b082 100644 --- a/libsoup/soup-date.h +++ b/libsoup/soup-date.h @@ -1,23 +1,57 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 2005 Novell, Inc. + * Copyright (C) 2007 Red Hat, Inc. */ #ifndef SOUP_DATE_H #define SOUP_DATE_H 1 #include -#include +#include G_BEGIN_DECLS -time_t soup_mktime_utc (struct tm *tm); -void soup_gmtime (const time_t *when, struct tm *tm); - -time_t soup_date_parse (const char *timestamp); -char *soup_date_generate (time_t when); - -time_t soup_date_iso8601_parse (const char *timestamp); +typedef struct { + int year; + int month; + int day; + + int hour; + int minute; + int second; + + gboolean utc; + int offset; +} SoupDate; + +typedef enum { + SOUP_DATE_HTTP = 1, + SOUP_DATE_COOKIE, + SOUP_DATE_RFC2822, + SOUP_DATE_ISO8601_COMPACT, + SOUP_DATE_ISO8601_FULL, + SOUP_DATE_ISO8601 = SOUP_DATE_ISO8601_FULL, + SOUP_DATE_ISO8601_XMLRPC +} SoupDateFormat; + +GType soup_date_get_type (void); +#define SOUP_TYPE_DATE (soup_date_get_type ()) + +SoupDate *soup_date_new (int year, + int month, + int day, + int hour, + int minute, + int second); +SoupDate *soup_date_new_from_string (const char *date_string); +SoupDate *soup_date_new_from_time_t (time_t when); +SoupDate *soup_date_new_from_now (int offset_seconds); + +char *soup_date_to_string (SoupDate *date, + SoupDateFormat format); +SoupDate *soup_date_copy (SoupDate *date); +void soup_date_free (SoupDate *date); G_END_DECLS diff --git a/libsoup/soup-dns.c b/libsoup/soup-dns.c index 6ab3f4f..31d1d20 100644 --- a/libsoup/soup-dns.c +++ b/libsoup/soup-dns.c @@ -19,6 +19,7 @@ #include "soup-dns.h" #include "soup-misc.h" +#include "soup-status.h" #ifndef INET_ADDRSTRLEN # define INET_ADDRSTRLEN 16 @@ -122,7 +123,7 @@ typedef struct { gboolean resolved; GThread *resolver_thread; - GSList *lookups; + GSList *async_lookups; } SoupDNSCacheEntry; static GHashTable *soup_dns_cache; @@ -132,10 +133,9 @@ struct SoupDNSLookup { SoupDNSCacheEntry *entry; GMainContext *async_context; + GCancellable *cancellable; SoupDNSCallback callback; gpointer user_data; - - gboolean running; }; static GMutex *soup_dns_lock; @@ -226,7 +226,7 @@ soup_dns_cache_entry_unref (SoupDNSCacheEntry *entry) * have reached zero. So no cleanup needed there. */ - g_free (entry); + g_slice_free (SoupDNSCacheEntry, entry); } } @@ -235,7 +235,7 @@ soup_dns_cache_entry_new (const char *name) { SoupDNSCacheEntry *entry; - entry = g_new0 (SoupDNSCacheEntry, 1); + entry = g_slice_new0 (SoupDNSCacheEntry); entry->entry_name = g_strdup (name); entry->ref_count = 2; /* One for the caller, one for the cache */ soup_dns_cache_entry_set_from_phys (entry); @@ -320,8 +320,10 @@ resolve_address (SoupDNSCacheEntry *entry) retval = getaddrinfo (entry->hostname, NULL, &hints, &res); if (retval == 0) { entry->sockaddr = g_memdup (res->ai_addr, res->ai_addrlen); + entry->resolved = TRUE; freeaddrinfo (res); - } + } else + entry->resolved = (retval != EAI_AGAIN); #else /* !HAVE_GETADDRINFO */ @@ -336,7 +338,10 @@ resolve_address (SoupDNSCacheEntry *entry) sin.sin_family = AF_INET; memcpy (&sin.sin_addr, h->h_addr_list[0], sizeof (struct in_addr)); entry->sockaddr = g_memdup (&sin, sizeof (struct sockaddr_in)); - } + entry->resolved = TRUE; + } else + entry->resolved = (h || h_errno != TRY_AGAIN); + g_mutex_unlock (soup_gethost_lock); @@ -363,10 +368,13 @@ resolve_name (SoupDNSCacheEntry *entry) #endif ); - if (retval == 0) + if (retval == 0) { entry->hostname = name; - else + entry->resolved = TRUE; + } else { g_free (name); + entry->resolved = (retval != EAI_AGAIN); + } #else /* !HAVE_GETNAMEINFO */ @@ -377,8 +385,11 @@ resolve_name (SoupDNSCacheEntry *entry) if (sin->sin_family == AF_INET) { h = gethostbyaddr (&sin->sin_addr, sizeof (sin->sin_addr), AF_INET); - if (h) + if (h) { entry->hostname = g_strdup (h->h_name); + entry->resolved = TRUE; + } else + entry->resolved = (h_errno != TRY_AGAIN); } g_mutex_unlock (soup_gethost_lock); @@ -425,7 +436,7 @@ soup_dns_lookup_name (const char *name) entry->resolved = TRUE; } - lookup = g_new0 (SoupDNSLookup, 1); + lookup = g_slice_new0 (SoupDNSLookup); lookup->entry = entry; g_mutex_unlock (soup_dns_lock); @@ -459,25 +470,37 @@ soup_dns_lookup_address (struct sockaddr *sockaddr) entry = soup_dns_cache_entry_new (name); // FIXME g_free (name); - lookup = g_new0 (SoupDNSLookup, 1); + lookup = g_slice_new0 (SoupDNSLookup); lookup->entry = entry; g_mutex_unlock (soup_dns_lock); return lookup; } +static inline guint +resolve_status (SoupDNSCacheEntry *entry, GCancellable *cancellable) +{ + if (entry->hostname && entry->sockaddr) + return SOUP_STATUS_OK; + else if (g_cancellable_is_cancelled (cancellable)) + return SOUP_STATUS_CANCELLED; + else + return SOUP_STATUS_CANT_RESOLVE; +} + +static void async_cancel (GCancellable *cancellable, gpointer user_data); + static gboolean do_async_callback (gpointer user_data) { SoupDNSLookup *lookup = user_data; + SoupDNSCacheEntry *entry = lookup->entry; + GCancellable *cancellable = lookup->cancellable; - if (lookup->running) { - SoupDNSCacheEntry *entry = lookup->entry; - gboolean success = (entry->hostname != NULL && entry->sockaddr != NULL); - - lookup->running = FALSE; - lookup->callback (lookup, success, lookup->user_data); - } + lookup->callback (lookup, resolve_status (entry, cancellable), + lookup->user_data); + if (cancellable) + g_signal_handlers_disconnect_by_func (cancellable, async_cancel, lookup); return FALSE; } @@ -486,27 +509,26 @@ static gpointer resolver_thread (gpointer user_data) { SoupDNSCacheEntry *entry = user_data; - GSList *lookups; + GSList *async_lookups; SoupDNSLookup *lookup; if (entry->hostname == NULL) resolve_name (entry); - if (entry->sockaddr == NULL) + else if (entry->sockaddr == NULL) resolve_address (entry); - entry->resolved = TRUE; entry->resolver_thread = NULL; g_mutex_lock (soup_dns_lock); - lookups = entry->lookups; - entry->lookups = NULL; + async_lookups = entry->async_lookups; + entry->async_lookups = NULL; g_mutex_unlock (soup_dns_lock); g_cond_broadcast (soup_dns_cond); - while (lookups) { - lookup = lookups->data; - lookups = g_slist_remove (lookups, lookup); + while (async_lookups) { + lookup = async_lookups->data; + async_lookups = g_slist_remove (async_lookups, lookup); soup_add_idle (lookup->async_context, do_async_callback, lookup); } @@ -515,52 +537,100 @@ resolver_thread (gpointer user_data) return NULL; } +static void +sync_cancel (GCancellable *cancellable, gpointer user_data) +{ + /* We can't actually cancel the resolver thread. So we just + * wake up the blocking thread, which will see that + * @cancellable has been cancelled and then stop waiting for + * the result. If the resolver thread eventually finishes, + * its result will make it to the cache. + */ + g_cond_broadcast (soup_dns_cond); +} + /** * soup_dns_lookup_resolve: * @lookup: a #SoupDNSLookup + * @cancellable: a #GCancellable, or %NULL * - * Synchronously resolves @lookup. You can cancel a pending resolution - * using soup_dns_lookup_cancel(). + * Synchronously resolves @lookup. * - * Return value: success or failure. + * Return value: %SOUP_STATUS_OK, %SOUP_STATUS_CANT_RESOLVE, or + * %SOUP_STATUS_CANCELLED **/ -gboolean -soup_dns_lookup_resolve (SoupDNSLookup *lookup) +guint +soup_dns_lookup_resolve (SoupDNSLookup *lookup, GCancellable *cancellable) { SoupDNSCacheEntry *entry = lookup->entry; + guint cancel_id = 0; g_mutex_lock (soup_dns_lock); - lookup->running = TRUE; + if (!entry->resolved) { + if (!entry->resolver_thread) { + soup_dns_cache_entry_ref (entry); + entry->resolver_thread = + g_thread_create (resolver_thread, entry, + FALSE, NULL); + } - if (!entry->resolved && !entry->resolver_thread) { - soup_dns_cache_entry_ref (entry); - entry->resolver_thread = - g_thread_create (resolver_thread, entry, FALSE, NULL); + if (cancellable) { + cancel_id = g_signal_connect (cancellable, "cancelled", + G_CALLBACK (sync_cancel), + NULL); + } } - while (!entry->resolved && lookup->running) + while (entry->resolver_thread && + !g_cancellable_is_cancelled (cancellable)) g_cond_wait (soup_dns_cond, soup_dns_lock); - lookup->running = FALSE; + if (cancel_id) + g_signal_handler_disconnect (cancellable, cancel_id); + + g_mutex_unlock (soup_dns_lock); + + return resolve_status (entry, cancellable); +} + +static void +async_cancel (GCancellable *cancellable, gpointer user_data) +{ + SoupDNSLookup *lookup = user_data; + SoupDNSCacheEntry *entry = lookup->entry; + + /* We can't actually cancel the resolver thread. So we just + * remove @lookup from the list of pending async lookups and + * invoke its callback now. If the resolver thread eventually + * finishes, its result will make it to the cache. + */ + g_mutex_lock (soup_dns_lock); + + if (g_slist_find (entry->async_lookups, lookup)) { + entry->async_lookups = g_slist_remove (entry->async_lookups, + lookup); + soup_add_idle (lookup->async_context, do_async_callback, lookup); + } g_mutex_unlock (soup_dns_lock); - return entry->hostname != NULL && entry->sockaddr != NULL; } /** * soup_dns_lookup_resolve_async: * @lookup: a #SoupDNSLookup * @async_context: #GMainContext to call @callback in + * @cancellable: a #GCancellable, or %NULL * @callback: callback to call when @lookup is resolved * @user_data: data to pass to @callback; * * Tries to asynchronously resolve @lookup. Invokes @callback when it - * has succeeded or failed. You can cancel a pending resolution using - * soup_dns_lookup_cancel(). + * has succeeded or failed. **/ void -soup_dns_lookup_resolve_async (SoupDNSLookup *lookup, GMainContext *async_context, +soup_dns_lookup_resolve_async (SoupDNSLookup *lookup, + GMainContext *async_context, + GCancellable *cancellable, SoupDNSCallback callback, gpointer user_data) { SoupDNSCacheEntry *entry = lookup->entry; @@ -568,16 +638,23 @@ soup_dns_lookup_resolve_async (SoupDNSLookup *lookup, GMainContext *async_contex g_mutex_lock (soup_dns_lock); lookup->async_context = async_context; + lookup->cancellable = cancellable; lookup->callback = callback; lookup->user_data = user_data; - lookup->running = TRUE; if (!entry->resolved) { - entry->lookups = g_slist_prepend (entry->lookups, lookup); + entry->async_lookups = g_slist_prepend (entry->async_lookups, + lookup); + if (cancellable) { + g_signal_connect (cancellable, "cancelled", + G_CALLBACK (async_cancel), lookup); + } + if (!entry->resolver_thread) { soup_dns_cache_entry_ref (entry); entry->resolver_thread = - g_thread_create (resolver_thread, entry, FALSE, NULL); + g_thread_create (resolver_thread, entry, + FALSE, NULL); } } else soup_add_idle (lookup->async_context, do_async_callback, lookup); @@ -586,28 +663,6 @@ soup_dns_lookup_resolve_async (SoupDNSLookup *lookup, GMainContext *async_contex } /** - * soup_dns_lookup_cancel: - * @lookup: a #SoupDNSLookup - * - * Cancels @lookup. If @lookup was running synchronously in another - * thread, it will immediately return %FALSE. If @lookup was running - * asynchronously, its callback function will not be called. - **/ -void -soup_dns_lookup_cancel (SoupDNSLookup *lookup) -{ - /* We never really cancel the DNS lookup itself (since GThread - * doesn't have a kill function, and it might mess up - * underlying resolver data anyway). But clearing lookup->running - * and broadcasting on soup_dns_cond will immediately stop any - * blocking synchronous lookups, and clearing lookup->running - * will also make sure that its async callback is never invoked. - */ - lookup->running = FALSE; - g_cond_broadcast (soup_dns_cond); -} - -/** * soup_dns_lookup_get_hostname: * @lookup: a #SoupDNSLookup * @@ -642,14 +697,12 @@ soup_dns_lookup_get_address (SoupDNSLookup *lookup) * soup_dns_lookup_free: * @lookup: a #SoupDNSLookup * - * Frees @lookup. If @lookup is still running, it will be canceled - * first. + * Frees @lookup. It is an error to cancel a lookup while it is + * running. **/ void soup_dns_lookup_free (SoupDNSLookup *lookup) { - if (lookup->running) - soup_dns_lookup_cancel (lookup); soup_dns_cache_entry_unref (lookup->entry); - g_free (lookup); + g_slice_free (SoupDNSLookup, lookup); } diff --git a/libsoup/soup-dns.h b/libsoup/soup-dns.h index 2a8bb0f..6519a9c 100644 --- a/libsoup/soup-dns.h +++ b/libsoup/soup-dns.h @@ -7,6 +7,7 @@ #define SOUP_DNS_H #include +#include #include #include @@ -14,33 +15,21 @@ void soup_dns_init (void); char *soup_dns_ntop (struct sockaddr *sa); -/** - * SoupDNSLookup: - * - * An opaque type that represents a DNS lookup operation. - **/ typedef struct SoupDNSLookup SoupDNSLookup; SoupDNSLookup *soup_dns_lookup_name (const char *name); SoupDNSLookup *soup_dns_lookup_address (struct sockaddr *sockaddr); void soup_dns_lookup_free (SoupDNSLookup *lookup); -/** - * SoupDNSCallback: - * @lookup: the completed lookup - * @success: %TRUE if @lookup completed successfully, %FALSE if it failed - * @user_data: the data passed to soup_dns_lookup_resolve_async() - * - * The callback function passed to soup_dns_lookup_resolve_async(). - **/ -typedef void (*SoupDNSCallback) (SoupDNSLookup *lookup, gboolean success, gpointer user_data); - -gboolean soup_dns_lookup_resolve (SoupDNSLookup *lookup); +typedef void (*SoupDNSCallback) (SoupDNSLookup *lookup, guint status, gpointer user_data); + +guint soup_dns_lookup_resolve (SoupDNSLookup *lookup, + GCancellable *cancellable); void soup_dns_lookup_resolve_async (SoupDNSLookup *lookup, GMainContext *async_context, + GCancellable *cancellable, SoupDNSCallback callback, gpointer user_data); -void soup_dns_lookup_cancel (SoupDNSLookup *lookup); char *soup_dns_lookup_get_hostname (SoupDNSLookup *lookup); struct sockaddr *soup_dns_lookup_get_address (SoupDNSLookup *lookup); diff --git a/libsoup/soup-enum-types.c.tmpl b/libsoup/soup-enum-types.c.tmpl new file mode 100644 index 0000000..ea10782 --- /dev/null +++ b/libsoup/soup-enum-types.c.tmpl @@ -0,0 +1,33 @@ +/*** BEGIN file-header ***/ +#include "soup.h" +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +@enum_name@_get_type (void) +{ + static GType etype = 0; + if (G_UNLIKELY (etype == 0)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + } + return etype; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ +/*** END file-tail ***/ diff --git a/libsoup/soup-enum-types.h.tmpl b/libsoup/soup-enum-types.h.tmpl new file mode 100644 index 0000000..e18d7e0 --- /dev/null +++ b/libsoup/soup-enum-types.h.tmpl @@ -0,0 +1,24 @@ +/*** BEGIN file-header ***/ +#ifndef __SOUP_ENUM_TYPES_H__ +#define __SOUP_ENUM_TYPES_H__ + +#include + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType @enum_name@_get_type (void) G_GNUC_CONST; +#define SOUP_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __SOUP_ENUM_TYPES_H__ */ +/*** END file-tail ***/ diff --git a/libsoup/soup-form.c b/libsoup/soup-form.c new file mode 100644 index 0000000..299fc88 --- /dev/null +++ b/libsoup/soup-form.c @@ -0,0 +1,163 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* soup-form.c : utility functions for HTML forms */ + +/* + * Copyright 2008 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "soup-form.h" + +#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10) +#define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2])) + +static gboolean +form_decode (char *part) +{ + unsigned char *s, *d; + + s = d = (unsigned char *)part; + do { + if (*s == '%') { + if (!g_ascii_isxdigit (s[1]) || + !g_ascii_isxdigit (s[2])) + return FALSE; + *d++ = HEXCHAR (s); + s += 2; + } else if (*s == '+') + *d++ = ' '; + else + *d++ = *s; + } while (*s++); + + return TRUE; +} + +/** + * soup_form_decode_urlencoded: + * @encoded_form: data of type "application/x-www-form-urlencoded" + * + * Decodes @form, which is an urlencoded dataset as defined in the + * HTML 4.01 spec. + * + * Return value: a hash table containing the name/value pairs from + * @encoded_form, which you can free with g_hash_table_destroy(). + **/ +GHashTable * +soup_form_decode_urlencoded (const char *encoded_form) +{ + GHashTable *form_data_set; + char **pairs, *eq, *name, *value; + int i; + + form_data_set = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + pairs = g_strsplit (encoded_form, "&", -1); + for (i = 0; pairs[i]; i++) { + name = pairs[i]; + eq = strchr (name, '='); + if (!form_decode (name)) { + g_free (name); + continue; + } + if (eq) { + *eq = '\0'; + value = eq + 1; + } else + value = NULL; + + g_hash_table_insert (form_data_set, name, value); + } + g_free (pairs); + + return form_data_set; +} + +static void +append_form_encoded (GString *str, const char *in) +{ + const unsigned char *s = (const unsigned char *)in; + + while (*s) { + if (*s == ' ') { + g_string_append_c (str, '+'); + s++; + } else if (!g_ascii_isalnum (*s)) + g_string_append_printf (str, "%%%02X", (int)*s++); + else + g_string_append_c (str, *s++); + } +} + +static void +encode_pair (GString *str, const char *name, const char *value) +{ + if (str->len) + g_string_append_c (str, '&'); + append_form_encoded (str, name); + g_string_append_c (str, '='); + append_form_encoded (str, value); +} + +static void +hash_encode_foreach (gpointer name, gpointer value, gpointer str) +{ + encode_pair (str, name, value); +} + +/** + * soup_form_encode_urlencoded: + * @form_data_set: a hash table containing name/value pairs + * + * Encodes @form_data_set into a value of type + * "application/x-www-form-urlencoded", as defined in the HTML 4.01 + * spec. + * + * Note that the spec states that "The control names/values are listed + * in the order they appear in the document." Since + * soup_form_encode_urlencoded() takes a hash table, this cannot be + * enforced; if you care about the ordering of the form fields, use + * soup_form_encode_urlencoded_list(). + * + * Return value: the encoded form + **/ +char * +soup_form_encode_urlencoded (GHashTable *form_data_set) +{ + GString *str = g_string_new (NULL); + + g_hash_table_foreach (form_data_set, hash_encode_foreach, str); + return g_string_free (str, FALSE); +} + +static void +datalist_encode_foreach (GQuark key_id, gpointer value, gpointer str) +{ + encode_pair (str, g_quark_to_string (key_id), value); +} + +/** + * soup_form_encode_urlencoded_list: + * @form_data_set: a hash table containing name/value pairs + * + * Encodes @form_data_set into a value of type + * "application/x-www-form-urlencoded", as defined in the HTML 4.01 + * spec. Unlike soup_form_encode_urlencoded(), this preserves the + * ordering of the form elements, which may be required in some + * situations. + * + * Return value: the encoded form + **/ +char * +soup_form_encode_urlencoded_list (GData **form_data_set) +{ + GString *str = g_string_new (NULL); + + g_datalist_foreach (form_data_set, datalist_encode_foreach, str); + return g_string_free (str, FALSE); +} diff --git a/libsoup/soup-form.h b/libsoup/soup-form.h new file mode 100644 index 0000000..9502244 --- /dev/null +++ b/libsoup/soup-form.h @@ -0,0 +1,20 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright 2008 Red Hat, Inc. + */ + +#ifndef SOUP_FORM_H +#define SOUP_FORM_H 1 + +#include + +G_BEGIN_DECLS + +GHashTable *soup_form_decode_urlencoded (const char *encoded_form); + +char *soup_form_encode_urlencoded (GHashTable *form_data_set); +char *soup_form_encode_urlencoded_list (GData **form_data_set); + +G_END_DECLS + +#endif /* SOUP_FORM_H */ diff --git a/libsoup/soup-gnutls.c b/libsoup/soup-gnutls.c index c4b66f0..b402850 100644 --- a/libsoup/soup-gnutls.c +++ b/libsoup/soup-gnutls.c @@ -26,6 +26,11 @@ #include "soup-ssl.h" #include "soup-misc.h" +/** + * soup_ssl_supported: + * + * Can be used to test if libsoup was compiled with ssl support. + **/ gboolean soup_ssl_supported = TRUE; #define DH_BITS 1024 @@ -309,7 +314,7 @@ soup_gnutls_free (GIOChannel *channel) g_io_channel_unref (chan->real_sock); gnutls_deinit (chan->session); g_free (chan->hostname); - g_free (chan); + g_slice_free (SoupGNUTLSChannel, chan); } static GIOStatus @@ -412,7 +417,7 @@ soup_ssl_wrap_iochannel (GIOChannel *sock, SoupSSLType type, gnutls_transport_set_ptr (session, GINT_TO_POINTER (sockfd)); - chan = g_new0 (SoupGNUTLSChannel, 1); + chan = g_slice_new0 (SoupGNUTLSChannel); chan->fd = sockfd; chan->real_sock = sock; chan->session = session; @@ -475,7 +480,7 @@ soup_ssl_get_client_credentials (const char *ca_file) if (!soup_gnutls_inited) soup_gnutls_init (); - creds = g_new0 (SoupSSLCredentials, 1); + creds = g_slice_new0 (SoupSSLCredentials); gnutls_certificate_allocate_credentials (&creds->creds); if (ca_file) { @@ -507,7 +512,7 @@ void soup_ssl_free_client_credentials (SoupSSLCredentials *creds) { gnutls_certificate_free_credentials (creds->creds); - g_free (creds); + g_slice_free (SoupSSLCredentials, creds); } /** @@ -535,7 +540,7 @@ soup_ssl_get_server_credentials (const char *cert_file, const char *key_file) return NULL; } - creds = g_new0 (SoupSSLCredentials, 1); + creds = g_slice_new0 (SoupSSLCredentials); gnutls_certificate_allocate_credentials (&creds->creds); if (gnutls_certificate_set_x509_key_file (creds->creds, @@ -562,7 +567,7 @@ void soup_ssl_free_server_credentials (SoupSSLCredentials *creds) { gnutls_certificate_free_credentials (creds->creds); - g_free (creds); + g_slice_free (SoupSSLCredentials, creds); } #endif /* HAVE_SSL */ diff --git a/libsoup/soup-headers.c b/libsoup/soup-headers.c index dd7b516..1e69b02 100644 --- a/libsoup/soup-headers.c +++ b/libsoup/soup-headers.c @@ -14,14 +14,12 @@ #include "soup-misc.h" static gboolean -soup_headers_parse (const char *str, - int len, - GHashTable *dest) +soup_headers_parse (const char *str, int len, SoupMessageHeaders *dest) { - const char *end = str + len; - const char *name_start, *name_end, *value_start, *value_end; - char *name, *value, *eol, *sol; - GSList *hdrs; + const char *headers_start; + char *headers_copy, *name, *name_end, *value, *value_end; + char *eol, *sol; + gboolean success = FALSE; /* Technically, the grammar does allow NUL bytes in the * headers, but this is probably a bug, and if it's not, we @@ -36,39 +34,46 @@ soup_headers_parse (const char *str, */ /* Skip over the Request-Line / Status-Line */ - value_end = memchr (str, '\n', len); - if (!value_end) + headers_start = memchr (str, '\n', len); + if (!headers_start) return FALSE; - while (value_end < end - 1) { - name_start = value_end + 1; - name_end = memchr (name_start, ':', end - name_start); + /* We work on a copy of the headers, which we can write '\0's + * into, so that we don't have to individually g_strndup and + * then g_free each header name and value. + */ + headers_copy = g_strndup (headers_start, len - (headers_start - str)); + value_end = headers_copy; + + while (*(value_end + 1)) { + name = value_end + 1; + name_end = strchr (name, ':'); if (!name_end) - return FALSE; + goto done; /* Find the end of the value; ie, an end-of-line that * isn't followed by a continuation line. */ - value_end = memchr (name_start, '\n', end - name_start); + value = name_end + 1; + value_end = strchr (name, '\n'); if (!value_end || value_end < name_end) - return FALSE; - while (value_end != end - 1 && - (*(value_end + 1) == ' ' || *(value_end + 1) == '\t')) { - value_end = memchr (value_end + 1, '\n', end - value_end); + goto done; + while (*(value_end + 1) == ' ' || *(value_end + 1) == '\t') { + value_end = strchr (value_end + 1, '\n'); if (!value_end) - return FALSE; + goto done; } - name = g_strndup (name_start, name_end - name_start); + *name_end = '\0'; + *value_end = '\0'; - value_start = name_end + 1; - while (value_start < value_end && - (*value_start == ' ' || *value_start == '\t' || - *value_start == '\r' || *value_start == '\n')) - value_start++; - value = g_strndup (value_start, value_end - value_start); + /* Skip leading whitespace */ + while (value < value_end && + (*value == ' ' || *value == '\t' || + *value == '\r' || *value == '\n')) + value++; - /* Collapse continuation lines inside value */ + /* Collapse continuation lines */ while ((eol = strchr (value, '\n'))) { /* find start of next line */ sol = eol + 1; @@ -91,46 +96,46 @@ soup_headers_parse (const char *str, eol--; *eol = '\0'; - hdrs = g_hash_table_lookup (dest, name); - hdrs = g_slist_append (hdrs, value); - if (!hdrs->next) - g_hash_table_insert (dest, name, hdrs); - else - g_free (name); + soup_message_headers_append (dest, name, value); } + success = TRUE; - return TRUE; +done: + g_free (headers_copy); + return success; } /** * soup_headers_parse_request: * @str: the header string (including the trailing blank line) * @len: length of @str up to (but not including) the terminating blank line. - * @dest: #GHashTable to store the header values in + * @req_headers: #SoupMessageHeaders to store the header values in * @req_method: if non-%NULL, will be filled in with the request method * @req_path: if non-%NULL, will be filled in with the request path * @ver: if non-%NULL, will be filled in with the HTTP version * * Parses the headers of an HTTP request in @str and stores the - * results in @req_method, @req_path, @ver, and @dest. + * results in @req_method, @req_path, @ver, and @req_headers. * - * Beware that @dest may be modified even on failure. + * Beware that @req_headers may be modified even on failure. * - * Return value: success or failure. + * Return value: %SOUP_STATUS_OK if the headers could be parsed, or an + * HTTP error to be returned to the client if they could not be. **/ -gboolean -soup_headers_parse_request (const char *str, - int len, - GHashTable *dest, - char **req_method, - char **req_path, - SoupHttpVersion *ver) +guint +soup_headers_parse_request (const char *str, + int len, + SoupMessageHeaders *req_headers, + char **req_method, + char **req_path, + SoupHTTPVersion *ver) { - const char *method, *method_end, *path, *path_end, *version, *headers; - int minor_version; + const char *method, *method_end, *path, *path_end; + const char *version, *version_end, *headers; + unsigned long major_version, minor_version; + char *p; - if (!str || !*str) - return FALSE; + g_return_val_if_fail (str && *str, SOUP_STATUS_MALFORMED); /* RFC 2616 4.1 "servers SHOULD ignore any empty line(s) * received where a Request-Line is expected." @@ -148,43 +153,47 @@ soup_headers_parse_request (const char *str, while (method_end < str + len && *method_end != ' ' && *method_end != '\t') method_end++; if (method_end >= str + len) - return FALSE; + return SOUP_STATUS_BAD_REQUEST; path = method_end; while (path < str + len && (*path == ' ' || *path == '\t')) path++; if (path >= str + len) - return FALSE; + return SOUP_STATUS_BAD_REQUEST; path_end = path; while (path_end < str + len && *path_end != ' ' && *path_end != '\t') path_end++; if (path_end >= str + len) - return FALSE; + return SOUP_STATUS_BAD_REQUEST; version = path_end; while (version < str + len && (*version == ' ' || *version == '\t')) version++; if (version + 8 >= str + len) - return FALSE; - - /* FIXME: we want SoupServer to return - * SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED here - */ - if (strncmp (version, "HTTP/1.", 7) != 0) - return FALSE; - minor_version = version[7] - '0'; + return SOUP_STATUS_BAD_REQUEST; + + if (strncmp (version, "HTTP/", 5) != 0 || + !g_ascii_isdigit (version[5])) + return SOUP_STATUS_BAD_REQUEST; + major_version = strtoul (version + 5, &p, 10); + if (*p != '.' || !g_ascii_isdigit (p[1])) + return SOUP_STATUS_BAD_REQUEST; + minor_version = strtoul (p + 1, &p, 10); + version_end = p; + if (major_version != 1) + return SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED; if (minor_version < 0 || minor_version > 1) - return FALSE; + return SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED; - headers = version + 8; + headers = version_end; while (headers < str + len && (*headers == '\r' || *headers == ' ')) headers++; if (headers >= str + len || *headers != '\n') - return FALSE; + return SOUP_STATUS_BAD_REQUEST; - if (!soup_headers_parse (str, len, dest)) - return FALSE; + if (!soup_headers_parse (str, len, req_headers)) + return SOUP_STATUS_BAD_REQUEST; if (req_method) *req_method = g_strndup (method, method_end - method); @@ -193,7 +202,7 @@ soup_headers_parse_request (const char *str, if (ver) *ver = (minor_version == 0) ? SOUP_HTTP_1_0 : SOUP_HTTP_1_1; - return TRUE; + return SOUP_STATUS_OK; } /** @@ -212,22 +221,29 @@ soup_headers_parse_request (const char *str, **/ gboolean soup_headers_parse_status_line (const char *status_line, - SoupHttpVersion *ver, + SoupHTTPVersion *ver, guint *status_code, char **reason_phrase) { - guint minor_version, code; + unsigned long major_version, minor_version, code; const char *code_start, *code_end, *phrase_start, *phrase_end; + char *p; - if (strncmp (status_line, "HTTP/1.", 7) != 0) + if (strncmp (status_line, "HTTP/", 5) != 0 || + !g_ascii_isdigit (status_line[5])) + return FALSE; + major_version = strtoul (status_line + 5, &p, 10); + if (*p != '.' || !g_ascii_isdigit (p[1])) + return FALSE; + minor_version = strtoul (p + 1, &p, 10); + if (major_version != 1) return FALSE; - minor_version = status_line[7] - '0'; if (minor_version < 0 || minor_version > 1) return FALSE; if (ver) *ver = (minor_version == 0) ? SOUP_HTTP_1_0 : SOUP_HTTP_1_1; - code_start = status_line + 8; + code_start = p; while (*code_start == ' ' || *code_start == '\t') code_start++; code_end = code_start; @@ -258,31 +274,31 @@ soup_headers_parse_status_line (const char *status_line, * soup_headers_parse_response: * @str: the header string (including the trailing blank line) * @len: length of @str up to (but not including) the terminating blank line. - * @dest: #GHashTable to store the header values in + * @headers: #SoupMessageheaders to store the header values in * @ver: if non-%NULL, will be filled in with the HTTP version * @status_code: if non-%NULL, will be filled in with the status code * @reason_phrase: if non-%NULL, will be filled in with the reason * phrase * * Parses the headers of an HTTP response in @str and stores the - * results in @ver, @status_code, @reason_phrase, and @dest. + * results in @ver, @status_code, @reason_phrase, and @headers. * - * Beware that @dest may be modified even on failure. + * Beware that @headers may be modified even on failure. * * Return value: success or failure. **/ gboolean -soup_headers_parse_response (const char *str, - int len, - GHashTable *dest, - SoupHttpVersion *ver, - guint *status_code, - char **reason_phrase) +soup_headers_parse_response (const char *str, + int len, + SoupMessageHeaders *headers, + SoupHTTPVersion *ver, + guint *status_code, + char **reason_phrase) { if (!str || !*str) return FALSE; - if (!soup_headers_parse (str, len, dest)) + if (!soup_headers_parse (str, len, headers)) return FALSE; if (!soup_headers_parse_status_line (str, @@ -296,149 +312,314 @@ soup_headers_parse_response (const char *str, /* - * HTTP parameterized header parsing + * Parsing of specific HTTP header types */ -char * -soup_header_param_copy_token (GHashTable *tokens, char *t) +static const char * +skip_lws (const char *s) { - char *data; - - g_return_val_if_fail (tokens, NULL); - g_return_val_if_fail (t, NULL); - - if ( (data = g_hash_table_lookup (tokens, t))) - return g_strdup (data); - else - return NULL; + while (g_ascii_isspace (*s)) + s++; + return s; } -static void -decode_lwsp (char **in) +static const char * +unskip_lws (const char *s, const char *start) { - char *inptr = *in; - - while (isspace (*inptr)) - inptr++; - - *in = inptr; + while (s > start && g_ascii_isspace (*(s - 1))) + s--; + return s; } -static char * -decode_quoted_string (char **in) +static const char * +skip_commas (const char *s) { - char *inptr = *in; - char *out = NULL, *outptr; - int outlen; - int c; - - decode_lwsp (&inptr); - if (*inptr == '"') { - char *intmp; - int skip = 0; - - /* first, calc length */ - inptr++; - intmp = inptr; - while ( (c = *intmp++) && c != '"') { - if (c == '\\' && *intmp) { - intmp++; - skip++; - } - } - - outlen = intmp - inptr - skip; - out = outptr = g_malloc (outlen + 1); - - while ( (c = *inptr++) && c != '"') { - if (c == '\\' && *inptr) { - c = *inptr++; - } - *outptr++ = c; - } - *outptr = 0; - } - - *in = inptr; - - return out; + /* The grammar allows for multiple commas */ + while (g_ascii_isspace (*s) || *s == ',') + s++; + return s; } -char * -soup_header_param_decode_token (char **in) +static const char * +skip_item (const char *s) { - char *inptr = *in; - char *start; + gboolean quoted = FALSE; + const char *start = s; + + /* A list item ends at the last non-whitespace character + * before a comma which is not inside a quoted-string. Or at + * the end of the string. + */ - decode_lwsp (&inptr); - start = inptr; + while (*s) { + if (*s == '"') + quoted = !quoted; + else if (quoted) { + if (*s == '\\' && *(s + 1)) + s++; + } else { + if (*s == ',') + break; + } + s++; + } - while (*inptr && *inptr != '=' && *inptr != ',') - inptr++; + return unskip_lws (s, start); +} - if (inptr > start) { - *in = inptr; - return g_strndup (start, inptr - start); +/** + * soup_header_parse_list: + * @header: a header value + * + * Parses a header whose content is described by RFC2616 as + * "#something", where "something" does not itself contain commas, + * except as part of quoted-strings. + * + * Return value: a #GSList of list elements, as allocated strings + **/ +GSList * +soup_header_parse_list (const char *header) +{ + GSList *list = NULL; + const char *end; + + header = skip_commas (header); + while (*header) { + end = skip_item (header); + list = g_slist_prepend (list, g_strndup (header, end - header)); + header = skip_commas (end); } - else - return NULL; + + return g_slist_reverse (list); } -static char * -decode_value (char **in) +typedef struct { + char *item; + double qval; +} QualityItem; + +static int +sort_by_qval (const void *a, const void *b) { - char *inptr = *in; + QualityItem *qia = (QualityItem *)a; + QualityItem *qib = (QualityItem *)b; - decode_lwsp (&inptr); - if (*inptr == '"') - return decode_quoted_string (in); + if (qia->qval == qib->qval) + return 0; + else if (qia->qval < qib->qval) + return 1; else - return soup_header_param_decode_token (in); + return -1; } -GHashTable * -soup_header_param_parse_list (const char *header) +/** + * soup_header_parse_quality_list: + * @header: a header value + * @unacceptable: on return, will contain a list of unacceptable + * values + * + * Parses a header whose content is a list of items with optional + * "qvalue"s (eg, Accept, Accept-Charset, Accept-Encoding, + * Accept-Language, TE). + * + * If @unacceptable is not %NULL, then on return, it will contain the + * items with qvalue 0. Either way, those items will be removed from + * the main list. + * + * Return value: a #GSList of acceptable values (as allocated + * strings), highest-qvalue first. + **/ +GSList * +soup_header_parse_quality_list (const char *header, GSList **unacceptable) { - char *ptr; - gboolean added = FALSE; - GHashTable *params = g_hash_table_new (soup_str_case_hash, - soup_str_case_equal); - - ptr = (char *) header; - while (ptr && *ptr) { - char *name; - char *value; - - name = soup_header_param_decode_token (&ptr); - if (*ptr == '=') { - ptr++; - value = decode_value (&ptr); - g_hash_table_insert (params, name, value); - added = TRUE; + GSList *unsorted; + QualityItem *array; + GSList *sorted, *iter; + char *item, *semi; + const char *param, *equal, *value; + double qval; + int n; + + if (unacceptable) + *unacceptable = NULL; + + unsorted = soup_header_parse_list (header); + array = g_new0 (QualityItem, g_slist_length (unsorted)); + for (iter = unsorted, n = 0; iter; iter = iter->next) { + item = iter->data; + qval = 1.0; + for (semi = strchr (item, ';'); semi; semi = strchr (semi + 1, ';')) { + param = skip_lws (semi + 1); + if (*param != 'q') + continue; + equal = skip_lws (param + 1); + if (!equal || *equal != '=') + continue; + value = skip_lws (equal + 1); + if (!value) + continue; + + if (value[0] != '0' && value[0] != '1') + continue; + qval = (double)(value[0] - '0'); + if (value[0] == '0' && value[1] == '.') { + if (g_ascii_isdigit (value[2])) + qval += (double)(value[2] - '0') / 10; + if (g_ascii_isdigit (value[3])) + qval += (double)(value[3] - '0') / 100; + if (g_ascii_isdigit (value[4])) + qval += (double)(value[4] - '0') / 1000; + } + + *semi = '\0'; + break; } - if (*ptr == ',') - ptr++; + if (qval == 0.0) { + if (unacceptable) { + *unacceptable = g_slist_prepend (*unacceptable, + item); + } + } else { + array[n].item = item; + array[n].qval = qval; + n++; + } } + g_slist_free (unsorted); + + qsort (array, n, sizeof (QualityItem), sort_by_qval); + sorted = NULL; + while (n--) + sorted = g_slist_prepend (sorted, array[n].item); + g_free (array); - if (!added) { - g_hash_table_destroy (params); - params = NULL; + return sorted; +} + +/** + * soup_header_free_list: + * @list: a #GSList returned from soup_header_parse_list() or + * soup_header_parse_quality_list() + * + * Frees @list. + **/ +void +soup_header_free_list (GSList *list) +{ + GSList *l; + + for (l = list; l; l = l->next) + g_free (l->data); + g_slist_free (l); +} + +/** + * soup_header_contains: + * @header: An HTTP header suitable for parsing with + * soup_header_parse_list() + * @token: a token + * + * Parses @header to see if it contains the token @token (matched + * case-insensitively). Note that this can't be used with lists + * that have qvalues. + * + * Return value: whether or not @header contains @token + **/ +gboolean +soup_header_contains (const char *header, const char *token) +{ + const char *end; + guint len = strlen (token); + + header = skip_commas (header); + while (*header) { + end = skip_item (header); + if (end - header == len && + !g_ascii_strncasecmp (header, token, len)) + return TRUE; + header = skip_commas (end); } - return params; + return FALSE; } static void -destroy_param_hash_elements (gpointer key, gpointer value, gpointer user_data) +decode_quoted_string (char *quoted_string) { - g_free (key); - g_free (value); + char *src, *dst; + + src = quoted_string + 1; + dst = quoted_string; + while (*src && *src != '"') { + if (*src == '\\' && *(src + 1)) + src++; + *dst++ = *src++; + } + *dst = '\0'; } +/** + * soup_header_parse_param_list: + * @header: a header value + * + * Parses a header which is a list of something like + * token [ "=" ( token | quoted-string ) ] + * + * Tokens that don't have an associated value will still be added to + * the resulting hash table, but with a %NULL value. + * + * Return value: a #GHashTable of list elements. + **/ +GHashTable * +soup_header_parse_param_list (const char *header) +{ + GHashTable *params; + GSList *list, *iter; + char *item, *eq, *name_end, *value; + + list = soup_header_parse_list (header); + if (!list) + return NULL; + + params = g_hash_table_new_full (soup_str_case_hash, + soup_str_case_equal, + g_free, NULL); + + for (iter = list; iter; iter = iter->next) { + item = iter->data; + + eq = strchr (item, '='); + if (eq) { + name_end = (char *)unskip_lws (eq, item); + if (name_end == item) { + /* That's no good... */ + g_free (item); + continue; + } + + *name_end = '\0'; + + value = (char *)skip_lws (eq + 1); + if (*value == '"') + decode_quoted_string (value); + } else + value = NULL; + + g_hash_table_insert (params, item, value); + } + + return params; +} + +/** + * soup_header_free_param_list: + * @param_list: a #GHashTable returned from soup_header_parse_param_list() + * + * Frees @param_list. + **/ void -soup_header_param_destroy_hash (GHashTable *table) +soup_header_free_param_list (GHashTable *param_list) { - g_hash_table_foreach (table, destroy_param_hash_elements, NULL); - g_hash_table_destroy (table); + g_hash_table_destroy (param_list); } diff --git a/libsoup/soup-headers.h b/libsoup/soup-headers.h index 428ab94..d040e75 100644 --- a/libsoup/soup-headers.h +++ b/libsoup/soup-headers.h @@ -13,35 +13,37 @@ G_BEGIN_DECLS /* HTTP Header Parsing */ -gboolean soup_headers_parse_request (const char *str, - int len, - GHashTable *dest, - char **req_method, - char **req_path, - SoupHttpVersion *ver); - -gboolean soup_headers_parse_status_line (const char *status_line, - SoupHttpVersion *ver, - guint *status_code, - char **reason_phrase); - -gboolean soup_headers_parse_response (const char *str, - int len, - GHashTable *dest, - SoupHttpVersion *ver, - guint *status_code, - char **reason_phrase); - -/* HTTP parameterized header parsing */ - -char *soup_header_param_decode_token (char **in); - -GHashTable *soup_header_param_parse_list (const char *header); - -char *soup_header_param_copy_token (GHashTable *tokens, - char *t); - -void soup_header_param_destroy_hash (GHashTable *table); +guint soup_headers_parse_request (const char *str, + int len, + SoupMessageHeaders *req_headers, + char **req_method, + char **req_path, + SoupHTTPVersion *ver); + +gboolean soup_headers_parse_status_line (const char *status_line, + SoupHTTPVersion *ver, + guint *status_code, + char **reason_phrase); + +gboolean soup_headers_parse_response (const char *str, + int len, + SoupMessageHeaders *headers, + SoupHTTPVersion *ver, + guint *status_code, + char **reason_phrase); + +/* Individual header parsing */ + +GSList *soup_header_parse_list (const char *header); +GSList *soup_header_parse_quality_list (const char *header, + GSList **unacceptable); +void soup_header_free_list (GSList *list); + +gboolean soup_header_contains (const char *header, + const char *token); + +GHashTable *soup_header_parse_param_list (const char *header); +void soup_header_free_param_list (GHashTable *param_list); G_END_DECLS diff --git a/libsoup/soup-logger.c b/libsoup/soup-logger.c new file mode 100644 index 0000000..a822134 --- /dev/null +++ b/libsoup/soup-logger.c @@ -0,0 +1,638 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-logger.c + * + * Copyright (C) 2001-2004 Novell, Inc. + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "soup-logger.h" +#include "soup-message.h" +#include "soup-uri.h" + +/** + * SECTION:soup-logger + * @short_description: Debug logging support + * + * #SoupLogger watches a #SoupSession and logs the HTTP traffic that + * it generates, for debugging purposes. Many applications use an + * environment variable to determine whether or not to use + * #SoupLogger, and to determine the amount of debugging output. + * + * To use #SoupLogger, first create a logger with soup_logger_new(), + * optionally configure it with soup_logger_set_request_filter(), + * soup_logger_set_response_filter(), and soup_logger_set_printer(), + * and then attach it to a session (or multiple sessions) with + * soup_logger_attach(). + * + * By default, the debugging output is sent to %stdout, and looks + * something like: + * + * + * > POST /unauth HTTP/1.1 + * > Soup-Debug-Timestamp: 1200171744 + * > Soup-Debug: session 1 (0x612190), msg 1 (0x617000), conn 1 (0x612220) + * > Host: localhost + * > Content-Type: text/plain + * > Connection: close + * > + * > This is a test. + * + * < HTTP/1.1 201 Created + * < Soup-Debug-Timestamp: 1200171744 + * < Soup-Debug: msg 1 (0x617000) + * < Date: Sun, 12 Jan 2008 21:02:24 GMT + * < Content-Length: 0 + * + * + * The Soup-Debug-Timestamp line gives the time (as + * a #time_t) when the request was sent, or the response fully + * received. + * + * The Soup-Debug line gives further debugging + * information about the #SoupSession, #SoupMessage, and #SoupSocket + * involved; the hex numbers are the addresses of the objects in + * question (which may be useful if you are running in a debugger). + * The decimal IDs are simply counters that uniquely identify objects + * across the lifetime of the #SoupLogger. In particular, this can be + * used to identify when multiple messages are sent across the same + * connection. + **/ + +G_DEFINE_TYPE (SoupLogger, soup_logger, G_TYPE_OBJECT) + +typedef struct { + /* We use a mutex so that if requests are being run in + * multiple threads, we don't mix up the output. + */ + GMutex *lock; + + GQuark tag; + GHashTable *ids; + + SoupLoggerLogLevel level; + int max_body_size; + + SoupLoggerFilter request_filter; + gpointer request_filter_data; + GDestroyNotify request_filter_dnotify; + + SoupLoggerFilter response_filter; + gpointer response_filter_data; + GDestroyNotify response_filter_dnotify; + + SoupLoggerPrinter printer; + gpointer printer_data; + GDestroyNotify printer_dnotify; + char direction; +} SoupLoggerPrivate; +#define SOUP_LOGGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_LOGGER, SoupLoggerPrivate)) + +static void +soup_logger_init (SoupLogger *logger) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + priv->lock = g_mutex_new (); + priv->tag = g_quark_from_static_string (g_strdup_printf ("SoupLogger-%p", logger)); + priv->ids = g_hash_table_new (NULL, NULL); +} + +static void +finalize (GObject *object) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (object); + + g_hash_table_destroy (priv->ids); + + if (priv->request_filter_dnotify) + priv->request_filter_dnotify (priv->request_filter_data); + if (priv->response_filter_dnotify) + priv->response_filter_dnotify (priv->response_filter_data); + if (priv->printer_dnotify) + priv->printer_dnotify (priv->printer_data); + + g_mutex_free (priv->lock); + + G_OBJECT_CLASS (soup_logger_parent_class)->finalize (object); +} + +static void +soup_logger_class_init (SoupLoggerClass *logger_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (logger_class); + + g_type_class_add_private (logger_class, sizeof (SoupLoggerPrivate)); + + object_class->finalize = finalize; +} + +/** + * SoupLoggerLogLevel: + * @SOUP_LOGGER_LOG_NONE: No logging + * @SOUP_LOGGER_LOG_MINIMAL: Log the Request-Line or Status-Line and + * the Soup-Debug pseudo-headers + * @SOUP_LOGGER_LOG_HEADERS: Log the full request/response headers + * @SOUP_LOGGER_LOG_BODY: Log the full headers and request/response + * bodies. + * + * Describes the level of logging output to provide. + **/ + +/** + * soup_logger_new: + * @level: the debug level + * @max_body_size: the maximum body size to output, or -1 + * + * Creates a new #SoupLogger with the given debug level. If @level is + * %SOUP_LOGGER_LOG_BODY, @max_body_size gives the maximum number of + * bytes of the body that will be logged. (-1 means "no limit".) + * + * If you need finer control over what message parts are and aren't + * logged, use soup_logger_set_request_filter() and + * soup_logger_set_response_filter(). + **/ +SoupLogger * +soup_logger_new (SoupLoggerLogLevel level, int max_body_size) +{ + SoupLogger *logger; + SoupLoggerPrivate *priv; + + logger = g_object_new (SOUP_TYPE_LOGGER, NULL); + + priv = SOUP_LOGGER_GET_PRIVATE (logger); + priv->level = level; + priv->max_body_size = max_body_size; + + return logger; +} + +/** + * SoupLoggerFilter: + * @logger: the #SoupLogger + * @msg: the message being logged + * @user_data: the data passed to soup_logger_set_request_filter() + * or soup_logger_set_response_filter() + * + * The prototype for a logging filter. The filter callback will be + * invoked for each request or response, and should analyze it and + * return a #SoupLoggerLogLevel value indicating how much of the + * message to log. Eg, it might choose between %SOUP_LOGGER_LOG_BODY + * and %SOUP_LOGGER_LOG_HEADERS depending on the Content-Type. + * + * Return value: a #SoupLoggerLogLevel value indicating how much of + * the message to log + **/ + +/** + * soup_logger_set_request_filter: + * @logger: a #SoupLogger + * @request_filter: the callback for request debugging + * @filter_data: data to pass to the callback + * @destroy: a #GDestroyNotify to free @filter_data + * + * Sets up a filter to determine the log level for a given request. + * For each HTTP request @logger will invoke @request_filter to + * determine how much (if any) of that request to log. (If you do not + * set a request filter, @logger will just always log requests at the + * level passed to soup_logger_new().) + **/ +void +soup_logger_set_request_filter (SoupLogger *logger, + SoupLoggerFilter request_filter, + gpointer filter_data, + GDestroyNotify destroy) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + priv->request_filter = request_filter; + priv->request_filter_data = filter_data; + priv->request_filter_dnotify = destroy; +} + +/** + * soup_logger_set_response_filter: + * @logger: a #SoupLogger + * @response_filter: the callback for response debugging + * @filter_data: data to pass to the callback + * @destroy: a #GDestroyNotify to free @filter_data + * + * Sets up a filter to determine the log level for a given response. + * For each HTTP response @logger will invoke @response_filter to + * determine how much (if any) of that response to log. (If you do not + * set a response filter, @logger will just always log responses at + * the level passed to soup_logger_new().) + **/ +void +soup_logger_set_response_filter (SoupLogger *logger, + SoupLoggerFilter response_filter, + gpointer filter_data, + GDestroyNotify destroy) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + priv->response_filter = response_filter; + priv->response_filter_data = filter_data; + priv->response_filter_dnotify = destroy; +} + +/** + * SoupLoggerPrinter: + * @logger: the #SoupLogger + * @level: the level of the information being printed. + * @direction: a single-character prefix to @data + * @data: data to print + * @user_data: the data passed to soup_logger_set_printer() + * + * The prototype for a custom printing callback. + * + * @level indicates what kind of information is being printed. Eg, it + * will be %SOUP_LOGGER_LOG_HEADERS if @data is header data. + * + * @direction is either '<', '>', or ' ', and @data is the single line + * to print; the printer is expected to add a terminating newline. + * + * To get the effect of the default printer, you would do: + * + * + * printf ("%c %s\n", direction, data); + * + **/ + +/** + * soup_logger_set_printer: + * @logger: a #SoupLogger + * @printer: the callback for printing logging output + * @printer_data: data to pass to the callback + * @destroy: a #GDestroyNotify to free @printer_data + * + * Sets up an alternate log printing routine, if you don't want + * the log to go to %stdout. + **/ +void +soup_logger_set_printer (SoupLogger *logger, + SoupLoggerPrinter printer, + gpointer printer_data, + GDestroyNotify destroy) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + priv->printer = printer; + priv->printer_data = printer_data; + priv->printer_dnotify = destroy; +} + +static guint +soup_logger_get_id (SoupLogger *logger, gpointer object) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + return GPOINTER_TO_UINT (g_object_get_qdata (object, priv->tag)); +} + +static guint +soup_logger_set_id (SoupLogger *logger, gpointer object) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + gpointer klass = G_OBJECT_GET_CLASS (object); + gpointer id; + + id = g_hash_table_lookup (priv->ids, klass); + id = (char *)id + 1; + g_hash_table_insert (priv->ids, klass, id); + + g_object_set_qdata (object, priv->tag, id); + return GPOINTER_TO_UINT (id); +} + +static void +soup_logger_clear_id (SoupLogger *logger, gpointer object) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + g_object_set_qdata (object, priv->tag, NULL); +} + +static void request_started (SoupSession *session, SoupMessage *msg, + SoupSocket *socket, gpointer user_data); + +/** + * soup_logger_attach: + * @logger: a #SoupLogger + * @session: a #SoupSession + * + * Sets @logger to watch @session and print debug information for + * its messages. + * + * (The session will take a reference on @logger, which will be + * removed when you call soup_logger_detach(), or when the session is + * destroyed.) + **/ +void +soup_logger_attach (SoupLogger *logger, + SoupSession *session) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + if (!soup_logger_get_id (logger, session)) + soup_logger_set_id (logger, session); + g_signal_connect (session, "request_started", + G_CALLBACK (request_started), logger); + + g_object_set_qdata_full (G_OBJECT (session), priv->tag, + g_object_ref (logger), + g_object_unref); +} + +/** + * soup_logger_detach: + * @logger: a #SoupLogger + * @session: a #SoupSession + * + * Stops @logger from watching @session. + **/ +void +soup_logger_detach (SoupLogger *logger, + SoupSession *session) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + g_signal_handlers_disconnect_by_func (session, request_started, logger); + + g_object_set_qdata (G_OBJECT (session), priv->tag, NULL); +} + +static void +soup_logger_print (SoupLogger *logger, SoupLoggerLogLevel level, + const char *format, ...) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + va_list args; + char *data, *line, *end; + + va_start (args, format); + data = g_strdup_vprintf (format, args); + va_end (args); + + if (level == SOUP_LOGGER_LOG_BODY && priv->max_body_size > 0) { + if (strlen (data) > priv->max_body_size + 6) + strcpy (data + priv->max_body_size, "\n[...]"); + } + + line = data; + do { + end = strchr (line, '\n'); + if (end) + *end = '\0'; + if (priv->printer) { + priv->printer (logger, level, priv->direction, + line, priv->printer_data); + } else + printf ("%c %s\n", priv->direction, line); + + line = end + 1; + } while (end && *line); + + g_free (data); +} + +static void +print_header (const char *name, const char *value, gpointer logger) +{ + if (!g_ascii_strcasecmp (name, "Authorization") && + !g_ascii_strncasecmp (value, "Basic ", 6)) { + char *decoded, *p; + gsize len; + + decoded = (char *)g_base64_decode (value + 6, &len); + if (!decoded) + decoded = g_strdup (value); + p = strchr (decoded, ':'); + if (p) { + while (++p < decoded + len) + *p = '*'; + } + soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, + "%s: Basic [%.*s]", name, len, decoded); + g_free (decoded); + } else { + soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, + "%s: %s", name, value); + } +} + +static void +print_request (SoupLogger *logger, SoupMessage *msg, + SoupSession *session, SoupSocket *socket, + gboolean restarted) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + SoupLoggerLogLevel log_level; + SoupURI *uri; + + if (priv->request_filter) { + log_level = priv->request_filter (logger, msg, + priv->request_filter_data); + } else + log_level = priv->level; + + if (log_level == SOUP_LOGGER_LOG_NONE) + return; + + priv->direction = '>'; + + uri = soup_message_get_uri (msg); + if (msg->method == SOUP_METHOD_CONNECT) { + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, + "CONNECT %s:%u HTTP/1.%d", + uri->host, uri->port, + soup_message_get_http_version (msg)); + } else { + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, + "%s %s%s%s HTTP/1.%d", + msg->method, uri->path, + uri->query ? "?" : "", + uri->query ? uri->query : "", + soup_message_get_http_version (msg)); + } + + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, + "Soup-Debug-Timestamp: %lu", + (unsigned long)time (0)); + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, + "Soup-Debug: session %u (%p), msg %u (%p), conn %u (%p)%s", + soup_logger_get_id (logger, session), session, + soup_logger_get_id (logger, msg), msg, + soup_logger_get_id (logger, socket), socket, + restarted ? ", restarted" : ""); + + if (log_level == SOUP_LOGGER_LOG_MINIMAL) + return; + + print_header ("Host", uri->host, logger); + soup_message_headers_foreach (msg->request_headers, + print_header, logger); + if (log_level == SOUP_LOGGER_LOG_HEADERS) + return; + + if (msg->request_body->length) { + SoupBuffer *request; + + request = soup_message_body_flatten (msg->request_body); + soup_buffer_free (request); + + if (soup_message_headers_get_expectations (msg->request_headers) != SOUP_EXPECTATION_CONTINUE) { + soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, + "\n%s", msg->request_body->data); + } + } +} + +static void +print_response (SoupLogger *logger, SoupMessage *msg) +{ + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + SoupLoggerLogLevel log_level; + + if (priv->response_filter) { + log_level = priv->response_filter (logger, msg, + priv->response_filter_data); + } else + log_level = priv->level; + + if (log_level == SOUP_LOGGER_LOG_NONE) + return; + + priv->direction = '<'; + + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, + "HTTP/1.%d %u %s\n", + soup_message_get_http_version (msg), + msg->status_code, msg->reason_phrase); + + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, + "Soup-Debug-Timestamp: %lu", + (unsigned long)time (0)); + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, + "Soup-Debug: msg %u (%p)", + soup_logger_get_id (logger, msg), msg); + + if (log_level == SOUP_LOGGER_LOG_MINIMAL) + return; + + soup_message_headers_foreach (msg->response_headers, + print_header, logger); + if (log_level == SOUP_LOGGER_LOG_HEADERS) + return; + + if (msg->response_body->length) { + soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, + "\n%s", msg->response_body->data); + } +} + +static void +got_informational (SoupMessage *msg, gpointer user_data) +{ + SoupLogger *logger = user_data; + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + g_mutex_lock (priv->lock); + + print_response (logger, msg); + priv->direction = ' '; + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ""); + + if (msg->status_code == SOUP_STATUS_CONTINUE && msg->request_body->data) { + SoupLoggerLogLevel log_level; + + priv->direction = '>'; + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, + "[Now sending request body...]"); + + if (priv->request_filter) { + log_level = priv->request_filter (logger, msg, + priv->request_filter_data); + } else + log_level = priv->level; + + if (log_level == SOUP_LOGGER_LOG_BODY) { + soup_logger_print (logger, SOUP_LOGGER_LOG_BODY, + "%s", msg->request_body->data); + } + + priv->direction = ' '; + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ""); + } + + g_mutex_unlock (priv->lock); +} + +static void +got_body (SoupMessage *msg, gpointer user_data) +{ + SoupLogger *logger = user_data; + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + + g_mutex_lock (priv->lock); + + print_response (logger, msg); + priv->direction = ' '; + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ""); + + g_mutex_unlock (priv->lock); +} + +static void +finished_handler (SoupMessage *msg, gpointer user_data) +{ + SoupLogger *logger = user_data; + + g_signal_handlers_disconnect_by_func (msg, got_informational, logger); + g_signal_handlers_disconnect_by_func (msg, got_body, logger); + g_signal_handlers_disconnect_by_func (msg, finished_handler, logger); + + soup_logger_clear_id (logger, msg); +} + +static void +request_started (SoupSession *session, SoupMessage *msg, + SoupSocket *socket, gpointer user_data) +{ + SoupLogger *logger = user_data; + SoupLoggerPrivate *priv = SOUP_LOGGER_GET_PRIVATE (logger); + gboolean restarted; + guint msg_id; + + msg_id = soup_logger_get_id (logger, msg); + if (msg_id) + restarted = TRUE; + else { + msg_id = soup_logger_set_id (logger, msg); + restarted = FALSE; + + g_signal_connect (msg, "got-informational", + G_CALLBACK (got_informational), + logger); + g_signal_connect (msg, "got-body", + G_CALLBACK (got_body), + logger); + g_signal_connect (msg, "finished", + G_CALLBACK (finished_handler), + logger); + } + + if (!soup_logger_get_id (logger, socket)) + soup_logger_set_id (logger, socket); + + print_request (logger, msg, session, socket, restarted); + priv->direction = ' '; + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ""); +} diff --git a/libsoup/soup-logger.h b/libsoup/soup-logger.h new file mode 100644 index 0000000..dc96bc0 --- /dev/null +++ b/libsoup/soup-logger.h @@ -0,0 +1,76 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifndef SOUP_LOGGER_H +#define SOUP_LOGGER_H 1 + +#include + +#define SOUP_TYPE_LOGGER (soup_logger_get_type ()) +#define SOUP_LOGGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_LOGGER, SoupLogger)) +#define SOUP_LOGGER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_LOGGER, SoupLoggerClass)) +#define SOUP_IS_LOGGER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_LOGGER)) +#define SOUP_IS_LOGGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_LOGGER)) +#define SOUP_LOGGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_LOGGER, SoupLoggerClass)) + +typedef struct SoupLogger SoupLogger; +typedef struct SoupLoggerClass SoupLoggerClass; + +typedef enum { + SOUP_LOGGER_LOG_NONE, + SOUP_LOGGER_LOG_MINIMAL, + SOUP_LOGGER_LOG_HEADERS, + SOUP_LOGGER_LOG_BODY +} SoupLoggerLogLevel; + +typedef SoupLoggerLogLevel (*SoupLoggerFilter) (SoupLogger *logger, + SoupMessage *msg, + gpointer user_data); + +typedef void (*SoupLoggerPrinter) (SoupLogger *logger, + SoupLoggerLogLevel level, + char direction, + const char *data, + gpointer user_data); + +struct SoupLogger { + GObject parent; + +}; + +struct SoupLoggerClass { + GObjectClass parent_class; + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); +}; + +GType soup_logger_get_type (void); + +SoupLogger *soup_logger_new (SoupLoggerLogLevel level, + int max_body_size); +void soup_logger_attach (SoupLogger *logger, + SoupSession *session); +void soup_logger_detach (SoupLogger *logger, + SoupSession *session); + +void soup_logger_set_request_filter (SoupLogger *logger, + SoupLoggerFilter request_filter, + gpointer filter_data, + GDestroyNotify destroy); +void soup_logger_set_response_filter (SoupLogger *logger, + SoupLoggerFilter response_filter, + gpointer filter_data, + GDestroyNotify destroy); + +void soup_logger_set_printer (SoupLogger *logger, + SoupLoggerPrinter printer, + gpointer printer_data, + GDestroyNotify destroy); + +#endif /* SOUP_LOGGER_H */ diff --git a/libsoup/soup-marshal.list b/libsoup/soup-marshal.list index 9f42926..4e1b2bc 100644 --- a/libsoup/soup-marshal.list +++ b/libsoup/soup-marshal.list @@ -1,4 +1,7 @@ -NONE:NONE +NONE:BOXED NONE:INT +NONE:NONE NONE:OBJECT -NONE:OBJECT,STRING,STRING,POINTER,POINTER +NONE:OBJECT,OBJECT +NONE:OBJECT,POINTER +NONE:OBJECT,OBJECT,BOOLEAN diff --git a/libsoup/soup-md5-utils.c b/libsoup/soup-md5-utils.c deleted file mode 100644 index e3dedfa..0000000 --- a/libsoup/soup-md5-utils.c +++ /dev/null @@ -1,306 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * This code implements the MD5 message-digest algorithm. - * The algorithm is due to Ron Rivest. This code was - * written by Colin Plumb in 1993, no copyright is claimed. - * This code is in the public domain; do with it what you wish. - * - * Equivalent code is available from RSA Data Security, Inc. - * This code has been tested against that, and is equivalent, - * except that you don't need to include two pages of legalese - * with every copy. - * - * To compute the message digest of a chunk of bytes, declare an - * SoupMD5Context structure, pass it to soup_md5_init, call soup_md5_update as - * needed on buffers full of bytes, and then call soup_md5_Final, which - * will fill a supplied 16-byte array with the digest. - */ - -#include "soup-md5-utils.h" -#include - -static void soup_md5_transform (guint32 buf[4], const guint32 in[16]); - -/* - * Note: this code is harmless on little-endian machines. - */ -static void -byte_reverse (guchar *buf, guint32 longs) -{ - guint32 t; - do { - t = (guint32) ((guint32) buf[3] << 8 | buf[2]) << 16 | - ((guint32) buf[1] << 8 | buf[0]); - *(guint32 *) buf = t; - buf += 4; - } while (--longs); -} - -/** - * soup_md5_init: Initialise an md5 context object - * @ctx: md5 context - * - * Initialise an md5 buffer. - * - **/ -void -soup_md5_init (SoupMD5Context *ctx) -{ - ctx->buf[0] = 0x67452301; - ctx->buf[1] = 0xefcdab89; - ctx->buf[2] = 0x98badcfe; - ctx->buf[3] = 0x10325476; - - ctx->bits[0] = 0; - ctx->bits[1] = 0; - - ctx->doByteReverse = (G_BYTE_ORDER == G_BIG_ENDIAN); -} - - - -/** - * soup_md5_update: add a buffer to md5 hash computation - * @ctx: conetxt object used for md5 computaion - * @buf: buffer to add - * @len: buffer length - * - * Update context to reflect the concatenation of another buffer full - * of bytes. Use this to progressively construct an md5 hash. - **/ -void -soup_md5_update (SoupMD5Context *ctx, gconstpointer buf, gsize len) -{ - const char *cbuf = buf; - guint32 t; - - /* Update bitcount */ - - t = ctx->bits[0]; - if ((ctx->bits[0] = t + ((guint32) len << 3)) < t) - ctx->bits[1]++; /* Carry from low to high */ - ctx->bits[1] += len >> 29; - - t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ - - /* Handle any leading odd-sized chunks */ - - if (t) { - guchar *p = (guchar *) ctx->in + t; - - t = 64 - t; - if (len < t) { - memcpy (p, cbuf, len); - return; - } - memcpy (p, cbuf, t); - if (ctx->doByteReverse) - byte_reverse (ctx->in, 16); - soup_md5_transform (ctx->buf, (guint32 *) ctx->in); - cbuf += t; - len -= t; - } - /* Process data in 64-byte chunks */ - - while (len >= 64) { - memcpy (ctx->in, cbuf, 64); - if (ctx->doByteReverse) - byte_reverse (ctx->in, 16); - soup_md5_transform (ctx->buf, (guint32 *) ctx->in); - cbuf += 64; - len -= 64; - } - - /* Handle any remaining bytes of data. */ - - memcpy (ctx->in, cbuf, len); -} - -/* - * Final wrapup - pad to 64-byte boundary with the bit pattern - * 1 0* (64-bit count of bits processed, MSB-first) - */ -/** - * soup_md5_final: copy the final md5 hash to a bufer - * @digest: 16 bytes buffer - * @ctx: context containing the calculated md5 - * - * Performs the final md5 transformation on the context, and - * then copies the resulting md5 hash to a buffer - **/ -void -soup_md5_final (SoupMD5Context *ctx, guchar digest[16]) -{ - guint32 count; - guchar *p; - - /* Compute number of bytes mod 64 */ - count = (ctx->bits[0] >> 3) & 0x3F; - - /* Set the first char of padding to 0x80. This is safe since there is - always at least one byte free */ - p = ctx->in + count; - *p++ = 0x80; - - /* Bytes of padding needed to make 64 bytes */ - count = 64 - 1 - count; - - /* Pad out to 56 mod 64 */ - if (count < 8) { - /* Two lots of padding: Pad the first block to 64 bytes */ - memset (p, 0, count); - if (ctx->doByteReverse) - byte_reverse (ctx->in, 16); - soup_md5_transform (ctx->buf, (guint32 *) ctx->in); - - /* Now fill the next block with 56 bytes */ - memset (ctx->in, 0, 56); - } else { - /* Pad block to 56 bytes */ - memset (p, 0, count - 8); - } - if (ctx->doByteReverse) - byte_reverse (ctx->in, 14); - - /* Append length in bits and transform */ - ((guint32 *) ctx->in)[14] = ctx->bits[0]; - ((guint32 *) ctx->in)[15] = ctx->bits[1]; - - soup_md5_transform (ctx->buf, (guint32 *) ctx->in); - if (ctx->doByteReverse) - byte_reverse ((guchar *) ctx->buf, 4); - memcpy (digest, ctx->buf, 16); -} - - - -/** - * soup_md5_final_hex: copy the final md5 hash to a bufer - * @digest: 33 bytes buffer (32 hex digits plus NUL) - * @ctx: context containing the calculated md5 - * - * As soup_md5_final(), but copies the final md5 hash - * to a buffer as a NUL-terminated hexadecimal string - **/ -void -soup_md5_final_hex (SoupMD5Context *ctx, char hex_digest[33]) -{ - static const char hexdigits[16] = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - }; - guchar digest[16]; - int p; - - soup_md5_final (ctx, digest); - - hex_digest[32] = 0; - for (p = 15; p >= 0; p--) { - guchar b = digest[p]; - hex_digest[p * 2 + 1] = hexdigits[ (b & 0x0F ) ]; - hex_digest[p * 2] = hexdigits[ (b & 0xF0 ) >> 4 ]; - } -} - - -/* The four core functions - F1 is optimized somewhat */ - -/* #define F1(x, y, z) (x & y | ~x & z) */ -#define F1(x, y, z) (z ^ (x & (y ^ z))) -#define F2(x, y, z) F1(z, x, y) -#define F3(x, y, z) (x ^ y ^ z) -#define F4(x, y, z) (y ^ (x | ~z)) - -/* This is the central step in the MD5 algorithm. */ -#define MD5STEP(f, w, x, y, z, data, s) \ - ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) - -/* - * The core of the MD5 algorithm, this alters an existing MD5 hash to - * reflect the addition of 16 longwords of new data. soup_md5_Update blocks - * the data and converts bytes into longwords for this routine. - */ -static void -soup_md5_transform (guint32 buf[4], const guint32 in[16]) -{ - register guint32 a, b, c, d; - - a = buf[0]; - b = buf[1]; - c = buf[2]; - d = buf[3]; - - MD5STEP (F1, a, b, c, d, in[0] + 0xd76aa478, 7); - MD5STEP (F1, d, a, b, c, in[1] + 0xe8c7b756, 12); - MD5STEP (F1, c, d, a, b, in[2] + 0x242070db, 17); - MD5STEP (F1, b, c, d, a, in[3] + 0xc1bdceee, 22); - MD5STEP (F1, a, b, c, d, in[4] + 0xf57c0faf, 7); - MD5STEP (F1, d, a, b, c, in[5] + 0x4787c62a, 12); - MD5STEP (F1, c, d, a, b, in[6] + 0xa8304613, 17); - MD5STEP (F1, b, c, d, a, in[7] + 0xfd469501, 22); - MD5STEP (F1, a, b, c, d, in[8] + 0x698098d8, 7); - MD5STEP (F1, d, a, b, c, in[9] + 0x8b44f7af, 12); - MD5STEP (F1, c, d, a, b, in[10] + 0xffff5bb1, 17); - MD5STEP (F1, b, c, d, a, in[11] + 0x895cd7be, 22); - MD5STEP (F1, a, b, c, d, in[12] + 0x6b901122, 7); - MD5STEP (F1, d, a, b, c, in[13] + 0xfd987193, 12); - MD5STEP (F1, c, d, a, b, in[14] + 0xa679438e, 17); - MD5STEP (F1, b, c, d, a, in[15] + 0x49b40821, 22); - - MD5STEP (F2, a, b, c, d, in[1] + 0xf61e2562, 5); - MD5STEP (F2, d, a, b, c, in[6] + 0xc040b340, 9); - MD5STEP (F2, c, d, a, b, in[11] + 0x265e5a51, 14); - MD5STEP (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); - MD5STEP (F2, a, b, c, d, in[5] + 0xd62f105d, 5); - MD5STEP (F2, d, a, b, c, in[10] + 0x02441453, 9); - MD5STEP (F2, c, d, a, b, in[15] + 0xd8a1e681, 14); - MD5STEP (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); - MD5STEP (F2, a, b, c, d, in[9] + 0x21e1cde6, 5); - MD5STEP (F2, d, a, b, c, in[14] + 0xc33707d6, 9); - MD5STEP (F2, c, d, a, b, in[3] + 0xf4d50d87, 14); - MD5STEP (F2, b, c, d, a, in[8] + 0x455a14ed, 20); - MD5STEP (F2, a, b, c, d, in[13] + 0xa9e3e905, 5); - MD5STEP (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); - MD5STEP (F2, c, d, a, b, in[7] + 0x676f02d9, 14); - MD5STEP (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); - - MD5STEP (F3, a, b, c, d, in[5] + 0xfffa3942, 4); - MD5STEP (F3, d, a, b, c, in[8] + 0x8771f681, 11); - MD5STEP (F3, c, d, a, b, in[11] + 0x6d9d6122, 16); - MD5STEP (F3, b, c, d, a, in[14] + 0xfde5380c, 23); - MD5STEP (F3, a, b, c, d, in[1] + 0xa4beea44, 4); - MD5STEP (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); - MD5STEP (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); - MD5STEP (F3, b, c, d, a, in[10] + 0xbebfbc70, 23); - MD5STEP (F3, a, b, c, d, in[13] + 0x289b7ec6, 4); - MD5STEP (F3, d, a, b, c, in[0] + 0xeaa127fa, 11); - MD5STEP (F3, c, d, a, b, in[3] + 0xd4ef3085, 16); - MD5STEP (F3, b, c, d, a, in[6] + 0x04881d05, 23); - MD5STEP (F3, a, b, c, d, in[9] + 0xd9d4d039, 4); - MD5STEP (F3, d, a, b, c, in[12] + 0xe6db99e5, 11); - MD5STEP (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); - MD5STEP (F3, b, c, d, a, in[2] + 0xc4ac5665, 23); - - MD5STEP (F4, a, b, c, d, in[0] + 0xf4292244, 6); - MD5STEP (F4, d, a, b, c, in[7] + 0x432aff97, 10); - MD5STEP (F4, c, d, a, b, in[14] + 0xab9423a7, 15); - MD5STEP (F4, b, c, d, a, in[5] + 0xfc93a039, 21); - MD5STEP (F4, a, b, c, d, in[12] + 0x655b59c3, 6); - MD5STEP (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); - MD5STEP (F4, c, d, a, b, in[10] + 0xffeff47d, 15); - MD5STEP (F4, b, c, d, a, in[1] + 0x85845dd1, 21); - MD5STEP (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); - MD5STEP (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); - MD5STEP (F4, c, d, a, b, in[6] + 0xa3014314, 15); - MD5STEP (F4, b, c, d, a, in[13] + 0x4e0811a1, 21); - MD5STEP (F4, a, b, c, d, in[4] + 0xf7537e82, 6); - MD5STEP (F4, d, a, b, c, in[11] + 0xbd3af235, 10); - MD5STEP (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); - MD5STEP (F4, b, c, d, a, in[9] + 0xeb86d391, 21); - - buf[0] += a; - buf[1] += b; - buf[2] += c; - buf[3] += d; -} - diff --git a/libsoup/soup-md5-utils.h b/libsoup/soup-md5-utils.h deleted file mode 100644 index 83eb031..0000000 --- a/libsoup/soup-md5-utils.h +++ /dev/null @@ -1,42 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * This code implements the MD5 message-digest algorithm. - * The algorithm is due to Ron Rivest. This code was - * written by Colin Plumb in 1993, no copyright is claimed. - * This code is in the public domain; do with it what you wish. - * - * Equivalent code is available from RSA Data Security, Inc. - * This code has been tested against that, and is equivalent, - * except that you don't need to include two pages of legalese - * with every copy. - * - * To compute the message digest of a chunk of bytes, declare an - * MD5Context structure, pass it to rpmMD5Init, call rpmMD5Update as - * needed on buffers full of bytes, and then call rpmMD5Final, which - * will fill a supplied 16-byte array with the digest. - */ - -#ifndef SOUP_MD5_UTILS_H -#define SOUP_MD5_UTILS_H - -#include - -typedef struct { - /*< private >*/ - guint32 buf[4]; - guint32 bits[2]; - guchar in[64]; - gboolean doByteReverse; -} SoupMD5Context; - -void soup_md5_init (SoupMD5Context *ctx); -void soup_md5_update (SoupMD5Context *ctx, - gconstpointer buf, - gsize len); -void soup_md5_final (SoupMD5Context *ctx, - guchar digest[16]); -void soup_md5_final_hex (SoupMD5Context *ctx, - char digest[33]); - - -#endif /* SOUP_MD5_UTILS_H */ diff --git a/libsoup/soup-message-body.c b/libsoup/soup-message-body.c new file mode 100644 index 0000000..effabd7 --- /dev/null +++ b/libsoup/soup-message-body.c @@ -0,0 +1,429 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-message-body.c: SoupMessage request/response bodies + * + * Copyright (C) 2000-2003, Ximian, Inc. + */ + +#include + +#include "soup-message-body.h" + +/** + * SECTION:soup-message-body + * @short_description: HTTP message body + * @see_also: #SoupMessage + * + * #SoupMessageBody represents the request or response body of a + * #SoupMessage. + * + * In addition to #SoupMessageBody, libsoup also defines a "smaller" + * data buffer type, #SoupBuffer, which is primarily used as a + * component of #SoupMessageBody. In particular, when using chunked + * encoding to transmit or receive a message, each chunk is + * represented as a #SoupBuffer. + **/ + +/** + * SoupMemoryUse: + * @SOUP_MEMORY_STATIC: The memory is statically allocated and + * constant; libsoup can use the passed-in buffer directly and not + * need to worry about it being modified or freed. + * @SOUP_MEMORY_TAKE: The caller has allocated the memory for the + * #SoupBuffer's use; libsoup will assume ownership of it and free it + * (with g_free()) when it is done with it. + * @SOUP_MEMORY_COPY: The passed-in data belongs to the caller; the + * #SoupBuffer will copy it into new memory, leaving the caller free + * to reuse the original memory. + * @SOUP_MEMORY_TEMPORARY: The passed-in data belongs to the caller, + * but will remain valid for the lifetime of the #SoupBuffer. The + * difference between this and @SOUP_MEMORY_STATIC is that if you copy + * a @SOUP_MEMORY_TEMPORARY buffer, it will make a copy of the memory + * as well, rather than reusing the original memory. + * + * Describes how #SoupBuffer should use the data passed in by the + * caller. + **/ + +/** + * SoupBuffer: + * @data: the data + * @length: length of @data + * + * A data buffer, generally used to represent a chunk of a + * #SoupMessageBody. + * + * @data is a #char because that's generally convenient; in some + * situations you may need to cast it to #guchar or another type. + **/ + +typedef struct { + SoupBuffer buffer; + SoupMemoryUse use; + guint refcount; + + /* @other is used in subbuffers to store a reference to + * the parent buffer, or in TEMPORARY buffers to store a + * reference to a copy (see soup_buffer_copy()). Either + * way, we hold a ref. + */ + SoupBuffer *other; +} SoupBufferPrivate; + +/** + * soup_buffer_new: + * @use: how @data is to be used by the buffer + * @data: data + * @length: length of @data + * + * Creates a new #SoupBuffer containing @length bytes from @data. + * + * Return value: the new #SoupBuffer. + **/ +SoupBuffer * +soup_buffer_new (SoupMemoryUse use, gconstpointer data, gsize length) +{ + SoupBufferPrivate *priv = g_slice_new0 (SoupBufferPrivate); + + if (use == SOUP_MEMORY_COPY) { + priv->buffer.data = g_memdup (data, length); + priv->use = SOUP_MEMORY_TAKE; + } else { + priv->buffer.data = data; + priv->use = use; + } + priv->buffer.length = length; + priv->refcount = 1; + + return (SoupBuffer *)priv; +} + +/** + * soup_buffer_new_subbuffer: + * @parent: the parent #SoupBuffer + * @offset: offset within @parent to start at + * @length: number of bytes to copy from @parent + * + * Creates a new #SoupBuffer containing @length bytes "copied" from + * @parent starting at @offset. (Normally this will not actually copy + * any data, but will instead simply reference the same data as + * @parent does.) + * + * Return value: the new #SoupBuffer. + **/ +SoupBuffer * +soup_buffer_new_subbuffer (SoupBuffer *parent, gsize offset, gsize length) +{ + SoupBufferPrivate *priv; + + priv = g_slice_new0 (SoupBufferPrivate); + priv->other = soup_buffer_copy (parent); + priv->buffer.data = priv->other->data + offset; + priv->buffer.length = length; + priv->use = SOUP_MEMORY_STATIC; + priv->refcount = 1; + return (SoupBuffer *)priv; +} + +/** + * soup_buffer_copy: + * @buffer: a #SoupBuffer + * + * Makes a copy of @buffer. In reality, #SoupBuffer is a refcounted + * type, and calling soup_buffer_copy() will normally just increment + * the refcount on @buffer and return it. However, if @buffer was + * created with #SOUP_MEMORY_TEMPORARY memory, then soup_buffer_copy() + * will actually return a copy of it, so that the data in the copy + * will remain valid after the temporary buffer is freed. + * + * Return value: the new (or newly-reffed) buffer + **/ +SoupBuffer * +soup_buffer_copy (SoupBuffer *buffer) +{ + SoupBufferPrivate *priv = (SoupBufferPrivate *)buffer; + + /* For non-TEMPORARY buffers, this is just a ref */ + if (priv->use != SOUP_MEMORY_TEMPORARY) { + priv->refcount++; + return buffer; + } + + /* For TEMPORARY buffers, we need to do a real copy the + * first time, and then after that, we just keep returning + * the copy. Use priv->other to store the copy. + */ + + if (!priv->other) { + priv->other = soup_buffer_new (SOUP_MEMORY_COPY, + buffer->data, buffer->length); + } + return soup_buffer_copy (priv->other); +} + +/** + * soup_buffer_free: + * @buffer: a #SoupBuffer + * + * Frees @buffer. (In reality, as described in the documentation for + * soup_buffer_copy(), this is actually an "unref" operation, and may + * or may not actually free @buffer.) + **/ +void +soup_buffer_free (SoupBuffer *buffer) +{ + SoupBufferPrivate *priv = (SoupBufferPrivate *)buffer; + + if (!--priv->refcount) { + if (priv->use == SOUP_MEMORY_TAKE) + g_free ((gpointer)buffer->data); + if (priv->other) + soup_buffer_free (priv->other); + g_slice_free (SoupBufferPrivate, priv); + } +} + +GType +soup_buffer_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static ( + g_intern_static_string ("SoupBuffer"), + (GBoxedCopyFunc)soup_buffer_copy, + (GBoxedFreeFunc)soup_buffer_free); + } + return type; +} + + +/** + * SoupMessageBody: + * @data: the data + * @length: length of @data + * + * A #SoupMessage request or response body. + * + * Note that while @length always reflects the full length of the + * message body, @data is normally %NULL, and will only be filled in + * after soup_message_body_flatten() is called. For client-side + * messages, this automatically happens for the response body after it + * has been fully read, unless you set the + * %SOUP_MESSAGE_OVERWRITE_CHUNKS flags. Likewise, for server-side + * messages, the request body is automatically filled in after being + * read. + * + * As an added bonus, when @data is filled in, it is always terminated + * with a '\0' byte (which is not reflected in @length). + **/ + +typedef struct { + SoupMessageBody body; + GSList *chunks, *last; + SoupBuffer *flattened; +} SoupMessageBodyPrivate; + +/** + * soup_message_body_new: + * + * Creates a new #SoupMessageBody. #SoupMessage uses this internally; you + * will not normally need to call it yourself. + * + * Return value: a new #SoupMessageBody. + **/ +SoupMessageBody * +soup_message_body_new (void) +{ + return (SoupMessageBody *)g_slice_new0 (SoupMessageBodyPrivate); +} + +static void +append_buffer (SoupMessageBody *body, SoupBuffer *buffer) +{ + SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; + + if (priv->last) { + priv->last = g_slist_append (priv->last, buffer); + priv->last = priv->last->next; + } else + priv->chunks = priv->last = g_slist_append (NULL, buffer); + + if (priv->flattened) { + soup_buffer_free (priv->flattened); + priv->flattened = NULL; + body->data = NULL; + } + body->length += buffer->length; +} + +/** + * soup_message_body_append: + * @body: a #SoupMessageBody + * @use: how to use @data + * @data: data to append + * @length: length of @data + * + * Appends @length bytes from @data to @body according to @use. + **/ +void +soup_message_body_append (SoupMessageBody *body, SoupMemoryUse use, + gconstpointer data, gsize length) +{ + if (length > 0) + append_buffer (body, soup_buffer_new (use, data, length)); +} + +/** + * soup_message_body_append_buffer: + * @body: a #SoupMessageBody + * @buffer: a #SoupBuffer + * + * Appends the data from @buffer to @body. (#SoupMessageBody uses + * #SoupBuffers internally, so this is normally a constant-time + * operation that doesn't actually require copying the data in + * @buffer.) + **/ +void +soup_message_body_append_buffer (SoupMessageBody *body, SoupBuffer *buffer) +{ + g_return_if_fail (buffer->length > 0); + append_buffer (body, soup_buffer_copy (buffer)); +} + +/** + * soup_message_body_truncate: + * @body: a #SoupMessageBody + * + * Deletes all of the data in @body. + **/ +void +soup_message_body_truncate (SoupMessageBody *body) +{ + SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; + GSList *iter; + + for (iter = priv->chunks; iter; iter = iter->next) + soup_buffer_free (iter->data); + g_slist_free (priv->chunks); + priv->chunks = priv->last = NULL; + + if (priv->flattened) { + soup_buffer_free (priv->flattened); + priv->flattened = NULL; + body->data = NULL; + } + body->length = 0; +} + +/** + * soup_message_body_complete: + * @body: a #SoupMessageBody + * + * Tags @body as being complete; Call this when using chunked encoding + * after you have appended the last chunk. + **/ +void +soup_message_body_complete (SoupMessageBody *body) +{ + append_buffer (body, soup_buffer_new (SOUP_MEMORY_STATIC, NULL, 0)); +} + +/** + * soup_message_body_flatten: + * @body: a #SoupMessageBody + * + * Fills in @body's data field with a buffer containing all of the + * data in @body (plus an additional '\0' byte not counted by @body's + * length field). + * + * Return value: a #SoupBuffer containing the same data as @body. + * (You must free this buffer if you do not want it.) + **/ +SoupBuffer * +soup_message_body_flatten (SoupMessageBody *body) +{ + SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; + char *buf, *ptr; + GSList *iter; + SoupBuffer *chunk; + + if (!priv->flattened) { +#if GLIB_SIZEOF_SIZE_T < 8 + g_return_val_if_fail (body->length < G_MAXSIZE, NULL); +#endif + + buf = ptr = g_malloc (body->length + 1); + for (iter = priv->chunks; iter; iter = iter->next) { + chunk = iter->data; + memcpy (ptr, chunk->data, chunk->length); + ptr += chunk->length; + } + *ptr = '\0'; + + priv->flattened = soup_buffer_new (SOUP_MEMORY_TAKE, + buf, body->length); + body->data = priv->flattened->data; + } + + return soup_buffer_copy (priv->flattened); +} + +/** + * soup_message_body_get_chunk: + * @body: a #SoupMessageBody + * @offset: an offset + * + * Gets a #SoupBuffer containing data from @body starting at @offset. + * The size of the returned chunk is unspecified. You can iterate + * through the entire body by first calling + * soup_message_body_get_chunk() with an offset of 0, and then on each + * successive call, increment the offset by the length of the + * previously-returned chunk. + * + * If @offset is greater than or equal to the total length of @body, + * then the return value depends on whether or not + * soup_message_body_complete() has been called or not; if it has, + * then soup_message_body_get_chunk() will return a 0-length chunk + * (indicating the end of @body). If it has not, then + * soup_message_body_get_chunk() will return %NULL (indicating that + * @body may still potentially have more data, but that data is not + * currently available). + * + * Return value: a #SoupBuffer, or %NULL. + **/ +SoupBuffer * +soup_message_body_get_chunk (SoupMessageBody *body, goffset offset) +{ + SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; + GSList *iter; + SoupBuffer *chunk = NULL; + + for (iter = priv->chunks; iter; iter = iter->next) { + chunk = iter->data; + + if (offset < chunk->length || offset == 0) + break; + + offset -= chunk->length; + } + + if (!iter) + return NULL; + + if (offset == 0) + return soup_buffer_copy (chunk); + else { + return soup_buffer_new_subbuffer (chunk, offset, + chunk->length - offset); + } +} + +void +soup_message_body_free (SoupMessageBody *body) +{ + SoupMessageBodyPrivate *priv = (SoupMessageBodyPrivate *)body; + + soup_message_body_truncate (body); + g_slice_free (SoupMessageBodyPrivate, priv); +} diff --git a/libsoup/soup-message-body.h b/libsoup/soup-message-body.h new file mode 100644 index 0000000..329c773 --- /dev/null +++ b/libsoup/soup-message-body.h @@ -0,0 +1,63 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2000-2003, Ximian, Inc. + */ + +#ifndef SOUP_MESSAGE_BODY_H +#define SOUP_MESSAGE_BODY_H 1 + +#include + +G_BEGIN_DECLS + +typedef enum { + SOUP_MEMORY_STATIC, + SOUP_MEMORY_TAKE, + SOUP_MEMORY_COPY, + SOUP_MEMORY_TEMPORARY, +} SoupMemoryUse; + +typedef struct { + const char *data; + gsize length; +} SoupBuffer; + +GType soup_buffer_get_type (void); +#define SOUP_TYPE_BUFFER (soup_buffer_get_type ()) + +SoupBuffer *soup_buffer_new (SoupMemoryUse use, + gconstpointer data, + gsize length); +SoupBuffer *soup_buffer_new_subbuffer (SoupBuffer *parent, + gsize offset, + gsize length); + +SoupBuffer *soup_buffer_copy (SoupBuffer *buffer); +void soup_buffer_free (SoupBuffer *buffer); + +typedef struct { + const char *data; + goffset length; +} SoupMessageBody; + +SoupMessageBody *soup_message_body_new (void); + +void soup_message_body_append (SoupMessageBody *body, + SoupMemoryUse use, + gconstpointer data, + gsize length); +void soup_message_body_append_buffer (SoupMessageBody *body, + SoupBuffer *buffer); +void soup_message_body_truncate (SoupMessageBody *body); +void soup_message_body_complete (SoupMessageBody *body); + +SoupBuffer *soup_message_body_flatten (SoupMessageBody *body); + +SoupBuffer *soup_message_body_get_chunk (SoupMessageBody *body, + goffset offset); + +void soup_message_body_free (SoupMessageBody *body); + +G_END_DECLS + +#endif /* SOUP_MESSAGE_BODY_H */ diff --git a/libsoup/soup-message-client-io.c b/libsoup/soup-message-client-io.c index 109d976..5d5b244 100644 --- a/libsoup/soup-message-client-io.c +++ b/libsoup/soup-message-client-io.c @@ -20,13 +20,11 @@ static guint parse_response_headers (SoupMessage *req, char *headers, guint headers_len, - SoupTransferEncoding *encoding, - guint *content_len, + SoupEncoding *encoding, gpointer user_data) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); - SoupHttpVersion version; - GHashTable *resp_hdrs; + SoupHTTPVersion version; g_free((char*)req->reason_phrase); req->reason_phrase = NULL; @@ -37,42 +35,47 @@ parse_response_headers (SoupMessage *req, (char **) &req->reason_phrase)) return SOUP_STATUS_MALFORMED; - if (version < priv->http_version) - priv->http_version = version; + g_object_notify (G_OBJECT (req), SOUP_MESSAGE_STATUS_CODE); + g_object_notify (G_OBJECT (req), SOUP_MESSAGE_REASON_PHRASE); - resp_hdrs = req->response_headers; + if (version < priv->http_version) { + priv->http_version = version; + g_object_notify (G_OBJECT (req), SOUP_MESSAGE_HTTP_VERSION); + } - *encoding = soup_message_get_response_encoding (req, content_len); - if (*encoding == SOUP_TRANSFER_NONE) { - *encoding = SOUP_TRANSFER_CONTENT_LENGTH; - *content_len = 0; - } else if (*encoding == SOUP_TRANSFER_UNKNOWN) + if ((req->method == SOUP_METHOD_HEAD || + req->status_code == SOUP_STATUS_NO_CONTENT || + req->status_code == SOUP_STATUS_NOT_MODIFIED || + SOUP_STATUS_IS_INFORMATIONAL (req->status_code)) || + (req->method == SOUP_METHOD_CONNECT && + SOUP_STATUS_IS_SUCCESSFUL (req->status_code))) + *encoding = SOUP_ENCODING_NONE; + else + *encoding = soup_message_headers_get_encoding (req->response_headers); + + if (*encoding == SOUP_ENCODING_UNRECOGNIZED) return SOUP_STATUS_MALFORMED; return SOUP_STATUS_OK; } static void -add_header (gpointer name, gpointer value, gpointer data) +add_header (const char *name, const char *value, gpointer data) { GString *headers = data; - - g_string_append_printf (headers, "%s: %s\r\n", - (char *)name, (char *)value); + g_string_append_printf (headers, "%s: %s\r\n", name, value); } static void get_request_headers (SoupMessage *req, GString *header, - SoupTransferEncoding *encoding, - gpointer user_data) + SoupEncoding *encoding, gpointer user_data) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); gboolean proxy = GPOINTER_TO_UINT (user_data); - const SoupUri *uri = soup_message_get_uri (req); - const char *expect; + SoupURI *uri = soup_message_get_uri (req); char *uri_string; - if (!strcmp (req->method, "CONNECT")) { + if (req->method == SOUP_METHOD_CONNECT) { /* CONNECT URI is hostname:port for tunnel destination */ uri_string = g_strdup_printf ("%s:%d", uri->host, uri->port); } else { @@ -98,46 +101,20 @@ get_request_headers (SoupMessage *req, GString *header, } g_free (uri_string); - if (req->request.length > 0) { - if (!soup_message_get_header (req->request_headers, - "Content-Type")) { - g_string_append (header, "Content-Type: text/xml; " - "charset=utf-8\r\n"); - } - g_string_append_printf (header, "Content-Length: %d\r\n", - req->request.length); - *encoding = SOUP_TRANSFER_CONTENT_LENGTH; + *encoding = soup_message_headers_get_encoding (req->request_headers); + if (*encoding != SOUP_ENCODING_CHUNKED && + req->request_body->length > 0) { + soup_message_headers_set_content_length (req->request_headers, + req->request_body->length); } - soup_message_foreach_header (req->request_headers, add_header, header); + soup_message_headers_foreach (req->request_headers, add_header, header); g_string_append (header, "\r\n"); - - expect = soup_message_get_header (req->request_headers, "Expect"); - if (expect && !strcmp (expect, "100-continue")) - priv->msg_flags |= SOUP_MESSAGE_EXPECT_CONTINUE; } -/** - * soup_message_send_request: - * @req: a #SoupMessage - * @sock: the #SoupSocket to send @req on - * @is_via_proxy: %TRUE if @sock is a connection to a proxy server - * rather than a direct connection to the desired HTTP server - * - * Begins the process of sending @msg across @sock. (If @sock is - * synchronous, then soup_message_send_request() won't return until - * the response has been received.) - **/ void soup_message_send_request (SoupMessage *req, SoupSocket *sock, - gboolean is_via_proxy) -{ - soup_message_send_request_internal (req, sock, NULL, is_via_proxy); -} - -void -soup_message_send_request_internal (SoupMessage *req, SoupSocket *sock, - SoupConnection *conn, gboolean is_via_proxy) + SoupConnection *conn, gboolean is_via_proxy) { soup_message_cleanup_response (req); soup_message_io_client (req, sock, conn, diff --git a/libsoup/soup-message-filter.c b/libsoup/soup-message-filter.c deleted file mode 100644 index 4b516c1..0000000 --- a/libsoup/soup-message-filter.c +++ /dev/null @@ -1,30 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-filter-offset: 8 -*- */ -/* - * soup-message-filter.c: Interface for arbitrary message manipulation - * - * Copyright (C) 2003, Ximian, Inc. - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include "soup-message-filter.h" - -SOUP_MAKE_INTERFACE (soup_message_filter, SoupMessageFilter, NULL) - -/** - * soup_message_filter_setup_message: - * @filter: an object that implements the #SoupMessageFilter interface - * @msg: a #SoupMessage - * - * Performs some sort of processing on @msg in preparation for it - * being sent. This will generally involve some combination of adding - * headers, adding handlers, and connecting to signals. - **/ -void -soup_message_filter_setup_message (SoupMessageFilter *filter, - SoupMessage *msg) -{ - SOUP_MESSAGE_FILTER_GET_CLASS (filter)->setup_message (filter, msg); -} diff --git a/libsoup/soup-message-filter.h b/libsoup/soup-message-filter.h deleted file mode 100644 index cebd93e..0000000 --- a/libsoup/soup-message-filter.h +++ /dev/null @@ -1,34 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2000-2003, Ximian, Inc. - */ - -#ifndef SOUP_MESSAGE_FILTER_H -#define SOUP_MESSAGE_FILTER_H 1 - -#include - -G_BEGIN_DECLS - -#define SOUP_TYPE_MESSAGE_FILTER (soup_message_filter_get_type ()) -#define SOUP_MESSAGE_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_MESSAGE_FILTER, SoupMessageFilter)) -#define SOUP_MESSAGE_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_MESSAGE_FILTER, SoupMessageFilterClass)) -#define SOUP_IS_MESSAGE_FILTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_MESSAGE_FILTER)) -#define SOUP_IS_MESSAGE_FILTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_MESSAGE_FILTER)) -#define SOUP_MESSAGE_FILTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), SOUP_TYPE_MESSAGE_FILTER, SoupMessageFilterClass)) - -typedef struct { - GTypeInterface parent; - - /* methods */ - void (*setup_message) (SoupMessageFilter *filter, SoupMessage *msg); -} SoupMessageFilterClass; - -GType soup_message_filter_get_type (void); - -void soup_message_filter_setup_message (SoupMessageFilter *filter, - SoupMessage *msg); - -G_END_DECLS - -#endif /* SOUP_MESSAGE_FILTER_H */ diff --git a/libsoup/soup-message-handlers.c b/libsoup/soup-message-handlers.c deleted file mode 100644 index 3d59821..0000000 --- a/libsoup/soup-message-handlers.c +++ /dev/null @@ -1,268 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-message-handlers.c: HTTP response handlers - * - * Copyright (C) 2000-2003, Ximian, Inc. - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "soup-message.h" -#include "soup-message-private.h" - -typedef enum { - SOUP_HANDLER_HEADER = 1, - SOUP_HANDLER_STATUS_CODE, - SOUP_HANDLER_STATUS_CLASS -} SoupHandlerKind; - -typedef struct { - SoupHandlerPhase phase; - SoupMessageCallbackFn handler_cb; - gpointer user_data; - - SoupHandlerKind kind; - union { - guint status_code; - SoupStatusClass status_class; - const char *header; - } data; -} SoupHandlerData; - -static inline void -run_handler (SoupMessage *msg, - SoupHandlerPhase invoke_phase, - SoupHandlerData *data) -{ - if (data->phase != invoke_phase) - return; - - switch (data->kind) { - case SOUP_HANDLER_HEADER: - if (!soup_message_get_header (msg->response_headers, - data->data.header)) - return; - break; - case SOUP_HANDLER_STATUS_CODE: - if (msg->status_code != data->data.status_code) - return; - break; - case SOUP_HANDLER_STATUS_CLASS: - if (msg->status_code < data->data.status_class * 100 || - msg->status_code >= (data->data.status_class + 1) * 100) - return; - break; - default: - break; - } - - (*data->handler_cb) (msg, data->user_data); -} - -/** - * soup_message_run_handlers: - * @msg: a #SoupMessage - * @phase: which group of handlers to run - * - * Run each @phase handler on @msg. If a handler requeues the message, - * we stop processing at that point. - */ -void -soup_message_run_handlers (SoupMessage *msg, SoupHandlerPhase phase) -{ - SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); - GSList *copy, *list; - - g_return_if_fail (SOUP_IS_MESSAGE (msg)); - - /* Jump through hoops to deal with callbacks that modify the list. */ - copy = g_slist_copy (priv->content_handlers); - - for (list = copy; list; list = list->next) { - if (!g_slist_find (priv->content_handlers, list->data)) - continue; - run_handler (msg, phase, list->data); - - if (SOUP_MESSAGE_IS_STARTING (msg)) - break; - } - - g_slist_free (copy); -} - -static void -add_handler (SoupMessage *msg, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, - gpointer user_data, - SoupHandlerKind kind, - const char *header, - guint status_code, - SoupStatusClass status_class) -{ - SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); - SoupHandlerData *data; - - data = g_new0 (SoupHandlerData, 1); - data->phase = phase; - data->handler_cb = handler_cb; - data->user_data = user_data; - data->kind = kind; - - switch (kind) { - case SOUP_HANDLER_HEADER: - data->data.header = header; - break; - case SOUP_HANDLER_STATUS_CODE: - data->data.status_code = status_code; - break; - case SOUP_HANDLER_STATUS_CLASS: - data->data.status_class = status_class; - break; - default: - break; - } - - priv->content_handlers = - g_slist_append (priv->content_handlers, data); -} - -/** - * soup_message_add_header_handler: - * @msg: a #SoupMessage - * @header: HTTP response header to match against - * @phase: processing phase to run the handler in - * @handler_cb: the handler - * @user_data: data to pass to @handler_cb - * - * Adds a handler to @msg for messages containing the given response - * header. - **/ -void -soup_message_add_header_handler (SoupMessage *msg, - const char *header, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, - gpointer user_data) -{ - g_return_if_fail (SOUP_IS_MESSAGE (msg)); - g_return_if_fail (header != NULL); - g_return_if_fail (handler_cb != NULL); - - add_handler (msg, phase, handler_cb, user_data, - SOUP_HANDLER_HEADER, - header, 0, 0); -} - -/** - * soup_message_add_status_code_handler: - * @msg: a #SoupMessage - * @status_code: HTTP status code to match against - * @phase: processing phase to run the handler in - * @handler_cb: the handler - * @user_data: data to pass to @handler_cb - * - * Adds a handler to @msg for messages receiving the given status - * code. - **/ -void -soup_message_add_status_code_handler (SoupMessage *msg, - guint status_code, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, - gpointer user_data) -{ - g_return_if_fail (SOUP_IS_MESSAGE (msg)); - g_return_if_fail (status_code != 0); - g_return_if_fail (handler_cb != NULL); - - add_handler (msg, phase, handler_cb, user_data, - SOUP_HANDLER_STATUS_CODE, - NULL, status_code, 0); -} - -/** - * soup_message_add_status_class_handler: - * @msg: a #SoupMessage - * @status_class: HTTP status code class to match against - * @phase: processing phase to run the handler in - * @handler_cb: the handler - * @user_data: data to pass to @handler_cb - * - * Adds a handler to @msg for messages receiving a status code in - * the given class. - **/ -void -soup_message_add_status_class_handler (SoupMessage *msg, - SoupStatusClass status_class, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, - gpointer user_data) -{ - g_return_if_fail (SOUP_IS_MESSAGE (msg)); - g_return_if_fail (status_class != 0); - g_return_if_fail (handler_cb != NULL); - - add_handler (msg, phase, handler_cb, user_data, - SOUP_HANDLER_STATUS_CLASS, - NULL, 0, status_class); -} - -/** - * soup_message_add_handler: - * @msg: a #SoupMessage - * @phase: processing phase to run the handler in - * @handler_cb: the handler - * @user_data: data to pass to @handler_cb - * - * Adds a handler to @msg for all messages - **/ -void -soup_message_add_handler (SoupMessage *msg, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, - gpointer user_data) -{ - g_return_if_fail (SOUP_IS_MESSAGE (msg)); - g_return_if_fail (handler_cb != NULL); - - add_handler (msg, phase, handler_cb, user_data, 0, NULL, 0, 0); -} - -/** - * soup_message_remove_handler: - * @msg: a #SoupMessage - * @phase: processing phase to run the handler in - * @handler_cb: the handler - * @user_data: data to pass to @handler_cb - * - * Removes all matching handlers from @msg - **/ -void -soup_message_remove_handler (SoupMessage *msg, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, - gpointer user_data) -{ - SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); - - GSList *iter = priv->content_handlers; - - while (iter) { - SoupHandlerData *data = iter->data; - - if (data->handler_cb == handler_cb && - data->user_data == user_data && - data->phase == phase) { - priv->content_handlers = - g_slist_remove (priv->content_handlers, - data); - g_free (data); - break; - } - - iter = iter->next; - } -} diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c new file mode 100644 index 0000000..646aaed --- /dev/null +++ b/libsoup/soup-message-headers.c @@ -0,0 +1,540 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-message-headers.c: HTTP message header arrays + * + * Copyright (C) 2007, 2008 Red Hat, Inc. + */ + +#include + +#include "soup-message-headers.h" +#include "soup-misc.h" + +/** + * SECTION:soup-message-headers + * @short_description: HTTP message headers + * @see_also: #SoupMessage + * + * #SoupMessageHeaders represents the HTTP message headers associated + * with a request or response. + **/ + +typedef void (*SoupHeaderSetter) (SoupMessageHeaders *, const char *); +static const char *intern_header_name (const char *name, SoupHeaderSetter *setter); + +typedef struct { + const char *name; + char *value; +} SoupHeader; + +struct SoupMessageHeaders { + GArray *array; + GHashTable *concat; + SoupMessageHeadersType type; + + SoupEncoding encoding; + goffset content_length; + SoupExpectation expectations; +}; + +/** + * soup_message_headers_new: + * @type: the type of headers + * + * Creates a #SoupMessageHeaders. (#SoupMessage does this + * automatically for its own headers. You would only need to use this + * method if you are manually parsing or generating message headers.) + * + * Return value: a new #SoupMessageHeaders + **/ +SoupMessageHeaders * +soup_message_headers_new (SoupMessageHeadersType type) +{ + SoupMessageHeaders *hdrs; + + hdrs = g_slice_new0 (SoupMessageHeaders); + /* FIXME: is "5" a good default? */ + hdrs->array = g_array_sized_new (TRUE, FALSE, sizeof (SoupHeader), 5); + hdrs->type = type; + hdrs->encoding = -1; + + return hdrs; +} + +/** + * soup_message_headers_free: + * @hdrs: a #SoupMessageHeaders + * + * Frees @hdrs. + **/ +void +soup_message_headers_free (SoupMessageHeaders *hdrs) +{ + soup_message_headers_clear (hdrs); + g_array_free (hdrs->array, TRUE); + g_slice_free (SoupMessageHeaders, hdrs); +} + +/** + * soup_message_headers_clear: + * @hdrs: a #SoupMessageHeaders + * + * Clears @hdrs. + **/ +void +soup_message_headers_clear (SoupMessageHeaders *hdrs) +{ + SoupHeader *hdr_array = (SoupHeader *)hdrs->array->data; + int i; + + for (i = 0; i < hdrs->array->len; i++) + g_free (hdr_array[i].value); + g_array_set_size (hdrs->array, 0); + + if (hdrs->concat) + g_hash_table_destroy (hdrs->concat); + + hdrs->encoding = -1; +} + +/** + * soup_message_headers_append: + * @hdrs: a #SoupMessageHeaders + * @name: the header name to add + * @value: the new value of @name + * + * Appends a new header with name @name and value @value to @hdrs. + **/ +void +soup_message_headers_append (SoupMessageHeaders *hdrs, + const char *name, const char *value) +{ + SoupHeader header; + SoupHeaderSetter setter; + + header.name = intern_header_name (name, &setter); + header.value = g_strdup (value); + g_array_append_val (hdrs->array, header); + if (hdrs->concat) + g_hash_table_remove (hdrs->concat, header.name); + if (setter) + setter (hdrs, header.value); +} + +/** + * soup_message_headers_replace: + * @hdrs: a #SoupMessageHeaders + * @name: the header name to replace + * @value: the new value of @name + * + * Replaces the value of the header @name in @hdrs with @value. + **/ +void +soup_message_headers_replace (SoupMessageHeaders *hdrs, + const char *name, const char *value) +{ + soup_message_headers_remove (hdrs, name); + soup_message_headers_append (hdrs, name, value); +} + +static int +find_header (SoupHeader *hdr_array, const char *interned_name, int nth) +{ + int i; + + for (i = 0; hdr_array[i].name; i++) { + if (hdr_array[i].name == interned_name) { + if (nth-- == 0) + return i; + } + } + return -1; +} + +/** + * soup_message_headers_remove: + * @hdrs: a #SoupMessageHeaders + * @name: the header name to remove + * + * Removes @name from @hdrs. If there are multiple values for @name, + * they are all removed. + **/ +void +soup_message_headers_remove (SoupMessageHeaders *hdrs, const char *name) +{ + SoupHeader *hdr_array = (SoupHeader *)(hdrs->array->data); + SoupHeaderSetter setter; + int index; + + name = intern_header_name (name, &setter); + while ((index = find_header (hdr_array, name, 0)) != -1) { + g_free (hdr_array[index].value); + g_array_remove_index (hdrs->array, index); + } + if (hdrs->concat) + g_hash_table_remove (hdrs->concat, name); + if (setter) + setter (hdrs, NULL); +} + +/** + * soup_message_headers_get: + * @hdrs: a #SoupMessageHeaders + * @name: header name + * + * Gets the value of header @name in @hdrs. + * + * If @name has multiple values in @hdrs, soup_message_headers_get() + * will concatenate all of the values together, separated by commas. + * This is sometimes awkward to parse (eg, WWW-Authenticate, + * Set-Cookie), but you have to be able to deal with it anyway, + * because an upstream proxy could do the same thing. + * + * Return value: the header's value or %NULL if not found. + **/ +const char * +soup_message_headers_get (SoupMessageHeaders *hdrs, const char *name) +{ + SoupHeader *hdr_array = (SoupHeader *)(hdrs->array->data); + GString *concat; + char *value; + int index, i; + + name = intern_header_name (name, NULL); + if (hdrs->concat) { + value = g_hash_table_lookup (hdrs->concat, name); + if (value) + return value; + } + + index = find_header (hdr_array, name, 0); + if (index == -1) + return NULL; + else if (find_header (hdr_array, name, 1) == -1) + return hdr_array[index].value; + + concat = g_string_new (NULL); + for (i = 0; (index = find_header (hdr_array, name, i)) != -1; i++) { + if (i != 0) + g_string_append (concat, ", "); + g_string_append (concat, hdr_array[index].value); + } + value = g_string_free (concat, FALSE); + + if (!hdrs->concat) + hdrs->concat = g_hash_table_new_full (NULL, NULL, NULL, g_free); + g_hash_table_insert (hdrs->concat, (gpointer)name, value); + return value; +} + +/** + * SoupMessageHeadersForeachFunc: + * @name: the header name + * @value: the header value + * @user_data: the data passed to soup_message_headers_foreach() + * + * The callback passed to soup_message_headers_foreach(). + **/ + +/** + * soup_message_headers_foreach: + * @hdrs: a #SoupMessageHeaders + * @func: callback function to run for each header + * @user_data: data to pass to @func + * + * Calls @func once for each header value in @hdrs. + * + * Beware that unlike soup_message_headers_get(), this processes the + * headers in exactly the way they were added, rather than + * concatenating multiple same-named headers into a single value. + * (This is intentional; it ensures that if you call + * soup_message_headers_append() multiple times with the same name, + * then the I/O code will output multiple copies of the header when + * sending the message to the remote implementation, which may be + * required for interoperability in some cases.) + **/ +void +soup_message_headers_foreach (SoupMessageHeaders *hdrs, + SoupMessageHeadersForeachFunc func, + gpointer user_data) +{ + SoupHeader *hdr_array = (SoupHeader *)hdrs->array->data; + int i; + + for (i = 0; i < hdrs->array->len; i++) + func (hdr_array[i].name, hdr_array[i].value, user_data); +} + + +static GStaticMutex header_pool_mutex = G_STATIC_MUTEX_INIT; +static GHashTable *header_pool, *header_setters; + +static void transfer_encoding_setter (SoupMessageHeaders *, const char *); +static void content_length_setter (SoupMessageHeaders *, const char *); +static void expectation_setter (SoupMessageHeaders *, const char *); + +static char * +intern_header_locked (const char *name) +{ + char *interned; + + interned = g_hash_table_lookup (header_pool, name); + if (!interned) { + char *dup = g_strdup (name); + g_hash_table_insert (header_pool, dup, dup); + interned = dup; + } + return interned; +} + +static const char * +intern_header_name (const char *name, SoupHeaderSetter *setter) +{ + const char *interned; + + g_static_mutex_lock (&header_pool_mutex); + + if (!header_pool) { + header_pool = g_hash_table_new (soup_str_case_hash, soup_str_case_equal); + header_setters = g_hash_table_new (NULL, NULL); + g_hash_table_insert (header_setters, + intern_header_locked ("Transfer-Encoding"), + transfer_encoding_setter); + g_hash_table_insert (header_setters, + intern_header_locked ("Content-Length"), + content_length_setter); + g_hash_table_insert (header_setters, + intern_header_locked ("Expect"), + expectation_setter); + } + + interned = intern_header_locked (name); + if (setter) + *setter = g_hash_table_lookup (header_setters, interned); + + g_static_mutex_unlock (&header_pool_mutex); + return interned; +} + + +/* Specific headers */ + +static void +transfer_encoding_setter (SoupMessageHeaders *hdrs, const char *value) +{ + if (value) { + if (g_ascii_strcasecmp (value, "chunked") == 0) + hdrs->encoding = SOUP_ENCODING_CHUNKED; + else + hdrs->encoding = SOUP_ENCODING_UNRECOGNIZED; + } else + hdrs->encoding = -1; +} + +static void +content_length_setter (SoupMessageHeaders *hdrs, const char *value) +{ + /* Transfer-Encoding trumps Content-Length */ + if (hdrs->encoding == SOUP_ENCODING_CHUNKED) + return; + + if (value) { + char *end; + + hdrs->content_length = g_ascii_strtoull (value, &end, 10); + if (*end) + hdrs->encoding = SOUP_ENCODING_UNRECOGNIZED; + else + hdrs->encoding = SOUP_ENCODING_CONTENT_LENGTH; + } else + hdrs->encoding = -1; +} + +/** + * SoupEncoding: + * @SOUP_ENCODING_UNRECOGNIZED: unknown / error + * @SOUP_ENCODING_NONE: no body is present (which is not the same as a + * 0-length body, and only occurs in certain places) + * @SOUP_ENCODING_CONTENT_LENGTH: Content-Length encoding + * @SOUP_ENCODING_EOF: Response body ends when the connection is closed + * @SOUP_ENCODING_CHUNKED: chunked encoding (currently only supported + * for response) + * @SOUP_ENCODING_BYTERANGES: multipart/byteranges (Reserved for future + * use: NOT CURRENTLY IMPLEMENTED) + * + * How a message body is encoded for transport + **/ + +/** + * soup_message_headers_get_encoding: + * @hdrs: a #SoupMessageHeaders + * + * Gets the message body encoding that @hdrs declare. This may not + * always correspond to the encoding used on the wire; eg, a HEAD + * response may declare a Content-Length or Transfer-Encoding, but + * it will never actually include a body. + * + * Return value: the encoding declared by @hdrs. + **/ +SoupEncoding +soup_message_headers_get_encoding (SoupMessageHeaders *hdrs) +{ + const char *header; + + if (hdrs->encoding != -1) + return hdrs->encoding; + + /* If Transfer-Encoding was set, hdrs->encoding would already + * be set. So we don't need to check that possibility. + */ + header = soup_message_headers_get (hdrs, "Content-Length"); + if (header) { + content_length_setter (hdrs, header); + if (hdrs->encoding != -1) + return hdrs->encoding; + } + + hdrs->encoding = (hdrs->type == SOUP_MESSAGE_HEADERS_REQUEST) ? + SOUP_ENCODING_NONE : SOUP_ENCODING_EOF; + return hdrs->encoding; +} + +/** + * soup_message_headers_set_encoding: + * @hdrs: a #SoupMessageHeaders + * @encoding: a #SoupEncoding + * + * Sets the message body encoding that @hdrs will declare. In particular, + * you should use this if you are going to send a request or response in + * chunked encoding. + **/ +void +soup_message_headers_set_encoding (SoupMessageHeaders *hdrs, + SoupEncoding encoding) +{ + if (encoding == hdrs->encoding) + return; + + switch (encoding) { + case SOUP_ENCODING_NONE: + case SOUP_ENCODING_EOF: + soup_message_headers_remove (hdrs, "Transfer-Encoding"); + soup_message_headers_remove (hdrs, "Content-Length"); + break; + + case SOUP_ENCODING_CONTENT_LENGTH: + soup_message_headers_remove (hdrs, "Transfer-Encoding"); + break; + + case SOUP_ENCODING_CHUNKED: + soup_message_headers_remove (hdrs, "Content-Length"); + soup_message_headers_replace (hdrs, "Transfer-Encoding", "chunked"); + break; + + default: + g_return_if_reached (); + } + + hdrs->encoding = encoding; +} + +/** + * soup_message_headers_get_content_length: + * @hdrs: a #SoupMessageHeaders + * + * Gets the message body length that @hdrs declare. This will only + * be non-0 if soup_message_headers_get_encoding() returns + * %SOUP_ENCODING_CONTENT_LENGTH. + * + * Return value: the message body length declared by @hdrs. + **/ +goffset +soup_message_headers_get_content_length (SoupMessageHeaders *hdrs) +{ + return (hdrs->encoding == SOUP_ENCODING_CONTENT_LENGTH) ? + hdrs->content_length : 0; +} + +/** + * soup_message_headers_set_content_length: + * @hdrs: a #SoupMessageHeaders + * @content_length: the message body length + * + * Sets the message body length that @hdrs will declare, and sets + * @hdrs's encoding to %SOUP_ENCODING_CONTENT_LENGTH. + * + * You do not normally need to call this; if @hdrs is set to use + * Content-Length encoding, libsoup will automatically set its + * Content-Length header for you immediately before sending the + * headers. One situation in which this method is useful is when + * generating the response to a HEAD request; Calling + * soup_message_headers_set_content_length() allows you to put the + * correct content length into the response without needing to waste + * memory by filling in a response body which won't actually be sent. + **/ +void +soup_message_headers_set_content_length (SoupMessageHeaders *hdrs, + goffset content_length) +{ + char length[128]; + + snprintf (length, sizeof (length), "%" G_GUINT64_FORMAT, + content_length); + soup_message_headers_remove (hdrs, "Transfer-Encoding"); + soup_message_headers_replace (hdrs, "Content-Length", length); +} + +static void +expectation_setter (SoupMessageHeaders *hdrs, const char *value) +{ + if (value) { + if (!g_ascii_strcasecmp (value, "100-continue")) + hdrs->expectations = SOUP_EXPECTATION_CONTINUE; + else + hdrs->expectations = SOUP_EXPECTATION_UNRECOGNIZED; + } else + hdrs->expectations = 0; +} + +/** + * soup_message_headers_get_expectations: + * @hdrs: a #SoupMessageHeaders + * + * Gets the expectations declared by @hdrs's "Expect" header. + * Currently this will either be %SOUP_EXPECTATION_CONTINUE or + * %SOUP_EXPECTATION_UNRECOGNIZED. + * + * Return value: the contents of @hdrs's "Expect" header + **/ +SoupExpectation +soup_message_headers_get_expectations (SoupMessageHeaders *hdrs) +{ + return hdrs->expectations; +} + +/** + * soup_message_headers_set_expectations: + * @hdrs: a #SoupMessageHeaders + * @expectations: the expectations to set + * + * Sets @hdrs's "Expect" header according to @expectations. + * + * Currently %SOUP_EXPECTATION_CONTINUE is the only known expectation + * value. You should set this value on a request if you are sending a + * large message body (eg, via POST or PUT), and want to give the + * server a chance to reject the request after seeing just the headers + * (eg, because it will require authentication before allowing you to + * post). This saves you from having to transmit the large request + * body when the server is just going to ignore it anyway. + **/ +void +soup_message_headers_set_expectations (SoupMessageHeaders *hdrs, + SoupExpectation expectations) +{ + g_return_if_fail ((expectations & ~SOUP_EXPECTATION_CONTINUE) == 0); + + if (expectations & SOUP_EXPECTATION_CONTINUE) + soup_message_headers_replace (hdrs, "Expect", "100-continue"); + else + soup_message_headers_remove (hdrs, "Expect"); +} diff --git a/libsoup/soup-message-headers.h b/libsoup/soup-message-headers.h new file mode 100644 index 0000000..b6eb94e --- /dev/null +++ b/libsoup/soup-message-headers.h @@ -0,0 +1,71 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2005 Novell, Inc. + */ + +#ifndef SOUP_MESSAGE_HEADERS_H +#define SOUP_MESSAGE_HEADERS_H 1 + +#include + +typedef struct SoupMessageHeaders SoupMessageHeaders; +typedef enum { + SOUP_MESSAGE_HEADERS_REQUEST, + SOUP_MESSAGE_HEADERS_RESPONSE +} SoupMessageHeadersType; + +SoupMessageHeaders *soup_message_headers_new (SoupMessageHeadersType type); + +void soup_message_headers_free (SoupMessageHeaders *hdrs); + +void soup_message_headers_append (SoupMessageHeaders *hdrs, + const char *name, + const char *value); +void soup_message_headers_replace (SoupMessageHeaders *hdrs, + const char *name, + const char *value); + +void soup_message_headers_remove (SoupMessageHeaders *hdrs, + const char *name); +void soup_message_headers_clear (SoupMessageHeaders *hdrs); + +const char *soup_message_headers_get (SoupMessageHeaders *hdrs, + const char *name); + +typedef void (*SoupMessageHeadersForeachFunc)(const char *name, + const char *value, + gpointer user_data); + +void soup_message_headers_foreach (SoupMessageHeaders *hdrs, + SoupMessageHeadersForeachFunc func, + gpointer user_data); + +/* Specific headers */ + +typedef enum { + SOUP_ENCODING_UNRECOGNIZED, + SOUP_ENCODING_NONE, + SOUP_ENCODING_CONTENT_LENGTH, + SOUP_ENCODING_EOF, + SOUP_ENCODING_CHUNKED, + SOUP_ENCODING_BYTERANGES +} SoupEncoding; + +SoupEncoding soup_message_headers_get_encoding (SoupMessageHeaders *hdrs); +void soup_message_headers_set_encoding (SoupMessageHeaders *hdrs, + SoupEncoding encoding); + +goffset soup_message_headers_get_content_length (SoupMessageHeaders *hdrs); +void soup_message_headers_set_content_length (SoupMessageHeaders *hdrs, + goffset content_length); + +typedef enum { + SOUP_EXPECTATION_UNRECOGNIZED = (1 << 0), + SOUP_EXPECTATION_CONTINUE = (1 << 1) +} SoupExpectation; + +SoupExpectation soup_message_headers_get_expectations (SoupMessageHeaders *hdrs); +void soup_message_headers_set_expectations (SoupMessageHeaders *hdrs, + SoupExpectation expectations); + +#endif /* SOUP_MESSAGE_HEADERS_H */ diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c index 7c089e8..f10d4c3 100644 --- a/libsoup/soup-message-io.c +++ b/libsoup/soup-message-io.c @@ -48,16 +48,17 @@ typedef struct { SoupMessageIOMode mode; SoupMessageIOState read_state; - SoupTransferEncoding read_encoding; - GByteArray *read_buf; + SoupEncoding read_encoding; GByteArray *read_meta_buf; - SoupDataBuffer *read_body; + SoupMessageBody *read_body; guint read_length; SoupMessageIOState write_state; - SoupTransferEncoding write_encoding; + SoupEncoding write_encoding; GString *write_buf; - SoupDataBuffer *write_body; + SoupMessageBody *write_body; + SoupBuffer *write_chunk; + gsize write_body_offset; guint written; guint read_tag, write_tag, err_tag; @@ -96,13 +97,13 @@ soup_message_io_cleanup (SoupMessage *msg) if (io->conn) g_object_unref (io->conn); - if (io->read_buf) - g_byte_array_free (io->read_buf, TRUE); g_byte_array_free (io->read_meta_buf, TRUE); g_string_free (io->write_buf, TRUE); + if (io->write_chunk) + soup_buffer_free (io->write_chunk); - g_free (io); + g_slice_free (SoupMessageIOData, io); } /** @@ -168,32 +169,37 @@ soup_message_io_finished (SoupMessage *msg) static void io_read (SoupSocket *sock, SoupMessage *msg); static void -io_error (SoupSocket *sock, SoupMessage *msg) +io_error (SoupSocket *sock, SoupMessage *msg, GError *error) +{ + if (!SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) { + if (error && error->domain == SOUP_SSL_ERROR) { + soup_message_set_status_full (msg, + SOUP_STATUS_SSL_FAILED, + error->message); + } else + soup_message_set_status (msg, SOUP_STATUS_IO_ERROR); + } + if (error) + g_error_free (error); + + soup_message_io_finished (msg); +} + +static void +io_disconnected (SoupSocket *sock, SoupMessage *msg) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; /* Closing the connection to signify EOF is sometimes ok */ if (io->read_state == SOUP_MESSAGE_IO_STATE_BODY && - io->read_encoding == SOUP_TRANSFER_EOF) { + io->read_encoding == SOUP_ENCODING_EOF) { io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; io_read (sock, msg); return; } - if (!SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) { - GError *err = g_object_get_data (G_OBJECT (sock), - "SoupSocket-last_error"); - - if (err && err->domain == SOUP_SSL_ERROR) { - soup_message_set_status_full (msg, - SOUP_STATUS_SSL_FAILED, - err->message); - } else - soup_message_set_status (msg, SOUP_STATUS_IO_ERROR); - } - - soup_message_io_finished (msg); + io_error (sock, msg, NULL); } /* Reads data from io->sock into io->read_meta_buf up until @boundary. @@ -220,12 +226,13 @@ read_metadata (SoupMessage *msg, const char *boundary) guint boundary_len = strlen (boundary); gsize nread; gboolean done; + GError *error = NULL; do { status = soup_socket_read_until (io->sock, read_buf, sizeof (read_buf), boundary, boundary_len, - &nread, &done); + &nread, &done, NULL, &error); switch (status) { case SOUP_SOCKET_OK: g_byte_array_append (io->read_meta_buf, read_buf, nread); @@ -233,7 +240,7 @@ read_metadata (SoupMessage *msg, const char *boundary) case SOUP_SOCKET_ERROR: case SOUP_SOCKET_EOF: - io_error (io->sock, msg); + io_error (io->sock, msg, error); return FALSE; case SOUP_SOCKET_WOULD_BLOCK: @@ -247,9 +254,8 @@ read_metadata (SoupMessage *msg, const char *boundary) /* Reads as much message body data as is available on io->sock (but no * further than the end of the current message body or chunk). On a * successful read, emits "got_chunk" (possibly multiple times), and - * if io->read_buf is non-%NULL (meaning that the message doesn't have - * %SOUP_MESSAGE_OVERWRITE_CHUNKS set), the data will be appended to - * it. + * if %SOUP_MESSAGE_OVERWRITE_CHUNKS wasn't set, appends the chunk + * to io->read_body. * * See the note at read_metadata() for an explanation of the return * value. @@ -262,34 +268,34 @@ read_body_chunk (SoupMessage *msg) SoupSocketIOStatus status; guchar read_buf[RESPONSE_BLOCK_SIZE]; guint len = sizeof (read_buf); - gboolean read_to_eof = (io->read_encoding == SOUP_TRANSFER_EOF); + gboolean read_to_eof = (io->read_encoding == SOUP_ENCODING_EOF); gsize nread; + GError *error = NULL; + SoupBuffer *buffer; while (read_to_eof || io->read_length > 0) { if (!read_to_eof) len = MIN (len, io->read_length); - status = soup_socket_read (io->sock, read_buf, len, &nread); + status = soup_socket_read (io->sock, read_buf, len, + &nread, NULL, &error); switch (status) { case SOUP_SOCKET_OK: if (!nread) break; - if (io->read_buf) - g_byte_array_append (io->read_buf, read_buf, nread); - io->read_length -= nread; + buffer = soup_buffer_new (SOUP_MEMORY_TEMPORARY, + read_buf, nread); + if (!(priv->msg_flags & SOUP_MESSAGE_OVERWRITE_CHUNKS)) + soup_message_body_append_buffer (io->read_body, buffer); - io->read_body->owner = SOUP_BUFFER_STATIC; - io->read_body->body = (char *)read_buf; - io->read_body->length = nread; + io->read_length -= nread; SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; - soup_message_got_chunk (msg); - if (priv->io_data == io) - memset (io->read_body, 0, sizeof (SoupDataBuffer)); + soup_message_got_chunk (msg, buffer); + soup_buffer_free (buffer); SOUP_MESSAGE_IO_RETURN_VAL_IF_CANCELLED_OR_PAUSED (FALSE); - break; case SOUP_SOCKET_EOF: @@ -298,7 +304,7 @@ read_body_chunk (SoupMessage *msg) /* else fall through */ case SOUP_SOCKET_ERROR: - io_error (io->sock, msg); + io_error (io->sock, msg, error); return FALSE; case SOUP_SOCKET_WOULD_BLOCK: @@ -319,16 +325,17 @@ write_data (SoupMessage *msg, const char *data, guint len) SoupMessageIOData *io = priv->io_data; SoupSocketIOStatus status; gsize nwrote; + GError *error = NULL; while (len > io->written) { status = soup_socket_write (io->sock, data + io->written, len - io->written, - &nwrote); + &nwrote, NULL, &error); switch (status) { case SOUP_SOCKET_EOF: case SOUP_SOCKET_ERROR: - io_error (io->sock, msg); + io_error (io->sock, msg, error); return FALSE; case SOUP_SOCKET_WOULD_BLOCK: @@ -345,12 +352,10 @@ write_data (SoupMessage *msg, const char *data, guint len) } static inline SoupMessageIOState -io_body_state (SoupTransferEncoding encoding) +io_body_state (SoupEncoding encoding) { - if (encoding == SOUP_TRANSFER_CHUNKED) + if (encoding == SOUP_ENCODING_CHUNKED) return SOUP_MESSAGE_IO_STATE_CHUNK_SIZE; - else if (encoding == SOUP_TRANSFER_NONE) - return SOUP_MESSAGE_IO_STATE_FINISHING; else return SOUP_MESSAGE_IO_STATE_BODY; } @@ -368,14 +373,15 @@ io_body_state (SoupTransferEncoding encoding) * W:DONE / R:BODY <- R:DONE / W:BODY * W:DONE / R:DONE R:DONE / W:DONE * - * and the "Expect: 100-continue" request/response, in which each - * writer has to pause and wait for the other at some point: + * and the "Expect: 100-continue" request/response, with the client + * blocking halfway through its request, and then either continuing or + * aborting, depending on the server response: * * Client Server * W:HEADERS / R:NOT_STARTED -> R:HEADERS / W:NOT_STARTED - * W:BLOCKING / R:HEADERS (100) <- R:BLOCKING / W:HEADERS (100) - * W:BODY / R:BLOCKING -> R:BODY / W:BLOCKING - * W:DONE / R:HEADERS <- R:DONE / W:HEADERS + * W:BLOCKING / R:HEADERS <- R:BLOCKING / W:HEADERS + * [W:BODY / R:BLOCKING -> R:BODY / W:BLOCKING] + * [W:DONE / R:HEADERS <- R:DONE / W:HEADERS] * W:DONE / R:BODY <- R:DONE / W:BODY * W:DONE / R:DONE R:DONE / W:DONE */ @@ -425,17 +431,27 @@ io_write (SoupSocket *sock, SoupMessage *msg) */ } } else if (io->mode == SOUP_MESSAGE_IO_CLIENT && - priv->msg_flags & SOUP_MESSAGE_EXPECT_CONTINUE) { + soup_message_headers_get_expectations (msg->request_headers) & SOUP_EXPECTATION_CONTINUE) { /* Need to wait for the Continue response */ io->write_state = SOUP_MESSAGE_IO_STATE_BLOCKING; io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; - } else + } else { io->write_state = io_body_state (io->write_encoding); + /* If the client was waiting for a Continue + * but we sent something else, then they're + * now done writing. + */ + if (io->mode == SOUP_MESSAGE_IO_SERVER && + io->read_state == SOUP_MESSAGE_IO_STATE_BLOCKING) + io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; + } + SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; - if (SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) + if (SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { soup_message_wrote_informational (msg); - else + soup_message_cleanup_response (msg); + } else soup_message_wrote_headers (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; break; @@ -454,9 +470,13 @@ io_write (SoupSocket *sock, SoupMessage *msg) case SOUP_MESSAGE_IO_STATE_BODY: - if (!write_data (msg, io->write_body->body, - io->write_body->length)) + if (!io->write_chunk) + io->write_chunk = soup_message_body_flatten (io->write_body); + if (!write_data (msg, io->write_chunk->data, + io->write_chunk->length)) return; + soup_buffer_free (io->write_chunk); + io->write_chunk = NULL; io->write_state = SOUP_MESSAGE_IO_STATE_FINISHING; @@ -467,22 +487,15 @@ io_write (SoupSocket *sock, SoupMessage *msg) case SOUP_MESSAGE_IO_STATE_CHUNK_SIZE: - if (!io->write_buf->len) { - SoupDataBuffer *chunk; - - SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; - chunk = soup_message_pop_chunk (msg); - SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; - - if (!chunk) { + if (!io->write_chunk) { + io->write_chunk = soup_message_body_get_chunk (io->write_body, io->write_body_offset); + if (!io->write_chunk) { soup_message_io_pause (msg); return; } - memcpy (io->write_body, chunk, sizeof (SoupDataBuffer)); - g_free (chunk); - - g_string_append_printf (io->write_buf, "%x\r\n", - io->write_body->length); + g_string_append_printf (io->write_buf, "%lx\r\n", + (unsigned long) io->write_chunk->length); + io->write_body_offset += io->write_chunk->length; } if (!write_data (msg, io->write_buf->str, io->write_buf->len)) @@ -490,7 +503,7 @@ io_write (SoupSocket *sock, SoupMessage *msg) g_string_truncate (io->write_buf, 0); - if (io->write_body->length == 0) { + if (io->write_chunk->length == 0) { /* The last chunk has no CHUNK_END... */ io->write_state = SOUP_MESSAGE_IO_STATE_TRAILERS; break; @@ -501,15 +514,19 @@ io_write (SoupSocket *sock, SoupMessage *msg) case SOUP_MESSAGE_IO_STATE_CHUNK: - if (!write_data (msg, io->write_body->body, - io->write_body->length)) + if (!write_data (msg, io->write_chunk->data, + io->write_chunk->length)) return; + soup_buffer_free (io->write_chunk); + io->write_chunk = NULL; + io->write_state = SOUP_MESSAGE_IO_STATE_CHUNK_END; SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_wrote_chunk (msg); SOUP_MESSAGE_IO_RETURN_IF_CANCELLED_OR_PAUSED; + /* fall through */ @@ -518,10 +535,6 @@ io_write (SoupSocket *sock, SoupMessage *msg) SOUP_MESSAGE_IO_EOL_LEN)) return; - if (io->write_body->owner == SOUP_BUFFER_SYSTEM_OWNED) - g_free (io->write_body->body); - memset (io->write_body, 0, sizeof (SoupDataBuffer)); - io->write_state = SOUP_MESSAGE_IO_STATE_CHUNK_SIZE; break; @@ -584,7 +597,6 @@ io_read (SoupSocket *sock, SoupMessage *msg) status = io->parse_headers_cb (msg, (char *)io->read_meta_buf->data, io->read_meta_buf->len, &io->read_encoding, - &io->read_length, io->user_data); g_byte_array_set_size (io->read_meta_buf, 0); @@ -597,12 +609,19 @@ io_read (SoupSocket *sock, SoupMessage *msg) * closed when we're done. */ soup_message_set_status (msg, status); - soup_message_add_header (msg->request_headers, - "Connection", "close"); + soup_message_headers_append (msg->request_headers, + "Connection", "close"); io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; break; } + if (io->read_encoding == SOUP_ENCODING_CONTENT_LENGTH) { + SoupMessageHeaders *hdrs = + (io->mode == SOUP_MESSAGE_IO_CLIENT) ? + msg->response_headers : msg->request_headers; + io->read_length = soup_message_headers_get_content_length (hdrs); + } + if (io->mode == SOUP_MESSAGE_IO_CLIENT && SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { if (msg->status_code == SOUP_STATUS_CONTINUE && @@ -617,17 +636,28 @@ io_read (SoupSocket *sock, SoupMessage *msg) io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; } } else if (io->mode == SOUP_MESSAGE_IO_SERVER && - (priv->msg_flags & SOUP_MESSAGE_EXPECT_CONTINUE)) { - /* The client requested a Continue response. */ + soup_message_headers_get_expectations (msg->request_headers) & SOUP_EXPECTATION_CONTINUE) { + /* The client requested a Continue response. The + * got_headers handler may change this to something + * else though. + */ soup_message_set_status (msg, SOUP_STATUS_CONTINUE); - io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS; io->read_state = SOUP_MESSAGE_IO_STATE_BLOCKING; - } else + } else { io->read_state = io_body_state (io->read_encoding); - if (SOUP_STATUS_IS_INFORMATIONAL (msg->status_code) && - !(priv->msg_flags & SOUP_MESSAGE_EXPECT_CONTINUE)) { + /* If the client was waiting for a Continue + * but got something else, then it's done + * writing. + */ + if (io->mode == SOUP_MESSAGE_IO_CLIENT && + io->write_state == SOUP_MESSAGE_IO_STATE_BLOCKING) + io->write_state = SOUP_MESSAGE_IO_STATE_FINISHING; + } + + if (io->mode == SOUP_MESSAGE_IO_CLIENT && + SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) { SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; soup_message_got_informational (msg); soup_message_cleanup_response (msg); @@ -652,15 +682,6 @@ io_read (SoupSocket *sock, SoupMessage *msg) return; got_body: - if (io->read_buf) { - io->read_body->owner = SOUP_BUFFER_SYSTEM_OWNED; - io->read_body->body = (char *)io->read_buf->data; - io->read_body->length = io->read_buf->len; - - g_byte_array_free (io->read_buf, FALSE); - io->read_buf = NULL; - } - io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; SOUP_MESSAGE_IO_PREPARE_FOR_CALLBACK; @@ -744,19 +765,14 @@ new_iostate (SoupMessage *msg, SoupSocket *sock, SoupMessageIOMode mode, SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io; - io = g_new0 (SoupMessageIOData, 1); + io = g_slice_new0 (SoupMessageIOData); io->sock = g_object_ref (sock); io->mode = mode; io->get_headers_cb = get_headers_cb; io->parse_headers_cb = parse_headers_cb; io->user_data = user_data; - io->read_encoding = SOUP_TRANSFER_UNKNOWN; - io->write_encoding = SOUP_TRANSFER_UNKNOWN; - io->read_meta_buf = g_byte_array_new (); - if (!(priv->msg_flags & SOUP_MESSAGE_OVERWRITE_CHUNKS)) - io->read_buf = g_byte_array_new (); io->write_buf = g_string_new (NULL); io->read_tag = g_signal_connect (io->sock, "readable", @@ -764,7 +780,7 @@ new_iostate (SoupMessage *msg, SoupSocket *sock, SoupMessageIOMode mode, io->write_tag = g_signal_connect (io->sock, "writable", G_CALLBACK (io_write), msg); io->err_tag = g_signal_connect (io->sock, "disconnected", - G_CALLBACK (io_error), msg); + G_CALLBACK (io_disconnected), msg); io->read_state = SOUP_MESSAGE_IO_STATE_NOT_STARTED; io->write_state = SOUP_MESSAGE_IO_STATE_NOT_STARTED; @@ -775,19 +791,6 @@ new_iostate (SoupMessage *msg, SoupSocket *sock, SoupMessageIOMode mode, return io; } -/** - * soup_message_io_client: - * @msg: a #SoupMessage - * @sock: socket to send @msg across - * @conn: the connection that owns @sock (or %NULL) - * @get_headers_cb: callback function to generate request headers - * @parse_headers_cb: callback function to parse response headers - * @user_data: data to pass to the callbacks - * - * Begins the process of sending @msg across @sock. - * - * Don't call this. Use soup_message_send_request(). - **/ void soup_message_io_client (SoupMessage *msg, SoupSocket *sock, SoupConnection *conn, @@ -803,25 +806,13 @@ soup_message_io_client (SoupMessage *msg, SoupSocket *sock, if (conn) io->conn = g_object_ref (conn); - io->read_body = &msg->response; - io->write_body = &msg->request; + io->read_body = msg->response_body; + io->write_body = msg->request_body; io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS; io_write (sock, msg); } -/** - * soup_message_io_server: - * @msg: an empty #SoupServerMessage - * @sock: socket to receive a request on - * @get_headers_cb: callback function to generate response headers - * @parse_headers_cb: callback function to parse request headers - * @user_data: data to pass to the callbacks - * - * Begins the process of receiving a request from @sock into @msg. - * - * Don't use this. Use soup_message_receive_request() instead. - **/ void soup_message_io_server (SoupMessage *msg, SoupSocket *sock, SoupMessageGetHeadersFn get_headers_cb, @@ -833,22 +824,13 @@ soup_message_io_server (SoupMessage *msg, SoupSocket *sock, io = new_iostate (msg, sock, SOUP_MESSAGE_IO_SERVER, get_headers_cb, parse_headers_cb, user_data); - io->read_body = &msg->request; - io->write_body = &msg->response; + io->read_body = msg->request_body; + io->write_body = msg->response_body; io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; io_read (sock, msg); } -/** - * soup_message_io_pause: - * @msg: a #SoupMessage - * - * Pauses I/O on @msg. This can be used in a #SoupServer handler when - * you don't have the data ready to return yet, or with a client-side - * message if you are not ready to process any more of the response at - * this time; call soup_message_io_unpause() to resume I/O. - **/ void soup_message_io_pause (SoupMessage *msg) { @@ -896,18 +878,6 @@ io_unpause_internal (gpointer msg) return FALSE; } -/** - * soup_message_io_unpause: - * @msg: a #SoupMessage - * - * Resumes I/O on @msg. Use this to resume after calling - * soup_message_io_pause(), or after adding a new chunk to a chunked - * response. - * - * If @msg is being sent via blocking I/O, this will resume reading or - * writing immediately. If @msg is using non-blocking I/O, then - * reading or writing won't resume until you return to the main loop. - **/ void soup_message_io_unpause (SoupMessage *msg) { diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h index 14f55d2..1a22ac6 100644 --- a/libsoup/soup-message-private.h +++ b/libsoup/soup-message-private.h @@ -8,45 +8,51 @@ #include "soup-message.h" #include "soup-auth.h" +#include "soup-connection.h" + +typedef enum { + SOUP_MESSAGE_IO_STATUS_IDLE, + SOUP_MESSAGE_IO_STATUS_QUEUED, + SOUP_MESSAGE_IO_STATUS_CONNECTING, + SOUP_MESSAGE_IO_STATUS_RUNNING, + SOUP_MESSAGE_IO_STATUS_FINISHED +} SoupMessageIOStatus; typedef struct { gpointer io_data; + SoupMessageIOStatus io_status; guint msg_flags; - GSList *chunks, *last_chunk; - - GSList *content_handlers; + SoupHTTPVersion http_version; - SoupHttpVersion http_version; - - SoupUri *uri; + SoupURI *uri; SoupAuth *auth, *proxy_auth; } SoupMessagePrivate; #define SOUP_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_MESSAGE, SoupMessagePrivate)) -void soup_message_run_handlers (SoupMessage *msg, - SoupHandlerPhase phase); +#define SOUP_MESSAGE_IS_STARTING(msg) (SOUP_MESSAGE_GET_PRIVATE (msg)->io_status == SOUP_MESSAGE_IO_STATUS_QUEUED || SOUP_MESSAGE_GET_PRIVATE (msg)->io_status == SOUP_MESSAGE_IO_STATUS_CONNECTING) void soup_message_cleanup_response (SoupMessage *req); typedef void (*SoupMessageGetHeadersFn) (SoupMessage *msg, GString *headers, - SoupTransferEncoding *encoding, + SoupEncoding *encoding, gpointer user_data); typedef guint (*SoupMessageParseHeadersFn)(SoupMessage *msg, char *headers, guint header_len, - SoupTransferEncoding *encoding, - guint *content_len, + SoupEncoding *encoding, gpointer user_data); -void soup_message_send_request_internal (SoupMessage *req, - SoupSocket *sock, - SoupConnection *conn, - gboolean via_proxy); +void soup_message_send_request (SoupMessage *req, + SoupSocket *sock, + SoupConnection *conn, + gboolean via_proxy); +void soup_message_read_request (SoupMessage *req, + SoupSocket *sock); void soup_message_io_client (SoupMessage *msg, SoupSocket *sock, @@ -69,4 +75,13 @@ void soup_message_set_proxy_auth (SoupMessage *msg, SoupAuth *auth); SoupAuth *soup_message_get_proxy_auth (SoupMessage *msg); +/* I/O */ +void soup_message_set_io_status (SoupMessage *msg, + SoupMessageIOStatus status); +SoupMessageIOStatus soup_message_get_io_status (SoupMessage *msg); +void soup_message_io_stop (SoupMessage *msg); +void soup_message_io_pause (SoupMessage *msg); +void soup_message_io_unpause (SoupMessage *msg); +gboolean soup_message_io_in_progress (SoupMessage *msg); + #endif /* SOUP_MESSAGE_PRIVATE_H */ diff --git a/libsoup/soup-message-queue.c b/libsoup/soup-message-queue.c index d730dc3..a73749a 100644 --- a/libsoup/soup-message-queue.c +++ b/libsoup/soup-message-queue.c @@ -30,7 +30,7 @@ soup_message_queue_new (void) { SoupMessageQueue *queue; - queue = g_new0 (SoupMessageQueue, 1); + queue = g_slice_new0 (SoupMessageQueue); queue->mutex = g_mutex_new (); return queue; } @@ -49,7 +49,7 @@ soup_message_queue_destroy (SoupMessageQueue *queue) g_list_free (queue->head); g_list_free (queue->iters); g_mutex_free (queue->mutex); - g_free (queue); + g_slice_free (SoupMessageQueue, queue); } /** diff --git a/libsoup/soup-message-queue.h b/libsoup/soup-message-queue.h index d3b0185..24e8ddb 100644 --- a/libsoup/soup-message-queue.h +++ b/libsoup/soup-message-queue.h @@ -13,14 +13,7 @@ G_BEGIN_DECLS typedef struct SoupMessageQueue SoupMessageQueue; -/** - * SoupMessageQueueIter: - * - * An opaque data structure used to iterate the elements of a - * #SoupMessageQueue. - **/ typedef struct { - /*< private >*/ GList *cur, *next; } SoupMessageQueueIter; diff --git a/libsoup/soup-message-server-io.c b/libsoup/soup-message-server-io.c index 7927319..c4b85cb 100644 --- a/libsoup/soup-message-server-io.c +++ b/libsoup/soup-message-server-io.c @@ -16,85 +16,76 @@ #include "soup-address.h" #include "soup-auth.h" #include "soup-headers.h" -#include "soup-server-message.h" #include "soup-server.h" #include "soup-socket.h" static guint parse_request_headers (SoupMessage *msg, char *headers, guint headers_len, - SoupTransferEncoding *encoding, guint *content_len, - gpointer sock) + SoupEncoding *encoding, gpointer sock) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); - SoupUri *uri; - char *req_path = NULL, *url; - const char *expect, *req_host; - SoupServer *server; - - if (!soup_headers_parse_request (headers, headers_len, - msg->request_headers, - (char **) &msg->method, - &req_path, - &priv->http_version)) - return SOUP_STATUS_BAD_REQUEST; - - expect = soup_message_get_header (msg->request_headers, "Expect"); - if (expect && !strcmp (expect, "100-continue")) - priv->msg_flags |= SOUP_MESSAGE_EXPECT_CONTINUE; + char *req_method, *req_path, *url; + SoupHTTPVersion version; + const char *req_host; + guint status; + SoupURI *uri; + + status = soup_headers_parse_request (headers, headers_len, + msg->request_headers, + &req_method, + &req_path, + &version); + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) + return status; + + g_object_set (G_OBJECT (msg), + SOUP_MESSAGE_METHOD, req_method, + SOUP_MESSAGE_HTTP_VERSION, version, + NULL); + g_free (req_method); /* Handle request body encoding */ - *encoding = soup_message_get_request_encoding (msg, content_len); - if (*encoding == SOUP_TRANSFER_NONE) { - *encoding = SOUP_TRANSFER_CONTENT_LENGTH; - *content_len = 0; - } else if (*encoding == SOUP_TRANSFER_UNKNOWN) { - if (soup_message_get_header (msg->request_headers, "Transfer-Encoding")) + *encoding = soup_message_headers_get_encoding (msg->request_headers); + if (*encoding == SOUP_ENCODING_UNRECOGNIZED) { + if (soup_message_headers_get (msg->request_headers, "Transfer-Encoding")) return SOUP_STATUS_NOT_IMPLEMENTED; else return SOUP_STATUS_BAD_REQUEST; } /* Generate correct context for request */ - server = soup_server_message_get_server (SOUP_SERVER_MESSAGE (msg)); - req_host = soup_message_get_header (msg->request_headers, "Host"); + req_host = soup_message_headers_get (msg->request_headers, "Host"); if (*req_path != '/') { /* Check for absolute URI */ - SoupUri *absolute; - - absolute = soup_uri_new (req_path); - if (absolute) { - url = g_strdup (req_path); - soup_uri_free (absolute); - } else { + uri = soup_uri_new (req_path); + if (!uri) { g_free (req_path); return SOUP_STATUS_BAD_REQUEST; } } else if (req_host) { url = g_strdup_printf ("%s://%s%s", - soup_server_get_protocol (server) == SOUP_PROTOCOL_HTTPS ? "https" : "http", + soup_socket_is_ssl (sock) ? "https" : "http", req_host, req_path); + uri = soup_uri_new (url); + g_free (url); } else if (priv->http_version == SOUP_HTTP_1_0) { /* No Host header, no AbsoluteUri */ SoupAddress *addr = soup_socket_get_local_address (sock); const char *host = soup_address_get_physical (addr); url = g_strdup_printf ("%s://%s:%d%s", - soup_server_get_protocol (server) == SOUP_PROTOCOL_HTTPS ? "https" : "http", - host, soup_server_get_port (server), + soup_socket_is_ssl (sock) ? "https" : "http", + host, soup_address_get_port (addr), req_path); - } else { - g_free (req_path); - return SOUP_STATUS_BAD_REQUEST; - } + uri = soup_uri_new (url); + g_free (url); + } else + uri = NULL; - uri = soup_uri_new (url); - g_free (url); g_free (req_path); - if (!uri) return SOUP_STATUS_BAD_REQUEST; - soup_message_set_uri (msg, uri); soup_uri_free (uri); @@ -102,46 +93,42 @@ parse_request_headers (SoupMessage *msg, char *headers, guint headers_len, } static void -write_header (gpointer name, gpointer value, gpointer headers) +write_header (const char *name, const char *value, gpointer headers) { - g_string_append_printf (headers, "%s: %s\r\n", - (char *)name, (char *)value); + g_string_append_printf (headers, "%s: %s\r\n", name, value); } static void get_response_headers (SoupMessage *msg, GString *headers, - SoupTransferEncoding *encoding, - gpointer user_data) + SoupEncoding *encoding, gpointer user_data) { - SoupServerMessage *smsg = SOUP_SERVER_MESSAGE (msg); - SoupTransferEncoding claimed_encoding; + SoupEncoding claimed_encoding; g_string_append_printf (headers, "HTTP/1.1 %d %s\r\n", msg->status_code, msg->reason_phrase); - soup_message_foreach_header (msg->response_headers, - write_header, headers); - - *encoding = soup_message_get_response_encoding (msg, NULL); - - claimed_encoding = soup_server_message_get_encoding (smsg); - if (claimed_encoding == SOUP_TRANSFER_CONTENT_LENGTH && - !soup_message_get_header (msg->response_headers, "Content-Length")) { - g_string_append_printf (headers, "Content-Length: %d\r\n", - msg->response.length); - } else if (claimed_encoding == SOUP_TRANSFER_CHUNKED) - g_string_append (headers, "Transfer-Encoding: chunked\r\n"); + claimed_encoding = soup_message_headers_get_encoding (msg->response_headers); + if ((msg->method == SOUP_METHOD_HEAD || + msg->status_code == SOUP_STATUS_NO_CONTENT || + msg->status_code == SOUP_STATUS_NOT_MODIFIED || + SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) || + (msg->method == SOUP_METHOD_CONNECT && + SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))) + *encoding = SOUP_ENCODING_NONE; + else + *encoding = claimed_encoding; + + if (claimed_encoding == SOUP_ENCODING_CONTENT_LENGTH && + !soup_message_headers_get_content_length (msg->response_headers)) { + soup_message_headers_set_content_length (msg->response_headers, + msg->response_body->length); + } + soup_message_headers_foreach (msg->response_headers, + write_header, headers); g_string_append (headers, "\r\n"); } -/** - * soup_message_read_request: - * @req: an empty #SoupServerMessage - * @sock: socket to receive the request on - * - * Begins the process of receiving a request from @sock into @req. - **/ void soup_message_read_request (SoupMessage *req, SoupSocket *sock) { diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c index 5a0f8ba..12f62e1 100644 --- a/libsoup/soup-message.c +++ b/libsoup/soup-message.c @@ -9,13 +9,50 @@ #include #include "soup-auth.h" +#include "soup-enum-types.h" #include "soup-marshal.h" #include "soup-message.h" #include "soup-message-private.h" #include "soup-misc.h" -#include "soup-server-message.h" #include "soup-uri.h" +/** + * SECTION:soup-message + * @short_description: An HTTP request and response. + * @see_also: #SoupMessageHeaders, #SoupMessageBody + * + **/ + +/** + * SoupMessage: + * @method: the HTTP method + * @status_code: the HTTP status code + * @reason_phrase: the status phrase associated with @status_code + * @request_body: the request body + * @request_headers: the request headers + * @response_body: the response body + * @response_headers: the response headers + * + * Represents an HTTP message being sent or received. + * + * As described in the #SoupMessageBody documentation, the + * @request_body and @response_body %data fields will not necessarily + * be filled in at all times. When they are filled in, they will be + * terminated with a '\0' byte (which is not included in the %length), + * so you can use them as ordinary C strings (assuming that you know + * that the body doesn't have any other '\0' bytes). + * + * For a client-side #SoupMessage, @request_body's %data is usually + * filled in right before libsoup writes the request to the network, + * but you should not count on this; use soup_message_body_flatten() + * if you want to ensure that %data is filled in. @response_body's + * %data will be filled in before #SoupMessage::finished is emitted, + * unless you set the %SOUP_MESSAGE_OVERWRITE_CHUNKS flag. + * + * For a server-side #SoupMessage, @request_body's %data will be + * filled in before #SoupMessage::got_body is emitted. + **/ + G_DEFINE_TYPE (SoupMessage, soup_message, G_TYPE_OBJECT) enum { @@ -37,26 +74,40 @@ enum { static guint signals[LAST_SIGNAL] = { 0 }; -static void wrote_body (SoupMessage *req); -static void got_headers (SoupMessage *req); -static void got_chunk (SoupMessage *req); +enum { + PROP_0, + + PROP_METHOD, + PROP_URI, + PROP_HTTP_VERSION, + PROP_FLAGS, + PROP_STATUS_CODE, + PROP_REASON_PHRASE, + + LAST_PROP +}; + static void got_body (SoupMessage *req); static void restarted (SoupMessage *req); static void finished (SoupMessage *req); -static void free_chunks (SoupMessage *msg); + +static void set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); +static void get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); static void soup_message_init (SoupMessage *msg) { - msg->status = SOUP_MESSAGE_STATUS_IDLE; - - msg->request_headers = g_hash_table_new (soup_str_case_hash, - soup_str_case_equal); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); - msg->response_headers = g_hash_table_new (soup_str_case_hash, - soup_str_case_equal); + priv->io_status = SOUP_MESSAGE_IO_STATUS_IDLE; + priv->http_version = SOUP_HTTP_1_1; - SOUP_MESSAGE_GET_PRIVATE (msg)->http_version = SOUP_HTTP_1_1; + msg->request_body = soup_message_body_new (); + msg->request_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST); + msg->response_body = soup_message_body_new (); + msg->response_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE); } static void @@ -75,20 +126,10 @@ finalize (GObject *object) if (priv->proxy_auth) g_object_unref (priv->proxy_auth); - if (msg->request.owner == SOUP_BUFFER_SYSTEM_OWNED) - g_free (msg->request.body); - if (msg->response.owner == SOUP_BUFFER_SYSTEM_OWNED) - g_free (msg->response.body); - free_chunks (msg); - - soup_message_clear_headers (msg->request_headers); - g_hash_table_destroy (msg->request_headers); - - soup_message_clear_headers (msg->response_headers); - g_hash_table_destroy (msg->response_headers); - - g_slist_foreach (priv->content_handlers, (GFunc) g_free, NULL); - g_slist_free (priv->content_handlers); + soup_message_body_free (msg->request_body); + soup_message_headers_free (msg->request_headers); + soup_message_body_free (msg->response_body); + soup_message_headers_free (msg->response_headers); g_free ((char *) msg->reason_phrase); @@ -103,15 +144,14 @@ soup_message_class_init (SoupMessageClass *message_class) g_type_class_add_private (message_class, sizeof (SoupMessagePrivate)); /* virtual method definition */ - message_class->wrote_body = wrote_body; - message_class->got_headers = got_headers; - message_class->got_chunk = got_chunk; message_class->got_body = got_body; message_class->restarted = restarted; message_class->finished = finished; /* virtual method override */ object_class->finalize = finalize; + object_class->set_property = set_property; + object_class->get_property = get_property; /* signals */ @@ -120,7 +160,7 @@ soup_message_class_init (SoupMessageClass *message_class) * @msg: the message * * Emitted immediately after writing a 1xx (Informational) - * response for a message. + * response for a (server-side) message. **/ signals[WROTE_INFORMATIONAL] = g_signal_new ("wrote_informational", @@ -135,7 +175,10 @@ soup_message_class_init (SoupMessageClass *message_class) * SoupMessage::wrote-headers: * @msg: the message * - * Emitted immediately after writing the headers for a message. + * Emitted immediately after writing the headers for a + * message. (For a client-side message, this is after writing + * the request headers; for a server-side message, it is after + * writing the response headers.) **/ signals[WROTE_HEADERS] = g_signal_new ("wrote_headers", @@ -165,7 +208,12 @@ soup_message_class_init (SoupMessageClass *message_class) * SoupMessage::wrote-body: * @msg: the message * - * Emitted immediately after writing the complete body for a message. + * Emitted immediately after writing the complete body for a + * message. (For a client-side message, this means that + * libsoup is done writing and is now waiting for the response + * from the server. For a server-side message, this means that + * libsoup has finished writing the response and is nearly + * done with the message.) **/ signals[WROTE_BODY] = g_signal_new ("wrote_body", @@ -181,7 +229,14 @@ soup_message_class_init (SoupMessageClass *message_class) * @msg: the message * * Emitted after receiving a 1xx (Informational) response for - * a message. + * a (client-side) message. The response_headers will be + * filled in with the headers associated with the + * informational response; however, those header values will + * be erased after this signal is done. + * + * If you cancel or requeue @msg while processing this signal, + * then the current HTTP I/O will be stopped after this signal + * emission finished, and @msg's connection will be closed. **/ signals[GOT_INFORMATIONAL] = g_signal_new ("got_informational", @@ -197,6 +252,23 @@ soup_message_class_init (SoupMessageClass *message_class) * @msg: the message * * Emitted after receiving all message headers for a message. + * (For a client-side message, this is after receiving the + * Status-Line and response headers; for a server-side + * message, it is after receiving the Request-Line and request + * headers.) + * + * See also soup_message_add_header_handler() and + * soup_message_add_status_code_handler(), which can be used + * to connect to a subset of emissions of this signal. + * + * If you cancel or requeue @msg while processing this signal, + * then the current HTTP I/O will be stopped after this signal + * emission finished, and @msg's connection will be closed. + * (If you need to requeue a message--eg, after handling + * authentication or redirection--it is usually better to + * requeue it from a #SoupMessage::got_body handler rather + * than a #SoupMessage::got_header handler, so that the + * existing HTTP connection can be reused.) **/ signals[GOT_HEADERS] = g_signal_new ("got_headers", @@ -210,11 +282,16 @@ soup_message_class_init (SoupMessageClass *message_class) /** * SoupMessage::got-chunk: * @msg: the message + * @chunk: the just-read chunk * * Emitted after receiving a chunk of a message body. Note * that "chunk" in this context means any subpiece of the * body, not necessarily the specific HTTP 1.1 chunks sent by * the other side. + * + * If you cancel or requeue @msg while processing this signal, + * then the current HTTP I/O will be stopped after this signal + * emission finished, and @msg's connection will be closed. **/ signals[GOT_CHUNK] = g_signal_new ("got_chunk", @@ -222,14 +299,22 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, got_chunk), NULL, NULL, - soup_marshal_NONE__NONE, - G_TYPE_NONE, 0); + soup_marshal_NONE__BOXED, + G_TYPE_NONE, 1, + SOUP_TYPE_BUFFER); /** * SoupMessage::got-body: * @msg: the message * - * Emitted after receiving the complete message body. + * Emitted after receiving the complete message body. (For a + * server-side message, this means it has received the request + * body. For a client-side message, this means it has received + * the response body and is nearly done with the message.) + * + * See also soup_message_add_header_handler() and + * soup_message_add_status_code_handler(), which can be used + * to connect to a subset of emissions of this signal. **/ signals[GOT_BODY] = g_signal_new ("got_body", @@ -244,7 +329,10 @@ soup_message_class_init (SoupMessageClass *message_class) * SoupMessage::restarted: * @msg: the message * - * Emitted when a message is about to be re-queued. + * Emitted when a request that was already sent once is now + * being sent again (eg, because the first attempt received a + * redirection response, or because we needed to use + * authentication). **/ signals[RESTARTED] = g_signal_new ("restarted", @@ -260,8 +348,8 @@ soup_message_class_init (SoupMessageClass *message_class) * @msg: the message * * Emitted when all HTTP processing is finished for a message. - * (After #read-body for client-side code, or after - * #wrote-body for server-side code.) + * (After #SoupMessage::got_body for client-side messages, or + * after #SoupMessage::wrote_body for server-side messages.) **/ signals[FINISHED] = g_signal_new ("finished", @@ -271,6 +359,114 @@ soup_message_class_init (SoupMessageClass *message_class) NULL, NULL, soup_marshal_NONE__NONE, G_TYPE_NONE, 0); + + /* properties */ + g_object_class_install_property ( + object_class, PROP_METHOD, + g_param_spec_string (SOUP_MESSAGE_METHOD, + "Method", + "The message's HTTP method", + SOUP_METHOD_GET, + G_PARAM_READWRITE)); + g_object_class_install_property ( + object_class, PROP_URI, + g_param_spec_boxed (SOUP_MESSAGE_URI, + "URI", + "The message's Request-URI", + SOUP_TYPE_URI, + G_PARAM_READWRITE)); + g_object_class_install_property ( + object_class, PROP_HTTP_VERSION, + g_param_spec_enum (SOUP_MESSAGE_HTTP_VERSION, + "HTTP Version", + "The HTTP protocol version to use", + SOUP_TYPE_HTTP_VERSION, + SOUP_HTTP_1_1, + G_PARAM_READWRITE)); + g_object_class_install_property ( + object_class, PROP_FLAGS, + g_param_spec_flags (SOUP_MESSAGE_FLAGS, + "Flags", + "Various message options", + SOUP_TYPE_MESSAGE_FLAGS, + 0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + object_class, PROP_STATUS_CODE, + g_param_spec_uint (SOUP_MESSAGE_STATUS_CODE, + "Status code", + "The HTTP response status code", + 0, 599, 0, + G_PARAM_READWRITE)); + g_object_class_install_property ( + object_class, PROP_REASON_PHRASE, + g_param_spec_string (SOUP_MESSAGE_REASON_PHRASE, + "Reason phrase", + "The HTTP response reason phrase", + NULL, + G_PARAM_READWRITE)); +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + SoupMessage *msg = SOUP_MESSAGE (object); + + switch (prop_id) { + case PROP_METHOD: + msg->method = g_intern_string (g_value_get_string (value)); + break; + case PROP_URI: + soup_message_set_uri (msg, g_value_get_boxed (value)); + break; + case PROP_HTTP_VERSION: + soup_message_set_http_version (msg, g_value_get_enum (value)); + break; + case PROP_FLAGS: + soup_message_set_flags (msg, g_value_get_flags (value)); + break; + case PROP_STATUS_CODE: + soup_message_set_status (msg, g_value_get_uint (value)); + break; + case PROP_REASON_PHRASE: + soup_message_set_status_full (msg, msg->status_code, + g_value_get_string (value)); + break; + default: + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + SoupMessage *msg = SOUP_MESSAGE (object); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + + switch (prop_id) { + case PROP_METHOD: + g_value_set_string (value, msg->method); + break; + case PROP_URI: + g_value_set_boxed (value, priv->uri); + break; + case PROP_HTTP_VERSION: + g_value_set_enum (value, priv->http_version); + break; + case PROP_FLAGS: + g_value_set_flags (value, priv->msg_flags); + break; + case PROP_STATUS_CODE: + g_value_set_uint (value, msg->status_code); + break; + case PROP_REASON_PHRASE: + g_value_set_string (value, msg->reason_phrase); + break; + default: + break; + } } @@ -288,99 +484,106 @@ SoupMessage * soup_message_new (const char *method, const char *uri_string) { SoupMessage *msg; - SoupUri *uri; + SoupURI *uri; + + g_return_val_if_fail (method != NULL, NULL); + g_return_val_if_fail (uri_string != NULL, NULL); uri = soup_uri_new (uri_string); if (!uri) return NULL; - if (!uri->host) { soup_uri_free (uri); return NULL; } - msg = g_object_new (SOUP_TYPE_MESSAGE, NULL); - msg->method = method ? method : SOUP_METHOD_GET; - SOUP_MESSAGE_GET_PRIVATE (msg)->uri = uri; - + msg = soup_message_new_from_uri (method, uri); + soup_uri_free (uri); return msg; } /** * soup_message_new_from_uri: * @method: the HTTP method for the created request - * @uri: the destination endpoint (as a #SoupUri) + * @uri: the destination endpoint (as a #SoupURI) * * Creates a new empty #SoupMessage, which will connect to @uri * * Return value: the new #SoupMessage */ SoupMessage * -soup_message_new_from_uri (const char *method, const SoupUri *uri) +soup_message_new_from_uri (const char *method, SoupURI *uri) { - SoupMessage *msg; - - msg = g_object_new (SOUP_TYPE_MESSAGE, NULL); - msg->method = method ? method : SOUP_METHOD_GET; - SOUP_MESSAGE_GET_PRIVATE (msg)->uri = soup_uri_copy (uri); - - return msg; + return g_object_new (SOUP_TYPE_MESSAGE, + SOUP_MESSAGE_METHOD, method, + SOUP_MESSAGE_URI, uri, + NULL); } /** * soup_message_set_request: * @msg: the message * @content_type: MIME Content-Type of the body - * @req_owner: the #SoupOwnership of the passed data buffer. + * @req_use: a #SoupMemoryUse describing how to handle @req_body * @req_body: a data buffer containing the body of the message request. * @req_length: the byte length of @req_body. * - * Convenience function to set the request body of a #SoupMessage + * Convenience function to set the request body of a #SoupMessage. If + * @content_type is %NULL, the request body must be empty as well. */ void -soup_message_set_request (SoupMessage *msg, - const char *content_type, - SoupOwnership req_owner, - char *req_body, - gulong req_length) +soup_message_set_request (SoupMessage *msg, + const char *content_type, + SoupMemoryUse req_use, + const char *req_body, + gsize req_length) { g_return_if_fail (SOUP_IS_MESSAGE (msg)); - g_return_if_fail (content_type != NULL); - g_return_if_fail (req_body != NULL || req_length == 0); - - soup_message_add_header (msg->request_headers, - "Content-Type", content_type); - msg->request.owner = req_owner; - msg->request.body = req_body; - msg->request.length = req_length; + g_return_if_fail (content_type != NULL || req_length == 0); + + if (content_type) { + soup_message_headers_replace (msg->request_headers, + "Content-Type", content_type); + soup_message_body_append (msg->request_body, req_use, + req_body, req_length); + } else { + soup_message_headers_remove (msg->request_headers, + "Content-Type"); + soup_message_body_truncate (msg->request_body); + } } /** * soup_message_set_response: * @msg: the message * @content_type: MIME Content-Type of the body - * @resp_owner: the #SoupOwnership of the passed data buffer. + * @resp_use: a #SoupMemoryUse describing how to handle @resp_body * @resp_body: a data buffer containing the body of the message response. * @resp_length: the byte length of @resp_body. * - * Convenience function to set the response body of a #SoupMessage + * Convenience function to set the response body of a #SoupMessage. If + * @content_type is %NULL, the response body must be empty as well. */ void -soup_message_set_response (SoupMessage *msg, - const char *content_type, - SoupOwnership resp_owner, - char *resp_body, - gulong resp_length) +soup_message_set_response (SoupMessage *msg, + const char *content_type, + SoupMemoryUse resp_use, + const char *resp_body, + gsize resp_length) { g_return_if_fail (SOUP_IS_MESSAGE (msg)); - g_return_if_fail (content_type != NULL); - g_return_if_fail (resp_body != NULL || resp_length == 0); - - soup_message_add_header (msg->response_headers, - "Content-Type", content_type); - msg->response.owner = resp_owner; - msg->response.body = resp_body; - msg->response.length = resp_length; + g_return_if_fail (content_type != NULL || resp_length == 0); + + if (content_type) { + soup_message_headers_replace (msg->response_headers, + "Content-Type", content_type); + soup_message_body_append (msg->response_body, resp_use, + resp_body, resp_length); + } else { + soup_message_headers_remove (msg->response_headers, + "Content-Type"); + soup_message_body_truncate (msg->response_body); + } } /** @@ -422,14 +625,6 @@ soup_message_wrote_chunk (SoupMessage *msg) g_signal_emit (msg, signals[WROTE_CHUNK], 0); } -static void -wrote_body (SoupMessage *req) -{ - g_object_ref (req); - soup_message_run_handlers (req, SOUP_HANDLER_POST_REQUEST); - g_object_unref (req); -} - /** * soup_message_wrote_body: * @msg: a #SoupMessage @@ -456,16 +651,6 @@ soup_message_got_informational (SoupMessage *msg) g_signal_emit (msg, signals[GOT_INFORMATIONAL], 0); } -static void -got_headers (SoupMessage *req) -{ - g_object_ref (req); - soup_message_run_handlers (req, SOUP_HANDLER_PRE_BODY); - if (SOUP_MESSAGE_IS_STARTING (req)) - g_signal_stop_emission (req, signals[GOT_HEADERS], 0); - g_object_unref (req); -} - /** * soup_message_got_headers: * @msg: a #SoupMessage @@ -479,37 +664,35 @@ soup_message_got_headers (SoupMessage *msg) g_signal_emit (msg, signals[GOT_HEADERS], 0); } -static void -got_chunk (SoupMessage *req) -{ - g_object_ref (req); - soup_message_run_handlers (req, SOUP_HANDLER_BODY_CHUNK); - if (SOUP_MESSAGE_IS_STARTING (req)) - g_signal_stop_emission (req, signals[GOT_CHUNK], 0); - g_object_unref (req); -} - /** * soup_message_got_chunk: * @msg: a #SoupMessage + * @chunk: the newly-read chunk * * Emits the %got_chunk signal, indicating that the IO layer finished * reading a chunk of @msg's body. **/ void -soup_message_got_chunk (SoupMessage *msg) +soup_message_got_chunk (SoupMessage *msg, SoupBuffer *chunk) { - g_signal_emit (msg, signals[GOT_CHUNK], 0); + g_signal_emit (msg, signals[GOT_CHUNK], 0, chunk); } static void got_body (SoupMessage *req) { - g_object_ref (req); - soup_message_run_handlers (req, SOUP_HANDLER_POST_BODY); - if (SOUP_MESSAGE_IS_STARTING (req)) - g_signal_stop_emission (req, signals[GOT_BODY], 0); - g_object_unref (req); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); + + if (!(priv->msg_flags & SOUP_MESSAGE_OVERWRITE_CHUNKS)) { + SoupBuffer *buffer; + + /* Figure out *which* body we read, and flatten it. */ + if (req->status_code == 0) + buffer = soup_message_body_flatten (req->request_body); + else + buffer = soup_message_body_flatten (req->response_body); + soup_buffer_free (buffer); + } } /** @@ -547,8 +730,10 @@ soup_message_restarted (SoupMessage *msg) static void finished (SoupMessage *req) { + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); + soup_message_io_stop (req); - req->status = SOUP_MESSAGE_STATUS_FINISHED; + priv->io_status = SOUP_MESSAGE_IO_STATUS_FINISHED; } /** @@ -564,170 +749,150 @@ soup_message_finished (SoupMessage *msg) g_signal_emit (msg, signals[FINISHED], 0); } -static gboolean -free_header_list (gpointer name, gpointer vals, gpointer user_data) -{ - g_free (name); - g_slist_foreach (vals, (GFunc) g_free, NULL); - g_slist_free (vals); - - return TRUE; -} - -/** - * soup_message_clear_headers: - * @hash: a header table (the %request_headers or %response_headers - * field of a #SoupMessage) - * - * Clears @hash. - **/ -void -soup_message_clear_headers (GHashTable *hash) +static void +header_handler_free (gpointer header_name, GClosure *closure) { - g_return_if_fail (hash != NULL); - - g_hash_table_foreach_remove (hash, free_header_list, NULL); + g_free (header_name); } -/** - * soup_message_remove_header: - * @hash: a header table (the %request_headers or %response_headers - * field of a #SoupMessage) - * @name: the header name to remove - * - * Removes @name from @hash. If there are multiple values for @name, - * they are all removed. - **/ -void -soup_message_remove_header (GHashTable *hash, const char *name) +static void +header_handler_metamarshal (GClosure *closure, GValue *return_value, + guint n_param_values, const GValue *param_values, + gpointer invocation_hint, gpointer marshal_data) { - gpointer old_key, old_vals; + SoupMessage *msg = g_value_get_object (¶m_values[0]); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + const char *header_name = marshal_data; + SoupMessageHeaders *hdrs; - g_return_if_fail (hash != NULL); - g_return_if_fail (name != NULL || name[0] != '\0'); + if (priv->io_status != SOUP_MESSAGE_IO_STATUS_RUNNING) + return; - if (g_hash_table_lookup_extended (hash, name, &old_key, &old_vals)) { - g_hash_table_remove (hash, name); - free_header_list (old_key, old_vals, NULL); + /* If status_code is SOUP_STATUS_NONE, we're still processing + * the request side; if it's not, we're processing the + * response side. + */ + hdrs = (msg->status_code == SOUP_STATUS_NONE) ? + msg->request_headers : msg->response_headers; + + if (soup_message_headers_get (hdrs, header_name)) { + closure->marshal (closure, return_value, n_param_values, + param_values, invocation_hint, + ((GCClosure *)closure)->callback); } } /** - * soup_message_add_header: - * @hash: a header table (the %request_headers or %response_headers - * field of a #SoupMessage) - * @name: the header name to add - * @value: the value of the new header + * soup_message_add_header_handler: + * @msg: a #SoupMessage + * @signal: signal to connect the handler to. + * @header: HTTP response header to match against + * @callback: the header handler + * @user_data: data to pass to @handler_cb * - * Adds a header with name @name and value @value to @hash. If there - * was already a header with name @name, this one does not replace it, - * it is merely added to it. - **/ -void -soup_message_add_header (GHashTable *hash, const char *name, const char *value) -{ - GSList *old_value; - - g_return_if_fail (hash != NULL); - g_return_if_fail (name != NULL || name [0] != '\0'); - g_return_if_fail (value != NULL); - - old_value = g_hash_table_lookup (hash, name); - - if (old_value) - old_value = g_slist_append (old_value, g_strdup (value)); - else { - g_hash_table_insert (hash, g_strdup (name), - g_slist_append (NULL, g_strdup (value))); - } -} - -/** - * soup_message_get_header: - * @hash: a header table (the %request_headers or %response_headers - * field of a #SoupMessage) - * @name: header name. - * - * Finds the first header in @hash with name @name. - * - * Return value: the header's value or %NULL if not found. + * Adds a signal handler to @msg for @signal, as with + * g_signal_connect(), but with two differences: the @callback will + * only be run if @msg has a header named @header, and it will only be + * run if no earlier handler cancelled or requeued the message. + * + * If @signal is one of the "got" signals (eg, "got_headers"), or + * "finished" or "restarted", then @header is matched against the + * incoming message headers (that is, the #request_headers for a + * client #SoupMessage, or the #response_headers for a server + * #SoupMessage). If @signal is one of the "wrote" signals, then + * @header is matched against the outgoing message headers. + * + * Return value: the handler ID from g_signal_connect() **/ -const char * -soup_message_get_header (GHashTable *hash, const char *name) +guint +soup_message_add_header_handler (SoupMessage *msg, + const char *signal, + const char *header, + GCallback callback, + gpointer user_data) { - GSList *vals; + SoupMessagePrivate *priv; + GClosure *closure; + char *header_name; - g_return_val_if_fail (hash != NULL, NULL); - g_return_val_if_fail (name != NULL || name [0] != '\0', NULL); + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), 0); + g_return_val_if_fail (signal != NULL, 0); + g_return_val_if_fail (header != NULL, 0); + g_return_val_if_fail (callback != NULL, 0); - vals = g_hash_table_lookup (hash, name); - if (vals) - return vals->data; + priv = SOUP_MESSAGE_GET_PRIVATE (msg); - return NULL; -} + closure = g_cclosure_new (callback, user_data, NULL); -/** - * soup_message_get_header_list: - * @hash: a header table (the %request_headers or %response_headers - * field of a #SoupMessage) - * @name: header name. - * - * Finds all headers in @hash with name @name. - * - * Return value: a (possibly empty) list of values of headers with - * name @name. The caller should not modify or free this list. - **/ -const GSList * -soup_message_get_header_list (GHashTable *hash, const char *name) -{ - g_return_val_if_fail (hash != NULL, NULL); - g_return_val_if_fail (name != NULL || name [0] != '\0', NULL); + header_name = g_strdup (header); + g_closure_set_meta_marshal (closure, header_name, + header_handler_metamarshal); + g_closure_add_finalize_notifier (closure, header_name, + header_handler_free); - return g_hash_table_lookup (hash, name); + return g_signal_connect_closure (msg, signal, closure, FALSE); } -typedef struct { - GHFunc func; - gpointer user_data; -} SoupMessageForeachHeaderData; - static void -foreach_value_in_list (gpointer name, gpointer value, gpointer user_data) +status_handler_metamarshal (GClosure *closure, GValue *return_value, + guint n_param_values, const GValue *param_values, + gpointer invocation_hint, gpointer marshal_data) { - GSList *vals = value; - SoupMessageForeachHeaderData *data = user_data; + SoupMessage *msg = g_value_get_object (¶m_values[0]); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + guint status = GPOINTER_TO_UINT (marshal_data); + + if (priv->io_status != SOUP_MESSAGE_IO_STATUS_RUNNING) + return; - while (vals) { - (*data->func) (name, vals->data, data->user_data); - vals = vals->next; + if (msg->status_code == status) { + closure->marshal (closure, return_value, n_param_values, + param_values, invocation_hint, + ((GCClosure *)closure)->callback); } } /** - * soup_message_foreach_header: - * @hash: a header table (the %request_headers or %response_headers - * field of a #SoupMessage) - * @func: callback function to run for each header - * @user_data: data to pass to @func - * - * Calls @func once for each header value in @hash. (If there are - * headers will multiple values, @func will be called once on each - * value.) + * soup_message_add_status_code_handler: + * @msg: a #SoupMessage + * @signal: signal to connect the handler to. + * @status_code: status code to match against + * @callback: the header handler + * @user_data: data to pass to @handler_cb + * + * Adds a signal handler to @msg for @signal, as with + * g_signal_connect() but with two differences: the @callback will + * only be run if @msg has the status @status_code, and it will only + * be run if no earlier handler cancelled or requeued the message. + * + * @signal must be a signal that will be emitted after @msg's status + * is set. For a client #SoupMessage, this means it can't be a "wrote" + * signal. For a server #SoupMessage, this means it can't be a "got" + * signal. + * + * Return value: the handler ID from g_signal_connect() **/ -void -soup_message_foreach_header (GHashTable *hash, GHFunc func, gpointer user_data) +guint +soup_message_add_status_code_handler (SoupMessage *msg, + const char *signal, + guint status_code, + GCallback callback, + gpointer user_data) { - SoupMessageForeachHeaderData data; + GClosure *closure; - g_return_if_fail (hash != NULL); - g_return_if_fail (func != NULL); + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), 0); + g_return_val_if_fail (signal != NULL, 0); + g_return_val_if_fail (callback != NULL, 0); + + closure = g_cclosure_new (callback, user_data, NULL); + g_closure_set_meta_marshal (closure, GUINT_TO_POINTER (status_code), + status_handler_metamarshal); - data.func = func; - data.user_data = user_data; - g_hash_table_foreach (hash, foreach_value_in_list, &data); + return g_signal_connect_closure (msg, signal, closure, FALSE); } + /** * soup_message_set_auth: * @msg: a #SoupMessage @@ -751,7 +916,8 @@ soup_message_set_auth (SoupMessage *msg, SoupAuth *auth) if (priv->auth) { g_object_unref (priv->auth); - soup_message_remove_header (msg->request_headers, "Authorization"); + soup_message_headers_remove (msg->request_headers, + "Authorization"); } priv->auth = auth; if (!priv->auth) @@ -759,7 +925,8 @@ soup_message_set_auth (SoupMessage *msg, SoupAuth *auth) g_object_ref (priv->auth); token = soup_auth_get_authorization (auth, msg); - soup_message_add_header (msg->request_headers, "Authorization", token); + soup_message_headers_append (msg->request_headers, + "Authorization", token); g_free (token); } @@ -803,8 +970,8 @@ soup_message_set_proxy_auth (SoupMessage *msg, SoupAuth *auth) if (priv->proxy_auth) { g_object_unref (priv->proxy_auth); - soup_message_remove_header (msg->request_headers, - "Proxy-Authorization"); + soup_message_headers_remove (msg->request_headers, + "Proxy-Authorization"); } priv->proxy_auth = auth; if (!priv->proxy_auth) @@ -812,8 +979,8 @@ soup_message_set_proxy_auth (SoupMessage *msg, SoupAuth *auth) g_object_ref (priv->proxy_auth); token = soup_auth_get_authorization (auth, msg); - soup_message_add_header (msg->request_headers, - "Proxy-Authorization", token); + soup_message_headers_append (msg->request_headers, + "Proxy-Authorization", token); g_free (token); } @@ -845,25 +1012,33 @@ soup_message_get_proxy_auth (SoupMessage *msg) void soup_message_cleanup_response (SoupMessage *req) { - if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED) - g_free (req->response.body); - - req->response.owner = 0; - req->response.body = NULL; - req->response.length = 0; - - free_chunks (req); - - soup_message_clear_headers (req->response_headers); + soup_message_body_truncate (req->response_body); + soup_message_headers_clear (req->response_headers); req->status_code = SOUP_STATUS_NONE; if (req->reason_phrase) { g_free ((char *) req->reason_phrase); req->reason_phrase = NULL; } + g_object_notify (G_OBJECT (req), SOUP_MESSAGE_STATUS_CODE); + g_object_notify (G_OBJECT (req), SOUP_MESSAGE_REASON_PHRASE); } /** + * SoupMessageFlags: + * @SOUP_MESSAGE_NO_REDIRECT: The session should not follow redirect + * (3xx) responses received by this message. + * @SOUP_MESSAGE_OVERWRITE_CHUNKS: Each chunk of the response will be + * freed after its corresponding %got_chunk signal is emitted, meaning + * %response will still be empty after the message is complete. You + * can use this to save memory if you expect the response to be large + * and you are able to process it a chunk at a time. + * + * Various flags that can be set on a #SoupMessage to alter its + * behavior. + **/ + +/** * soup_message_set_flags: * @msg: a #SoupMessage * @flags: a set of #SoupMessageFlags values @@ -871,11 +1046,12 @@ soup_message_cleanup_response (SoupMessage *req) * Sets the specified flags on @msg. **/ void -soup_message_set_flags (SoupMessage *msg, guint flags) +soup_message_set_flags (SoupMessage *msg, SoupMessageFlags flags) { g_return_if_fail (SOUP_IS_MESSAGE (msg)); SOUP_MESSAGE_GET_PRIVATE (msg)->msg_flags = flags; + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_FLAGS); } /** @@ -886,7 +1062,7 @@ soup_message_set_flags (SoupMessage *msg, guint flags) * * Return value: the flags **/ -guint +SoupMessageFlags soup_message_get_flags (SoupMessage *msg) { g_return_val_if_fail (SOUP_IS_MESSAGE (msg), 0); @@ -895,6 +1071,14 @@ soup_message_get_flags (SoupMessage *msg) } /** + * SoupHTTPVersion: + * @SOUP_HTTP_1_0: HTTP 1.0 (RFC 1945) + * @SOUP_HTTP_1_1: HTTP 1.1 (RFC 2616) + * + * Indicates the HTTP protocol version being used. + **/ + +/** * soup_message_set_http_version: * @msg: a #SoupMessage * @version: the HTTP version @@ -904,11 +1088,12 @@ soup_message_get_flags (SoupMessage *msg) * functionality from being used. **/ void -soup_message_set_http_version (SoupMessage *msg, SoupHttpVersion version) +soup_message_set_http_version (SoupMessage *msg, SoupHTTPVersion version) { g_return_if_fail (SOUP_IS_MESSAGE (msg)); SOUP_MESSAGE_GET_PRIVATE (msg)->http_version = version; + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_HTTP_VERSION); } /** @@ -920,7 +1105,7 @@ soup_message_set_http_version (SoupMessage *msg, SoupHttpVersion version) * * Return value: the HTTP version **/ -SoupHttpVersion +SoupHTTPVersion soup_message_get_http_version (SoupMessage *msg) { g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_HTTP_1_0); @@ -933,7 +1118,8 @@ soup_message_get_http_version (SoupMessage *msg) * @msg: a #SoupMessage * * Determines whether or not @msg's connection can be kept alive for - * further requests after processing @msg. + * further requests after processing @msg, based on the HTTP version, + * Connection header, etc. * * Return value: %TRUE or %FALSE. **/ @@ -942,11 +1128,11 @@ soup_message_is_keepalive (SoupMessage *msg) { const char *c_conn, *s_conn; - c_conn = soup_message_get_header (msg->request_headers, "Connection"); - s_conn = soup_message_get_header (msg->response_headers, "Connection"); + c_conn = soup_message_headers_get (msg->request_headers, "Connection"); + s_conn = soup_message_headers_get (msg->response_headers, "Connection"); if (msg->status_code == SOUP_STATUS_OK && - soup_method_get_id (msg->method) == SOUP_METHOD_ID_CONNECT) + msg->method == SOUP_METHOD_CONNECT) return TRUE; if (SOUP_MESSAGE_GET_PRIVATE (msg)->http_version == SOUP_HTTP_1_0) { @@ -956,20 +1142,20 @@ soup_message_is_keepalive (SoupMessage *msg) if (!c_conn || !s_conn) return FALSE; - if (g_ascii_strcasecmp (c_conn, "Keep-Alive") != 0 || - g_ascii_strcasecmp (s_conn, "Keep-Alive") != 0) + if (soup_header_contains (c_conn, "Keep-Alive") || + soup_header_contains (s_conn, "Keep-Alive")) return FALSE; return TRUE; } else { /* Normally persistent unless either side requested otherwise */ - if (c_conn && g_ascii_strcasecmp (c_conn, "close") == 0) + if (c_conn && soup_header_contains (c_conn, "close")) return FALSE; - if (s_conn && g_ascii_strcasecmp (s_conn, "close") == 0) + if (s_conn && soup_header_contains (s_conn, "close")) return FALSE; /* But not if the server sent a terminate-by-EOF response */ - if (soup_message_get_response_encoding (msg, NULL) == SOUP_TRANSFER_EOF) + if (soup_message_headers_get_encoding (msg->response_headers) == SOUP_ENCODING_EOF) return FALSE; return TRUE; @@ -979,14 +1165,14 @@ soup_message_is_keepalive (SoupMessage *msg) /** * soup_message_set_uri: * @msg: a #SoupMessage - * @uri: the new #SoupUri + * @uri: the new #SoupURI * * Sets @msg's URI to @uri. If @msg has already been sent and you want * to re-send it with the new URI, you need to call * soup_session_requeue_message(). **/ void -soup_message_set_uri (SoupMessage *msg, const SoupUri *uri) +soup_message_set_uri (SoupMessage *msg, SoupURI *uri) { SoupMessagePrivate *priv; @@ -996,6 +1182,8 @@ soup_message_set_uri (SoupMessage *msg, const SoupUri *uri) if (priv->uri) soup_uri_free (priv->uri); priv->uri = soup_uri_copy (uri); + + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_URI); } /** @@ -1006,7 +1194,7 @@ soup_message_set_uri (SoupMessage *msg, const SoupUri *uri) * * Return value: the URI @msg is targeted for. **/ -const SoupUri * +SoupURI * soup_message_get_uri (SoupMessage *msg) { g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL); @@ -1015,129 +1203,6 @@ soup_message_get_uri (SoupMessage *msg) } /** - * soup_message_get_request_encoding: - * @msg: a #SoupMessage - * @content_length: a pointer to store the Content-Length in (or - * %NULL). - * - * Gets @msg's request encoding. For an outgoing (client) request, - * this is only valid after the message has been fully set up (from - * the library's perspective, that means not until the message has - * been queued). For an incoming (server) request, this is valid after - * the request headers have been read and @msg->request_headers filled - * in. - * - * Return value: the request encoding (which cannot be - * %SOUP_TRANSFER_UNKNOWN or %SOUP_TRANSFER_EOF). If it is - * %SOUP_TRANSFER_CONTENT_LENGTH, *@content_length will be set to the - * request body's length. - **/ -SoupTransferEncoding -soup_message_get_request_encoding (SoupMessage *msg, guint *content_length) -{ - if (SOUP_IS_SERVER_MESSAGE (msg)) { - const char *enc, *len; - - enc = soup_message_get_header (msg->request_headers, - "Transfer-Encoding"); - len = soup_message_get_header (msg->request_headers, - "Content-Length"); - if (enc) { - if (g_ascii_strcasecmp (enc, "chunked") == 0) - return SOUP_TRANSFER_CHUNKED; - else - return SOUP_TRANSFER_UNKNOWN; - } else if (len) { - int lval = atoi (len); - - if (lval < 0) - return SOUP_TRANSFER_UNKNOWN; - else { - if (content_length) - *content_length = lval; - return SOUP_TRANSFER_CONTENT_LENGTH; - } - } else - return SOUP_TRANSFER_NONE; - } else { - if (msg->request.length) { - if (content_length) - *content_length = msg->request.length; - return SOUP_TRANSFER_CONTENT_LENGTH; - } else - return SOUP_TRANSFER_NONE; - } -} - -/** - * soup_message_get_response_encoding: - * @msg: a #SoupMessage - * @content_length: a pointer to store the Content-Length in (or - * %NULL). - * - * Gets @msg's response encoding. For an outgoing (client) request, - * this is only valid after the response headers have been read and - * @msg->response_headers filled in. For an incoming (server) request, - * this is valid after the server handler has run. - * - * Note that the returned value is the encoding actually used on the - * wire; this will not agree with the response headers in some cases - * (eg, a HEAD response may have a Content-Length header, but will - * still be considered %SOUP_TRANSFER_NONE by this function). - * - * Return value: the response encoding (which will not be - * %SOUP_TRANSFER_UNKNOWN). If it is %SOUP_TRANSFER_CONTENT_LENGTH, - * *@content_length will be set to the response body's length. - **/ -SoupTransferEncoding -soup_message_get_response_encoding (SoupMessage *msg, guint *content_length) -{ - SoupMethodId method = soup_method_get_id (msg->method); - - if (method == SOUP_METHOD_ID_HEAD || - msg->status_code == SOUP_STATUS_NO_CONTENT || - msg->status_code == SOUP_STATUS_NOT_MODIFIED || - SOUP_STATUS_IS_INFORMATIONAL (msg->status_code)) - return SOUP_TRANSFER_NONE; - - if (SOUP_IS_SERVER_MESSAGE (msg)) { - SoupTransferEncoding enc = - soup_server_message_get_encoding ((SoupServerMessage *)msg); - if (enc == SOUP_TRANSFER_UNKNOWN) - enc = SOUP_TRANSFER_CONTENT_LENGTH; - if (enc == SOUP_TRANSFER_CONTENT_LENGTH && content_length) - *content_length = msg->response.length; - return enc; - } else { - const char *enc, *len; - - enc = soup_message_get_header (msg->response_headers, - "Transfer-Encoding"); - len = soup_message_get_header (msg->response_headers, - "Content-Length"); - if (enc) { - if (g_ascii_strcasecmp (enc, "chunked") == 0) - return SOUP_TRANSFER_CHUNKED; - else - return SOUP_TRANSFER_UNKNOWN; - } else if (len) { - int lval = atoi (len); - - if (lval < 0) - return SOUP_TRANSFER_UNKNOWN; - else { - if (content_length) - *content_length = lval; - return SOUP_TRANSFER_CONTENT_LENGTH; - } - } else if (method == SOUP_METHOD_ID_CONNECT) - return SOUP_TRANSFER_NONE; - else - return SOUP_TRANSFER_EOF; - } -} - -/** * soup_message_set_status: * @msg: a #SoupMessage * @status_code: an HTTP status code @@ -1155,6 +1220,8 @@ soup_message_set_status (SoupMessage *msg, guint status_code) msg->status_code = status_code; msg->reason_phrase = g_strdup (soup_status_get_phrase (status_code)); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_STATUS_CODE); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_REASON_PHRASE); } /** @@ -1178,110 +1245,23 @@ soup_message_set_status_full (SoupMessage *msg, msg->status_code = status_code; msg->reason_phrase = g_strdup (reason_phrase); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_STATUS_CODE); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_REASON_PHRASE); } - -/** - * soup_message_add_chunk: - * @msg: a #SoupMessage - * @owner: the ownership of @body - * @body: body data - * @length: length of @body - * - * Adds a chunk of response data to @body. (Note that currently - * there is no way to send a request using chunked encoding.) - **/ -void -soup_message_add_chunk (SoupMessage *msg, - SoupOwnership owner, - const char *body, - guint length) -{ - SoupMessagePrivate *priv; - SoupDataBuffer *chunk; - - g_return_if_fail (SOUP_IS_MESSAGE (msg)); - priv = SOUP_MESSAGE_GET_PRIVATE (msg); - g_return_if_fail (body != NULL || length == 0); - - chunk = g_new0 (SoupDataBuffer, 1); - if (owner == SOUP_BUFFER_USER_OWNED) { - chunk->owner = SOUP_BUFFER_SYSTEM_OWNED; - chunk->body = g_memdup (body, length); - } else { - chunk->owner = owner; - chunk->body = (char *)body; - } - chunk->length = length; - - if (priv->chunks) { - priv->last_chunk = g_slist_append (priv->last_chunk, chunk); - priv->last_chunk = priv->last_chunk->next; - } else { - priv->chunks = priv->last_chunk = - g_slist_append (NULL, chunk); - } -} - -/** - * soup_message_add_final_chunk: - * @msg: a #SoupMessage - * - * Adds a final, empty chunk of response data to @body. This must - * be called after adding the last real chunk, to indicate that - * there is no more data. - **/ void -soup_message_add_final_chunk (SoupMessage *msg) -{ - soup_message_add_chunk (msg, SOUP_BUFFER_STATIC, NULL, 0); -} - -/** - * soup_message_pop_chunk: - * @msg: a #SoupMessage - * - * Pops a chunk of response data from @msg's chunk list. The caller - * must free @chunk itself, and must handle the data in @chunk - * according to its %ownership. - * - * Return value: the chunk, or %NULL if there are no chunks left. - **/ -SoupDataBuffer * -soup_message_pop_chunk (SoupMessage *msg) +soup_message_set_io_status (SoupMessage *msg, + SoupMessageIOStatus status) { - SoupMessagePrivate *priv; - SoupDataBuffer *chunk; - - g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL); - priv = SOUP_MESSAGE_GET_PRIVATE (msg); - - if (!priv->chunks) - return NULL; - - chunk = priv->chunks->data; - priv->chunks = g_slist_remove (priv->chunks, chunk); - if (!priv->chunks) - priv->last_chunk = NULL; + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); - return chunk; + priv->io_status = status; } -static void -free_chunks (SoupMessage *msg) +SoupMessageIOStatus +soup_message_get_io_status (SoupMessage *msg) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); - SoupDataBuffer *chunk; - GSList *ch; - - for (ch = priv->chunks; ch; ch = ch->next) { - chunk = ch->data; - - if (chunk->owner == SOUP_BUFFER_SYSTEM_OWNED) - g_free (chunk->body); - g_free (chunk); - } - g_slist_free (priv->chunks); - priv->chunks = priv->last_chunk = NULL; + return priv->io_status; } diff --git a/libsoup/soup-message.h b/libsoup/soup-message.h index e048a4a..6f38a31 100644 --- a/libsoup/soup-message.h +++ b/libsoup/soup-message.h @@ -7,6 +7,8 @@ #define SOUP_MESSAGE_H 1 #include +#include +#include #include G_BEGIN_DECLS @@ -18,106 +20,6 @@ G_BEGIN_DECLS #define SOUP_IS_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_MESSAGE)) #define SOUP_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_MESSAGE, SoupMessageClass)) -/** - * SoupMessageStatus: - * @SOUP_MESSAGE_STATUS_IDLE: The message has not yet been queued. - * @SOUP_MESSAGE_STATUS_QUEUED: The message has been queued, but is - * waiting for a connection to be available. - * @SOUP_MESSAGE_STATUS_CONNECTING: The message is waiting for a - * specific connection to finish connecting. - * @SOUP_MESSAGE_STATUS_RUNNING: The message is being processed. - * @SOUP_MESSAGE_STATUS_FINISHED: The message is complete (request and - * response both processed). - * - * Enum indicating the lifecycle of a #SoupMessage. - **/ -typedef enum { - SOUP_MESSAGE_STATUS_IDLE, - SOUP_MESSAGE_STATUS_QUEUED, - SOUP_MESSAGE_STATUS_CONNECTING, - SOUP_MESSAGE_STATUS_RUNNING, - SOUP_MESSAGE_STATUS_FINISHED -} SoupMessageStatus; - -/** - * SOUP_MESSAGE_IS_STARTING: - * @msg: a #SoupMessage - * - * Tests if @msg is in a "starting" state, waiting to be sent. (More - * commonly used to test if a message has been requeued after its - * first attempt.) - * - * Return value: %TRUE if @msg is waiting to be sent. - **/ -#define SOUP_MESSAGE_IS_STARTING(msg) (msg->status == SOUP_MESSAGE_STATUS_QUEUED || msg->status == SOUP_MESSAGE_STATUS_CONNECTING) - -/** - * SoupTransferEncoding: - * @SOUP_TRANSFER_UNKNOWN: unknown / error - * @SOUP_TRANSFER_CHUNKED: chunked encoding (currently only supported - * for response) - * @SOUP_TRANSFER_CONTENT_LENGTH: Content-Length encoding - * @SOUP_TRANSFER_BYTERANGES: multipart/byteranges (Reserved for future - * use: NOT CURRENTLY IMPLEMENTED) - * @SOUP_TRANSFER_NONE: no body is present (which is not the same as a - * 0-length body, and only occurs in certain places) - * @SOUP_TRANSFER_EOF: Response body ends when the connection is closed - * - * How the length of a request or response is to be encoded. - **/ -typedef enum { - SOUP_TRANSFER_UNKNOWN = 0, - SOUP_TRANSFER_CHUNKED, - SOUP_TRANSFER_CONTENT_LENGTH, - SOUP_TRANSFER_BYTERANGES, - SOUP_TRANSFER_NONE, - SOUP_TRANSFER_EOF -} SoupTransferEncoding; - -/** - * SoupOwnership: - * @SOUP_BUFFER_SYSTEM_OWNED: The data is owned by soup and it can - * free it when it is done with it. - * @SOUP_BUFFER_USER_OWNED: The data is owned by the user, who is - * responsible for freeing it at the right point - * @SOUP_BUFFER_STATIC: The data should not be freed. - * - * Used by #SoupDataBuffer (and several functions) to indicate the - * ownership of a buffer. - **/ -typedef enum { - SOUP_BUFFER_SYSTEM_OWNED = 0, - SOUP_BUFFER_USER_OWNED, - SOUP_BUFFER_STATIC -} SoupOwnership; - -/** - * SoupDataBuffer: - * @owner: the ownership of the data - * @body: the data itself - * @length: length of @body - * - * A data buffer used in several places. - **/ -typedef struct { - SoupOwnership owner; - char *body; - guint length; -} SoupDataBuffer; - -/** - * SoupMessage: - * @method: the HTTP method - * @status_code: the HTTP status code - * @reason_phrase: the status phrase associated with @status_code - * @request: the request buffer - * @request_headers: the request headers - * @response: the response buffer - * @response_headers: the response headers - * @status: the processing status of the message - * - * Represents an HTTP message being sent or received. - **/ struct SoupMessage { GObject parent; @@ -127,13 +29,11 @@ struct SoupMessage { guint status_code; const char *reason_phrase; - SoupDataBuffer request; - GHashTable *request_headers; + SoupMessageBody *request_body; + SoupMessageHeaders *request_headers; - SoupDataBuffer response; - GHashTable *response_headers; - - SoupMessageStatus status; + SoupMessageBody *response_body; + SoupMessageHeaders *response_headers; }; typedef struct { @@ -146,167 +46,80 @@ typedef struct { void (*wrote_body) (SoupMessage *msg); void (*got_informational) (SoupMessage *msg); void (*got_headers) (SoupMessage *msg); - void (*got_chunk) (SoupMessage *msg); + void (*got_chunk) (SoupMessage *msg, SoupBuffer *chunk); void (*got_body) (SoupMessage *msg); void (*restarted) (SoupMessage *msg); void (*finished) (SoupMessage *msg); + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); } SoupMessageClass; GType soup_message_get_type (void); -/** - * SoupMessageCallbackFn: - * @req: the #SoupMessage in question - * @user_data: user data - * - * A callback function used by many #SoupMessage methods. - **/ -typedef void (*SoupMessageCallbackFn) (SoupMessage *req, gpointer user_data); +#define SOUP_MESSAGE_METHOD "method" +#define SOUP_MESSAGE_URI "uri" +#define SOUP_MESSAGE_HTTP_VERSION "http-version" +#define SOUP_MESSAGE_FLAGS "flags" +#define SOUP_MESSAGE_STATUS_CODE "status-code" +#define SOUP_MESSAGE_REASON_PHRASE "reason-phrase" SoupMessage *soup_message_new (const char *method, const char *uri_string); SoupMessage *soup_message_new_from_uri (const char *method, - const SoupUri *uri); + SoupURI *uri); void soup_message_set_request (SoupMessage *msg, const char *content_type, - SoupOwnership req_owner, - char *req_body, - gulong req_length); - + SoupMemoryUse req_use, + const char *req_body, + gsize req_length); void soup_message_set_response (SoupMessage *msg, const char *content_type, - SoupOwnership resp_owner, - char *resp_body, - gulong resp_length); - -void soup_message_add_header (GHashTable *hash, - const char *name, - const char *value); - -const char *soup_message_get_header (GHashTable *hash, - const char *name); - -const GSList *soup_message_get_header_list (GHashTable *hash, - const char *name); - -void soup_message_foreach_header (GHashTable *hash, - GHFunc func, - gpointer user_data); - -void soup_message_remove_header (GHashTable *hash, - const char *name); + SoupMemoryUse resp_use, + const char *resp_body, + gsize resp_length); -void soup_message_clear_headers (GHashTable *hash); - -/** - * SoupHttpVersion: - * @SOUP_HTTP_1_0: HTTP 1.0 (RFC 1945) - * @SOUP_HTTP_1_1: HTTP 1.1 (RFC 2616) - * - * Indicates the HTTP protocol version being used. - **/ typedef enum { SOUP_HTTP_1_0 = 0, SOUP_HTTP_1_1 = 1 -} SoupHttpVersion; +} SoupHTTPVersion; void soup_message_set_http_version (SoupMessage *msg, - SoupHttpVersion version); -SoupHttpVersion soup_message_get_http_version (SoupMessage *msg); + SoupHTTPVersion version); +SoupHTTPVersion soup_message_get_http_version (SoupMessage *msg); gboolean soup_message_is_keepalive (SoupMessage *msg); -const SoupUri *soup_message_get_uri (SoupMessage *msg); +SoupURI *soup_message_get_uri (SoupMessage *msg); void soup_message_set_uri (SoupMessage *msg, - const SoupUri *uri); - -SoupTransferEncoding soup_message_get_request_encoding (SoupMessage *msg, - guint *content_length); -SoupTransferEncoding soup_message_get_response_encoding (SoupMessage *msg, - guint *content_length); + SoupURI *uri); -/** - * SoupMessageFlags: - * @SOUP_MESSAGE_NO_REDIRECT: The session should not follow redirect - * (3xx) responses received by this message. - * @SOUP_MESSAGE_OVERWRITE_CHUNKS: Rather than building up the - * response body in %response, each new chunk should overwrite the - * previous one. (This can be used if you are connecting to the - * %got_chunk signal or have installed a %SOUP_MESSAGE_BODY_CHUNK - * handler. - * @SOUP_MESSAGE_EXPECT_CONTINUE: This will cause an "Expect: - * 100-continue" header to be added to the outgoing request, giving - * the server the opportunity to reject the message (eg, with a 401 - * Unauthorized) before the full request body is sent. - * - * Various flags that can be set on a #SoupMessage to alter its - * behavior. - **/ typedef enum { SOUP_MESSAGE_NO_REDIRECT = (1 << 1), SOUP_MESSAGE_OVERWRITE_CHUNKS = (1 << 3), - SOUP_MESSAGE_EXPECT_CONTINUE = (1 << 4) } SoupMessageFlags; void soup_message_set_flags (SoupMessage *msg, - guint flags); - -guint soup_message_get_flags (SoupMessage *msg); + SoupMessageFlags flags); -/* - * Handler Registration - */ - -/** - * SoupHandlerPhase: - * @SOUP_HANDLER_POST_REQUEST: The handler should run immediately - * after sending the request body - * @SOUP_HANDLER_PRE_BODY: The handler should run before reading the - * response body (after reading the headers). - * @SOUP_HANDLER_BODY_CHUNK: The handler should run after every body - * chunk is read. (See also %SOUP_MESSAGE_OVERWRITE_CHUNKS.) - * @SOUP_HANDLER_POST_BODY: The handler should run after the entire - * message body has been read. - * - * Indicates when a handler added with soup_message_add_handler() or - * the like will be run. - **/ -typedef enum { - SOUP_HANDLER_POST_REQUEST = 1, - SOUP_HANDLER_PRE_BODY, - SOUP_HANDLER_BODY_CHUNK, - SOUP_HANDLER_POST_BODY -} SoupHandlerPhase; - -void soup_message_add_handler (SoupMessage *msg, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, - gpointer user_data); +SoupMessageFlags soup_message_get_flags (SoupMessage *msg); -void soup_message_add_header_handler (SoupMessage *msg, +/* Specialized signal handlers */ +guint soup_message_add_header_handler (SoupMessage *msg, + const char *signal, const char *header, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, + GCallback callback, gpointer user_data); -void soup_message_add_status_code_handler ( +guint soup_message_add_status_code_handler ( SoupMessage *msg, + const char *signal, guint status_code, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, - gpointer user_data); - -void soup_message_add_status_class_handler ( - SoupMessage *msg, - SoupStatusClass status_class, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, - gpointer user_data); - -void soup_message_remove_handler (SoupMessage *msg, - SoupHandlerPhase phase, - SoupMessageCallbackFn handler_cb, + GCallback callback, gpointer user_data); /* @@ -320,34 +133,13 @@ void soup_message_set_status_full (SoupMessage *msg, const char *reason_phrase); -/* Chunked encoding */ -void soup_message_add_chunk (SoupMessage *msg, - SoupOwnership owner, - const char *body, - guint length); -void soup_message_add_final_chunk (SoupMessage *msg); - -SoupDataBuffer*soup_message_pop_chunk (SoupMessage *msg); - - -/* I/O */ -void soup_message_send_request (SoupMessage *req, - SoupSocket *sock, - gboolean is_via_proxy); -void soup_message_read_request (SoupMessage *req, - SoupSocket *sock); -void soup_message_io_stop (SoupMessage *msg); -void soup_message_io_pause (SoupMessage *msg); -void soup_message_io_unpause (SoupMessage *msg); -gboolean soup_message_io_in_progress (SoupMessage *msg); - void soup_message_wrote_informational (SoupMessage *msg); void soup_message_wrote_headers (SoupMessage *msg); void soup_message_wrote_chunk (SoupMessage *msg); void soup_message_wrote_body (SoupMessage *msg); void soup_message_got_informational (SoupMessage *msg); void soup_message_got_headers (SoupMessage *msg); -void soup_message_got_chunk (SoupMessage *msg); +void soup_message_got_chunk (SoupMessage *msg, SoupBuffer *chunk); void soup_message_got_body (SoupMessage *msg); void soup_message_restarted (SoupMessage *msg); void soup_message_finished (SoupMessage *msg); diff --git a/libsoup/soup-method.c b/libsoup/soup-method.c deleted file mode 100644 index 26f5c24..0000000 --- a/libsoup/soup-method.c +++ /dev/null @@ -1,83 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-method.c: HTTP Method related processing. - * - * Copyright (C) 2001-2002, Ximian, Inc. - */ - -#include - -#include "soup-method.h" - -/** - * soup_method_get_id: - * @method: an HTTP method - * - * Converts @method into a corresponding #SoupMethodId (possibly - * %SOUP_METHOD_ID_UNKNOWN). - * - * Return value: the #SoupMethodId - **/ -SoupMethodId -soup_method_get_id (const char *method) -{ - g_return_val_if_fail (method != NULL, SOUP_METHOD_ID_UNKNOWN); - - switch (*method) { - case 'H': - if (g_ascii_strcasecmp (method, "HEAD") == 0) - return SOUP_METHOD_ID_HEAD; - break; - case 'G': - if (g_ascii_strcasecmp (method, "GET") == 0) - return SOUP_METHOD_ID_GET; - break; - case 'P': - if (g_ascii_strcasecmp (method, "POST") == 0) - return SOUP_METHOD_ID_POST; - if (g_ascii_strcasecmp (method, "PUT") == 0) - return SOUP_METHOD_ID_PUT; - if (g_ascii_strcasecmp (method, "PATCH") == 0) - return SOUP_METHOD_ID_PATCH; - if (g_ascii_strcasecmp (method, "PROPFIND") == 0) - return SOUP_METHOD_ID_PROPFIND; - if (g_ascii_strcasecmp (method, "PROPPATCH") == 0) - return SOUP_METHOD_ID_PROPPATCH; - break; - case 'D': - if (g_ascii_strcasecmp (method, "DELETE") == 0) - return SOUP_METHOD_ID_DELETE; - break; - case 'C': - if (g_ascii_strcasecmp (method, "CONNECT") == 0) - return SOUP_METHOD_ID_CONNECT; - if (g_ascii_strcasecmp (method, "COPY") == 0) - return SOUP_METHOD_ID_COPY; - break; - case 'M': - if (g_ascii_strcasecmp (method, "MKCOL") == 0) - return SOUP_METHOD_ID_MKCOL; - if (g_ascii_strcasecmp (method, "MOVE") == 0) - return SOUP_METHOD_ID_MOVE; - break; - case 'O': - if (g_ascii_strcasecmp (method, "OPTIONS") == 0) - return SOUP_METHOD_ID_OPTIONS; - break; - case 'T': - if (g_ascii_strcasecmp (method, "TRACE") == 0) - return SOUP_METHOD_ID_TRACE; - break; - case 'L': - if (g_ascii_strcasecmp (method, "LOCK") == 0) - return SOUP_METHOD_ID_LOCK; - break; - case 'U': - if (g_ascii_strcasecmp (method, "UNLOCK") == 0) - return SOUP_METHOD_ID_UNLOCK; - break; - } - - return SOUP_METHOD_ID_UNKNOWN; -} - diff --git a/libsoup/soup-method.h b/libsoup/soup-method.h index 4ef69bf..861f7ef 100644 --- a/libsoup/soup-method.h +++ b/libsoup/soup-method.h @@ -6,48 +6,47 @@ #ifndef SOUP_METHOD_H #define SOUP_METHOD_H 1 -#include - G_BEGIN_DECLS -#define SOUP_METHOD_POST "POST" -#define SOUP_METHOD_GET "GET" -#define SOUP_METHOD_HEAD "HEAD" -#define SOUP_METHOD_OPTIONS "OPTIONS" -#define SOUP_METHOD_PUT "PUT" -#define SOUP_METHOD_MOVE "MOVE" -#define SOUP_METHOD_COPY "COPY" -#define SOUP_METHOD_DELETE "DELETE" -#define SOUP_METHOD_TRACE "TRACE" -#define SOUP_METHOD_CONNECT "CONNECT" -#define SOUP_METHOD_MKCOL "MKCOL" -#define SOUP_METHOD_PROPPATCH "PROPPATCH" -#define SOUP_METHOD_PROPFIND "PROPFIND" -#define SOUP_METHOD_PATCH "PATCH" -#define SOUP_METHOD_LOCK "LOCK" -#define SOUP_METHOD_UNLOCK "UNLOCK" - -typedef enum { - SOUP_METHOD_ID_UNKNOWN = 0, - SOUP_METHOD_ID_POST, - SOUP_METHOD_ID_GET, - SOUP_METHOD_ID_HEAD, - SOUP_METHOD_ID_OPTIONS, - SOUP_METHOD_ID_PUT, - SOUP_METHOD_ID_MOVE, - SOUP_METHOD_ID_COPY, - SOUP_METHOD_ID_DELETE, - SOUP_METHOD_ID_TRACE, - SOUP_METHOD_ID_CONNECT, - SOUP_METHOD_ID_MKCOL, - SOUP_METHOD_ID_PROPPATCH, - SOUP_METHOD_ID_PROPFIND, - SOUP_METHOD_ID_PATCH, - SOUP_METHOD_ID_LOCK, - SOUP_METHOD_ID_UNLOCK -} SoupMethodId; - -SoupMethodId soup_method_get_id (const char *method); +/** + * SECTION:soup-method + * @short_description: HTTP method definitions + * + * soup-method.h contains a number of defines for standard HTTP and + * WebDAV headers. You do not need to use these defines; you can pass + * arbitrary strings to soup_message_new() if you prefer. + * + * The thing that these defines are useful for is + * performing quick comparisons against #SoupMessage's %method field; + * because that field always contains an interned string, and these + * macros return interned strings, you can compare %method directly + * against these macros rather than needing to use strcmp(). This is + * most useful in SoupServer handlers. Eg: + * + * + * if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_HEAD) { + * soup_message_set_status (msg, SOUP_METHOD_NOT_IMPLEMENTED); + * return; + * } + * + **/ + +#define SOUP_METHOD_POST (g_intern_static_string ("POST")) +#define SOUP_METHOD_GET (g_intern_static_string ("GET")) +#define SOUP_METHOD_HEAD (g_intern_static_string ("HEAD")) +#define SOUP_METHOD_OPTIONS (g_intern_static_string ("OPTIONS")) +#define SOUP_METHOD_PUT (g_intern_static_string ("PUT")) +#define SOUP_METHOD_MOVE (g_intern_static_string ("MOVE")) +#define SOUP_METHOD_COPY (g_intern_static_string ("COPY")) +#define SOUP_METHOD_DELETE (g_intern_static_string ("DELETE")) +#define SOUP_METHOD_TRACE (g_intern_static_string ("TRACE")) +#define SOUP_METHOD_CONNECT (g_intern_static_string ("CONNECT")) +#define SOUP_METHOD_MKCOL (g_intern_static_string ("MKCOL")) +#define SOUP_METHOD_PROPPATCH (g_intern_static_string ("PROPPATCH")) +#define SOUP_METHOD_PROPFIND (g_intern_static_string ("PROPFIND")) +#define SOUP_METHOD_PATCH (g_intern_static_string ("PATCH")) +#define SOUP_METHOD_LOCK (g_intern_static_string ("LOCK")) +#define SOUP_METHOD_UNLOCK (g_intern_static_string ("UNLOCK")) G_END_DECLS diff --git a/libsoup/soup-misc.c b/libsoup/soup-misc.c index 252596d..79a7399 100644 --- a/libsoup/soup-misc.c +++ b/libsoup/soup-misc.c @@ -11,6 +11,12 @@ #include "soup-misc.h" /** + * SECTION:soup-misc + * @short_description: Miscellaneous functions + * + **/ + +/** * soup_str_case_hash: * @key: ASCII string to hash * @@ -50,68 +56,6 @@ soup_str_case_equal (gconstpointer v1, return g_ascii_strcasecmp (string1, string2) == 0; } -int -soup_base64_encode_close (const guchar *in, - int inlen, - gboolean break_lines, - guchar *out, - int *state, - int *save) -{ - if (inlen > 0) { - out += soup_base64_encode_step (in, - inlen, - break_lines, - out, - state, - save); - } - - return (int)g_base64_encode_close (break_lines, (char *) out, - state, save); -} - -int -soup_base64_encode_step (const guchar *in, - int len, - gboolean break_lines, - guchar *out, - int *state, - int *save) -{ - return (int)g_base64_encode_step (in, len, break_lines, - (char *)out, state, save); -} - -char * -soup_base64_encode (const char *text, int len) -{ - return g_base64_encode ((const guchar *)text, len); -} - -int -soup_base64_decode_step (const guchar *in, - int len, - guchar *out, - int *state, - guint *save) -{ - return (int) g_base64_decode_step ((const char *)in, len, - out, state, save); -} - -char * -soup_base64_decode (const char *text, - int *out_len) -{ - char *ret; - gsize out_len_tmp; - - ret = (char *) g_base64_decode (text, &out_len_tmp); - *out_len = out_len_tmp; - return ret; -} - typedef struct { gpointer instance; guint signal_id; @@ -120,7 +64,7 @@ typedef struct { static void signal_once_object_destroyed (gpointer ssod, GObject *ex_object) { - g_free (ssod); + g_slice_free (SoupSignalOnceData, ssod); } static void @@ -137,7 +81,7 @@ signal_once_metamarshal (GClosure *closure, GValue *return_value, if (g_signal_handler_is_connected (ssod->instance, ssod->signal_id)) g_signal_handler_disconnect (ssod->instance, ssod->signal_id); g_object_weak_unref (G_OBJECT (ssod->instance), signal_once_object_destroyed, ssod); - g_free (ssod); + g_slice_free (SoupSignalOnceData, ssod); } /** @@ -164,7 +108,7 @@ soup_signal_connect_once (gpointer instance, const char *detailed_signal, g_return_val_if_fail (detailed_signal != NULL, 0); g_return_val_if_fail (c_handler != NULL, 0); - ssod = g_new0 (SoupSignalOnceData, 1); + ssod = g_slice_new0 (SoupSignalOnceData); ssod->instance = instance; g_object_weak_ref (G_OBJECT (instance), signal_once_object_destroyed, ssod); @@ -252,21 +196,3 @@ soup_add_timeout (GMainContext *async_context, g_source_unref (source); return source; } - -/** - * soup_xml_real_node: - * @node: an %xmlNodePtr - * - * Finds the first "real" node (ie, not a comment or whitespace) at or - * after @node at its level in the tree. - * - * Return: a node, or %NULL - **/ -xmlNode * -soup_xml_real_node (xmlNode *node) -{ - while (node && (node->type == XML_COMMENT_NODE || - xmlIsBlankNode (node))) - node = node->next; - return node; -} diff --git a/libsoup/soup-misc.h b/libsoup/soup-misc.h index d88edae..3699606 100644 --- a/libsoup/soup-misc.h +++ b/libsoup/soup-misc.h @@ -7,40 +7,9 @@ #define SOUP_MISC_H 1 #include -#include G_BEGIN_DECLS -#ifndef LIBSOUP_DISABLE_DEPRECATED -/* Base64 encoding/decoding. DEPRECATED: use */ - -char *soup_base64_encode (const char *text, - int len); - -int soup_base64_encode_close (const guchar *in, - int inlen, - gboolean break_lines, - guchar *out, - int *state, - int *save); - -int soup_base64_encode_step (const guchar *in, - int len, - gboolean break_lines, - guchar *out, - int *state, - int *save); - -char *soup_base64_decode (const gchar *text, - int *out_len); - -int soup_base64_decode_step (const guchar *in, - int len, - guchar *out, - int *state, - guint *save); -#endif /* LIBSOUP_DISABLE_DEPRECATED */ - /* Non-default-GMainContext operations */ GSource *soup_add_io_watch (GMainContext *async_context, GIOChannel *chan, @@ -66,15 +35,18 @@ guint soup_str_case_hash (gconstpointer key); gboolean soup_str_case_equal (gconstpointer v1, gconstpointer v2); -xmlNode *soup_xml_real_node (xmlNode *node); - -/** - * soup_ssl_supported: - * - * Can be used to test if libsoup was compiled with ssl support. - **/ extern gboolean soup_ssl_supported; +#define SOUP_SSL_ERROR soup_ssl_error_quark() + +GQuark soup_ssl_error_quark (void); + +typedef enum { + SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ, + SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE, + SOUP_SSL_ERROR_CERTIFICATE, +} SoupSSLError; + G_END_DECLS #endif /* SOUP_MISC_H */ diff --git a/libsoup/soup-path-map.c b/libsoup/soup-path-map.c new file mode 100644 index 0000000..60191b4 --- /dev/null +++ b/libsoup/soup-path-map.c @@ -0,0 +1,186 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-path-map.c: URI path prefix-matcher + * + * Copyright (C) 2007 Novell, Inc. + */ + +#include "soup-path-map.h" +#include + +/* This could be replaced with something more clever, like a Patricia + * trie, but it's probably not worth it since the total number of + * mappings is likely to always be small. So we keep an array of + * paths, sorted by decreasing length. (The first prefix match will + * therefore be the longest.) + */ + +typedef struct { + char *path; + int len; + gpointer data; +} SoupPathMapping; + +struct SoupPathMap { + GArray *mappings; + GDestroyNotify free_func; +}; + +/** + * soup_path_map_new: + * @data_free_func: function to use to free data added with + * soup_path_map_add(). + * + * Creates a new %SoupPathMap. + * + * Return value: the new %SoupPathMap + **/ +SoupPathMap * +soup_path_map_new (GDestroyNotify data_free_func) +{ + SoupPathMap *map; + + map = g_slice_new0 (SoupPathMap); + map->mappings = g_array_new (FALSE, FALSE, sizeof (SoupPathMapping)); + map->free_func = data_free_func; + + return map; +} + +/** + * soup_path_map_free: + * @map: a %SoupPathMap + * + * Frees @map and all data stored in it. + **/ +void +soup_path_map_free (SoupPathMap *map) +{ + SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data; + int i; + + for (i = 0; i < map->mappings->len; i++) { + g_free (mappings[i].path); + if (map->free_func) + map->free_func (mappings[i].data); + } + g_array_free (map->mappings, TRUE); + + g_slice_free (SoupPathMap, map); +} + +/* Scan @map looking for @path or one of its ancestors. + * Sets *@match to the index of a match, or -1 if no match is found. + * Sets *@insert to the index to insert @path at if a new mapping is + * desired. Returns %TRUE if *@match is an exact match. + */ +static gboolean +mapping_lookup (SoupPathMap *map, const char *path, int *match, int *insert) +{ + SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data; + int i, path_len; + gboolean exact = FALSE; + + *match = -1; + + path_len = strcspn (path, "?"); + for (i = 0; i < map->mappings->len; i++) { + if (mappings[i].len > path_len) + continue; + + if (insert && mappings[i].len < path_len) { + *insert = i; + /* Clear insert so we don't try to set it again */ + insert = NULL; + } + + if (!strncmp (mappings[i].path, path, mappings[i].len)) { + *match = i; + if (path_len == mappings[i].len) + exact = TRUE; + if (!insert) + return exact; + } + } + + if (insert) + *insert = i; + return exact; +} + +/** + * soup_path_map_add: + * @map: a %SoupPathMap + * @path: the path + * @data: the data + * + * Adds @data to @map at @path. If there was already data at @path it + * will be freed. + **/ +void +soup_path_map_add (SoupPathMap *map, const char *path, gpointer data) +{ + SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data; + int match, insert; + + if (mapping_lookup (map, path, &match, &insert)) { + if (map->free_func) + map->free_func (mappings[match].data); + mappings[match].data = data; + } else { + SoupPathMapping mapping; + + mapping.path = g_strdup (path); + mapping.len = strlen (path); + mapping.data = data; + g_array_insert_val (map->mappings, insert, mapping); + } +} + +/** + * soup_path_map_remove: + * @map: a %SoupPathMap + * @path: the path + * + * Removes @data from @map at @path. (This must be called with the same + * path the data was originally added with, not a subdirectory.) + **/ +void +soup_path_map_remove (SoupPathMap *map, const char *path) +{ + SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data; + int match; + + if (!mapping_lookup (map, path, &match, NULL)) + return; + + if (map->free_func) + map->free_func (mappings[match].data); + g_free (mappings[match].path); + g_array_remove_index (map->mappings, match); +} + +/** + * soup_path_map_lookup: + * @map: a %SoupPathMap + * @path: the path + * + * Finds the data associated with @path in @map. If there is no data + * specifically associated with @path, it will return the data for the + * closest parent directory of @path that has data associated with it. + * + * Return value: the data set with soup_path_map_add(), or %NULL if no + * data could be found for @path or any of its ancestors. + **/ +gpointer +soup_path_map_lookup (SoupPathMap *map, const char *path) +{ + SoupPathMapping *mappings = (SoupPathMapping *)map->mappings->data; + int match; + + mapping_lookup (map, path, &match, NULL); + if (match == -1) + return NULL; + else + return mappings[match].data; +} diff --git a/libsoup/soup-path-map.h b/libsoup/soup-path-map.h new file mode 100644 index 0000000..37b5fb9 --- /dev/null +++ b/libsoup/soup-path-map.h @@ -0,0 +1,26 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Novell, Inc. + */ + +#ifndef SOUP_PATH_MAP_H +#define SOUP_PATH_MAP_H 1 + +#include + +typedef struct SoupPathMap SoupPathMap; + +SoupPathMap *soup_path_map_new (GDestroyNotify data_free_func); +void soup_path_map_free (SoupPathMap *map); + +void soup_path_map_add (SoupPathMap *map, + const char *path, + gpointer data); +void soup_path_map_remove (SoupPathMap *map, + const char *path); + +gpointer soup_path_map_lookup (SoupPathMap *map, + const char *path); + + +#endif /* SOUP_PATH_MAP_H */ diff --git a/libsoup/soup-server-auth.c b/libsoup/soup-server-auth.c deleted file mode 100644 index e2c5138..0000000 --- a/libsoup/soup-server-auth.c +++ /dev/null @@ -1,459 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-server-auth.c: Server-side authentication handling - * - * Copyright (C) 2001-2003, Ximian, Inc. - */ - -#include -#include -#include -#include -#include - -#include "soup-server-auth.h" - -#include "soup-headers.h" -#include "soup-md5-utils.h" -#include "soup-misc.h" -#include "soup-uri.h" - -typedef struct { - const gchar *scheme; - SoupAuthType type; - gint strength; -} AuthScheme; - -static AuthScheme known_auth_schemes [] = { - { "Basic", SOUP_AUTH_TYPE_BASIC, 0 }, - { "Digest", SOUP_AUTH_TYPE_DIGEST, 3 }, - { NULL } -}; - -static SoupAuthType -soup_auth_get_strongest_header (guint auth_types, - const GSList *vals, - gchar **out_hdr) -{ - gchar *header = NULL; - AuthScheme *scheme = NULL, *iter; - - g_return_val_if_fail (vals != NULL, 0); - - if (!auth_types) - return 0; - - while (vals) { - for (iter = known_auth_schemes; iter->scheme; iter++) { - gchar *tryheader = vals->data; - - if ((iter->type & auth_types) && - !g_ascii_strncasecmp (tryheader, - iter->scheme, - strlen (iter->scheme))) { - if (!scheme || - scheme->strength < iter->strength) { - header = tryheader; - scheme = iter; - } - break; - } - } - - vals = vals->next; - } - - if (!scheme) - return 0; - - *out_hdr = header + strlen (scheme->scheme) + 1; - return scheme->type; -} - -static gboolean -check_digest_passwd (SoupServerAuthDigest *digest, - gchar *passwd) -{ - SoupMD5Context ctx; - guchar d[16]; - char hex_a1 [33], hex_a2[33], o[33]; - char *tmp; - - /* compute A1 */ - soup_md5_init (&ctx); - soup_md5_update (&ctx, digest->user, strlen (digest->user)); - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, digest->realm, strlen (digest->realm)); - soup_md5_update (&ctx, ":", 1); - - if (passwd) - soup_md5_update (&ctx, passwd, strlen (passwd)); - - if (digest->algorithm == SOUP_ALGORITHM_MD5_SESS) { - soup_md5_final (&ctx, d); - - soup_md5_init (&ctx); - soup_md5_update (&ctx, d, 16); - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, digest->nonce, strlen (digest->nonce)); - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, digest->cnonce, strlen (digest->cnonce)); - } - - /* hexify A1 */ - soup_md5_final_hex (&ctx, hex_a1); - - /* compute A2 */ - soup_md5_init (&ctx); - soup_md5_update (&ctx, - digest->request_method, - strlen (digest->request_method)); - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, digest->digest_uri, strlen (digest->digest_uri)); - - if (digest->integrity) { - /* FIXME: Actually implement. Ugh. */ - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, "00000000000000000000000000000000", 32); - } - - /* hexify A2 */ - soup_md5_final_hex (&ctx, hex_a2); - - /* compute KD */ - soup_md5_init (&ctx); - soup_md5_update (&ctx, hex_a1, 32); - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, digest->nonce, strlen (digest->nonce)); - soup_md5_update (&ctx, ":", 1); - - tmp = g_strdup_printf ("%.8x", digest->nonce_count); - soup_md5_update (&ctx, tmp, strlen (tmp)); - g_free (tmp); - - soup_md5_update (&ctx, ":", 1); - soup_md5_update (&ctx, digest->cnonce, strlen (digest->cnonce)); - soup_md5_update (&ctx, ":", 1); - - if (digest->integrity) - tmp = "auth-int"; - else - tmp = "auth"; - - soup_md5_update (&ctx, tmp, strlen (tmp)); - soup_md5_update (&ctx, ":", 1); - - soup_md5_update (&ctx, hex_a2, 32); - soup_md5_final_hex (&ctx, o); - - return strcmp (o, digest->digest_response) == 0; -} - -gboolean -soup_server_auth_check_passwd (SoupServerAuth *auth, - gchar *passwd) -{ - g_return_val_if_fail (auth != NULL, TRUE); - - switch (auth->type) { - case SOUP_AUTH_TYPE_BASIC: - if (passwd && auth->basic.passwd) - return strcmp (auth->basic.passwd, passwd) == 0; - else - return passwd == auth->basic.passwd; - case SOUP_AUTH_TYPE_DIGEST: - return check_digest_passwd (&auth->digest, passwd); - } - - return FALSE; -} - -const gchar * -soup_server_auth_get_user (SoupServerAuth *auth) -{ - g_return_val_if_fail (auth != NULL, NULL); - - switch (auth->type) { - case SOUP_AUTH_TYPE_BASIC: - return auth->basic.user; - case SOUP_AUTH_TYPE_DIGEST: - return auth->digest.user; - } - - return NULL; -} - -static gboolean -parse_digest (SoupServerAuthContext *auth_ctx, - gchar *header, - SoupMessage *msg, - SoupServerAuth *out_auth) -{ - GHashTable *tokens; - gchar *user, *realm, *uri, *response; - gchar *nonce, *cnonce; - gint nonce_count; - gboolean integrity; - - user = realm = uri = response = NULL; - nonce = cnonce = NULL; - nonce_count = 0; - integrity = FALSE; - - tokens = soup_header_param_parse_list (header); - if (!tokens) - goto DIGEST_AUTH_FAIL; - - /* Check uri */ - { - SoupUri *dig_uri; - const SoupUri *req_uri; - - uri = soup_header_param_copy_token (tokens, "uri"); - if (!uri) - goto DIGEST_AUTH_FAIL; - - req_uri = soup_message_get_uri (msg); - - dig_uri = soup_uri_new (uri); - if (dig_uri) { - if (!soup_uri_equal (dig_uri, req_uri)) { - soup_uri_free (dig_uri); - goto DIGEST_AUTH_FAIL; - } - soup_uri_free (dig_uri); - } else { - char *req_path; - - req_path = soup_uri_to_string (req_uri, TRUE); - if (strcmp (uri, req_path) != 0) { - g_free (req_path); - goto DIGEST_AUTH_FAIL; - } - g_free (req_path); - } - } - - /* Check qop */ - { - gchar *qop; - qop = soup_header_param_copy_token (tokens, "qop"); - if (!qop) - goto DIGEST_AUTH_FAIL; - - if (!strcmp (qop, "auth-int")) { - g_free (qop); - integrity = TRUE; - } else if (auth_ctx->digest_info.force_integrity) { - g_free (qop); - goto DIGEST_AUTH_FAIL; - } - } - - /* Check realm */ - realm = soup_header_param_copy_token (tokens, "realm"); - if (!realm && auth_ctx->digest_info.realm) - goto DIGEST_AUTH_FAIL; - else if (realm && - auth_ctx->digest_info.realm && - strcmp (realm, auth_ctx->digest_info.realm) != 0) - goto DIGEST_AUTH_FAIL; - - /* Check username */ - user = soup_header_param_copy_token (tokens, "username"); - if (!user) - goto DIGEST_AUTH_FAIL; - - /* Check nonce */ - nonce = soup_header_param_copy_token (tokens, "nonce"); - if (!nonce) - goto DIGEST_AUTH_FAIL; - - /* Check nonce count */ - { - gchar *nc; - nc = soup_header_param_copy_token (tokens, "nc"); - if (!nc) - goto DIGEST_AUTH_FAIL; - - nonce_count = atoi (nc); - if (nonce_count <= 0) { - g_free (nc); - goto DIGEST_AUTH_FAIL; - } - g_free (nc); - } - - cnonce = soup_header_param_copy_token (tokens, "cnonce"); - if (!cnonce) - goto DIGEST_AUTH_FAIL; - - response = soup_header_param_copy_token (tokens, "response"); - if (!response) - goto DIGEST_AUTH_FAIL; - - out_auth->digest.type = SOUP_AUTH_TYPE_DIGEST; - out_auth->digest.digest_uri = uri; - out_auth->digest.integrity = integrity; - out_auth->digest.realm = realm; - out_auth->digest.user = user; - out_auth->digest.nonce = nonce; - out_auth->digest.nonce_count = nonce_count; - out_auth->digest.cnonce = cnonce; - out_auth->digest.digest_response = response; - out_auth->digest.request_method = msg->method; - - soup_header_param_destroy_hash (tokens); - - return TRUE; - - DIGEST_AUTH_FAIL: - if (tokens) - soup_header_param_destroy_hash (tokens); - - g_free (user); - g_free (realm); - g_free (nonce); - g_free (response); - g_free (cnonce); - g_free (uri); - - return FALSE; -} - -SoupServerAuth * -soup_server_auth_new (SoupServerAuthContext *auth_ctx, - const GSList *auth_hdrs, - SoupMessage *msg) -{ - SoupServerAuth *ret; - SoupAuthType type; - gchar *header = NULL; - - g_return_val_if_fail (auth_ctx != NULL, NULL); - g_return_val_if_fail (msg != NULL, NULL); - - if (!auth_hdrs && auth_ctx->types) { - soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED); - return NULL; - } - - type = soup_auth_get_strongest_header (auth_ctx->types, - auth_hdrs, - &header); - - if (!type && auth_ctx->types) { - soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED); - return NULL; - } - - ret = g_new0 (SoupServerAuth, 1); - - switch (type) { - case SOUP_AUTH_TYPE_BASIC: - { - guchar *userpass, *colon; - gsize len; - - userpass = g_base64_decode (header, &len); - if (!userpass) - break; - - colon = memchr (userpass, ':', len); - if (!colon) { - g_free (userpass); - break; - } - - ret->basic.type = SOUP_AUTH_TYPE_BASIC; - ret->basic.user = g_strndup ((char *)userpass, - colon - userpass); - ret->basic.passwd = g_strndup ((char *)colon + 1, - len - (colon + 1 - userpass)); - - g_free (userpass); - - return ret; - } - case SOUP_AUTH_TYPE_DIGEST: - if (parse_digest (auth_ctx, header, msg, ret)) - return ret; - break; - } - - g_free (ret); - - soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED); - return NULL; -} - -void -soup_server_auth_free (SoupServerAuth *auth) -{ - g_return_if_fail (auth != NULL); - - switch (auth->type) { - case SOUP_AUTH_TYPE_BASIC: - g_free ((gchar *) auth->basic.user); - g_free ((gchar *) auth->basic.passwd); - break; - case SOUP_AUTH_TYPE_DIGEST: - g_free ((gchar *) auth->digest.realm); - g_free ((gchar *) auth->digest.user); - g_free ((gchar *) auth->digest.nonce); - g_free ((gchar *) auth->digest.cnonce); - g_free ((gchar *) auth->digest.digest_uri); - g_free ((gchar *) auth->digest.digest_response); - break; - } - - g_free (auth); -} - -void -soup_server_auth_context_challenge (SoupServerAuthContext *auth_ctx, - SoupMessage *msg, - gchar *header_name) -{ - if (auth_ctx->types & SOUP_AUTH_TYPE_BASIC) { - gchar *hdr; - - hdr = g_strdup_printf ("Basic realm=\"%s\"", - auth_ctx->basic_info.realm); - soup_message_add_header (msg->response_headers, - header_name, - hdr); - g_free (hdr); - } - - if (auth_ctx->types & SOUP_AUTH_TYPE_DIGEST) { - GString *str; - - str = g_string_new ("Digest "); - - if (auth_ctx->digest_info.realm) - g_string_sprintfa (str, - "realm=\"%s\", ", - auth_ctx->digest_info.realm); - - g_string_sprintfa (str, - "nonce=\"%lu%lu\", ", - (unsigned long) msg, - (unsigned long) time (0)); - - if (auth_ctx->digest_info.force_integrity) - g_string_sprintfa (str, "qop=\"auth-int\", "); - else - g_string_sprintfa (str, "qop=\"auth,auth-int\", "); - - if (auth_ctx->digest_info.allow_algorithms & SOUP_ALGORITHM_MD5_SESS) - g_string_sprintfa (str, "algorithm=\"MD5-sess\""); - else - g_string_sprintfa (str, "algorithm=\"MD5\""); - - soup_message_add_header (msg->response_headers, - header_name, - str->str); - g_string_free (str, TRUE); - } -} diff --git a/libsoup/soup-server-auth.h b/libsoup/soup-server-auth.h deleted file mode 100644 index f537e37..0000000 --- a/libsoup/soup-server-auth.h +++ /dev/null @@ -1,90 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-server-auth.h: Server-side authentication handling - * - * Copyright (C) 2001-2003, Ximian, Inc. - */ - -#ifndef SOUP_SERVER_AUTH_H -#define SOUP_SERVER_AUTH_H 1 - -#include - -G_BEGIN_DECLS - -typedef gboolean (*SoupServerAuthCallbackFn) (SoupServerAuthContext *auth_ctx, - SoupServerAuth *auth, - SoupMessage *msg, - gpointer data); - -struct SoupServerAuthContext { - guint types; - SoupServerAuthCallbackFn callback; - gpointer user_data; - - struct { - const gchar *realm; - } basic_info; - - struct { - const gchar *realm; - guint allow_algorithms; - gboolean force_integrity; - } digest_info; -}; - -void soup_server_auth_context_challenge (SoupServerAuthContext *auth_ctx, - SoupMessage *msg, - gchar *header_name); - - -typedef enum { - SOUP_AUTH_TYPE_BASIC = 1, - SOUP_AUTH_TYPE_DIGEST -} SoupAuthType; - -typedef struct { - SoupAuthType type; - const gchar *user; - const gchar *passwd; -} SoupServerAuthBasic; - -typedef enum { - SOUP_ALGORITHM_MD5 = 1 << 0, - SOUP_ALGORITHM_MD5_SESS = 1 << 1 -} SoupDigestAlgorithm; - -typedef struct { - SoupAuthType type; - SoupDigestAlgorithm algorithm; - gboolean integrity; - const gchar *realm; - const gchar *user; - const gchar *nonce; - gint nonce_count; - const gchar *cnonce; - const gchar *digest_uri; - const gchar *digest_response; - const gchar *request_method; -} SoupServerAuthDigest; - -union SoupServerAuth { - SoupAuthType type; - SoupServerAuthBasic basic; - SoupServerAuthDigest digest; -}; - -SoupServerAuth *soup_server_auth_new (SoupServerAuthContext *auth_ctx, - const GSList *auth_hdrs, - SoupMessage *msg); - -void soup_server_auth_free (SoupServerAuth *auth); - -const gchar *soup_server_auth_get_user (SoupServerAuth *auth); - -gboolean soup_server_auth_check_passwd (SoupServerAuth *auth, - gchar *passwd); - -G_END_DECLS - -#endif /* SOUP_SERVER_AUTH_H */ diff --git a/libsoup/soup-server-message.c b/libsoup/soup-server-message.c deleted file mode 100644 index ca4cc1e..0000000 --- a/libsoup/soup-server-message.c +++ /dev/null @@ -1,141 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-server-message.c: Server-side messages - * - * Copyright (C) 2001-2003, Ximian, Inc. - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include - -#include "soup-server-message.h" -#include "soup-server.h" - -typedef struct { - SoupServer *server; - - SoupTransferEncoding encoding; - - gboolean started; - gboolean finished; -} SoupServerMessagePrivate; -#define SOUP_SERVER_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SERVER_MESSAGE, SoupServerMessagePrivate)) - -G_DEFINE_TYPE (SoupServerMessage, soup_server_message, SOUP_TYPE_MESSAGE) - -static void -soup_server_message_init (SoupServerMessage *smsg) -{ - SOUP_SERVER_MESSAGE_GET_PRIVATE (smsg)->encoding = SOUP_TRANSFER_CONTENT_LENGTH; -} - -static void -finalize (GObject *object) -{ - SoupServerMessage *smsg = SOUP_SERVER_MESSAGE (object); - - /* FIXME */ - g_free ((char *) ((SoupMessage *)smsg)->method); - - G_OBJECT_CLASS (soup_server_message_parent_class)->finalize (object); -} - -static void -soup_server_message_class_init (SoupServerMessageClass *soup_server_message_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (soup_server_message_class); - - g_type_class_add_private (soup_server_message_class, sizeof (SoupServerMessagePrivate)); - - /* virtual method override */ - object_class->finalize = finalize; -} - - -SoupServerMessage * -soup_server_message_new (SoupServer *server) -{ - SoupServerMessage *smsg; - - g_return_val_if_fail (SOUP_IS_SERVER (server), NULL); - - smsg = g_object_new (SOUP_TYPE_SERVER_MESSAGE, NULL); - SOUP_SERVER_MESSAGE_GET_PRIVATE (smsg)->server = server; - - return smsg; -} - -SoupServer * -soup_server_message_get_server (SoupServerMessage *smsg) -{ - g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (smsg), NULL); - - return SOUP_SERVER_MESSAGE_GET_PRIVATE (smsg)->server; -} - -void -soup_server_message_set_encoding (SoupServerMessage *smsg, - SoupTransferEncoding encoding) -{ - g_return_if_fail (SOUP_IS_SERVER_MESSAGE (smsg)); - - if (encoding < SOUP_TRANSFER_UNKNOWN || - encoding > SOUP_TRANSFER_CONTENT_LENGTH) - return; - - SOUP_SERVER_MESSAGE_GET_PRIVATE (smsg)->encoding = encoding; -} - -SoupTransferEncoding -soup_server_message_get_encoding (SoupServerMessage *smsg) -{ - g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (smsg), SOUP_TRANSFER_UNKNOWN); - - return SOUP_SERVER_MESSAGE_GET_PRIVATE (smsg)->encoding; -} - -void -soup_server_message_start (SoupServerMessage *smsg) -{ - g_return_if_fail (SOUP_IS_SERVER_MESSAGE (smsg)); - - SOUP_SERVER_MESSAGE_GET_PRIVATE (smsg)->started = TRUE; - - soup_message_io_unpause (SOUP_MESSAGE (smsg)); -} - -gboolean -soup_server_message_is_started (SoupServerMessage *smsg) -{ - g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (smsg), TRUE); - - return SOUP_SERVER_MESSAGE_GET_PRIVATE (smsg)->started; -} - -void -soup_server_message_finish (SoupServerMessage *smsg) -{ - SoupServerMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SERVER_MESSAGE (smsg)); - priv = SOUP_SERVER_MESSAGE_GET_PRIVATE (smsg); - - priv->started = TRUE; - priv->finished = TRUE; - - soup_message_io_unpause (SOUP_MESSAGE (smsg)); -} - -gboolean -soup_server_message_is_finished (SoupServerMessage *smsg) -{ - g_return_val_if_fail (SOUP_IS_SERVER_MESSAGE (smsg), TRUE); - - return SOUP_SERVER_MESSAGE_GET_PRIVATE (smsg)->finished; -} diff --git a/libsoup/soup-server-message.h b/libsoup/soup-server-message.h deleted file mode 100644 index cae2774..0000000 --- a/libsoup/soup-server-message.h +++ /dev/null @@ -1,49 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2000-2003, Ximian, Inc. - */ - -#ifndef SOUP_SERVER_MESSAGE_H -#define SOUP_SERVER_MESSAGE_H 1 - -#include - -G_BEGIN_DECLS - -#define SOUP_TYPE_SERVER_MESSAGE (soup_server_message_get_type ()) -#define SOUP_SERVER_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_SERVER_MESSAGE, SoupServerMessage)) -#define SOUP_SERVER_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_SERVER_MESSAGE, SoupServerMessageClass)) -#define SOUP_IS_SERVER_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_SERVER_MESSAGE)) -#define SOUP_IS_SERVER_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_SERVER_MESSAGE)) -#define SOUP_SERVER_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_SERVER_MESSAGE, SoupServerMessageClass)) - -struct SoupServerMessage { - SoupMessage parent; - -}; - -typedef struct { - SoupMessageClass parent_class; - -} SoupServerMessageClass; - -GType soup_server_message_get_type (void); - - -SoupServerMessage *soup_server_message_new (SoupServer *server); - -SoupServer *soup_server_message_get_server (SoupServerMessage *smsg); - -void soup_server_message_set_encoding (SoupServerMessage *smsg, - SoupTransferEncoding encoding); -SoupTransferEncoding soup_server_message_get_encoding (SoupServerMessage *smsg); - -void soup_server_message_start (SoupServerMessage *smsg); -gboolean soup_server_message_is_started (SoupServerMessage *smsg); - -void soup_server_message_finish (SoupServerMessage *smsg); -gboolean soup_server_message_is_finished (SoupServerMessage *smsg); - -G_END_DECLS - -#endif /* SOUP_SERVER_H */ diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c index 051effe..933c80f 100644 --- a/libsoup/soup-server.c +++ b/libsoup/soup-server.c @@ -5,10 +5,6 @@ * Copyright (C) 2001-2003, Ximian, Inc. */ -/* - * FIXME: Split into SoupServerTCP and SoupServerCGI subclasses - */ - #ifdef HAVE_CONFIG_H #include #endif @@ -20,14 +16,76 @@ #include "soup-server.h" #include "soup-address.h" +#include "soup-auth-domain.h" +#include "soup-date.h" +#include "soup-form.h" #include "soup-headers.h" -#include "soup-server-auth.h" -#include "soup-server-message.h" +#include "soup-message-private.h" +#include "soup-marshal.h" +#include "soup-path-map.h" #include "soup-socket.h" #include "soup-ssl.h" +/** + * SECTION:soup-server + * @short_description: HTTP server + * @see_also: #SoupAuthDomain + * + * #SoupServer implements a simple HTTP server. + * + * To begin, create a server using soup_server_new(). Add at least one + * handler by calling soup_server_add_handler(); the handler will be + * called to process any requests underneath the path passed to + * soup_server_add_handler(). (If you want all requests to go to the + * same handler, just pass "/" (or %NULL) for the path.) Any request + * that does not match any handler will automatically be returned to + * the client with a 404 (Not Found) status. + * + * To add authentication to some or all paths, create an appropriate + * #SoupAuthDomain (qv), and add it to the server via + * soup_server_add_auth_domain. + * + * Additional processing options are available via #SoupServer's + * signals; Connect to #SoupServer::request-started to be notified + * every time a new request is being processed. (This gives you a + * chance to connect to the #SoupMessage "got-" signals in case you + * want to do processing before the body has been fully read.) + * + * Once the server is set up, start it processing connections by + * calling soup_server_run_async() or soup_server_run(). #SoupServer + * runs via the glib main loop; if you need to have a server that runs + * in another thread (or merely isn't bound to the default main loop), + * create a #GMainContext for it to use, and set that via the + * #SOUP_SERVER_ASYNC_CONTEXT property. + **/ + G_DEFINE_TYPE (SoupServer, soup_server, G_TYPE_OBJECT) +enum { + REQUEST_STARTED, + REQUEST_READ, + REQUEST_FINISHED, + REQUEST_ABORTED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct SoupClientContext { + SoupServer *server; + SoupSocket *sock; + SoupAuthDomain *auth_domain; + char *auth_user; +}; + +typedef struct { + char *path; + + SoupServerCallback callback; + GDestroyNotify destroy; + gpointer user_data; +} SoupServerHandler; + typedef struct { SoupAddress *interface; guint port; @@ -40,9 +98,12 @@ typedef struct { SoupSocket *listen_sock; GSList *client_socks; - GHashTable *handlers; /* KEY: path, VALUE: SoupServerHandler */ + gboolean raw_paths; + SoupPathMap *handlers; SoupServerHandler *default_handler; + GSList *auth_domains; + GMainContext *async_context; } SoupServerPrivate; #define SOUP_SERVER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SERVER, SoupServerPrivate)) @@ -55,43 +116,32 @@ enum { PROP_SSL_CERT_FILE, PROP_SSL_KEY_FILE, PROP_ASYNC_CONTEXT, + PROP_RAW_PATHS, LAST_PROP }; +static GObject *constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties); static void set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void -soup_server_init (SoupServer *server) +free_handler (SoupServerHandler *hand) { - SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); - - priv->handlers = g_hash_table_new (g_str_hash, g_str_equal); -} - -static void -free_handler (SoupServer *server, SoupServerHandler *hand) -{ - if (hand->unregister) - (*hand->unregister) (server, hand, hand->user_data); - - if (hand->auth_ctx) { - g_free ((char *) hand->auth_ctx->basic_info.realm); - g_free ((char *) hand->auth_ctx->digest_info.realm); - g_free (hand->auth_ctx); - } - g_free (hand->path); - g_free (hand); + g_slice_free (SoupServerHandler, hand); } static void -free_handler_foreach (gpointer key, gpointer hand, gpointer server) +soup_server_init (SoupServer *server) { - free_handler (server, hand); + SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); + + priv->handlers = soup_path_map_new ((GDestroyNotify)free_handler); } static void @@ -99,6 +149,7 @@ finalize (GObject *object) { SoupServer *server = SOUP_SERVER (object); SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); + GSList *iter; if (priv->interface) g_object_unref (priv->interface); @@ -120,10 +171,12 @@ finalize (GObject *object) } if (priv->default_handler) - free_handler (server, priv->default_handler); + free_handler (priv->default_handler); + soup_path_map_free (priv->handlers); - g_hash_table_foreach (priv->handlers, free_handler_foreach, server); - g_hash_table_destroy (priv->handlers); + for (iter = priv->auth_domains; iter; iter = iter->next) + g_object_unref (iter->data); + g_slist_free (priv->auth_domains); if (priv->loop) g_main_loop_unref (priv->loop); @@ -141,10 +194,116 @@ soup_server_class_init (SoupServerClass *server_class) g_type_class_add_private (server_class, sizeof (SoupServerPrivate)); /* virtual method override */ + object_class->constructor = constructor; object_class->finalize = finalize; object_class->set_property = set_property; object_class->get_property = get_property; + /* signals */ + + /** + * SoupServer::request-started + * @server: the server + * @message: the new message + * @client: the client context + * + * Emitted when the server has started reading a new request. + * @message will be completely blank; not even the + * Request-Line will have been read yet. About the only thing + * you can usefully do with it is connect to its signals. + * + * If the request is read successfully, this will eventually + * be followed by a #SoupServer::request_read signal. If a + * response is then sent, the request processing will end with + * a #SoupServer::request_finished signal. If a network error + * occurs, the processing will instead end with + * #SoupServer::request_aborted. + **/ + signals[REQUEST_STARTED] = + g_signal_new ("request-started", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (SoupServerClass, request_started), + NULL, NULL, + soup_marshal_NONE__OBJECT_POINTER, + G_TYPE_NONE, 2, + SOUP_TYPE_MESSAGE, + SOUP_TYPE_CLIENT_CONTEXT); + + /** + * SoupServer::request-read + * @server: the server + * @message: the message + * @client: the client context + * + * Emitted when the server has successfully read a request. + * @message will have all of its request-side information + * filled in, and if the message was authenticated, @client + * will have information about that. This signal is emitted + * before any handlers are called for the message, and if it + * sets the message's #status_code, then normal handler + * processing will be skipped. + **/ + signals[REQUEST_READ] = + g_signal_new ("request-read", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (SoupServerClass, request_read), + NULL, NULL, + soup_marshal_NONE__OBJECT_POINTER, + G_TYPE_NONE, 2, + SOUP_TYPE_MESSAGE, + SOUP_TYPE_CLIENT_CONTEXT); + + /** + * SoupServer::request-finished + * @server: the server + * @message: the message + * @client: the client context + * + * Emitted when the server has finished writing a response to + * a request. + **/ + signals[REQUEST_FINISHED] = + g_signal_new ("request-finished", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (SoupServerClass, request_finished), + NULL, NULL, + soup_marshal_NONE__OBJECT_POINTER, + G_TYPE_NONE, 2, + SOUP_TYPE_MESSAGE, + SOUP_TYPE_CLIENT_CONTEXT); + + /** + * SoupServer::request-aborted + * @server: the server + * @message: the message + * @client: the client context + * + * Emitted when processing has failed for a message; this + * could mean either that it could not be read (if + * #SoupServer::request_read has not been emitted for it yet), + * or that the response could not be written back (if + * #SoupServer::request_read has been emitted but + * #SoupServer::request_finished has not been). + * + * @message is in an undefined state when this signal is + * emitted; the signal exists primarily to allow the server to + * free any state that it may have allocated in + * #SoupServer::request_started. + **/ + signals[REQUEST_ABORTED] = + g_signal_new ("request-aborted", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (SoupServerClass, request_aborted), + NULL, NULL, + soup_marshal_NONE__OBJECT_POINTER, + G_TYPE_NONE, 2, + SOUP_TYPE_MESSAGE, + SOUP_TYPE_CLIENT_CONTEXT); + /* properties */ g_object_class_install_property ( object_class, PROP_PORT, @@ -180,8 +339,65 @@ soup_server_class_init (SoupServerClass *server_class) "Async GMainContext", "The GMainContext to dispatch async I/O in", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, PROP_RAW_PATHS, + g_param_spec_boolean (SOUP_SERVER_RAW_PATHS, + "Raw paths", + "If %TRUE, percent-encoding in the Request-URI path will not be automatically decoded.", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } +static GObject * +constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *server; + SoupServerPrivate *priv; + + server = G_OBJECT_CLASS (soup_server_parent_class)->constructor ( + type, n_construct_properties, construct_properties); + if (!server) + return NULL; + priv = SOUP_SERVER_GET_PRIVATE (server); + + if (!priv->interface) { + priv->interface = + soup_address_new_any (SOUP_ADDRESS_FAMILY_IPV4, + priv->port); + } + + if (priv->ssl_cert_file && priv->ssl_key_file) { + priv->ssl_creds = soup_ssl_get_server_credentials ( + priv->ssl_cert_file, + priv->ssl_key_file); + if (!priv->ssl_creds) { + g_object_unref (server); + return NULL; + } + } + + priv->listen_sock = + soup_socket_new (SOUP_SOCKET_LOCAL_ADDRESS, priv->interface, + SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds, + SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context, + NULL); + if (!soup_socket_listen (priv->listen_sock)) { + g_object_unref (server); + return NULL; + } + + /* Re-resolve the interface address, in particular in case + * the passed-in address had SOUP_ADDRESS_ANY_PORT. + */ + g_object_unref (priv->interface); + priv->interface = soup_socket_get_local_address (priv->listen_sock); + g_object_ref (priv->interface); + priv->port = soup_address_get_port (priv->interface); + + return server; +} static void set_property (GObject *object, guint prop_id, @@ -213,6 +429,9 @@ set_property (GObject *object, guint prop_id, if (priv->async_context) g_main_context_ref (priv->async_context); break; + case PROP_RAW_PATHS: + priv->raw_paths = g_value_get_boolean (value); + break; default: break; } @@ -240,16 +459,25 @@ get_property (GObject *object, guint prop_id, case PROP_ASYNC_CONTEXT: g_value_set_pointer (value, priv->async_context ? g_main_context_ref (priv->async_context) : NULL); break; + case PROP_RAW_PATHS: + g_value_set_boolean (value, priv->raw_paths); + break; default: break; } } +/** + * soup_server_new: + * @optname1: name of first property to set + * @...: value of @optname1, followed by additional property/value pairs + * + * Creates a new #SoupServer. + **/ SoupServer * soup_server_new (const char *optname1, ...) { SoupServer *server; - SoupServerPrivate *priv; va_list ap; va_start (ap, optname1); @@ -257,46 +485,19 @@ soup_server_new (const char *optname1, ...) optname1, ap); va_end (ap); - if (!server) - return NULL; - priv = SOUP_SERVER_GET_PRIVATE (server); - - if (!priv->interface) { - priv->interface = - soup_address_new_any (SOUP_ADDRESS_FAMILY_IPV4, - priv->port); - } - - if (priv->ssl_cert_file && priv->ssl_key_file) { - priv->ssl_creds = soup_ssl_get_server_credentials ( - priv->ssl_cert_file, - priv->ssl_key_file); - if (!priv->ssl_creds) { - g_object_unref (server); - return NULL; - } - } - - priv->listen_sock = - soup_socket_new (SOUP_SOCKET_SSL_CREDENTIALS, priv->ssl_creds, - SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context, - NULL); - if (!soup_socket_listen (priv->listen_sock, priv->interface)) { - g_object_unref (server); - return NULL; - } - - /* Re-resolve the interface address, in particular in case - * the passed-in address had SOUP_ADDRESS_ANY_PORT. - */ - g_object_unref (priv->interface); - priv->interface = soup_socket_get_local_address (priv->listen_sock); - g_object_ref (priv->interface); - priv->port = soup_address_get_port (priv->interface); - return server; } +/** + * soup_server_get_port: + * @server: a #SoupServer + * + * Gets the TCP port that @server is listening on. This is most useful + * when you did not request a specific port (or explicitly requested + * %SOUP_ADDRESS_ANY_PORT). + * + * Return value: the port @server is listening on. + **/ guint soup_server_get_port (SoupServer *server) { @@ -305,20 +506,39 @@ soup_server_get_port (SoupServer *server) return SOUP_SERVER_GET_PRIVATE (server)->port; } -SoupProtocol -soup_server_get_protocol (SoupServer *server) +/** + * soup_server_is_https: + * @server: a #SoupServer + * + * Checks whether @server is running plain http or https. + * + * In order for a server to run https, you must set the + * %SOUP_SERVER_SSL_CERT_FILE and %SOUP_SERVER_SSL_KEY_FILE properties + * to provide it with an SSL certificate to use. + * + * Return value: %TRUE if @server is serving https. + **/ +gboolean +soup_server_is_https (SoupServer *server) { SoupServerPrivate *priv; g_return_val_if_fail (SOUP_IS_SERVER (server), 0); priv = SOUP_SERVER_GET_PRIVATE (server); - if (priv->ssl_cert_file && priv->ssl_key_file) - return SOUP_PROTOCOL_HTTPS; - else - return SOUP_PROTOCOL_HTTP; + return (priv->ssl_cert_file && priv->ssl_key_file); } +/** + * soup_server_get_listener: + * @server: a #SoupServer + * + * Gets @server's listening socket. You should treat this as + * read-only; writing to it or modifiying it may cause @server to + * malfunction. + * + * Return value: the listening socket. + **/ SoupSocket * soup_server_get_listener (SoupServer *server) { @@ -330,125 +550,178 @@ soup_server_get_listener (SoupServer *server) return priv->listen_sock; } -static void start_request (SoupServer *, SoupSocket *); +static void start_request (SoupServer *, SoupClientContext *); + +static void +soup_client_context_cleanup (SoupClientContext *client) +{ + if (client->auth_domain) { + g_object_unref (client->auth_domain); + client->auth_domain = NULL; + } + if (client->auth_user) { + g_free (client->auth_user); + client->auth_user = NULL; + } +} static void -request_finished (SoupMessage *msg, gpointer sock) +request_finished (SoupMessage *msg, SoupClientContext *client) { - SoupServerMessage *smsg = SOUP_SERVER_MESSAGE (msg); + SoupServer *server = client->server; + SoupSocket *sock = client->sock; + + g_signal_emit (server, + msg->status_code == SOUP_STATUS_IO_ERROR ? + signals[REQUEST_ABORTED] : signals[REQUEST_FINISHED], + 0, msg, client); + soup_client_context_cleanup (client); if (soup_socket_is_connected (sock) && soup_message_is_keepalive (msg)) { /* Start a new request */ - start_request (soup_server_message_get_server (smsg), sock); - } else + start_request (server, client); + } else { soup_socket_disconnect (sock); + g_slice_free (SoupClientContext, client); + } g_object_unref (msg); g_object_unref (sock); } -static inline void -set_response_error (SoupMessage *req, guint code, char *phrase, char *body) +static SoupServerHandler * +soup_server_get_handler (SoupServer *server, const char *path) { - if (phrase) - soup_message_set_status_full (req, code, phrase); - else - soup_message_set_status (req, code); + SoupServerPrivate *priv; + SoupServerHandler *hand; - req->response.owner = SOUP_BUFFER_STATIC; - req->response.body = body; - req->response.length = body ? strlen (req->response.body) : 0; -} + g_return_val_if_fail (SOUP_IS_SERVER (server), NULL); + priv = SOUP_SERVER_GET_PRIVATE (server); + if (path) { + hand = soup_path_map_lookup (priv->handlers, path); + if (hand) + return hand; + } + return priv->default_handler; +} static void -call_handler (SoupMessage *req, SoupSocket *sock) +got_headers (SoupMessage *req, SoupClientContext *client) { - SoupServer *server; - SoupServerHandler *hand; - SoupServerAuth *auth = NULL; - const char *handler_path; - - g_return_if_fail (SOUP_IS_SERVER_MESSAGE (req)); + SoupServer *server = client->server; + SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); + SoupURI *uri; + SoupDate *date; + char *date_string; + SoupAuthDomain *domain; + GSList *iter; + gboolean rejected = FALSE; + char *auth_user; + + if (!priv->raw_paths) { + char *decoded_path; + + uri = soup_message_get_uri (req); + decoded_path = soup_uri_decode (uri->path); + soup_uri_set_path (uri, decoded_path); + } - server = soup_server_message_get_server (SOUP_SERVER_MESSAGE (req)); - handler_path = soup_message_get_uri (req)->path; + /* Add required response headers */ + date = soup_date_new_from_now (0); + date_string = soup_date_to_string (date, SOUP_DATE_HTTP); + soup_message_headers_replace (req->response_headers, "Date", + date_string); + g_free (date_string); + soup_date_free (date); + + /* Now handle authentication. (We do this here so that if + * the request uses "Expect: 100-continue", we can reject it + * immediately rather than waiting for the request body to + * be sent. + */ + for (iter = priv->auth_domains; iter; iter = iter->next) { + domain = iter->data; + + if (soup_auth_domain_covers (domain, req)) { + auth_user = soup_auth_domain_accepts (domain, req); + if (auth_user) { + client->auth_domain = g_object_ref (domain); + client->auth_user = auth_user; + return; + } - hand = soup_server_get_handler (server, handler_path); - if (!hand) { - set_response_error (req, SOUP_STATUS_NOT_FOUND, NULL, NULL); - return; + rejected = TRUE; + } } - if (hand->auth_ctx) { - SoupServerAuthContext *auth_ctx = hand->auth_ctx; - const GSList *auth_hdrs; + /* If no auth domain rejected it, then it's ok. */ + if (!rejected) + return; - auth_hdrs = soup_message_get_header_list (req->request_headers, - "Authorization"); - auth = soup_server_auth_new (auth_ctx, auth_hdrs, req); + for (iter = priv->auth_domains; iter; iter = iter->next) { + domain = iter->data; - if (auth_ctx->callback) { - gboolean ret = FALSE; + if (soup_auth_domain_covers (domain, req)) + soup_auth_domain_challenge (domain, req); + } +} - ret = (*auth_ctx->callback) (auth_ctx, - auth, - req, - auth_ctx->user_data); - if (!ret) { - soup_server_auth_context_challenge ( - auth_ctx, - req, - "WWW-Authenticate"); +static void +call_handler (SoupMessage *req, SoupClientContext *client) +{ + SoupServer *server = client->server; + SoupServerHandler *hand; + SoupURI *uri; - if (!req->status_code) - soup_message_set_status ( - req, - SOUP_STATUS_UNAUTHORIZED); + if (req->status_code != 0) + return; - return; - } - } else if (req->status_code) { - soup_server_auth_context_challenge ( - auth_ctx, - req, - "WWW-Authenticate"); - return; - } + uri = soup_message_get_uri (req); + hand = soup_server_get_handler (server, uri->path); + if (!hand) { + soup_message_set_status (req, SOUP_STATUS_NOT_FOUND); + return; } if (hand->callback) { - const SoupUri *uri = soup_message_get_uri (req); - SoupServerContext ctx; + GHashTable *form_data_set; - ctx.msg = req; - ctx.path = uri->path; - ctx.method_id = soup_method_get_id (req->method); - ctx.auth = auth; - ctx.server = server; - ctx.handler = hand; - ctx.sock = sock; + if (uri->query) + form_data_set = soup_form_decode_urlencoded (uri->query); + else + form_data_set = NULL; /* Call method handler */ - (*hand->callback) (&ctx, req, hand->user_data); - } + (*hand->callback) (server, req, + uri->path, form_data_set, + client, hand->user_data); - if (auth) - soup_server_auth_free (auth); + if (form_data_set) + g_hash_table_destroy (form_data_set); + } } static void -start_request (SoupServer *server, SoupSocket *server_sock) +start_request (SoupServer *server, SoupClientContext *client) { SoupMessage *msg; + soup_client_context_cleanup (client); + /* Listen for another request on this connection */ - msg = (SoupMessage *)soup_server_message_new (server); + msg = g_object_new (SOUP_TYPE_MESSAGE, NULL); + soup_message_headers_set_encoding (msg->response_headers, + SOUP_ENCODING_CONTENT_LENGTH); - g_signal_connect (msg, "got_body", G_CALLBACK (call_handler), server_sock); - g_signal_connect (msg, "finished", G_CALLBACK (request_finished), server_sock); + g_signal_connect (msg, "got_headers", G_CALLBACK (got_headers), client); + g_signal_connect (msg, "got_body", G_CALLBACK (call_handler), client); + g_signal_connect (msg, "finished", G_CALLBACK (request_finished), client); - g_object_ref (server_sock); - soup_message_read_request (msg, server_sock); + g_signal_emit (server, signals[REQUEST_STARTED], 0, + msg, client); + + g_object_ref (client->sock); + soup_message_read_request (msg, client->sock); } static void @@ -466,14 +739,29 @@ new_connection (SoupSocket *listner, SoupSocket *sock, gpointer user_data) { SoupServer *server = user_data; SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); + SoupClientContext *client = g_slice_new0 (SoupClientContext); - g_object_ref (sock); + client->server = server; + client->sock = g_object_ref (sock); priv->client_socks = g_slist_prepend (priv->client_socks, sock); g_signal_connect (sock, "disconnected", G_CALLBACK (socket_disconnected), server); - start_request (server, sock); + start_request (server, client); } +/** + * soup_server_run_async: + * @server: a #SoupServer + * + * Starts @server, causing it to listen for and process incoming + * connections. + * + * The server actually runs in @server's #GMainContext. It will not + * actually perform any processing unless the appropriate main loop is + * running. In the simple case where you did not set the server's + * %SOUP_SERVER_ASYNC_CONTEXT property, this means the server will run + * whenever the glib main loop is running. + **/ void soup_server_run_async (SoupServer *server) { @@ -492,12 +780,20 @@ soup_server_run_async (SoupServer *server) g_signal_connect (priv->listen_sock, "new_connection", G_CALLBACK (new_connection), server); - g_object_ref (server); return; } +/** + * soup_server_run: + * @server: a #SoupServer + * + * Starts @server, causing it to listen for and process incoming + * connections. Unlike soup_server_run_async(), this creates a + * #GMainLoop and runs it, and it will not return until someone calls + * soup_server_quit() to stop the server. + **/ void soup_server_run (SoupServer *server) { @@ -515,6 +811,16 @@ soup_server_run (SoupServer *server) g_main_loop_run (priv->loop); } +/** + * soup_server_quit: + * @server: a #SoupServer + * + * Stops processing for @server. Call this to clean up after + * soup_server_run_async(), or to terminate a call to soup_server_run(). + * + * @server is still in a working state after this call; you can start + * and stop a server as many times as you want. + **/ void soup_server_quit (SoupServer *server) { @@ -528,8 +834,6 @@ soup_server_quit (SoupServer *server) server); if (priv->loop) g_main_loop_quit (priv->loop); - - g_object_unref (server); } /** @@ -553,140 +857,242 @@ soup_server_get_async_context (SoupServer *server) return priv->async_context; } -static void -append_handler (gpointer key, gpointer value, gpointer user_data) +/** + * SoupClientContext: + * + * A #SoupClientContext provides additional information about the + * client making a particular request. In particular, you can use + * soup_client_context_get_auth_domain() and + * soup_client_context_get_auth_user() to determine if HTTP + * authentication was used successfully. + * + * soup_client_context_get_address() and/or + * soup_client_context_get_host() can be used to get information for + * logging or debugging purposes. soup_client_context_get_socket() may + * also be of use in some situations (eg, tracking when multiple + * requests are made on the same connection). + **/ +GType +soup_client_context_get_type (void) { - GSList **ret = user_data; + static GType type = 0; - *ret = g_slist_prepend (*ret, value); + if (type == 0) + type = g_pointer_type_register_static ("SoupClientContext"); + return type; } -GSList * -soup_server_list_handlers (SoupServer *server) -{ - SoupServerPrivate *priv; - GSList *ret = NULL; - - g_return_val_if_fail (SOUP_IS_SERVER (server), NULL); - priv = SOUP_SERVER_GET_PRIVATE (server); - - g_hash_table_foreach (priv->handlers, append_handler, &ret); - - return ret; -} - -SoupServerHandler * -soup_server_get_handler (SoupServer *server, const char *path) +/** + * soup_client_context_get_socket: + * @client: a #SoupClientContext + * + * Retrieves the #SoupSocket that @client is associated with. + * + * If you are using this method to observe when multiple requests are + * made on the same persistent HTTP connection (eg, as the ntlm-test + * test program does), you will need to pay attention to socket + * destruction as well (either by using weak references, or by + * connecting to the #SoupSocket::disconnected signal), so that you do + * not get fooled when the allocator reuses the memory address of a + * previously-destroyed socket to represent a new socket. + * + * Return value: the #SoupSocket that @client is associated with. + **/ +SoupSocket * +soup_client_context_get_socket (SoupClientContext *client) { - SoupServerPrivate *priv; - char *mypath, *dir; - SoupServerHandler *hand = NULL; - - g_return_val_if_fail (SOUP_IS_SERVER (server), NULL); - priv = SOUP_SERVER_GET_PRIVATE (server); - - if (!path || !priv->handlers) - return priv->default_handler; - - mypath = g_strdup (path); - - dir = strchr (mypath, '?'); - if (dir) *dir = '\0'; - - dir = mypath; + g_return_val_if_fail (client != NULL, NULL); - do { - hand = g_hash_table_lookup (priv->handlers, mypath); - if (hand) { - g_free (mypath); - return hand; - } - - dir = strrchr (mypath, '/'); - if (dir) *dir = '\0'; - } while (dir); - - g_free (mypath); - - return priv->default_handler; + return client->sock; } +/** + * soup_client_context_get_address: + * @client: a #SoupClientContext + * + * Retrieves the #SoupAddress associated with the remote end + * of a connection. + * + * Return value: the #SoupAddress associated with the remote end of a + * connection. + **/ SoupAddress * -soup_server_context_get_client_address (SoupServerContext *context) +soup_client_context_get_address (SoupClientContext *client) { - g_return_val_if_fail (context != NULL, NULL); + g_return_val_if_fail (client != NULL, NULL); - return soup_socket_get_remote_address (context->sock); + return soup_socket_get_remote_address (client->sock); } +/** + * soup_client_context_get_host: + * @client: a #SoupClientContext + * + * Retrieves the IP address associated with the remote end of a + * connection. (If you want the actual hostname, you'll have to call + * soup_client_context_get_address() and then call the appropriate + * #SoupAddress method to resolve it.) + * + * Return value: the IP address associated with the remote end of a + * connection. + **/ const char * -soup_server_context_get_client_host (SoupServerContext *context) +soup_client_context_get_host (SoupClientContext *client) { SoupAddress *address; - address = soup_server_context_get_client_address (context); + address = soup_client_context_get_address (client); return soup_address_get_physical (address); } -static SoupServerAuthContext * -auth_context_copy (SoupServerAuthContext *auth_ctx) +/** + * soup_client_context_get_auth_domain: + * @client: a #SoupClientContext + * + * Checks whether the request associated with @client has been + * authenticated, and if so returns the #SoupAuthDomain that + * authenticated it. + * + * Return value: a #SoupAuthDomain, or %NULL if the request was not + * authenticated. + **/ +SoupAuthDomain * +soup_client_context_get_auth_domain (SoupClientContext *client) { - SoupServerAuthContext *new_auth_ctx = NULL; - - new_auth_ctx = g_new0 (SoupServerAuthContext, 1); - - new_auth_ctx->types = auth_ctx->types; - new_auth_ctx->callback = auth_ctx->callback; - new_auth_ctx->user_data = auth_ctx->user_data; + g_return_val_if_fail (client != NULL, NULL); - new_auth_ctx->basic_info.realm = - g_strdup (auth_ctx->basic_info.realm); + return client->auth_domain; +} - new_auth_ctx->digest_info.realm = - g_strdup (auth_ctx->digest_info.realm); - new_auth_ctx->digest_info.allow_algorithms = - auth_ctx->digest_info.allow_algorithms; - new_auth_ctx->digest_info.force_integrity = - auth_ctx->digest_info.force_integrity; +/** + * soup_client_context_get_auth_user: + * @client: a #SoupClientContext + * + * Checks whether the request associated with @client has been + * authenticated, and if so returns the username that the client + * authenticated as. + * + * Return value: the authenticated-as user, or %NULL if the request + * was not authenticated. + **/ +const char * +soup_client_context_get_auth_user (SoupClientContext *client) +{ + g_return_val_if_fail (client != NULL, NULL); - return new_auth_ctx; + return client->auth_user; } +/** + * SoupServerCallback: + * @server: the #SoupServer + * @msg: the message being processed + * @path: the path component of @msg's Request-URI + * @query: the parsed query component of @msg's Request-URI + * @client: additional contextual information about the client + * @user_data: the data passed to @soup_server_add_handler + * + * A callback used to handle requests to a #SoupServer. The callback + * will be invoked after receiving the request body; @msg's %method, + * %request_headers, and %request_body fields will be filled in. + * + * @path and @query contain the likewise-named components of the + * Request-URI, subject to certain assumptions. By default, + * #SoupServer decodes all percent-encoding in the URI path, such that + * "/foo%2Fbar" is treated the same as "/foo/bar". If your + * server is serving resources in some non-POSIX-filesystem namespace, + * you may want to distinguish those as two distinct paths. In that + * case, you can set the %SOUP_SERVER_RAW_PATHS property when creating + * the #SoupServer, and it will leave those characters undecoded. (You + * may want to call soup_uri_normalize() to decode any percent-encoded + * characters that you aren't handling specially.) + * + * @query contains the query component of the Request-URI parsed + * according to the rules for HTML form handling. Although this is the + * only commonly-used query string format in HTTP, there is nothing + * that actually requires that HTTP URIs use that format; if your + * server needs to use some other format, you can just ignore @query, + * and call soup_message_get_uri() and parse the URI's query field + * yourself. + * + * After determining what to do with the request, the callback must at + * a minimum call soup_message_set_status() (or + * soup_message_set_status_full()) on @msg to set the response status + * code. Additionally, it may set response headers and/or fill in the + * response body. + * + * If the callback cannot fully fill in the response before returning + * (eg, if it needs to wait for information from a database, or + * another network server), it should call soup_server_pause_message() + * to tell #SoupServer to not send the response right away. When the + * response is ready, call soup_server_unpause_message() to cause it + * to be sent. + * + * To send the response body a bit at a time using "chunked" encoding, + * first call soup_message_headers_set_encoding() to set + * %SOUP_ENCODING_CHUNKED on the %response_headers. Then call + * soup_message_body_append() (or soup_message_body_append_buffer()) + * to append each chunk as it becomes ready, and + * soup_server_unpause_message() to make sure it's running. (The + * server will automatically pause the message if it is using chunked + * encoding but no more chunks are available.) When you are done, call + * soup_message_body_complete() to indicate that no more chunks are + * coming. + **/ + +/** + * soup_server_add_handler: + * @server: a #SoupServer + * @path: the toplevel path for the handler + * @callback: callback to invoke for requests under @path + * @user_data: data for @callback + * @destroy: destroy notifier to free @user_data + * + * Adds a handler to @server for requests under @path. See the + * documentation for #SoupServerCallback for information about + * how callbacks should behave. + **/ void soup_server_add_handler (SoupServer *server, const char *path, - SoupServerAuthContext *auth_ctx, - SoupServerCallbackFn callback, - SoupServerUnregisterFn unregister, - gpointer user_data) + SoupServerCallback callback, + gpointer user_data, + GDestroyNotify destroy) { SoupServerPrivate *priv; SoupServerHandler *hand; - SoupServerAuthContext *new_auth_ctx = NULL; g_return_if_fail (SOUP_IS_SERVER (server)); g_return_if_fail (callback != NULL); priv = SOUP_SERVER_GET_PRIVATE (server); - if (auth_ctx) - new_auth_ctx = auth_context_copy (auth_ctx); - - hand = g_new0 (SoupServerHandler, 1); + hand = g_slice_new0 (SoupServerHandler); hand->path = g_strdup (path); - hand->auth_ctx = new_auth_ctx; hand->callback = callback; - hand->unregister = unregister; + hand->destroy = destroy; hand->user_data = user_data; - if (path) { - soup_server_remove_handler (server, path); - g_hash_table_insert (priv->handlers, hand->path, hand); - } else { - soup_server_remove_handler (server, NULL); + soup_server_remove_handler (server, path); + if (path) + soup_path_map_add (priv->handlers, path, hand); + else priv->default_handler = hand; - } } +static void +unregister_handler (SoupServerHandler *handler) +{ + if (handler->destroy) + handler->destroy (handler->user_data); +} + +/** + * soup_server_remove_handler: + * @server: a #SoupServer + * @path: the toplevel path for the handler + * + * Removes the handler registered at @path. + **/ void soup_server_remove_handler (SoupServer *server, const char *path) { @@ -698,15 +1104,104 @@ soup_server_remove_handler (SoupServer *server, const char *path) if (!path) { if (priv->default_handler) { - free_handler (server, priv->default_handler); + unregister_handler (priv->default_handler); + free_handler (priv->default_handler); priv->default_handler = NULL; } return; } - hand = g_hash_table_lookup (priv->handlers, path); - if (hand) { - g_hash_table_remove (priv->handlers, path); - free_handler (server, hand); + hand = soup_path_map_lookup (priv->handlers, path); + if (hand && !strcmp (path, hand->path)) { + unregister_handler (hand); + soup_path_map_remove (priv->handlers, path); } } + +/** + * soup_server_add_auth_domain: + * @server: a #SoupServer + * @auth_domain: a #SoupAuthDomain + * + * Adds an authentication domain to @server. Each auth domain will + * have the chance to require authentication for each request that + * comes in; normally auth domains will require authentication for + * requests on certain paths that they have been set up to watch, or + * that meet other criteria set by the caller. If an auth domain + * determines that a request requires authentication (and the request + * doesn't contain authentication), @server will automatically reject + * the request with an appropriate status (401 Unauthorized or 407 + * Proxy Authentication Required). If the request used the + * "100-continue" Expectation, @server will reject it before the + * request body is sent. + **/ +void +soup_server_add_auth_domain (SoupServer *server, SoupAuthDomain *auth_domain) +{ + SoupServerPrivate *priv; + + g_return_if_fail (SOUP_IS_SERVER (server)); + priv = SOUP_SERVER_GET_PRIVATE (server); + + priv->auth_domains = g_slist_prepend (priv->auth_domains, auth_domain); +} + +/** + * soup_server_remove_auth_domain: + * @server: a #SoupServer + * @auth_domain: a #SoupAuthDomain + * + * Removes @auth_domain from @server. + **/ +void +soup_server_remove_auth_domain (SoupServer *server, SoupAuthDomain *auth_domain) +{ + SoupServerPrivate *priv; + + g_return_if_fail (SOUP_IS_SERVER (server)); + priv = SOUP_SERVER_GET_PRIVATE (server); + + priv->auth_domains = g_slist_remove (priv->auth_domains, auth_domain); + g_object_unref (auth_domain); +} + +/** + * soup_server_pause_message: + * @server: a #SoupServer + * @msg: a #SoupMessage associated with @server. + * + * Pauses I/O on @msg. This can be used when you need to return from + * the server handler without having the full response ready yet. Use + * soup_server_unpause_message() to resume I/O. + **/ +void +soup_server_pause_message (SoupServer *server, + SoupMessage *msg) +{ + g_return_if_fail (SOUP_IS_SERVER (server)); + g_return_if_fail (SOUP_IS_MESSAGE (msg)); + + soup_message_io_unpause (msg); +} + +/** + * soup_server_unpause_message: + * @server: a #SoupServer + * @msg: a #SoupMessage associated with @server. + * + * Resumes I/O on @msg. Use this to resume after calling + * soup_server_pause_message(), or after adding a new chunk to a + * chunked response. + * + * I/O won't actually resume until you return to the main loop. + **/ +void +soup_server_unpause_message (SoupServer *server, + SoupMessage *msg) +{ + g_return_if_fail (SOUP_IS_SERVER (server)); + g_return_if_fail (SOUP_IS_MESSAGE (msg)); + + soup_message_io_unpause (msg); +} + diff --git a/libsoup/soup-server.h b/libsoup/soup-server.h index aab2769..bfa0f2e 100644 --- a/libsoup/soup-server.h +++ b/libsoup/soup-server.h @@ -7,7 +7,6 @@ #define SOUP_SERVER_H 1 #include -#include #include G_BEGIN_DECLS @@ -19,6 +18,10 @@ G_BEGIN_DECLS #define SOUP_IS_SERVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_SERVER)) #define SOUP_SERVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_SERVER, SoupServerClass)) +typedef struct SoupClientContext SoupClientContext; +GType soup_client_context_get_type (void); +#define SOUP_TYPE_CLIENT_CONTEXT (soup_client_context_get_type ()) + struct SoupServer { GObject parent; @@ -27,80 +30,78 @@ struct SoupServer { typedef struct { GObjectClass parent_class; + /* signals */ + void (*request_started) (SoupServer *, SoupMessage *, SoupClientContext *); + void (*request_read) (SoupServer *, SoupMessage *, SoupClientContext *); + void (*request_finished) (SoupServer *, SoupMessage *, SoupClientContext *); + void (*request_aborted) (SoupServer *, SoupMessage *, SoupClientContext *); + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); } SoupServerClass; GType soup_server_get_type (void); - -typedef struct SoupServerHandler SoupServerHandler; - -typedef struct { - SoupMessage *msg; - char *path; - SoupMethodId method_id; - SoupServerAuth *auth; - SoupServer *server; - SoupServerHandler *handler; - SoupSocket *sock; -} SoupServerContext; - -typedef void (*SoupServerCallbackFn) (SoupServerContext *context, - SoupMessage *msg, - gpointer user_data); - -typedef void (*SoupServerUnregisterFn) (SoupServer *server, - SoupServerHandler *handler, - gpointer user_data); - -struct SoupServerHandler { - char *path; - - SoupServerAuthContext *auth_ctx; - - SoupServerCallbackFn callback; - SoupServerUnregisterFn unregister; - gpointer user_data; -}; +typedef void (*SoupServerCallback) (SoupServer *server, + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *client, + gpointer user_data); #define SOUP_SERVER_PORT "port" #define SOUP_SERVER_INTERFACE "interface" #define SOUP_SERVER_SSL_CERT_FILE "ssl-cert-file" #define SOUP_SERVER_SSL_KEY_FILE "ssl-key-file" #define SOUP_SERVER_ASYNC_CONTEXT "async-context" +#define SOUP_SERVER_RAW_PATHS "raw-paths" SoupServer *soup_server_new (const char *optname1, ...) G_GNUC_NULL_TERMINATED; -SoupProtocol soup_server_get_protocol (SoupServer *serv); -guint soup_server_get_port (SoupServer *serv); +gboolean soup_server_is_https (SoupServer *server); +guint soup_server_get_port (SoupServer *server); -SoupSocket *soup_server_get_listener (SoupServer *serv); +SoupSocket *soup_server_get_listener (SoupServer *server); -void soup_server_run (SoupServer *serv); -void soup_server_run_async (SoupServer *serv); -void soup_server_quit (SoupServer *serv); +void soup_server_run (SoupServer *server); +void soup_server_run_async (SoupServer *server); +void soup_server_quit (SoupServer *server); -GMainContext *soup_server_get_async_context (SoupServer *serv); +GMainContext *soup_server_get_async_context (SoupServer *server); -/* Handlers */ +/* Handlers and auth */ -void soup_server_add_handler (SoupServer *serv, +void soup_server_add_handler (SoupServer *server, const char *path, - SoupServerAuthContext *auth_ctx, - SoupServerCallbackFn callback, - SoupServerUnregisterFn unreg, - gpointer data); -void soup_server_remove_handler (SoupServer *serv, - const char *path); -SoupServerHandler *soup_server_get_handler (SoupServer *serv, + SoupServerCallback callback, + gpointer user_data, + GDestroyNotify destroy); +void soup_server_remove_handler (SoupServer *server, const char *path); -GSList *soup_server_list_handlers (SoupServer *serv); +void soup_server_add_auth_domain (SoupServer *server, + SoupAuthDomain *auth_domain); +void soup_server_remove_auth_domain (SoupServer *server, + SoupAuthDomain *auth_domain); + +/* I/O */ + +void soup_server_pause_message (SoupServer *server, + SoupMessage *msg); +void soup_server_unpause_message (SoupServer *server, + SoupMessage *msg); -/* Functions for accessing information about the specific connection */ +/* Client context */ -SoupAddress *soup_server_context_get_client_address (SoupServerContext *ctx); -const char *soup_server_context_get_client_host (SoupServerContext *ctx); +SoupSocket *soup_client_context_get_socket (SoupClientContext *client); +SoupAddress *soup_client_context_get_address (SoupClientContext *client); +const char *soup_client_context_get_host (SoupClientContext *client); +SoupAuthDomain *soup_client_context_get_auth_domain (SoupClientContext *client); +const char *soup_client_context_get_auth_user (SoupClientContext *client); G_END_DECLS diff --git a/libsoup/soup-session-async.c b/libsoup/soup-session-async.c index a5634f2..a383900 100644 --- a/libsoup/soup-session-async.c +++ b/libsoup/soup-session-async.c @@ -10,14 +10,23 @@ #endif #include "soup-session-async.h" -#include "soup-connection.h" +#include "soup-session-private.h" +#include "soup-message-private.h" #include "soup-misc.h" +/** + * SECTION:soup-session-async + * @short_description: Soup session for asynchronous (main-loop-based) I/O. + * + * #SoupSessionAsync is an implementation of #SoupSession that uses + * non-blocking I/O via the glib main loop. It is intended for use in + * single-threaded programs. + **/ + static gboolean run_queue (SoupSessionAsync *sa, gboolean try_pruning); static void queue_message (SoupSession *session, SoupMessage *req, - SoupMessageCallbackFn callback, - gpointer user_data); + SoupSessionCallback callback, gpointer user_data); static guint send_message (SoupSession *session, SoupMessage *req); G_DEFINE_TYPE (SoupSessionAsync, soup_session_async, SOUP_TYPE_SESSION) @@ -123,6 +132,7 @@ static gboolean run_queue (SoupSessionAsync *sa, gboolean try_pruning) { SoupSession *session = SOUP_SESSION (sa); + SoupMessageQueue *queue = soup_session_get_queue (session); SoupMessageQueueIter iter; SoupMessage *msg; SoupConnection *conn; @@ -131,7 +141,9 @@ run_queue (SoupSessionAsync *sa, gboolean try_pruning) /* FIXME: prefer CONNECTING messages */ try_again: - for (msg = soup_message_queue_first (session->queue, &iter); msg; msg = soup_message_queue_next (session->queue, &iter)) { + for (msg = soup_message_queue_first (queue, &iter); + msg; + msg = soup_message_queue_next (queue, &iter)) { if (!SOUP_MESSAGE_IS_STARTING (msg) || soup_message_io_in_progress (msg)) @@ -172,14 +184,27 @@ request_restarted (SoupMessage *req, gpointer sa) run_queue (sa, FALSE); } +typedef struct { + SoupSessionAsync *sa; + SoupSessionCallback callback; + gpointer callback_data; +} SoupSessionAsyncQueueData; + static void final_finished (SoupMessage *req, gpointer user_data) { - SoupSessionAsync *sa = user_data; + SoupSessionAsyncQueueData *saqd = user_data; + SoupSessionAsync *sa = saqd->sa; if (!SOUP_MESSAGE_IS_STARTING (req)) { - g_signal_handlers_disconnect_by_func (req, final_finished, sa); + g_signal_handlers_disconnect_by_func (req, final_finished, saqd); + if (saqd->callback) { + saqd->callback ((SoupSession *)sa, req, + saqd->callback_data); + } + g_object_unref (req); + g_slice_free (SoupSessionAsyncQueueData, saqd); } run_queue (sa, FALSE); @@ -202,19 +227,20 @@ idle_run_queue (gpointer user_data) static void queue_message (SoupSession *session, SoupMessage *req, - SoupMessageCallbackFn callback, gpointer user_data) + SoupSessionCallback callback, gpointer user_data) { SoupSessionAsync *sa = SOUP_SESSION_ASYNC (session); + SoupSessionAsyncQueueData *saqd; g_signal_connect (req, "restarted", G_CALLBACK (request_restarted), sa); - if (callback) { - g_signal_connect (req, "finished", - G_CALLBACK (callback), user_data); - } + saqd = g_slice_new (SoupSessionAsyncQueueData); + saqd->sa = sa; + saqd->callback = callback; + saqd->callback_data = user_data; g_signal_connect_after (req, "finished", - G_CALLBACK (final_finished), sa); + G_CALLBACK (final_finished), saqd); SOUP_SESSION_CLASS (soup_session_async_parent_class)->queue_message (session, req, callback, user_data); @@ -234,7 +260,7 @@ send_message (SoupSession *session, SoupMessage *req) queue_message (session, req, NULL, NULL); - while (req->status != SOUP_MESSAGE_STATUS_FINISHED && + while (soup_message_get_io_status (req) != SOUP_MESSAGE_IO_STATUS_FINISHED && !SOUP_STATUS_IS_TRANSPORT_ERROR (req->status_code)) g_main_context_iteration (async_context, TRUE); diff --git a/libsoup/soup-session-async.h b/libsoup/soup-session-async.h index 67e72f4..15fb22d 100644 --- a/libsoup/soup-session-async.h +++ b/libsoup/soup-session-async.h @@ -26,6 +26,11 @@ struct SoupSessionAsync { typedef struct { SoupSessionClass parent_class; + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); } SoupSessionAsyncClass; GType soup_session_async_get_type (void); diff --git a/libsoup/soup-session-private.h b/libsoup/soup-session-private.h new file mode 100644 index 0000000..5d3313a --- /dev/null +++ b/libsoup/soup-session-private.h @@ -0,0 +1,32 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2000-2003, Ximian, Inc. + */ + +#ifndef SOUP_SESSION_PRIVATE_H +#define SOUP_SESSION_PRIVATE_H 1 + +#include "soup-session.h" +#include "soup-connection.h" +#include "soup-message-queue.h" + +G_BEGIN_DECLS + +/* internal methods */ +void soup_session_emit_authenticate (SoupSession *session, + SoupMessage *msg, + SoupAuth *auth, + gboolean retrying); + +/* "protected" methods for subclasses */ +SoupMessageQueue *soup_session_get_queue (SoupSession *session); + +SoupConnection *soup_session_get_connection (SoupSession *session, + SoupMessage *msg, + gboolean *try_pruning, + gboolean *is_new); +gboolean soup_session_try_prune_connection (SoupSession *session); + +G_END_DECLS + +#endif /* SOUP_SESSION_PRIVATE_H */ diff --git a/libsoup/soup-session-sync.c b/libsoup/soup-session-sync.c index b45408e..2db8435 100644 --- a/libsoup/soup-session-sync.c +++ b/libsoup/soup-session-sync.c @@ -10,9 +10,18 @@ #endif #include "soup-session-sync.h" -#include "soup-connection.h" +#include "soup-session-private.h" +#include "soup-message-private.h" #include "soup-misc.h" +/** + * SECTION:soup-session-sync + * @short_description: Soup session for blocking I/O in multithreaded programs. + * + * #SoupSessionSync is an implementation of #SoupSession that uses + * synchronous I/O, intended for use in multi-threaded programs. + **/ + typedef struct { GMutex *lock; GCond *cond; @@ -20,10 +29,10 @@ typedef struct { #define SOUP_SESSION_SYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_SYNC, SoupSessionSyncPrivate)) static void queue_message (SoupSession *session, SoupMessage *msg, - SoupMessageCallbackFn callback, - gpointer user_data); + SoupSessionCallback callback, gpointer user_data); static guint send_message (SoupSession *session, SoupMessage *msg); -static void cancel_message (SoupSession *session, SoupMessage *msg); +static void cancel_message (SoupSession *session, SoupMessage *msg, + guint status_code); G_DEFINE_TYPE (SoupSessionSync, soup_session_sync, SOUP_TYPE_SESSION) @@ -102,7 +111,7 @@ soup_session_sync_new_with_options (const char *optname1, ...) typedef struct { SoupSession *session; SoupMessage *msg; - SoupMessageCallbackFn callback; + SoupSessionCallback callback; gpointer user_data; } SoupSessionSyncAsyncData; @@ -111,7 +120,7 @@ async_data_free (SoupSessionSyncAsyncData *sad) { g_object_unref (sad->session); g_object_unref (sad->msg); - g_free (sad); + g_slice_free (SoupSessionSyncAsyncData, sad); } static gboolean @@ -119,7 +128,7 @@ queue_message_callback (gpointer data) { SoupSessionSyncAsyncData *sad = data; - sad->callback (sad->msg, sad->user_data); + sad->callback (sad->session, sad->msg, sad->user_data); async_data_free (sad); return FALSE; } @@ -141,11 +150,11 @@ queue_message_thread (gpointer data) static void queue_message (SoupSession *session, SoupMessage *msg, - SoupMessageCallbackFn callback, gpointer user_data) + SoupSessionCallback callback, gpointer user_data) { SoupSessionSyncAsyncData *sad; - sad = g_new (SoupSessionSyncAsyncData, 1); + sad = g_slice_new (SoupSessionSyncAsyncData); sad->session = g_object_ref (session); sad->msg = g_object_ref (msg); sad->callback = callback; @@ -181,7 +190,7 @@ wait_for_connection (SoupSession *session, SoupMessage *msg) goto try_again; else if (!SOUP_STATUS_IS_SUCCESSFUL (status)) conn = NULL; - else if (msg->status == SOUP_MESSAGE_STATUS_FINISHED) { + else if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) { /* Message was cancelled while we were * connecting. */ @@ -201,7 +210,7 @@ wait_for_connection (SoupSession *session, SoupMessage *msg) g_cond_wait (priv->cond, priv->lock); /* See if something bad happened */ - if (msg->status == SOUP_MESSAGE_STATUS_FINISHED) { + if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_FINISHED) { g_mutex_unlock (priv->lock); return NULL; } @@ -225,17 +234,17 @@ send_message (SoupSession *session, SoupMessage *msg) soup_connection_send_request (conn, msg); g_cond_broadcast (priv->cond); - } while (msg->status != SOUP_MESSAGE_STATUS_FINISHED); + } while (soup_message_get_io_status (msg) != SOUP_MESSAGE_IO_STATUS_FINISHED); return msg->status_code; } static void -cancel_message (SoupSession *session, SoupMessage *msg) +cancel_message (SoupSession *session, SoupMessage *msg, guint status_code) { SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session); - SOUP_SESSION_CLASS (soup_session_sync_parent_class)->cancel_message (session, msg); + SOUP_SESSION_CLASS (soup_session_sync_parent_class)->cancel_message (session, msg, status_code); g_cond_broadcast (priv->cond); } diff --git a/libsoup/soup-session-sync.h b/libsoup/soup-session-sync.h index cf478d4..3edbb16 100644 --- a/libsoup/soup-session-sync.h +++ b/libsoup/soup-session-sync.h @@ -26,6 +26,11 @@ struct SoupSessionSync { typedef struct { SoupSessionClass parent_class; + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); } SoupSessionSyncClass; GType soup_session_sync_get_type (void); diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c index 37c1900..e6e5426 100644 --- a/libsoup/soup-session.c +++ b/libsoup/soup-session.c @@ -14,18 +14,28 @@ #include #include "soup-auth.h" -#include "soup-session.h" +#include "soup-auth-basic.h" +#include "soup-auth-digest.h" +#include "soup-auth-manager.h" #include "soup-connection.h" #include "soup-connection-ntlm.h" #include "soup-marshal.h" -#include "soup-message-filter.h" #include "soup-message-private.h" #include "soup-message-queue.h" +#include "soup-session.h" +#include "soup-session-private.h" +#include "soup-socket.h" #include "soup-ssl.h" #include "soup-uri.h" +/** + * SECTION:soup-session + * @short_description: Soup session state object + * + **/ + typedef struct { - SoupUri *root_uri; + SoupURI *root_uri; GSList *connections; /* CONTAINS: SoupConnection */ guint num_conns; @@ -35,21 +45,23 @@ typedef struct { } SoupSessionHost; typedef struct { - SoupUri *proxy_uri; + SoupURI *proxy_uri; + SoupAuth *proxy_auth; + guint max_conns, max_conns_per_host; gboolean use_ntlm; char *ssl_ca_file; SoupSSLCredentials *ssl_creds; - GSList *filters; + SoupMessageQueue *queue; - GHashTable *hosts; /* SoupUri -> SoupSessionHost */ + SoupAuthManager *auth_manager; + + GHashTable *hosts; /* SoupURI -> SoupSessionHost */ GHashTable *conns; /* SoupConnection -> SoupSessionHost */ guint num_conns; - SoupSessionHost *proxy_host; - /* Must hold the host_lock before potentially creating a * new SoupSessionHost, or adding/removing a connection. * Must not emit signals or destroy objects while holding it. @@ -65,30 +77,28 @@ typedef struct { } SoupSessionPrivate; #define SOUP_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION, SoupSessionPrivate)) -static guint host_uri_hash (gconstpointer key); -static gboolean host_uri_equal (gconstpointer v1, gconstpointer v2); static void free_host (SoupSessionHost *host); -static void setup_message (SoupMessageFilter *filter, SoupMessage *msg); - static void queue_message (SoupSession *session, SoupMessage *msg, - SoupMessageCallbackFn callback, - gpointer user_data); + SoupSessionCallback callback, gpointer user_data); static void requeue_message (SoupSession *session, SoupMessage *msg); -static void cancel_message (SoupSession *session, SoupMessage *msg); +static void cancel_message (SoupSession *session, SoupMessage *msg, + guint status_code); + +/* temporary until we fix this to index hosts by SoupAddress */ +extern guint soup_uri_host_hash (gconstpointer key); +extern gboolean soup_uri_host_equal (gconstpointer v1, + gconstpointer v2); +extern SoupURI *soup_uri_copy_root (SoupURI *uri); #define SOUP_SESSION_MAX_CONNS_DEFAULT 10 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 4 -static void filter_iface_init (SoupMessageFilterClass *filter_class); - -G_DEFINE_TYPE_EXTENDED (SoupSession, soup_session, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (SOUP_TYPE_MESSAGE_FILTER, - filter_iface_init)) +G_DEFINE_TYPE (SoupSession, soup_session, G_TYPE_OBJECT) enum { + REQUEST_STARTED, AUTHENTICATE, - REAUTHENTICATE, LAST_SIGNAL }; @@ -118,16 +128,21 @@ soup_session_init (SoupSession *session) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - session->queue = soup_message_queue_new (); + priv->queue = soup_message_queue_new (); priv->host_lock = g_mutex_new (); - priv->hosts = g_hash_table_new (host_uri_hash, host_uri_equal); + priv->hosts = g_hash_table_new (soup_uri_host_hash, + soup_uri_host_equal); priv->conns = g_hash_table_new (NULL, NULL); priv->max_conns = SOUP_SESSION_MAX_CONNS_DEFAULT; priv->max_conns_per_host = SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT; priv->timeout = 0; + + priv->auth_manager = soup_auth_manager_new (session); + soup_auth_manager_add_type (priv->auth_manager, SOUP_TYPE_AUTH_BASIC); + soup_auth_manager_add_type (priv->auth_manager, SOUP_TYPE_AUTH_DIGEST); } static gboolean @@ -144,7 +159,8 @@ cleanup_hosts (SoupSessionPrivate *priv) g_mutex_lock (priv->host_lock); old_hosts = priv->hosts; - priv->hosts = g_hash_table_new (host_uri_hash, host_uri_equal); + priv->hosts = g_hash_table_new (soup_uri_host_hash, + soup_uri_host_equal); g_mutex_unlock (priv->host_lock); g_hash_table_foreach_remove (old_hosts, foreach_free_host, NULL); @@ -156,18 +172,10 @@ dispose (GObject *object) { SoupSession *session = SOUP_SESSION (object); SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - GSList *f; soup_session_abort (session); cleanup_hosts (priv); - if (priv->filters) { - for (f = priv->filters; f; f = f->next) - g_object_unref (f->data); - g_slist_free (priv->filters); - priv->filters = NULL; - } - G_OBJECT_CLASS (soup_session_parent_class)->dispose (object); } @@ -177,16 +185,16 @@ finalize (GObject *object) SoupSession *session = SOUP_SESSION (object); SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - soup_message_queue_destroy (session->queue); + soup_message_queue_destroy (priv->queue); g_mutex_free (priv->host_lock); g_hash_table_destroy (priv->hosts); g_hash_table_destroy (priv->conns); + soup_auth_manager_free (priv->auth_manager); + if (priv->proxy_uri) soup_uri_free (priv->proxy_uri); - if (priv->proxy_host) - free_host (priv->proxy_host); if (priv->ssl_creds) soup_ssl_free_client_credentials (priv->ssl_creds); @@ -218,92 +226,58 @@ soup_session_class_init (SoupSessionClass *session_class) /* signals */ /** - * SoupSession::authenticate: + * SoupSession::request-started: * @session: the session - * @msg: the #SoupMessage being sent - * @auth_type: the authentication type - * @auth_realm: the realm being authenticated to - * @username: the signal handler should set this to point to - * the provided username - * @password: the signal handler should set this to point to - * the provided password + * @msg: the request being sent + * @socket: the socket the request is being sent on * - * Emitted when the session requires authentication. The - * credentials may come from the user, or from cached - * information. If no credentials are available, leave - * @username and @password unchanged. - * - * If the provided credentials fail, the #reauthenticate - * signal will be emitted. + * Emitted just before a request is sent. **/ - signals[AUTHENTICATE] = - g_signal_new ("authenticate", + signals[REQUEST_STARTED] = + g_signal_new ("request-started", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (SoupSessionClass, authenticate), + G_STRUCT_OFFSET (SoupSessionClass, request_started), NULL, NULL, - soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER, - G_TYPE_NONE, 5, + soup_marshal_NONE__OBJECT_OBJECT, + G_TYPE_NONE, 2, SOUP_TYPE_MESSAGE, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_POINTER, - G_TYPE_POINTER); + SOUP_TYPE_SOCKET); /** - * SoupSession::reauthenticate: + * SoupSession::authenticate: * @session: the session * @msg: the #SoupMessage being sent - * @auth_type: the authentication type - * @auth_realm: the realm being authenticated to - * @username: the signal handler should set this to point to - * the provided username - * @password: the signal handler should set this to point to - * the provided password - * - * Emitted when the credentials provided by the application to - * the #authenticate signal have failed. This gives the - * application a second chance to provide authentication - * credentials. If the new credentials also fail, #SoupSession - * will emit #reauthenticate again, and will continue doing so - * until the provided credentials work, or a #reauthenticate - * signal emission "fails" (because the handler left @username - * and @password unchanged). At that point, the 401 or 407 - * error status will be returned to the caller. + * @auth: the #SoupAuth to authenticate + * @retrying: %TRUE if this is the second (or later) attempt * - * If your application only uses cached passwords, it should - * only connect to #authenticate, and not #reauthenticate. - * - * If your application always prompts the user for a password, - * and never uses cached information, then you can connect the - * same handler to #authenticate and #reauthenticate. - * - * To get standard web-browser behavior, return either cached - * information or a user-provided password (whichever is - * available) from the #authenticate handler, but return only - * user-provided information from the #reauthenticate handler. + * Emitted when the session requires authentication. If + * credentials are available call soup_auth_authenticate() on + * @auth. If these credentials fail, the signal will be + * emitted again, with @retrying set to %TRUE, which will + * continue until you return without calling + * soup_auth_authenticate() on @auth. **/ - signals[REAUTHENTICATE] = - g_signal_new ("reauthenticate", + signals[AUTHENTICATE] = + g_signal_new ("authenticate", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (SoupSessionClass, reauthenticate), + G_STRUCT_OFFSET (SoupSessionClass, authenticate), NULL, NULL, - soup_marshal_NONE__OBJECT_STRING_STRING_POINTER_POINTER, - G_TYPE_NONE, 5, + soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN, + G_TYPE_NONE, 3, SOUP_TYPE_MESSAGE, - G_TYPE_STRING, - G_TYPE_STRING, - G_TYPE_POINTER, - G_TYPE_POINTER); + SOUP_TYPE_AUTH, + G_TYPE_BOOLEAN); /* properties */ g_object_class_install_property ( object_class, PROP_PROXY_URI, - g_param_spec_pointer (SOUP_SESSION_PROXY_URI, - "Proxy URI", - "The HTTP Proxy to use for this session", - G_PARAM_READWRITE)); + g_param_spec_boxed (SOUP_SESSION_PROXY_URI, + "Proxy URI", + "The HTTP Proxy to use for this session", + SOUP_TYPE_URI, + G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_MAX_CONNS, g_param_spec_int (SOUP_SESSION_MAX_CONNS, @@ -320,7 +294,7 @@ soup_session_class_init (SoupSessionClass *session_class) "The maximum number of connections that the session can open at once to a given host", 1, G_MAXINT, - 4, + 2, G_PARAM_READWRITE)); g_object_class_install_property ( object_class, PROP_USE_NTLM, @@ -351,16 +325,8 @@ soup_session_class_init (SoupSessionClass *session_class) G_PARAM_READWRITE)); } -static void -filter_iface_init (SoupMessageFilterClass *filter_class) -{ - /* interface implementation */ - filter_class->setup_message = setup_message; -} - - static gboolean -safe_uri_equal (const SoupUri *a, const SoupUri *b) +safe_uri_equal (SoupURI *a, SoupURI *b) { if (!a && !b) return TRUE; @@ -389,22 +355,22 @@ set_property (GObject *object, guint prop_id, { SoupSession *session = SOUP_SESSION (object); SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - gpointer pval; + SoupURI *uri; gboolean need_abort = FALSE; gboolean ca_file_changed = FALSE; const char *new_ca_file; switch (prop_id) { case PROP_PROXY_URI: - pval = g_value_get_pointer (value); + uri = g_value_get_boxed (value); - if (!safe_uri_equal (priv->proxy_uri, pval)) + if (!safe_uri_equal (priv->proxy_uri, uri)) need_abort = TRUE; if (priv->proxy_uri) soup_uri_free (priv->proxy_uri); - priv->proxy_uri = pval ? soup_uri_copy (pval) : NULL; + priv->proxy_uri = uri ? soup_uri_copy (uri) : NULL; if (need_abort) { soup_session_abort (session); @@ -462,9 +428,7 @@ get_property (GObject *object, guint prop_id, switch (prop_id) { case PROP_PROXY_URI: - g_value_set_pointer (value, priv->proxy_uri ? - soup_uri_copy (priv->proxy_uri) : - NULL); + g_value_set_boxed (value, priv->proxy_uri); break; case PROP_MAX_CONNS: g_value_set_int (value, priv->max_conns); @@ -491,47 +455,6 @@ get_property (GObject *object, guint prop_id, /** - * soup_session_add_filter: - * @session: a #SoupSession - * @filter: an object implementing the #SoupMessageFilter interface - * - * Adds @filter to @session's list of message filters to be applied to - * all messages. - **/ -void -soup_session_add_filter (SoupSession *session, SoupMessageFilter *filter) -{ - SoupSessionPrivate *priv; - - g_return_if_fail (SOUP_IS_SESSION (session)); - g_return_if_fail (SOUP_IS_MESSAGE_FILTER (filter)); - priv = SOUP_SESSION_GET_PRIVATE (session); - - g_object_ref (filter); - priv->filters = g_slist_prepend (priv->filters, filter); -} - -/** - * soup_session_remove_filter: - * @session: a #SoupSession - * @filter: an object implementing the #SoupMessageFilter interface - * - * Removes @filter from @session's list of message filters - **/ -void -soup_session_remove_filter (SoupSession *session, SoupMessageFilter *filter) -{ - SoupSessionPrivate *priv; - - g_return_if_fail (SOUP_IS_SESSION (session)); - g_return_if_fail (SOUP_IS_MESSAGE_FILTER (filter)); - priv = SOUP_SESSION_GET_PRIVATE (session); - - priv->filters = g_slist_remove (priv->filters, filter); - g_object_unref (filter); -} - -/** * soup_session_get_async_context: * @session: a #SoupSession * @@ -553,38 +476,17 @@ soup_session_get_async_context (SoupSession *session) } /* Hosts */ -static guint -host_uri_hash (gconstpointer key) -{ - const SoupUri *uri = key; - - return (uri->protocol << 16) + uri->port + g_str_hash (uri->host); -} - -static gboolean -host_uri_equal (gconstpointer v1, gconstpointer v2) -{ - const SoupUri *one = v1; - const SoupUri *two = v2; - - if (one->protocol != two->protocol) - return FALSE; - if (one->port != two->port) - return FALSE; - - return strcmp (one->host, two->host) == 0; -} static SoupSessionHost * -soup_session_host_new (SoupSession *session, const SoupUri *source_uri) +soup_session_host_new (SoupSession *session, SoupURI *source_uri) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupSessionHost *host; - host = g_new0 (SoupSessionHost, 1); + host = g_slice_new0 (SoupSessionHost); host->root_uri = soup_uri_copy_root (source_uri); - if (host->root_uri->protocol == SOUP_PROTOCOL_HTTPS && + if (host->root_uri->scheme == SOUP_URI_SCHEME_HTTPS && !priv->ssl_creds) { priv->ssl_creds = soup_ssl_get_client_credentials (priv->ssl_ca_file); @@ -602,7 +504,7 @@ get_host_for_message (SoupSession *session, SoupMessage *msg) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupSessionHost *host; - const SoupUri *source = soup_message_get_uri (msg); + SoupURI *source = soup_message_get_uri (msg); host = g_hash_table_lookup (priv->hosts, source); if (host) @@ -614,36 +516,6 @@ get_host_for_message (SoupSession *session, SoupMessage *msg) return host; } -/* Note: get_proxy_host doesn't lock the host_lock. The caller must do - * it itself if there's a chance the host doesn't already exist. - */ -static SoupSessionHost * -get_proxy_host (SoupSession *session) -{ - SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - - if (priv->proxy_host || !priv->proxy_uri) - return priv->proxy_host; - - priv->proxy_host = - soup_session_host_new (session, priv->proxy_uri); - return priv->proxy_host; -} - -static void -free_realm (gpointer path, gpointer scheme_realm, gpointer data) -{ - g_free (path); - g_free (scheme_realm); -} - -static void -free_auth (gpointer scheme_realm, gpointer auth, gpointer data) -{ - g_free (scheme_realm); - g_object_unref (auth); -} - static void free_host (SoupSessionHost *host) { @@ -654,257 +526,22 @@ free_host (SoupSessionHost *host) soup_connection_disconnect (conn); } - if (host->auth_realms) { - g_hash_table_foreach (host->auth_realms, free_realm, NULL); - g_hash_table_destroy (host->auth_realms); - } - if (host->auths) { - g_hash_table_foreach (host->auths, free_auth, NULL); - g_hash_table_destroy (host->auths); - } - soup_uri_free (host->root_uri); - g_free (host); + g_slice_free (SoupSessionHost, host); } -/* Authentication */ - -static SoupAuth * -lookup_auth (SoupSession *session, SoupMessage *msg, gboolean proxy) -{ - SoupSessionHost *host; - char *path, *dir; - const char *realm, *const_path; - - if (proxy) { - host = get_proxy_host (session); - const_path = "/"; - } else { - host = get_host_for_message (session, msg); - const_path = soup_message_get_uri (msg)->path; - - if (!const_path) - const_path = "/"; - } - g_return_val_if_fail (host != NULL, NULL); - - if (!host->auth_realms) - return NULL; - - path = g_strdup (const_path); - dir = path; - do { - realm = g_hash_table_lookup (host->auth_realms, path); - if (realm) - break; - - dir = strrchr (path, '/'); - if (dir) { - if (dir[1]) - dir[1] = '\0'; - else - *dir = '\0'; - } - } while (dir); - - g_free (path); - if (realm) - return g_hash_table_lookup (host->auths, realm); - else - return NULL; -} - -static void -invalidate_auth (SoupSessionHost *host, SoupAuth *auth) -{ - char *info; - gpointer key, value; - - info = soup_auth_get_info (auth); - if (g_hash_table_lookup_extended (host->auths, info, &key, &value) && - auth == (SoupAuth *)value) { - g_hash_table_remove (host->auths, info); - g_free (key); - g_object_unref (auth); - } - g_free (info); -} - -static gboolean -authenticate_auth (SoupSession *session, SoupAuth *auth, - SoupMessage *msg, gboolean prior_auth_failed, - gboolean proxy) -{ - SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - const SoupUri *uri; - char *username = NULL, *password = NULL; - - if (proxy) - uri = priv->proxy_uri; - else - uri = soup_message_get_uri (msg); - - if (uri->passwd && !prior_auth_failed) { - soup_auth_authenticate (auth, uri->user, uri->passwd); - return TRUE; - } - - g_signal_emit (session, signals[prior_auth_failed ? REAUTHENTICATE : AUTHENTICATE], 0, - msg, soup_auth_get_scheme_name (auth), - soup_auth_get_realm (auth), - &username, &password); - if (username || password) - soup_auth_authenticate (auth, username, password); - if (username) - g_free (username); - if (password) { - memset (password, 0, strlen (password)); - g_free (password); - } - - return soup_auth_is_authenticated (auth); -} - -static gboolean -update_auth_internal (SoupSession *session, SoupMessage *msg, - const GSList *headers, gboolean proxy) -{ - SoupSessionHost *host; - SoupAuth *new_auth, *prior_auth, *old_auth; - gpointer old_path, old_auth_info; - const SoupUri *msg_uri; - const char *path; - char *auth_info; - GSList *pspace, *p; - gboolean prior_auth_failed = FALSE; - - if (proxy) - host = get_proxy_host (session); - else - host = get_host_for_message (session, msg); - - g_return_val_if_fail (host != NULL, FALSE); - - /* Try to construct a new auth from the headers; if we can't, - * there's no way we'll be able to authenticate. - */ - msg_uri = soup_message_get_uri (msg); - new_auth = soup_auth_new_from_header_list (headers); - if (!new_auth) - return FALSE; - - auth_info = soup_auth_get_info (new_auth); - - /* See if this auth is the same auth we used last time */ - prior_auth = proxy ? soup_message_get_proxy_auth (msg) : soup_message_get_auth (msg); - if (prior_auth) { - char *old_auth_info = soup_auth_get_info (prior_auth); - - if (!strcmp (old_auth_info, auth_info)) { - /* The server didn't like the username/password we - * provided before. Invalidate it and note this fact. - */ - invalidate_auth (host, prior_auth); - prior_auth_failed = TRUE; - } - g_free (old_auth_info); - } - - if (!host->auth_realms) { - host->auth_realms = g_hash_table_new (g_str_hash, g_str_equal); - host->auths = g_hash_table_new (g_str_hash, g_str_equal); - } - - /* Record where this auth realm is used. RFC 2617 is somewhat - * unclear about the scope of protection spaces with regard to - * proxies. The only mention of it is as an aside in section - * 3.2.1, where it is defining the fields of a Digest - * challenge and says that the protection space is always the - * entire proxy. Is this the case for all authentication - * schemes or just Digest? Who knows, but we're assuming all. - */ - if (proxy) - pspace = g_slist_prepend (NULL, g_strdup ("")); - else - pspace = soup_auth_get_protection_space (new_auth, msg_uri); - - for (p = pspace; p; p = p->next) { - path = p->data; - if (g_hash_table_lookup_extended (host->auth_realms, path, - &old_path, &old_auth_info)) { - g_hash_table_remove (host->auth_realms, old_path); - g_free (old_path); - g_free (old_auth_info); - } - - g_hash_table_insert (host->auth_realms, - g_strdup (path), g_strdup (auth_info)); - } - soup_auth_free_protection_space (new_auth, pspace); - - /* Now, make sure the auth is recorded. (If there's a - * pre-existing auth, we keep that rather than the new one, - * since the old one might already be authenticated.) - */ - old_auth = g_hash_table_lookup (host->auths, auth_info); - if (old_auth) { - g_free (auth_info); - g_object_unref (new_auth); - new_auth = old_auth; - } else - g_hash_table_insert (host->auths, auth_info, new_auth); - - /* If we need to authenticate, try to do it. */ - if (!soup_auth_is_authenticated (new_auth)) { - return authenticate_auth (session, new_auth, - msg, prior_auth_failed, proxy); - } - - /* Otherwise we're good. */ - return TRUE; -} - -static void -connection_authenticate (SoupConnection *conn, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password, gpointer session) +void +soup_session_emit_authenticate (SoupSession *session, SoupMessage *msg, + SoupAuth *auth, gboolean retrying) { - g_signal_emit (session, signals[AUTHENTICATE], 0, - msg, auth_type, auth_realm, username, password); + g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying); } static void -connection_reauthenticate (SoupConnection *conn, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password, - gpointer user_data) +reemit_authenticate (SoupConnection *conn, SoupMessage *msg, + SoupAuth *auth, gboolean retrying, gpointer session) { - g_signal_emit (conn, signals[REAUTHENTICATE], 0, - msg, auth_type, auth_realm, username, password); -} - - -static void -authorize_handler (SoupMessage *msg, gpointer user_data) -{ - SoupSession *session = user_data; - const GSList *headers; - gboolean proxy; - - if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) { - headers = soup_message_get_header_list (msg->response_headers, - "Proxy-Authenticate"); - proxy = TRUE; - } else { - headers = soup_message_get_header_list (msg->response_headers, - "WWW-Authenticate"); - proxy = FALSE; - } - if (!headers) - return; - - if (update_auth_internal (session, msg, headers, proxy)) - soup_session_requeue_message (session, msg); + soup_session_emit_authenticate (session, msg, auth, retrying); } static void @@ -912,9 +549,12 @@ redirect_handler (SoupMessage *msg, gpointer user_data) { SoupSession *session = user_data; const char *new_loc; - SoupUri *new_uri; + SoupURI *new_uri; + + if (!SOUP_STATUS_IS_REDIRECTION (msg->status_code)) + return; - new_loc = soup_message_get_header (msg->response_headers, "Location"); + new_loc = soup_message_headers_get (msg->response_headers, "Location"); if (!new_loc) return; @@ -936,47 +576,13 @@ redirect_handler (SoupMessage *msg, gpointer user_data) } static void -add_auth (SoupSession *session, SoupMessage *msg, gboolean proxy) -{ - SoupAuth *auth; - - auth = lookup_auth (session, msg, proxy); - if (auth && !soup_auth_is_authenticated (auth)) { - if (!authenticate_auth (session, auth, msg, FALSE, proxy)) - auth = NULL; - } - - if (proxy) - soup_message_set_proxy_auth (msg, auth); - else - soup_message_set_auth (msg, auth); -} - -static void -setup_message (SoupMessageFilter *filter, SoupMessage *msg) +connection_started_request (SoupConnection *conn, SoupMessage *msg, + gpointer data) { - SoupSession *session = SOUP_SESSION (filter); - SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - GSList *f; + SoupSession *session = data; - for (f = priv->filters; f; f = f->next) { - filter = f->data; - soup_message_filter_setup_message (filter, msg); - } - - add_auth (session, msg, FALSE); - soup_message_add_status_code_handler ( - msg, SOUP_STATUS_UNAUTHORIZED, - SOUP_HANDLER_POST_BODY, - authorize_handler, session); - - if (priv->proxy_uri) { - add_auth (session, msg, TRUE); - soup_message_add_status_code_handler ( - msg, SOUP_STATUS_PROXY_UNAUTHORIZED, - SOUP_HANDLER_POST_BODY, - authorize_handler, session); - } + g_signal_emit (session, signals[REQUEST_STARTED], 0, + msg, soup_connection_get_socket (conn)); } static void @@ -1099,14 +705,14 @@ connect_result (SoupConnection *conn, guint status, gpointer user_data) * any messages waiting for this host, since they're out * of luck. */ - for (msg = soup_message_queue_first (session->queue, &iter); msg; msg = soup_message_queue_next (session->queue, &iter)) { + for (msg = soup_message_queue_first (priv->queue, &iter); msg; msg = soup_message_queue_next (priv->queue, &iter)) { if (get_host_for_message (session, msg) == host) { if (status == SOUP_STATUS_TRY_AGAIN) { - if (msg->status == SOUP_MESSAGE_STATUS_CONNECTING) - msg->status = SOUP_MESSAGE_STATUS_QUEUED; + if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_CONNECTING) + soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_QUEUED); } else { - soup_message_set_status (msg, status); - soup_session_cancel_message (session, msg); + soup_session_cancel_message (session, msg, + status); } } } @@ -1171,7 +777,7 @@ soup_session_get_connection (SoupSession *session, SoupMessage *msg, } } - if (msg->status == SOUP_MESSAGE_STATUS_CONNECTING) { + if (soup_message_get_io_status (msg) == SOUP_MESSAGE_IO_STATUS_CONNECTING) { /* We already started a connection for this * message, so don't start another one. */ @@ -1190,19 +796,12 @@ soup_session_get_connection (SoupSession *session, SoupMessage *msg, return NULL; } - /* Make sure priv->proxy_host gets set now while - * we have the host_lock. - */ - if (priv->proxy_uri) - get_proxy_host (session); - conn = g_object_new ( (priv->use_ntlm ? SOUP_TYPE_CONNECTION_NTLM : SOUP_TYPE_CONNECTION), SOUP_CONNECTION_ORIGIN_URI, host->root_uri, SOUP_CONNECTION_PROXY_URI, priv->proxy_uri, SOUP_CONNECTION_SSL_CREDENTIALS, priv->ssl_creds, - SOUP_CONNECTION_MESSAGE_FILTER, session, SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context, SOUP_CONNECTION_TIMEOUT, priv->timeout, NULL); @@ -1212,11 +811,11 @@ soup_session_get_connection (SoupSession *session, SoupMessage *msg, g_signal_connect (conn, "disconnected", G_CALLBACK (connection_disconnected), session); - g_signal_connect (conn, "authenticate", - G_CALLBACK (connection_authenticate), + g_signal_connect (conn, "request_started", + G_CALLBACK (connection_started_request), session); - g_signal_connect (conn, "reauthenticate", - G_CALLBACK (connection_reauthenticate), + g_signal_connect (conn, "authenticate", + G_CALLBACK (reemit_authenticate), session); g_hash_table_insert (priv->conns, conn, host); @@ -1231,47 +830,57 @@ soup_session_get_connection (SoupSession *session, SoupMessage *msg, /* Mark the request as connecting, so we don't try to open * another new connection for it while waiting for this one. */ - msg->status = SOUP_MESSAGE_STATUS_CONNECTING; + soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_CONNECTING); g_mutex_unlock (priv->host_lock); *is_new = TRUE; return conn; } +SoupMessageQueue * +soup_session_get_queue (SoupSession *session) +{ + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + + return priv->queue; +} + static void message_finished (SoupMessage *msg, gpointer user_data) { SoupSession *session = user_data; + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); if (!SOUP_MESSAGE_IS_STARTING (msg)) { - soup_message_queue_remove_message (session->queue, msg); + soup_message_queue_remove_message (priv->queue, msg); g_signal_handlers_disconnect_by_func (msg, message_finished, session); } } static void queue_message (SoupSession *session, SoupMessage *msg, - SoupMessageCallbackFn callback, gpointer user_data) + SoupSessionCallback callback, gpointer user_data) { + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + g_signal_connect_after (msg, "finished", G_CALLBACK (message_finished), session); if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_NO_REDIRECT)) { - soup_message_add_status_class_handler ( - msg, SOUP_STATUS_CLASS_REDIRECT, - SOUP_HANDLER_POST_BODY, - redirect_handler, session); + soup_message_add_header_handler ( + msg, "got_body", "Location", + G_CALLBACK (redirect_handler), session); } - msg->status = SOUP_MESSAGE_STATUS_QUEUED; - soup_message_queue_append (session->queue, msg); + soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_QUEUED); + soup_message_queue_append (priv->queue, msg); } /** * soup_session_queue_message: * @session: a #SoupSession * @msg: the message to queue - * @callback: a #SoupMessageCallbackFn which will be called after the + * @callback: a #SoupSessionCallback which will be called after the * message completes or when an unrecoverable error occurs. * @user_data: a pointer passed to @callback. * @@ -1286,7 +895,7 @@ queue_message (SoupSession *session, SoupMessage *msg, */ void soup_session_queue_message (SoupSession *session, SoupMessage *msg, - SoupMessageCallbackFn callback, gpointer user_data) + SoupSessionCallback callback, gpointer user_data) { g_return_if_fail (SOUP_IS_SESSION (session)); g_return_if_fail (SOUP_IS_MESSAGE (msg)); @@ -1298,7 +907,7 @@ soup_session_queue_message (SoupSession *session, SoupMessage *msg, static void requeue_message (SoupSession *session, SoupMessage *msg) { - msg->status = SOUP_MESSAGE_STATUS_QUEUED; + soup_message_set_io_status (msg, SOUP_MESSAGE_IO_STATUS_QUEUED); } /** @@ -1342,10 +951,55 @@ soup_session_send_message (SoupSession *session, SoupMessage *msg) } +/** + * soup_session_pause_message: + * @session: a #SoupSession + * @msg: a #SoupMessage currently running on @session + * + * Pauses HTTP I/O on @msg. Call soup_session_unpause_message() to + * resume I/O. + **/ +void +soup_session_pause_message (SoupSession *session, + SoupMessage *msg) +{ + g_return_if_fail (SOUP_IS_SESSION (session)); + g_return_if_fail (SOUP_IS_MESSAGE (msg)); + + soup_message_io_pause (msg); +} + +/** + * soup_session_unpause_message: + * @session: a #SoupSession + * @msg: a #SoupMessage currently running on @session + * + * Resumes HTTP I/O on @msg. Use this to resume after calling + * soup_sessino_pause_message(). + * + * If @msg is being sent via blocking I/O, this will resume reading or + * writing immediately. If @msg is using non-blocking I/O, then + * reading or writing won't resume until you return to the main loop. + **/ +void +soup_session_unpause_message (SoupSession *session, + SoupMessage *msg) +{ + g_return_if_fail (SOUP_IS_SESSION (session)); + g_return_if_fail (SOUP_IS_MESSAGE (msg)); + + soup_message_io_unpause (msg); +} + + static void -cancel_message (SoupSession *session, SoupMessage *msg) +cancel_message (SoupSession *session, SoupMessage *msg, guint status_code) { - soup_message_queue_remove_message (session->queue, msg); + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + + soup_message_queue_remove_message (priv->queue, msg); + soup_message_io_stop (msg); + soup_message_set_status (msg, status_code); soup_message_finished (msg); } @@ -1353,18 +1007,21 @@ cancel_message (SoupSession *session, SoupMessage *msg) * soup_session_cancel_message: * @session: a #SoupSession * @msg: the message to cancel + * @status_code: status code to set on @msg (generally + * %SOUP_STATUS_CANCELLED) * - * Causes @session to immediately finish processing @msg. You should - * set a status code on @msg with soup_message_set_status() before - * calling this function. + * Causes @session to immediately finish processing @msg, with a final + * status_code of @status_code. Depending on when you cancel it, the + * response state may be incomplete or inconsistent. **/ void -soup_session_cancel_message (SoupSession *session, SoupMessage *msg) +soup_session_cancel_message (SoupSession *session, SoupMessage *msg, + guint status_code) { g_return_if_fail (SOUP_IS_SESSION (session)); g_return_if_fail (SOUP_IS_MESSAGE (msg)); - SOUP_SESSION_GET_CLASS (session)->cancel_message (session, msg); + SOUP_SESSION_GET_CLASS (session)->cancel_message (session, msg, status_code); } static void @@ -1393,9 +1050,11 @@ soup_session_abort (SoupSession *session) g_return_if_fail (SOUP_IS_SESSION (session)); priv = SOUP_SESSION_GET_PRIVATE (session); - for (msg = soup_message_queue_first (session->queue, &iter); msg; msg = soup_message_queue_next (session->queue, &iter)) { - soup_message_set_status (msg, SOUP_STATUS_CANCELLED); - soup_session_cancel_message (session, msg); + for (msg = soup_message_queue_first (priv->queue, &iter); + msg; + msg = soup_message_queue_next (priv->queue, &iter)) { + soup_session_cancel_message (session, msg, + SOUP_STATUS_CANCELLED); } /* Close all connections */ diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h index 38ce382..cc63dcf 100644 --- a/libsoup/soup-session.h +++ b/libsoup/soup-session.h @@ -8,7 +8,6 @@ #include #include -#include G_BEGIN_DECLS @@ -19,33 +18,38 @@ G_BEGIN_DECLS #define SOUP_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_SESSION)) #define SOUP_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_SESSION, SoupSessionClass)) +typedef void (*SoupSessionCallback) (SoupSession *session, + SoupMessage *msg, + gpointer user_data); + struct SoupSession { GObject parent; - /* protected */ - SoupMessageQueue *queue; }; typedef struct { GObjectClass parent_class; /* signals */ - void (*authenticate) (SoupSession *, SoupMessage *, - const char *auth_type, const char *auth_realm, - char **username, char **password); - void (*reauthenticate) (SoupSession *, SoupMessage *, - const char *auth_type, const char *auth_realm, - char **username, char **password); + void (*request_started) (SoupSession *, SoupMessage *, SoupSocket *); + void (*authenticate) (SoupSession *, SoupMessage *, + SoupAuth *, gboolean); /* methods */ void (*queue_message) (SoupSession *session, SoupMessage *msg, - SoupMessageCallbackFn callback, + SoupSessionCallback callback, gpointer user_data); void (*requeue_message) (SoupSession *session, SoupMessage *msg); guint (*send_message) (SoupSession *session, SoupMessage *msg); - void (*cancel_message) (SoupSession *session, SoupMessage *msg); + void (*cancel_message) (SoupSession *session, SoupMessage *msg, + guint status_code); + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); } SoupSessionClass; GType soup_session_get_type (void); @@ -58,16 +62,11 @@ GType soup_session_get_type (void); #define SOUP_SESSION_ASYNC_CONTEXT "async-context" #define SOUP_SESSION_TIMEOUT "timeout" -void soup_session_add_filter (SoupSession *session, - SoupMessageFilter *filter); -void soup_session_remove_filter (SoupSession *session, - SoupMessageFilter *filter); - GMainContext *soup_session_get_async_context(SoupSession *session); void soup_session_queue_message (SoupSession *session, SoupMessage *msg, - SoupMessageCallbackFn callback, + SoupSessionCallback callback, gpointer user_data); void soup_session_requeue_message (SoupSession *session, SoupMessage *msg); @@ -75,17 +74,15 @@ void soup_session_requeue_message (SoupSession *session, guint soup_session_send_message (SoupSession *session, SoupMessage *msg); -void soup_session_cancel_message (SoupSession *session, +void soup_session_pause_message (SoupSession *session, + SoupMessage *msg); +void soup_session_unpause_message (SoupSession *session, SoupMessage *msg); -void soup_session_abort (SoupSession *session); - -/* Protected methods */ -SoupConnection *soup_session_get_connection (SoupSession *session, - SoupMessage *msg, - gboolean *try_pruning, - gboolean *is_new); -gboolean soup_session_try_prune_connection (SoupSession *session); +void soup_session_cancel_message (SoupSession *session, + SoupMessage *msg, + guint status_code); +void soup_session_abort (SoupSession *session); G_END_DECLS diff --git a/libsoup/soup-soap-message.c b/libsoup/soup-soap-message.c deleted file mode 100644 index 0c50c72..0000000 --- a/libsoup/soup-soap-message.c +++ /dev/null @@ -1,827 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2003, Novell, Inc. - */ - -#include -#include "soup-misc.h" -#include "soup-soap-message.h" -#include "soup-uri.h" - -G_DEFINE_TYPE (SoupSoapMessage, soup_soap_message, SOUP_TYPE_MESSAGE) - -typedef struct { - /* Serialization fields */ - xmlDocPtr doc; - xmlNodePtr last_node; - xmlNsPtr soap_ns; - xmlNsPtr xsi_ns; - xmlChar *env_prefix; - xmlChar *env_uri; - gboolean body_started; - gchar *action; -} SoupSoapMessagePrivate; -#define SOUP_SOAP_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SOAP_MESSAGE, SoupSoapMessagePrivate)) - -static void -finalize (GObject *object) -{ - SoupSoapMessagePrivate *priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (object); - - if (priv->doc) - xmlFreeDoc (priv->doc); - if (priv->action) - g_free (priv->action); - if (priv->env_uri) - xmlFree (priv->env_uri); - if (priv->env_prefix) - xmlFree (priv->env_prefix); - - G_OBJECT_CLASS (soup_soap_message_parent_class)->finalize (object); -} - -static void -soup_soap_message_class_init (SoupSoapMessageClass *soup_soap_message_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (soup_soap_message_class); - - g_type_class_add_private (soup_soap_message_class, sizeof (SoupSoapMessagePrivate)); - - object_class->finalize = finalize; -} - -static void -soup_soap_message_init (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - /* initialize XML structures */ - priv->doc = xmlNewDoc ((const xmlChar *)"1.0"); - priv->doc->standalone = FALSE; - priv->doc->encoding = xmlCharStrdup ("UTF-8"); -} - - -static xmlNsPtr -fetch_ns (SoupSoapMessage *msg, const char *prefix, const char *ns_uri) -{ - SoupSoapMessagePrivate *priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - xmlNsPtr ns = NULL; - - if (prefix && ns_uri) - ns = xmlNewNs (priv->last_node, (const xmlChar *)ns_uri, (const xmlChar *)prefix); - else if (prefix && !ns_uri) { - ns = xmlSearchNs (priv->doc, priv->last_node, (const xmlChar *)prefix); - if (!ns) - ns = xmlNewNs (priv->last_node, (const xmlChar *)"", (const xmlChar *)prefix); - } - - return ns; -} - -/** - * soup_soap_message_new: - * @method: the HTTP method for the created request. - * @uri_string: the destination endpoint (as a string). - * @standalone: ??? FIXME - * @xml_encoding: ??? FIXME - * @env_prefix: ??? FIXME - * @env_uri: ??? FIXME - * - * Creates a new empty #SoupSoapMessage, which will connect to @uri_string. - * - * Returns: the new #SoupSoapMessage (or %NULL if @uri_string could not be - * parsed). - */ -SoupSoapMessage * -soup_soap_message_new (const char *method, const char *uri_string, - gboolean standalone, const char *xml_encoding, - const char *env_prefix, const char *env_uri) -{ - SoupSoapMessage *msg; - SoupUri *uri; - - uri = soup_uri_new (uri_string); - if (!uri) - return NULL; - - msg = soup_soap_message_new_from_uri (method, uri, standalone, - xml_encoding, env_prefix, env_uri); - - soup_uri_free (uri); - - return msg; -} - -/** - * soup_soap_message_new_from_uri: - * @method: the HTTP method for the created request. - * @uri: the destination endpoint (as a #SoupUri). - * @standalone: ??? FIXME - * @xml_encoding: ??? FIXME - * @env_prefix: ??? FIXME - * @env_uri: ??? FIXME - * - * Creates a new empty #SoupSoapMessage, which will connect to @uri - * - * Returns: the new #SoupSoapMessage - */ -SoupSoapMessage * -soup_soap_message_new_from_uri (const char *method, const SoupUri *uri, - gboolean standalone, const char *xml_encoding, - const char *env_prefix, const char *env_uri) -{ - SoupSoapMessage *msg; - SoupSoapMessagePrivate *priv; - - msg = g_object_new (SOUP_TYPE_SOAP_MESSAGE, NULL); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - SOUP_MESSAGE (msg)->method = method ? method : SOUP_METHOD_GET; - soup_message_set_uri (SOUP_MESSAGE (msg), (const SoupUri *) uri); - - priv->doc->standalone = standalone; - - if (xml_encoding) { - xmlFree ((xmlChar *)priv->doc->encoding); - priv->doc->encoding = xmlCharStrdup (xml_encoding); - } - - if (env_prefix) - priv->env_prefix = xmlCharStrdup (env_prefix); - if (env_uri) - priv->env_uri = xmlCharStrdup (env_uri); - - return msg; -} - -/** - * soup_soap_message_start_envelope: - * @msg: the %SoupSoapMessage. - * - * Starts the top level SOAP Envelope element. - */ -void -soup_soap_message_start_envelope (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = priv->doc->xmlRootNode = - xmlNewDocNode (priv->doc, NULL, (const xmlChar *)"Envelope", NULL); - - priv->soap_ns = xmlNewNs (priv->doc->xmlRootNode, - priv->env_uri ? priv->env_uri : - (const xmlChar *)"http://schemas.xmlsoap.org/soap/envelope/", - priv->env_prefix ? priv->env_prefix : (const xmlChar *)"SOAP-ENV"); - if (priv->env_uri) { - xmlFree (priv->env_uri); - priv->env_uri = NULL; - } - if (priv->env_prefix) { - xmlFree (priv->env_prefix); - priv->env_prefix = NULL; - } - - xmlSetNs (priv->doc->xmlRootNode, priv->soap_ns); - - xmlNewNs (priv->doc->xmlRootNode, - (const xmlChar *)"http://schemas.xmlsoap.org/soap/encoding/", - (const xmlChar *)"SOAP-ENC"); - xmlNewNs (priv->doc->xmlRootNode, - (const xmlChar *)"http://www.w3.org/1999/XMLSchema", - (const xmlChar *)"xsd"); - xmlNewNs (priv->doc->xmlRootNode, - (const xmlChar *)"http://schemas.xmlsoap.org/soap/envelope/", - (const xmlChar *)"SOAP-ENV"); - priv->xsi_ns = xmlNewNs (priv->doc->xmlRootNode, - (const xmlChar *)"http://www.w3.org/1999/XMLSchema-instance", - (const xmlChar *)"xsi"); -} - -/** - * soup_soap_message_end_envelope: - * @msg: the %SoupSoapMessage. - * - * Closes the top level SOAP Envelope element. - */ -void -soup_soap_message_end_envelope (SoupSoapMessage *msg) -{ - soup_soap_message_end_element (msg); -} - -/** - * soup_soap_message_start_body: - * @msg: the %SoupSoapMessage. - * - * Starts the SOAP Body element. - */ -void -soup_soap_message_start_body (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - if (priv->body_started) - return; - - priv->last_node = xmlNewChild (priv->last_node, - priv->soap_ns, - (const xmlChar *)"Body", NULL); - - priv->body_started = TRUE; -} - -/** - * soup_soap_message_end_body: - * @msg: the %SoupSoapMessage. - * - * Closes the SOAP Body element. - */ -void -soup_soap_message_end_body (SoupSoapMessage *msg) -{ - soup_soap_message_end_element (msg); -} - -/** - * soup_soap_message_start_element: - * @msg: the #SoupSoapMessage. - * @name: the element name. - * @prefix: the namespace prefix - * @ns_uri: the namespace URI - * - * Starts a new arbitrary message element, with @name as the element - * name, @prefix as the XML Namespace prefix, and @ns_uri as the XML - * Namespace uri for * the created element. - * - * Passing @prefix with no @ns_uri will cause a recursive search for - * an existing namespace with the same prefix. Failing that a new ns - * will be created with an empty uri. - * - * Passing both @prefix and @ns_uri always causes new namespace - * attribute creation. - * - * Passing NULL for both @prefix and @ns_uri causes no prefix to be - * used, and the element will be in the default namespace. - */ -void -soup_soap_message_start_element (SoupSoapMessage *msg, - const char *name, - const char *prefix, - const char *ns_uri) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)name, NULL); - - xmlSetNs (priv->last_node, fetch_ns (msg, prefix, ns_uri)); - - if (priv->body_started && !priv->action) - priv->action = g_strconcat (ns_uri ? ns_uri : "", - "#", name, NULL); -} - -/** - * soup_soap_message_end_element: - * @msg: the #SoupSoapMessage. - * - * Closes the current message element. - */ -void -soup_soap_message_end_element (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = priv->last_node->parent; -} - -/** - * soup_soap_message_start_fault: - * @msg: the #SoupSoapMessage. - * @faultcode: faultcode element value - * @faultstring: faultstring element value - * @faultfactor: faultfactor element value - * - * Starts a new SOAP Fault element, creating faultcode, faultstring, - * and faultfactor child elements. - * - * If you wish to add the faultdetail element, use - * soup_soap_message_start_fault_detail(), and then - * soup_soap_message_start_element() to add arbitrary sub-elements. - */ -void -soup_soap_message_start_fault (SoupSoapMessage *msg, - const char *faultcode, - const char *faultstring, - const char *faultfactor) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, - priv->soap_ns, - (const xmlChar *)"Fault", NULL); - xmlNewChild (priv->last_node, priv->soap_ns, (const xmlChar *)"faultcode", (const xmlChar *)faultcode); - xmlNewChild (priv->last_node, priv->soap_ns, (const xmlChar *)"faultstring", (const xmlChar *)faultstring); - - priv->last_node = xmlNewChild (priv->last_node, priv->soap_ns, - (const xmlChar *)"faultfactor", (const xmlChar *)faultfactor); - if (!faultfactor) - soup_soap_message_set_null (msg); - - soup_soap_message_end_element (msg); -} - -/** - * soup_soap_message_end_fault: - * @msg: the #SoupSoapMessage. - * - * Closes the current SOAP Fault element. - */ -void -soup_soap_message_end_fault (SoupSoapMessage *msg) -{ - soup_soap_message_end_element (msg); -} - -/** - * soup_soap_message_start_fault_detail: - * @msg: the #SoupSoapMessage. - * - * Start the faultdetail child element of the current SOAP Fault - * element. The faultdetail element allows arbitrary data to be sent - * in a returned fault. - **/ -void -soup_soap_message_start_fault_detail (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, - priv->soap_ns, - (const xmlChar *)"detail", - NULL); -} - -/** - * soup_soap_message_end_fault_detail: - * @msg: the #SoupSoapMessage. - * - * Closes the current SOAP faultdetail element. - */ -void -soup_soap_message_end_fault_detail (SoupSoapMessage *msg) -{ - soup_soap_message_end_element (msg); -} - -/** - * soup_soap_message_start_header: - * @msg: the #SoupSoapMessage. - * - * Creates a new SOAP Header element. You can call - * soup_soap_message_start_header_element() after this to add a new - * header child element. SOAP Header elements allow out-of-band data - * to be transferred while not interfering with the message body. - * - * This should be called after soup_soap_message_start_envelope() and - * before soup_soap_message_start_body(). - */ -void -soup_soap_message_start_header (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, priv->soap_ns, - (const xmlChar *)"Header", NULL); -} - -/** - * soup_soap_message_end_header: - * @msg: the #SoupSoapMessage. - * - * Closes the current SOAP Header element. - */ -void -soup_soap_message_end_header (SoupSoapMessage *msg) -{ - soup_soap_message_end_element (msg); -} - -/** - * soup_soap_message_start_header_element: - * @msg: the #SoupSoapMessage. - * @name: name of the header element - * @must_understand: whether the recipient must understand the header in order - * to proceed with processing the message - * @actor_uri: the URI which represents the destination actor for this header. - * @prefix: the namespace prefix - * @ns_uri: the namespace URI - * - * Starts a new SOAP arbitrary header element. - */ -void -soup_soap_message_start_header_element (SoupSoapMessage *msg, - const char *name, - gboolean must_understand, - const char *actor_uri, - const char *prefix, - const char *ns_uri) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - soup_soap_message_start_element (msg, name, prefix, ns_uri); - if (actor_uri) - xmlNewNsProp (priv->last_node, priv->soap_ns, (const xmlChar *)"actorUri", (const xmlChar *)actor_uri); - if (must_understand) - xmlNewNsProp (priv->last_node, priv->soap_ns, (const xmlChar *)"mustUnderstand", (const xmlChar *)"1"); -} - -/** - * soup_soap_message_end_header_element: - * @msg: the #SoupSoapMessage. - * - * Closes the current SOAP header element. - */ -void -soup_soap_message_end_header_element (SoupSoapMessage *msg) -{ - soup_soap_message_end_element (msg); -} - -/** - * soup_soap_message_write_int: - * @msg: the #SoupSoapMessage. - * @i: the integer value to write. - * - * Writes the stringified value of @i as the current element's content. - */ -void -soup_soap_message_write_int (SoupSoapMessage *msg, glong i) -{ - char *str = g_strdup_printf ("%ld", i); - soup_soap_message_write_string (msg, str); - g_free (str); -} - -/** - * soup_soap_message_write_double: - * @msg: the #SoupSoapMessage. - * @d: the double value to write. - * - * Writes the stringified value of @d as the current element's content. - */ -void -soup_soap_message_write_double (SoupSoapMessage *msg, double d) -{ - char *str = g_strdup_printf ("%f", d); - soup_soap_message_write_string (msg, str); - g_free (str); -} - -/** - * soup_soap_message_write_base64: - * @msg: the #SoupSoapMessage - * @string: the binary data buffer to encode - * @len: the length of data to encode - * - * Writes the Base-64 encoded value of @string as the current - * element's content. - **/ -void -soup_soap_message_write_base64 (SoupSoapMessage *msg, const char *string, int len) -{ - gchar *str = g_base64_encode ((const guchar *)string, len); - soup_soap_message_write_string (msg, str); - g_free (str); -} - -/** - * soup_soap_message_write_time: - * @msg: the #SoupSoapMessage. - * @timeval: pointer to a time_t to encode - * - * Writes the stringified value of @timeval as the current element's - * content. - **/ -void -soup_soap_message_write_time (SoupSoapMessage *msg, const time_t *timeval) -{ - gchar *str = g_strchomp (ctime (timeval)); - soup_soap_message_write_string (msg, str); -} - -/** - * soup_soap_message_write_string: - * @msg: the #SoupSoapMessage. - * @string: string to write. - * - * Writes the @string as the current element's content. - */ -void -soup_soap_message_write_string (SoupSoapMessage *msg, const char *string) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlNodeAddContent (priv->last_node, (const xmlChar *)string); -} - -/** - * soup_soap_message_write_buffer: - * @msg: the #SoupSoapMessage. - * @buffer: the string data buffer to write. - * @len: length of @buffer. - * - * Writes the string buffer pointed to by @buffer as the current - * element's content. - */ -void -soup_soap_message_write_buffer (SoupSoapMessage *msg, const char *buffer, int len) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlNodeAddContentLen (priv->last_node, (const xmlChar *)buffer, len); -} - -/** - * soup_soap_message_set_element_type: - * @msg: the #SoupSoapMessage. - * @xsi_type: the type name for the element. - * - * Sets the current element's XML schema xsi:type attribute, which - * specifies the element's type name. - */ -void -soup_soap_message_set_element_type (SoupSoapMessage *msg, const char *xsi_type) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlNewNsProp (priv->last_node, priv->xsi_ns, (const xmlChar *)"type", (const xmlChar *)xsi_type); -} - -/** - * soup_soap_message_set_null: - * @msg: the #SoupSoapMessage. - * - * Sets the current element's XML Schema xsi:null attribute. - */ -void -soup_soap_message_set_null (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlNewNsProp (priv->last_node, priv->xsi_ns, (const xmlChar *)"null", (const xmlChar *)"1"); -} - -/** - * soup_soap_message_add_attribute: - * @msg: the #SoupSoapMessage. - * @name: name of the attribute - * @value: value of the attribute - * @prefix: the namespace prefix - * @ns_uri: the namespace URI - * - * Adds an XML attribute to the current element. - */ -void -soup_soap_message_add_attribute (SoupSoapMessage *msg, - const char *name, - const char *value, - const char *prefix, - const char *ns_uri) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlNewNsProp (priv->last_node, - fetch_ns (msg, prefix, ns_uri), - (const xmlChar *)name, (const xmlChar *)value); -} - -/** - * soup_soap_message_add_namespace: - * @msg: the #SoupSoapMessage. - * @prefix: the namespace prefix - * @ns_uri: the namespace URI, or NULL for empty namespace - * - * Adds a new XML namespace to the current element. - */ -void -soup_soap_message_add_namespace (SoupSoapMessage *msg, const char *prefix, const char *ns_uri) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlNewNs (priv->last_node, (const xmlChar *)(ns_uri ? ns_uri : ""), (const xmlChar *)prefix); -} - -/** - * soup_soap_message_set_default_namespace: - * @msg: the #SoupSoapMessage. - * @ns_uri: the namespace URI. - * - * Sets the default namespace to the URI specified in @ns_uri. The - * default namespace becomes the namespace all non-explicitly - * namespaced child elements fall into. - */ -void -soup_soap_message_set_default_namespace (SoupSoapMessage *msg, const char *ns_uri) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - soup_soap_message_add_namespace (msg, NULL, ns_uri); -} - -/** - * soup_soap_message_set_encoding_style: - * @msg: the #SoupSoapMessage. - * @enc_style: the new encodingStyle value - * - * Sets the encodingStyle attribute on the current element to the - * value of @enc_style. - */ -void -soup_soap_message_set_encoding_style (SoupSoapMessage *msg, const char *enc_style) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlNewNsProp (priv->last_node, priv->soap_ns, (const xmlChar *)"encodingStyle", (const xmlChar *)enc_style); -} - -/** - * soup_soap_message_reset: - * @msg: the #SoupSoapMessage. - * - * Resets the internal XML representation of the SOAP message. - */ -void -soup_soap_message_reset (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlFreeDoc (priv->doc); - priv->doc = xmlNewDoc ((const xmlChar *)"1.0"); - priv->last_node = NULL; - - g_free (priv->action); - priv->action = NULL; - priv->body_started = FALSE; - - if (priv->env_uri) - xmlFree (priv->env_uri); - priv->env_uri = NULL; - - if (priv->env_prefix) - xmlFree (priv->env_prefix); - priv->env_prefix = NULL; -} - -/** - * soup_soap_message_persist: - * @msg: the #SoupSoapMessage. - * - * Writes the serialized XML tree to the #SoupMessage's buffer. - */ -void -soup_soap_message_persist (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - xmlChar *body; - int len; - - g_return_if_fail (SOUP_IS_SOAP_MESSAGE (msg)); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlDocDumpMemory (priv->doc, &body, &len); - - /* serialize to SoupMessage class */ - soup_message_set_request (SOUP_MESSAGE (msg), "text/xml", - SOUP_BUFFER_SYSTEM_OWNED, (char *)body, len); -} - -/** - * soup_soap_message_get_namespace_prefix: - * @msg: the #SoupSoapMessage. - * @ns_uri: the namespace URI. - * - * Returns the namespace prefix for @ns_uri (or an empty string if - * @ns_uri is set to the default namespace) - * - * Return value: The namespace prefix, or %NULL if no namespace exists - * for the URI given. - */ -const char * -soup_soap_message_get_namespace_prefix (SoupSoapMessage *msg, const char *ns_uri) -{ - SoupSoapMessagePrivate *priv; - xmlNsPtr ns = NULL; - - g_return_val_if_fail (SOUP_IS_SOAP_MESSAGE (msg), NULL); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - g_return_val_if_fail (ns_uri != NULL, NULL); - - ns = xmlSearchNsByHref (priv->doc, priv->last_node, (const xmlChar *)ns_uri); - if (ns) { - if (ns->prefix) - return (const char *)ns->prefix; - else - return ""; - } - - return NULL; -} - -/** - * soup_soap_message_get_xml_doc: - * @msg: the #SoupSoapMessage. - * - * Returns the internal XML representation tree of the - * #SoupSoapMessage pointed to by @msg. - * - * Return value: the #xmlDocPtr representing the SOAP message. - */ -xmlDocPtr -soup_soap_message_get_xml_doc (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - - g_return_val_if_fail (SOUP_IS_SOAP_MESSAGE (msg), NULL); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - return priv->doc; -} - -/** - * soup_soap_message_parse_response: - * @msg: the #SoupSoapMessage. - * - * Parses the response returned by the server. - * - * Return value: a #SoupSoapResponse representing the response from - * the server, or %NULL if there was an error. - */ -SoupSoapResponse * -soup_soap_message_parse_response (SoupSoapMessage *msg) -{ - SoupSoapMessagePrivate *priv; - char *xmlstr; - SoupSoapResponse *soap_response; - - g_return_val_if_fail (SOUP_IS_SOAP_MESSAGE (msg), NULL); - priv = SOUP_SOAP_MESSAGE_GET_PRIVATE (msg); - - xmlstr = g_malloc0 (SOUP_MESSAGE (msg)->response.length + 1); - strncpy (xmlstr, SOUP_MESSAGE (msg)->response.body, SOUP_MESSAGE (msg)->response.length); - - soap_response = soup_soap_response_new_from_string (xmlstr); - g_free (xmlstr); - - return soap_response; -} diff --git a/libsoup/soup-soap-message.h b/libsoup/soup-soap-message.h deleted file mode 100644 index 9048d62..0000000 --- a/libsoup/soup-soap-message.h +++ /dev/null @@ -1,96 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2003, Novell, Inc. - */ - -#ifndef SOUP_SOAP_MESSAGE_H -#define SOUP_SOAP_MESSAGE_H 1 - -#include -#include -#include -#include - -G_BEGIN_DECLS - -#define SOUP_TYPE_SOAP_MESSAGE (soup_soap_message_get_type ()) -#define SOUP_SOAP_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_SOAP_MESSAGE, SoupSoapMessage)) -#define SOUP_SOAP_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_SOAP_MESSAGE, SoupSoapMessageClass)) -#define SOUP_IS_SOAP_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_SOAP_MESSAGE)) -#define SOUP_IS_SOAP_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_SOAP_MESSAGE)) -#define SOUP_SOAP_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_SOAP_MESSAGE, SoupSoapMessageClass)) - -typedef struct { - SoupMessage parent; - -} SoupSoapMessage; - -typedef struct { - SoupMessageClass parent_class; -} SoupSoapMessageClass; - -GType soup_soap_message_get_type (void); - -SoupSoapMessage *soup_soap_message_new (const char *method, const char *uri_string, - gboolean standalone, const char *xml_encoding, - const char *env_prefix, const char *env_uri); -SoupSoapMessage *soup_soap_message_new_from_uri (const char *method, const SoupUri *uri, - gboolean standalone, const char *xml_encoding, - const char *env_prefix, const char *env_uri); - -void soup_soap_message_start_envelope (SoupSoapMessage *msg); -void soup_soap_message_end_envelope (SoupSoapMessage *msg); -void soup_soap_message_start_body (SoupSoapMessage *msg); -void soup_soap_message_end_body (SoupSoapMessage *msg); -void soup_soap_message_start_element (SoupSoapMessage *msg, - const char *name, - const char *prefix, - const char *ns_uri); -void soup_soap_message_end_element (SoupSoapMessage *msg); -void soup_soap_message_start_fault (SoupSoapMessage *msg, - const char *faultcode, - const char *faultstring, - const char *faultfactor); -void soup_soap_message_end_fault (SoupSoapMessage *msg); -void soup_soap_message_start_fault_detail (SoupSoapMessage *msg); -void soup_soap_message_end_fault_detail (SoupSoapMessage *msg); -void soup_soap_message_start_header (SoupSoapMessage *msg); -void soup_soap_message_end_header (SoupSoapMessage *msg); -void soup_soap_message_start_header_element (SoupSoapMessage *msg, - const char *name, - gboolean must_understand, - const char *actor_uri, - const char *prefix, - const char *ns_uri); -void soup_soap_message_end_header_element (SoupSoapMessage *msg); -void soup_soap_message_write_int (SoupSoapMessage *msg, glong i); -void soup_soap_message_write_double (SoupSoapMessage *msg, double d); -void soup_soap_message_write_base64 (SoupSoapMessage *msg, const char *string, int len); -void soup_soap_message_write_time (SoupSoapMessage *msg, const time_t *timeval); -void soup_soap_message_write_string (SoupSoapMessage *msg, const char *string); -void soup_soap_message_write_buffer (SoupSoapMessage *msg, const char *buffer, int len); -void soup_soap_message_set_element_type (SoupSoapMessage *msg, const char *xsi_type); -void soup_soap_message_set_null (SoupSoapMessage *msg); -void soup_soap_message_add_attribute (SoupSoapMessage *msg, - const char *name, - const char *value, - const char *prefix, - const char *ns_uri); -void soup_soap_message_add_namespace (SoupSoapMessage *msg, - const char *prefix, - const char *ns_uri); -void soup_soap_message_set_default_namespace (SoupSoapMessage *msg, - const char *ns_uri); -void soup_soap_message_set_encoding_style (SoupSoapMessage *msg, const char *enc_style); -void soup_soap_message_reset (SoupSoapMessage *msg); -void soup_soap_message_persist (SoupSoapMessage *msg); - -const char *soup_soap_message_get_namespace_prefix (SoupSoapMessage *msg, const char *ns_uri); - -xmlDocPtr soup_soap_message_get_xml_doc (SoupSoapMessage *msg); - -SoupSoapResponse *soup_soap_message_parse_response (SoupSoapMessage *msg); - -G_END_DECLS - -#endif diff --git a/libsoup/soup-soap-response.c b/libsoup/soup-soap-response.c deleted file mode 100644 index 65b77aa..0000000 --- a/libsoup/soup-soap-response.c +++ /dev/null @@ -1,554 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2003, Novell, Inc. - */ - -#include -#include -#include -#include "soup-misc.h" -#include "soup-soap-response.h" -#include "soup-types.h" - -G_DEFINE_TYPE (SoupSoapResponse, soup_soap_response, G_TYPE_OBJECT) - -typedef struct { - /* the XML document */ - xmlDocPtr xmldoc; - xmlNodePtr xml_root; - xmlNodePtr xml_body; - xmlNodePtr xml_method; - xmlNodePtr soap_fault; - GList *parameters; -} SoupSoapResponsePrivate; -#define SOUP_SOAP_RESPONSE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SOAP_RESPONSE, SoupSoapResponsePrivate)) - -static void -finalize (GObject *object) -{ - SoupSoapResponsePrivate *priv = SOUP_SOAP_RESPONSE_GET_PRIVATE (object); - - if (priv->xmldoc) - xmlFreeDoc (priv->xmldoc); - if (priv->parameters != NULL) - g_list_free (priv->parameters); - - G_OBJECT_CLASS (soup_soap_response_parent_class)->finalize (object); -} - -static void -soup_soap_response_class_init (SoupSoapResponseClass *soup_soap_response_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (soup_soap_response_class); - - g_type_class_add_private (soup_soap_response_class, sizeof (SoupSoapResponsePrivate)); - - object_class->finalize = finalize; -} - -static void -soup_soap_response_init (SoupSoapResponse *response) -{ - SoupSoapResponsePrivate *priv = SOUP_SOAP_RESPONSE_GET_PRIVATE (response); - - priv->xmldoc = xmlNewDoc ((const xmlChar *)"1.0"); -} - - -/** - * soup_soap_response_new: - * - * Create a new empty #SoupSoapResponse object, which can be modified - * with the accessor functions provided with this class. - * - * Return value: the new #SoupSoapResponse (or %NULL if there was an - * error). - */ -SoupSoapResponse * -soup_soap_response_new (void) -{ - SoupSoapResponse *response; - - response = g_object_new (SOUP_TYPE_SOAP_RESPONSE, NULL); - return response; -} - -/** - * soup_soap_response_new_from_string: - * @xmlstr: the XML string to parse. - * - * Create a new #SoupSoapResponse object from the XML string contained - * in @xmlstr. - * - * Return value: the new #SoupSoapResponse (or %NULL if there was an - * error). - */ -SoupSoapResponse * -soup_soap_response_new_from_string (const char *xmlstr) -{ - SoupSoapResponse *response; - - g_return_val_if_fail (xmlstr != NULL, NULL); - - response = g_object_new (SOUP_TYPE_SOAP_RESPONSE, NULL); - if (!soup_soap_response_from_string (response, xmlstr)) { - g_object_unref (response); - return NULL; - } - - return response; -} - -static void -parse_parameters (SoupSoapResponsePrivate *priv, xmlNodePtr xml_method) -{ - xmlNodePtr tmp; - - for (tmp = soup_xml_real_node (xml_method->children); - tmp != NULL; - tmp = soup_xml_real_node (tmp->next)) { - if (!strcmp ((const char *)tmp->name, "Fault")) { - priv->soap_fault = tmp; - continue; - } else { - /* regular parameters */ - priv->parameters = g_list_append (priv->parameters, tmp); - } - } -} - -/** - * soup_soap_response_from_string: - * @response: the #SoupSoapResponse object. - * @xmlstr: XML string to parse. - * - * Parses the string contained in @xmlstr and sets all properties from - * it in the @response object. - * - * Return value: %TRUE if successful, %FALSE otherwise. - */ -gboolean -soup_soap_response_from_string (SoupSoapResponse *response, const char *xmlstr) -{ - SoupSoapResponsePrivate *priv; - xmlDocPtr old_doc = NULL; - xmlNodePtr xml_root, xml_body, xml_method = NULL; - - g_return_val_if_fail (SOUP_IS_SOAP_RESPONSE (response), FALSE); - priv = SOUP_SOAP_RESPONSE_GET_PRIVATE (response); - g_return_val_if_fail (xmlstr != NULL, FALSE); - - /* clear the previous contents */ - if (priv->xmldoc) - old_doc = priv->xmldoc; - - /* parse the string */ - priv->xmldoc = xmlParseMemory (xmlstr, strlen (xmlstr)); - if (!priv->xmldoc) { - priv->xmldoc = old_doc; - return FALSE; - } - - xml_root = xmlDocGetRootElement (priv->xmldoc); - if (!xml_root) { - xmlFreeDoc (priv->xmldoc); - priv->xmldoc = old_doc; - return FALSE; - } - - if (strcmp ((const char *)xml_root->name, "Envelope") != 0) { - xmlFreeDoc (priv->xmldoc); - priv->xmldoc = old_doc; - return FALSE; - } - - xml_body = soup_xml_real_node (xml_root->children); - if (xml_body != NULL) { - if (strcmp ((const char *)xml_body->name, "Header") == 0) - xml_body = soup_xml_real_node (xml_body->next); - if (strcmp ((const char *)xml_body->name, "Body") != 0) { - xmlFreeDoc (priv->xmldoc); - priv->xmldoc = old_doc; - return FALSE; - } - - xml_method = soup_xml_real_node (xml_body->children); - - /* read all parameters */ - if (xml_method) - parse_parameters (priv, xml_method); - } - - xmlFreeDoc (old_doc); - - priv->xml_root = xml_root; - priv->xml_body = xml_body; - priv->xml_method = xml_method; - - return TRUE; -} - -/** - * soup_soap_response_get_method_name: - * @response: the #SoupSoapResponse object. - * - * Gets the method name from the SOAP response. - * - * Return value: the method name. - */ -const char * -soup_soap_response_get_method_name (SoupSoapResponse *response) -{ - SoupSoapResponsePrivate *priv; - - g_return_val_if_fail (SOUP_IS_SOAP_RESPONSE (response), NULL); - priv = SOUP_SOAP_RESPONSE_GET_PRIVATE (response); - g_return_val_if_fail (priv->xml_method != NULL, NULL); - - return (const char *) priv->xml_method->name; -} - -/** - * soup_soap_response_set_method_name: - * @response: the #SoupSoapResponse object. - * @method_name: the method name to set. - * - * Sets the method name on the given #SoupSoapResponse. - */ -void -soup_soap_response_set_method_name (SoupSoapResponse *response, const char *method_name) -{ - SoupSoapResponsePrivate *priv; - - g_return_if_fail (SOUP_IS_SOAP_RESPONSE (response)); - priv = SOUP_SOAP_RESPONSE_GET_PRIVATE (response); - g_return_if_fail (priv->xml_method != NULL); - g_return_if_fail (method_name != NULL); - - xmlNodeSetName (priv->xml_method, (const xmlChar *)method_name); -} - -/** - * soup_soap_parameter_get_name: - * @param: the parameter - * - * Returns the parameter name. - * - * Return value: the parameter name. - */ -const char * -soup_soap_parameter_get_name (SoupSoapParameter *param) -{ - g_return_val_if_fail (param != NULL, NULL); - - return (const char *) param->name; -} - -/** - * soup_soap_parameter_get_int_value: - * @param: the parameter - * - * Returns the parameter's (integer) value. - * - * Return value: the parameter value as an integer - */ -int -soup_soap_parameter_get_int_value (SoupSoapParameter *param) -{ - int i; - xmlChar *s; - g_return_val_if_fail (param != NULL, -1); - - s = xmlNodeGetContent (param); - if (s) { - i = atoi ((char *)s); - xmlFree (s); - - return i; - } - - return -1; -} - -/** - * soup_soap_parameter_get_string_value: - * @param: the parameter - * - * Returns the parameter's value. - * - * Return value: the parameter value as a string, which must be freed - * by the caller. - */ -char * -soup_soap_parameter_get_string_value (SoupSoapParameter *param) -{ - xmlChar *xml_s; - char *s; - g_return_val_if_fail (param != NULL, NULL); - - xml_s = xmlNodeGetContent (param); - s = g_strdup ((char *)xml_s); - xmlFree (xml_s); - - return s; -} - -/** - * soup_soap_parameter_get_first_child: - * @param: A #SoupSoapParameter. - * - * Gets the first child of the given #SoupSoapParameter. This is used - * for compound data types, which can contain several parameters - * themselves. - * - * Return value: the first child or %NULL if there are no children. - */ -SoupSoapParameter * -soup_soap_parameter_get_first_child (SoupSoapParameter *param) -{ - g_return_val_if_fail (param != NULL, NULL); - - return soup_xml_real_node (param->children); -} - -/** - * soup_soap_parameter_get_first_child_by_name: - * @param: A #SoupSoapParameter. - * @name: The name of the child parameter to look for. - * - * Gets the first child of the given #SoupSoapParameter whose name is - * @name. - * - * Return value: the first child with the given name or %NULL if there - * are no children. - */ -SoupSoapParameter * -soup_soap_parameter_get_first_child_by_name (SoupSoapParameter *param, const char *name) -{ - SoupSoapParameter *tmp; - - g_return_val_if_fail (param != NULL, NULL); - g_return_val_if_fail (name != NULL, NULL); - - for (tmp = soup_soap_parameter_get_first_child (param); - tmp != NULL; - tmp = soup_soap_parameter_get_next_child (tmp)) { - if (!strcmp (name, (const char *)tmp->name)) - return tmp; - } - - return NULL; -} - -/** - * soup_soap_parameter_get_next_child: - * @param: A #SoupSoapParameter. - * - * Gets the next sibling of the given #SoupSoapParameter. This is used - * for compound data types, which can contain several parameters - * themselves. - * - * FIXME: the name of this method is wrong - * - * Return value: the next sibling, or %NULL if there are no more - * siblings. - */ -SoupSoapParameter * -soup_soap_parameter_get_next_child (SoupSoapParameter *param) -{ - g_return_val_if_fail (param != NULL, NULL); - - return soup_xml_real_node (param->next); -} - -/** - * soup_soap_parameter_get_next_child_by_name: - * @param: A #SoupSoapParameter. - * @name: The name of the sibling parameter to look for. - * - * Gets the next sibling of the given #SoupSoapParameter whose name is - * @name. - * - * FIXME: the name of this method is wrong - * - * Return value: the next sibling with the given name, or %NULL - */ -SoupSoapParameter * -soup_soap_parameter_get_next_child_by_name (SoupSoapParameter *param, - const char *name) -{ - SoupSoapParameter *tmp; - - g_return_val_if_fail (param != NULL, NULL); - g_return_val_if_fail (name != NULL, NULL); - - for (tmp = soup_soap_parameter_get_next_child (param); - tmp != NULL; - tmp = soup_soap_parameter_get_next_child (tmp)) { - if (!strcmp (name, (const char *)tmp->name)) - return tmp; - } - - return NULL; -} - -/** - * soup_soap_parameter_get_property: - * @param: the parameter - * @prop_name: Name of the property to retrieve. - * - * Returns the named property of @param. - * - * Return value: the property, which must be freed by the caller. - */ -char * -soup_soap_parameter_get_property (SoupSoapParameter *param, const char *prop_name) -{ - xmlChar *xml_s; - char *s; - - g_return_val_if_fail (param != NULL, NULL); - g_return_val_if_fail (prop_name != NULL, NULL); - - xml_s = xmlGetProp (param, (const xmlChar *)prop_name); - s = g_strdup ((char *)xml_s); - xmlFree (xml_s); - - return s; -} - -/** - * soup_soap_response_get_parameters: - * @response: the #SoupSoapResponse object. - * - * Returns the list of parameters received in the SOAP response. - * - * Return value: a list of #SoupSoapParameter - */ -const GList * -soup_soap_response_get_parameters (SoupSoapResponse *response) -{ - SoupSoapResponsePrivate *priv; - - g_return_val_if_fail (SOUP_IS_SOAP_RESPONSE (response), NULL); - priv = SOUP_SOAP_RESPONSE_GET_PRIVATE (response); - - return (const GList *) priv->parameters; -} - -/** - * soup_soap_response_get_first_parameter: - * @response: the #SoupSoapResponse object. - * - * Retrieves the first parameter contained in the SOAP response. - * - * Return value: a #SoupSoapParameter representing the first - * parameter, or %NULL if there are no parameters. - */ -SoupSoapParameter * -soup_soap_response_get_first_parameter (SoupSoapResponse *response) -{ - SoupSoapResponsePrivate *priv; - - g_return_val_if_fail (SOUP_IS_SOAP_RESPONSE (response), NULL); - priv = SOUP_SOAP_RESPONSE_GET_PRIVATE (response); - - return priv->parameters ? priv->parameters->data : NULL; -} - -/** - * soup_soap_response_get_first_parameter_by_name: - * @response: the #SoupSoapResponse object. - * @name: the name of the parameter to look for. - * - * Retrieves the first parameter contained in the SOAP response whose - * name is @name. - * - * Return value: a #SoupSoapParameter representing the first parameter - * with the given name, or %NULL. - */ -SoupSoapParameter * -soup_soap_response_get_first_parameter_by_name (SoupSoapResponse *response, - const char *name) -{ - SoupSoapResponsePrivate *priv; - GList *l; - - g_return_val_if_fail (SOUP_IS_SOAP_RESPONSE (response), NULL); - priv = SOUP_SOAP_RESPONSE_GET_PRIVATE (response); - g_return_val_if_fail (name != NULL, NULL); - - for (l = priv->parameters; l != NULL; l = l->next) { - SoupSoapParameter *param = (SoupSoapParameter *) l->data; - - if (!strcmp (name, (const char *)param->name)) - return param; - } - - return NULL; -} - -/** - * soup_soap_response_get_next_parameter: - * @response: the #SoupSoapResponse object. - * @from: the parameter to start from. - * - * Retrieves the parameter following @from in the #SoupSoapResponse - * object. - * - * Return value: a #SoupSoapParameter representing the parameter. - */ -SoupSoapParameter * -soup_soap_response_get_next_parameter (SoupSoapResponse *response, - SoupSoapParameter *from) -{ - SoupSoapResponsePrivate *priv; - GList *l; - - g_return_val_if_fail (SOUP_IS_SOAP_RESPONSE (response), NULL); - priv = SOUP_SOAP_RESPONSE_GET_PRIVATE (response); - g_return_val_if_fail (from != NULL, NULL); - - l = g_list_find (priv->parameters, (gconstpointer) from); - if (!l) - return NULL; - - return l->next ? (SoupSoapParameter *) l->next->data : NULL; -} - -/** - * soup_soap_response_get_next_parameter_by_name: - * @response: the #SoupSoapResponse object. - * @from: the parameter to start from. - * @name: the name of the parameter to look for. - * - * Retrieves the first parameter following @from in the - * #SoupSoapResponse object whose name matches @name. - * - * Return value: a #SoupSoapParameter representing the parameter. - */ -SoupSoapParameter * -soup_soap_response_get_next_parameter_by_name (SoupSoapResponse *response, - SoupSoapParameter *from, - const char *name) -{ - SoupSoapParameter *param; - - g_return_val_if_fail (SOUP_IS_SOAP_RESPONSE (response), NULL); - g_return_val_if_fail (from != NULL, NULL); - g_return_val_if_fail (name != NULL, NULL); - - param = soup_soap_response_get_next_parameter (response, from); - while (param) { - const char *param_name = soup_soap_parameter_get_name (param); - - if (param_name) { - if (!strcmp (name, param_name)) - return param; - } - - param = soup_soap_response_get_next_parameter (response, param); - } - - return NULL; -} diff --git a/libsoup/soup-soap-response.h b/libsoup/soup-soap-response.h deleted file mode 100644 index 32427e2..0000000 --- a/libsoup/soup-soap-response.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2003, Novell, Inc. - */ - -#ifndef SOUP_SOAP_RESPONSE_H -#define SOUP_SOAP_RESPONSE_H - -#include -#include - -G_BEGIN_DECLS - -#define SOUP_TYPE_SOAP_RESPONSE (soup_soap_response_get_type ()) -#define SOUP_SOAP_RESPONSE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_SOAP_RESPONSE, SoupSoapResponse)) -#define SOUP_SOAP_RESPONSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_SOAP_RESPONSE, SoupSoapResponseClass)) -#define SOUP_IS_SOAP_RESPONSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_SOAP_RESPONSE)) -#define SOUP_IS_SOAP_RESPONSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_SOAP_RESPONSE)) -#define SOUP_SOAP_RESPONSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_SOAP_RESPONSE, SoupSoapResponseClass)) - -typedef struct { - GObject parent; - -} SoupSoapResponse; - -typedef struct { - GObjectClass parent_class; -} SoupSoapResponseClass; - -GType soup_soap_response_get_type (void); - -SoupSoapResponse *soup_soap_response_new (void); -SoupSoapResponse *soup_soap_response_new_from_string (const char *xmlstr); - -gboolean soup_soap_response_from_string (SoupSoapResponse *response, const char *xmlstr); - -const char *soup_soap_response_get_method_name (SoupSoapResponse *response); -void soup_soap_response_set_method_name (SoupSoapResponse *response, - const char *method_name); - -typedef xmlNode SoupSoapParameter; - -const char *soup_soap_parameter_get_name (SoupSoapParameter *param); -int soup_soap_parameter_get_int_value (SoupSoapParameter *param); -char *soup_soap_parameter_get_string_value (SoupSoapParameter *param); -SoupSoapParameter *soup_soap_parameter_get_first_child (SoupSoapParameter *param); -SoupSoapParameter *soup_soap_parameter_get_first_child_by_name (SoupSoapParameter *param, - const char *name); -SoupSoapParameter *soup_soap_parameter_get_next_child (SoupSoapParameter *param); -SoupSoapParameter *soup_soap_parameter_get_next_child_by_name (SoupSoapParameter *param, - const char *name); -char *soup_soap_parameter_get_property (SoupSoapParameter *param, const char *prop_name); - -const GList *soup_soap_response_get_parameters (SoupSoapResponse *response); -SoupSoapParameter *soup_soap_response_get_first_parameter (SoupSoapResponse *response); -SoupSoapParameter *soup_soap_response_get_first_parameter_by_name (SoupSoapResponse *response, - const char *name); -SoupSoapParameter *soup_soap_response_get_next_parameter (SoupSoapResponse *response, - SoupSoapParameter *from); -SoupSoapParameter *soup_soap_response_get_next_parameter_by_name (SoupSoapResponse *response, - SoupSoapParameter *from, - const char *name); - -G_END_DECLS - -#endif diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index eeafa9c..953c6f8 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -24,10 +24,15 @@ #include #include +/** + * SECTION:soup-socket + * @short_description: A network socket + * + **/ + G_DEFINE_TYPE (SoupSocket, soup_socket, G_TYPE_OBJECT) enum { - CONNECT_RESULT, READABLE, WRITABLE, DISCONNECTED, @@ -40,10 +45,9 @@ static guint signals[LAST_SIGNAL] = { 0 }; enum { PROP_0, + PROP_LOCAL_ADDRESS, + PROP_REMOTE_ADDRESS, PROP_NON_BLOCKING, - PROP_NODELAY, - PROP_REUSEADDR, - PROP_CLOEXEC, PROP_IS_SERVER, PROP_SSL_CREDENTIALS, PROP_ASYNC_CONTEXT, @@ -58,9 +62,6 @@ typedef struct { GIOChannel *iochannel; guint non_blocking:1; - guint nodelay:1; - guint reuseaddr:1; - guint cloexec:1; guint is_server:1; gpointer ssl_creds; @@ -86,12 +87,10 @@ static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); #ifdef G_OS_WIN32 -#define SOUP_CLOSE_SOCKET(socket) closesocket (socket) #define SOUP_IS_SOCKET_ERROR(status) ((status) == SOCKET_ERROR) #define SOUP_IS_INVALID_SOCKET(socket) ((socket) == INVALID_SOCKET) #define SOUP_IS_CONNECT_STATUS_INPROGRESS() (WSAGetLastError () == WSAEWOULDBLOCK) #else -#define SOUP_CLOSE_SOCKET(socket) close (socket) #define SOUP_IS_SOCKET_ERROR(status) ((status) == -1) #define SOUP_IS_INVALID_SOCKET(socket) ((socket) < 0) #define SOUP_IS_CONNECT_STATUS_INPROGRESS() (errno == EINPROGRESS) @@ -103,9 +102,7 @@ soup_socket_init (SoupSocket *sock) SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); priv->sockfd = -1; - priv->non_blocking = priv->nodelay = TRUE; - priv->reuseaddr = TRUE; - priv->cloexec = FALSE; + priv->non_blocking = TRUE; priv->addrlock = g_mutex_new (); priv->iolock = g_mutex_new (); priv->timeout = 0; @@ -170,24 +167,6 @@ soup_socket_class_init (SoupSocketClass *socket_class) /* signals */ /** - * SoupSocket::connect-result: - * @sock: the socket - * @status: the status - * - * Emitted when a connection attempt succeeds or fails. This - * is used internally by soup_socket_client_new_async(). - **/ - signals[CONNECT_RESULT] = - g_signal_new ("connect_result", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (SoupSocketClass, connect_result), - NULL, NULL, - soup_marshal_NONE__INT, - G_TYPE_NONE, 1, - G_TYPE_INT); - - /** * SoupSocket::readable: * @sock: the socket * @@ -241,10 +220,10 @@ soup_socket_class_init (SoupSocketClass *socket_class) * @new: the new socket * * Emitted when a listening socket (set up with - * soup_socket_listen() or soup_socket_server_new()) receives a - * new connection. If you want to keep the connection, do not forget - * to add a reference to it. Otherwise the @new socket will be closed - * after this signal. + * soup_socket_listen()) receives a new connection. + * + * You must ref the @new if you want to keep it; otherwise it + * will be destroyed after the signal is emitted. **/ signals[NEW_CONNECTION] = g_signal_new ("new_connection", @@ -258,6 +237,20 @@ soup_socket_class_init (SoupSocketClass *socket_class) /* properties */ g_object_class_install_property ( + object_class, PROP_LOCAL_ADDRESS, + g_param_spec_object (SOUP_SOCKET_LOCAL_ADDRESS, + "Local address", + "Address of local end of socket", + SOUP_TYPE_ADDRESS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( + object_class, PROP_REMOTE_ADDRESS, + g_param_spec_object (SOUP_SOCKET_REMOTE_ADDRESS, + "Remote address", + "Address of remote end of socket", + SOUP_TYPE_ADDRESS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property ( object_class, PROP_NON_BLOCKING, g_param_spec_boolean (SOUP_SOCKET_FLAG_NONBLOCKING, "Non-blocking", @@ -265,27 +258,6 @@ soup_socket_class_init (SoupSocketClass *socket_class) TRUE, G_PARAM_READWRITE)); g_object_class_install_property ( - object_class, PROP_NODELAY, - g_param_spec_boolean (SOUP_SOCKET_FLAG_NODELAY, - "NODELAY", - "Whether or not the socket uses TCP NODELAY", - TRUE, - G_PARAM_READWRITE)); - g_object_class_install_property ( - object_class, PROP_REUSEADDR, - g_param_spec_boolean (SOUP_SOCKET_FLAG_REUSEADDR, - "REUSEADDR", - "Whether or not the socket uses the TCP REUSEADDR flag", - TRUE, - G_PARAM_READWRITE)); - g_object_class_install_property ( - object_class, PROP_CLOEXEC, - g_param_spec_boolean (SOUP_SOCKET_FLAG_CLOEXEC, - "CLOEXEC", - "Whether or not the socket will be closed automatically on exec()", - FALSE, - G_PARAM_READWRITE)); - g_object_class_install_property ( object_class, PROP_IS_SERVER, g_param_spec_boolean (SOUP_SOCKET_IS_SERVER, "Server", @@ -321,12 +293,12 @@ soup_socket_class_init (SoupSocketClass *socket_class) static void -update_fdflags (SoupSocketPrivate *priv) +set_nonblocking (SoupSocketPrivate *priv) { - int opt; - struct timeval timeout; #ifndef G_OS_WIN32 int flags; +#else + u_log val; #endif if (priv->sockfd == -1) @@ -341,28 +313,39 @@ update_fdflags (SoupSocketPrivate *priv) flags &= ~O_NONBLOCK; fcntl (priv->sockfd, F_SETFL, flags); } - flags = fcntl (priv->sockfd, F_GETFD, 0); - if (flags != -1) { - if (priv->cloexec) - flags |= FD_CLOEXEC; - else - flags &= ~FD_CLOEXEC; - fcntl (priv->sockfd, F_SETFD, flags); - } - #else - if (priv->non_blocking) { - u_long val = 1; - ioctlsocket (priv->sockfd, FIONBIO, &val); - } else { - u_long val = 0; - ioctlsocket (priv->sockfd, FIONBIO, &val); - } + val = priv->non_blocking ? 1 : 0; + ioctlsocket (priv->sockfd, FIONBIO, &val); +#endif +} + +static void +set_fdflags (SoupSocketPrivate *priv) +{ + int opt; + struct timeval timeout; +#ifndef G_OS_WIN32 + int flags; +#endif + + if (priv->sockfd == -1) + return; + + set_nonblocking (priv); + +#ifndef G_OS_WIN32 + flags = fcntl (priv->sockfd, F_GETFD, 0); + if (flags != -1) { + flags |= FD_CLOEXEC; + fcntl (priv->sockfd, F_SETFD, flags); + } #endif - opt = (priv->nodelay != 0); + opt = 1; setsockopt (priv->sockfd, IPPROTO_TCP, TCP_NODELAY, (void *) &opt, sizeof (opt)); + setsockopt (priv->sockfd, SOL_SOCKET, + SO_REUSEADDR, (void *) &opt, sizeof (opt)); timeout.tv_sec = priv->timeout; timeout.tv_usec = 0; @@ -374,9 +357,16 @@ update_fdflags (SoupSocketPrivate *priv) setsockopt (priv->sockfd, SOL_SOCKET, SO_SNDTIMEO, (void *) &timeout, sizeof (timeout)); - opt = (priv->reuseaddr != 0); - setsockopt (priv->sockfd, SOL_SOCKET, - SO_REUSEADDR, (void *) &opt, sizeof (opt)); +#ifndef G_OS_WIN32 + priv->iochannel = + g_io_channel_unix_new (priv->sockfd); +#else + priv->iochannel = + g_io_channel_win32_new_socket (priv->sockfd); +#endif + g_io_channel_set_close_on_unref (priv->iochannel, TRUE); + g_io_channel_set_encoding (priv->iochannel, NULL, NULL); + g_io_channel_set_buffered (priv->iochannel, FALSE); } static void @@ -386,21 +376,15 @@ set_property (GObject *object, guint prop_id, SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object); switch (prop_id) { - case PROP_NON_BLOCKING: - priv->non_blocking = g_value_get_boolean (value); - update_fdflags (priv); + case PROP_LOCAL_ADDRESS: + priv->local_addr = (SoupAddress *)g_value_dup_object (value); break; - case PROP_NODELAY: - priv->nodelay = g_value_get_boolean (value); - update_fdflags (priv); + case PROP_REMOTE_ADDRESS: + priv->remote_addr = (SoupAddress *)g_value_dup_object (value); break; - case PROP_REUSEADDR: - priv->reuseaddr = g_value_get_boolean (value); - update_fdflags (priv); - break; - case PROP_CLOEXEC: - priv->cloexec = g_value_get_boolean (value); - update_fdflags (priv); + case PROP_NON_BLOCKING: + priv->non_blocking = g_value_get_boolean (value); + set_nonblocking (priv); break; case PROP_SSL_CREDENTIALS: priv->ssl_creds = g_value_get_pointer (value); @@ -425,18 +409,15 @@ get_property (GObject *object, guint prop_id, SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (object); switch (prop_id) { - case PROP_NON_BLOCKING: - g_value_set_boolean (value, priv->non_blocking); + case PROP_LOCAL_ADDRESS: + g_value_set_object (value, soup_socket_get_local_address (SOUP_SOCKET (object))); break; - case PROP_NODELAY: - g_value_set_boolean (value, priv->nodelay); + case PROP_REMOTE_ADDRESS: + g_value_set_object (value, soup_socket_get_remote_address (SOUP_SOCKET (object))); break; - case PROP_REUSEADDR: - g_value_set_boolean (value, priv->reuseaddr); + case PROP_NON_BLOCKING: + g_value_set_boolean (value, priv->non_blocking); break; - case PROP_CLOEXEC: - g_value_set_boolean (value, priv->cloexec); - break; case PROP_IS_SERVER: g_value_set_boolean (value, priv->is_server); break; @@ -478,44 +459,43 @@ soup_socket_new (const char *optname1, ...) return sock; } -static GIOChannel * -get_iochannel (SoupSocketPrivate *priv) -{ - g_mutex_lock (priv->iolock); - if (!priv->iochannel) { -#ifndef G_OS_WIN32 - priv->iochannel = - g_io_channel_unix_new (priv->sockfd); -#else - priv->iochannel = - g_io_channel_win32_new_socket (priv->sockfd); -#endif - g_io_channel_set_close_on_unref (priv->iochannel, TRUE); - g_io_channel_set_encoding (priv->iochannel, NULL, NULL); - g_io_channel_set_buffered (priv->iochannel, FALSE); - } - g_mutex_unlock (priv->iolock); - return priv->iochannel; -} +typedef struct { + SoupSocket *sock; + GCancellable *cancellable; + guint cancel_id; + SoupSocketCallback callback; + gpointer user_data; +} SoupSocketAsyncConnectData; static gboolean idle_connect_result (gpointer user_data) { - SoupSocket *sock = user_data; - SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + SoupSocketAsyncConnectData *sacd = user_data; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock); + guint status; priv->watch_src = NULL; + if (sacd->cancel_id) + g_signal_handler_disconnect (sacd->cancellable, sacd->cancel_id); + + if (priv->sockfd == -1) { + if (g_cancellable_is_cancelled (sacd->cancellable)) + status = SOUP_STATUS_CANCELLED; + else + status = SOUP_STATUS_CANT_CONNECT; + } else + status = SOUP_STATUS_OK; - g_signal_emit (sock, signals[CONNECT_RESULT], 0, - priv->sockfd != -1 ? SOUP_STATUS_OK : SOUP_STATUS_CANT_CONNECT); + sacd->callback (sacd->sock, status, sacd->user_data); + g_slice_free (SoupSocketAsyncConnectData, sacd); return FALSE; } static gboolean connect_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data) { - SoupSocket *sock = data; - SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + SoupSocketAsyncConnectData *sacd = data; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock); int error = 0; int len = sizeof (error); @@ -523,123 +503,201 @@ connect_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data) g_source_destroy (priv->watch_src); priv->watch_src = NULL; - if (condition & ~(G_IO_IN | G_IO_OUT)) - goto cant_connect; + if ((condition & ~(G_IO_IN | G_IO_OUT)) || + (getsockopt (priv->sockfd, SOL_SOCKET, SO_ERROR, + (void *)&error, (void *)&len) != 0) || + error) + disconnect_internal (priv); - if (getsockopt (priv->sockfd, SOL_SOCKET, SO_ERROR, - (void *)&error, (void *)&len) != 0) - goto cant_connect; - if (error) - goto cant_connect; + return idle_connect_result (sacd); +} - return idle_connect_result (sock); +static void +got_address (SoupAddress *addr, guint status, gpointer user_data) +{ + SoupSocketAsyncConnectData *sacd = user_data; - cant_connect: - g_signal_emit (sock, signals[CONNECT_RESULT], 0, SOUP_STATUS_CANT_CONNECT); - return FALSE; + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { + sacd->callback (sacd->sock, status, sacd->user_data); + g_slice_free (SoupSocketAsyncConnectData, sacd); + return; + } + + soup_socket_connect_async (sacd->sock, sacd->cancellable, + sacd->callback, sacd->user_data); + g_slice_free (SoupSocketAsyncConnectData, sacd); } static void -got_address (SoupAddress *addr, guint status, gpointer user_data) +async_cancel (GCancellable *cancellable, gpointer user_data) +{ + SoupSocketAsyncConnectData *sacd = user_data; + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock); + + if (priv->watch_src) + g_source_destroy (priv->watch_src); + disconnect_internal (priv); + priv->watch_src = soup_add_idle (priv->async_context, + idle_connect_result, sacd); +} + +static guint +socket_connect_internal (SoupSocket *sock) { - SoupSocket *sock = user_data; SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + struct sockaddr *sa; + int len, status; - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_signal_emit (sock, signals[CONNECT_RESULT], 0, status); - g_object_unref (sock); + sa = soup_address_get_sockaddr (priv->remote_addr, &len); + if (!sa) + return SOUP_STATUS_CANT_RESOLVE; + + priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0); + if (SOUP_IS_INVALID_SOCKET (priv->sockfd)) + return SOUP_STATUS_CANT_CONNECT; + set_fdflags (priv); + + status = connect (priv->sockfd, sa, len); + + if (SOUP_IS_SOCKET_ERROR (status)) { + if (SOUP_IS_CONNECT_STATUS_INPROGRESS ()) + return SOUP_STATUS_CONTINUE; + + disconnect_internal (priv); + return SOUP_STATUS_CANT_CONNECT; + } else + return SOUP_STATUS_OK; +} + +/** + * SoupSocketCallback: + * @sock: the #SoupSocket + * @status: an HTTP status code indicating success or failure + * @user_data: the data passed to soup_socket_connect_async() + * + * The callback function passed to soup_socket_connect_async(). + **/ + +/** + * soup_socket_connect_async: + * @sock: a client #SoupSocket (which must not already be connected) + * @cancellable: a #GCancellable, or %NULL + * @callback: callback to call after connecting + * @user_data: data to pass to @callback + * + * Begins asynchronously connecting to @sock's remote address. The + * socket will call @callback when it succeeds or fails (but not + * before returning from this function). + * + * If @cancellable is non-%NULL, it can be used to cancel the + * connection. @callback will still be invoked in this case, with a + * status of %SOUP_STATUS_CANCELLED. + **/ +void +soup_socket_connect_async (SoupSocket *sock, GCancellable *cancellable, + SoupSocketCallback callback, gpointer user_data) +{ + SoupSocketPrivate *priv; + SoupSocketAsyncConnectData *sacd; + guint status; + + g_return_if_fail (SOUP_IS_SOCKET (sock)); + priv = SOUP_SOCKET_GET_PRIVATE (sock); + g_return_if_fail (priv->remote_addr != NULL); + + sacd = g_slice_new0 (SoupSocketAsyncConnectData); + sacd->sock = sock; + sacd->cancellable = cancellable; + sacd->callback = callback; + sacd->user_data = user_data; + + if (!soup_address_get_sockaddr (priv->remote_addr, NULL)) { + soup_address_resolve_async (priv->remote_addr, + priv->async_context, + cancellable, + got_address, sacd); return; } - soup_socket_connect (sock, priv->remote_addr); - /* soup_socket_connect re-reffed addr */ - g_object_unref (addr); + status = socket_connect_internal (sock); + if (status == SOUP_STATUS_CONTINUE) { + /* Wait for connect to succeed or fail */ + priv->watch_src = + soup_add_io_watch (priv->async_context, + priv->iochannel, + G_IO_IN | G_IO_OUT | + G_IO_PRI | G_IO_ERR | + G_IO_HUP | G_IO_NVAL, + connect_watch, sacd); + if (cancellable) { + sacd->cancel_id = + g_signal_connect (cancellable, "cancelled", + G_CALLBACK (async_cancel), + sacd); + } + } else { + priv->watch_src = soup_add_idle (priv->async_context, + idle_connect_result, sacd); + } +} - g_object_unref (sock); +static void +sync_cancel (GCancellable *cancellable, gpointer sock) +{ + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + + shutdown (priv->sockfd, SHUT_RDWR); } /** - * soup_socket_connect: + * soup_socket_connect_sync: * @sock: a client #SoupSocket (which must not already be connected) - * @remote_addr: address to connect to + * @cancellable: a #GCancellable, or %NULL * - * If %SOUP_SOCKET_FLAG_NONBLOCKING has been set on the socket, this - * begins asynchronously connecting to the given address. The socket - * will emit %connect_result when it succeeds or fails (but not before - * returning from this function). + * Attempt to synchronously connect @sock to its remote address. * - * If %SOUP_SOCKET_FLAG_NONBLOCKING has not been set, this will - * attempt to synchronously connect. + * If @cancellable is non-%NULL, it can be used to cancel the + * connection, in which case soup_socket_connect_sync() will return + * %SOUP_STATUS_CANCELLED. * - * Return value: %SOUP_STATUS_CONTINUE if connecting asynchronously, - * otherwise a success or failure code. + * Return value: a success or failure code. **/ guint -soup_socket_connect (SoupSocket *sock, SoupAddress *remote_addr) +soup_socket_connect_sync (SoupSocket *sock, GCancellable *cancellable) { SoupSocketPrivate *priv; - struct sockaddr *sa; - int len, status; + guint status, cancel_id; g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_STATUS_MALFORMED); priv = SOUP_SOCKET_GET_PRIVATE (sock); g_return_val_if_fail (!priv->is_server, SOUP_STATUS_MALFORMED); g_return_val_if_fail (priv->sockfd == -1, SOUP_STATUS_MALFORMED); - g_return_val_if_fail (SOUP_IS_ADDRESS (remote_addr), SOUP_STATUS_MALFORMED); + g_return_val_if_fail (priv->remote_addr != NULL, SOUP_STATUS_MALFORMED); - priv->remote_addr = g_object_ref (remote_addr); - if (!priv->non_blocking) { - status = soup_address_resolve_sync (remote_addr); + if (!soup_address_get_sockaddr (priv->remote_addr, NULL)) { + status = soup_address_resolve_sync (priv->remote_addr, + cancellable); if (!SOUP_STATUS_IS_SUCCESSFUL (status)) return status; } - sa = soup_address_get_sockaddr (priv->remote_addr, &len); - if (!sa) { - if (!priv->non_blocking) - return SOUP_STATUS_CANT_RESOLVE; - - g_object_ref (sock); - soup_address_resolve_async_full (remote_addr, priv->async_context, - got_address, sock); - return SOUP_STATUS_CONTINUE; - } - - priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0); - if (SOUP_IS_INVALID_SOCKET (priv->sockfd)) { - goto done; + if (cancellable) { + cancel_id = g_signal_connect (cancellable, "cancelled", + G_CALLBACK (sync_cancel), sock); } - update_fdflags (priv); - status = connect (priv->sockfd, sa, len); + status = socket_connect_internal (sock); - if (SOUP_IS_SOCKET_ERROR (status)) { - if (SOUP_IS_CONNECT_STATUS_INPROGRESS ()) { - /* Wait for connect to succeed or fail */ - priv->watch_src = - soup_add_io_watch (priv->async_context, - get_iochannel (priv), - G_IO_IN | G_IO_OUT | - G_IO_PRI | G_IO_ERR | - G_IO_HUP | G_IO_NVAL, - connect_watch, sock); - return SOUP_STATUS_CONTINUE; - } else { - SOUP_CLOSE_SOCKET (priv->sockfd); - priv->sockfd = -1; + if (cancellable) { + if (status != SOUP_STATUS_OK && + g_cancellable_is_cancelled (cancellable)) { + status = SOUP_STATUS_CANCELLED; + disconnect_internal (priv); } - } else - get_iochannel (priv); + g_signal_handler_disconnect (cancellable, cancel_id); + } - done: - if (priv->non_blocking) { - priv->watch_src = soup_add_idle (priv->async_context, - idle_connect_result, sock); - return SOUP_STATUS_CONTINUE; - } else if (SOUP_IS_INVALID_SOCKET (priv->sockfd)) - return SOUP_STATUS_CANT_CONNECT; - else - return SOUP_STATUS_OK; + return status; } static gboolean @@ -667,20 +725,18 @@ listen_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data) if (priv->async_context) new_priv->async_context = g_main_context_ref (priv->async_context); new_priv->non_blocking = priv->non_blocking; - new_priv->nodelay = priv->nodelay; new_priv->is_server = TRUE; new_priv->ssl_creds = priv->ssl_creds; - update_fdflags (new_priv); + set_fdflags (new_priv); new_priv->remote_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&sa, sa_len); if (new_priv->ssl_creds) { - if (!soup_socket_start_ssl (new)) { + if (!soup_socket_start_ssl (new, NULL)) { g_object_unref (new); return TRUE; } - } else - get_iochannel (new_priv); + } g_signal_emit (sock, signals[NEW_CONNECTION], 0, new); g_object_unref (new); @@ -692,15 +748,15 @@ listen_watch (GIOChannel* iochannel, GIOCondition condition, gpointer data) * soup_socket_listen: * @sock: a server #SoupSocket (which must not already be connected or * listening) - * @local_addr: Local address to bind to. * - * Makes @sock start listening on the given interface and port. When - * connections come in, @sock will emit %new_connection. + * Makes @sock start listening on its local address. When connections + * come in, @sock will emit %new_connection. * * Return value: whether or not @sock is now listening. **/ gboolean -soup_socket_listen (SoupSocket *sock, SoupAddress *local_addr) +soup_socket_listen (SoupSocket *sock) + { SoupSocketPrivate *priv; struct sockaddr *sa; @@ -709,7 +765,7 @@ soup_socket_listen (SoupSocket *sock, SoupAddress *local_addr) g_return_val_if_fail (SOUP_IS_SOCKET (sock), FALSE); priv = SOUP_SOCKET_GET_PRIVATE (sock); g_return_val_if_fail (priv->sockfd == -1, FALSE); - g_return_val_if_fail (SOUP_IS_ADDRESS (local_addr), FALSE); + g_return_val_if_fail (priv->local_addr != NULL, FALSE); priv->is_server = TRUE; @@ -719,33 +775,34 @@ soup_socket_listen (SoupSocket *sock, SoupAddress *local_addr) * have to make a new addr by calling getsockname(), which * will have the right port number. */ - sa = soup_address_get_sockaddr (local_addr, &sa_len); + sa = soup_address_get_sockaddr (priv->local_addr, &sa_len); g_return_val_if_fail (sa != NULL, FALSE); priv->sockfd = socket (sa->sa_family, SOCK_STREAM, 0); if (SOUP_IS_INVALID_SOCKET (priv->sockfd)) goto cant_listen; - update_fdflags (priv); + set_fdflags (priv); /* Bind */ if (bind (priv->sockfd, sa, sa_len) != 0) goto cant_listen; + /* Force local_addr to be re-resolved now */ + g_object_unref (priv->local_addr); + priv->local_addr = NULL; /* Listen */ if (listen (priv->sockfd, 10) != 0) goto cant_listen; priv->watch_src = soup_add_io_watch (priv->async_context, - get_iochannel (priv), + priv->iochannel, G_IO_IN | G_IO_ERR | G_IO_HUP, listen_watch, sock); return TRUE; cant_listen: - if (priv->sockfd != -1) { - SOUP_CLOSE_SOCKET (priv->sockfd); - priv->sockfd = -1; - } + if (priv->iochannel) + disconnect_internal (priv); return FALSE; } @@ -753,23 +810,25 @@ soup_socket_listen (SoupSocket *sock, SoupAddress *local_addr) /** * soup_socket_start_ssl: * @sock: the socket + * @cancellable: a #GCancellable * * Starts using SSL on @socket. * * Return value: success or failure **/ gboolean -soup_socket_start_ssl (SoupSocket *sock) +soup_socket_start_ssl (SoupSocket *sock, GCancellable *cancellable) { SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - return soup_socket_start_proxy_ssl (sock, soup_address_get_name (priv->remote_addr)); + return soup_socket_start_proxy_ssl (sock, soup_address_get_name (priv->remote_addr), cancellable); } /** * soup_socket_start_proxy_ssl: * @sock: the socket * @ssl_host: hostname of the SSL server + * @cancellable: a #GCancellable * * Starts using SSL on @socket, expecting to find a host named * @ssl_host. @@ -777,13 +836,14 @@ soup_socket_start_ssl (SoupSocket *sock) * Return value: success or failure **/ gboolean -soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host) +soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host, + GCancellable *cancellable) { SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); GIOChannel *ssl_chan; GIOChannel *real_chan; - real_chan = get_iochannel (priv); + real_chan = priv->iochannel; ssl_chan = soup_ssl_wrap_iochannel ( real_chan, priv->is_server ? SOUP_SSL_TYPE_SERVER : SOUP_SSL_TYPE_CLIENT, @@ -798,134 +858,14 @@ soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host) return TRUE; } - -/** - * soup_socket_client_new_async: - * @hostname: remote machine to connect to - * @port: remote port to connect to - * @ssl_creds: SSL credentials structure, or %NULL if not SSL - * @callback: callback to call when the socket is connected - * @user_data: data for @callback - * - * Creates a connection to @hostname and @port. @callback will be - * called when the connection completes (or fails). - * - * Uses the default #GMainContext. If you need to use an alternate - * context, use soup_socket_new() and soup_socket_connect() directly. - * - * Return value: the new socket (not yet ready for use). - **/ -SoupSocket * -soup_socket_client_new_async (const char *hostname, guint port, - gpointer ssl_creds, - SoupSocketCallback callback, gpointer user_data) -{ - SoupSocket *sock; - SoupAddress *addr; - - g_return_val_if_fail (hostname != NULL, NULL); - - sock = g_object_new (SOUP_TYPE_SOCKET, - SOUP_SOCKET_SSL_CREDENTIALS, ssl_creds, - NULL); - addr = soup_address_new (hostname, port); - soup_socket_connect (sock, addr); - g_object_unref (addr); - - if (callback) { - soup_signal_connect_once (sock, "connect_result", - G_CALLBACK (callback), user_data); - } - return sock; -} - -/** - * soup_socket_client_new_sync: - * @hostname: remote machine to connect to - * @port: remote port to connect to - * @ssl_creds: SSL credentials structure, or %NULL if not SSL - * @status_ret: pointer to return the soup status in - * - * Creates a connection to @hostname and @port. If @status_ret is not - * %NULL, it will contain a status code on return. - * - * Return value: the new socket, or %NULL if it could not connect. - **/ -SoupSocket * -soup_socket_client_new_sync (const char *hostname, guint port, - gpointer ssl_creds, guint *status_ret) -{ - SoupSocket *sock; - SoupSocketPrivate *priv; - SoupAddress *addr; - guint status; - - g_return_val_if_fail (hostname != NULL, NULL); - - sock = g_object_new (SOUP_TYPE_SOCKET, - SOUP_SOCKET_SSL_CREDENTIALS, ssl_creds, - NULL); - priv = SOUP_SOCKET_GET_PRIVATE (sock); - priv->non_blocking = FALSE; - addr = soup_address_new (hostname, port); - status = soup_socket_connect (sock, addr); - g_object_unref (addr); - - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_object_unref (sock); - sock = NULL; - } - - if (status_ret) - *status_ret = status; - return sock; -} - -/** - * soup_socket_server_new: - * @local_addr: Local address to bind to. (Use soup_address_any_new() to - * accept connections on any local address) - * @ssl_creds: SSL credentials, or %NULL if this is not an SSL server - * @callback: Callback to call when a client connects - * @user_data: data to pass to @callback. - * - * Create and open a new #SoupSocket listening on the specified - * address. @callback will be called each time a client connects, - * with a new #SoupSocket. - * - * Uses the default #GMainContext. If you need to use an alternate - * context, use soup_socket_new() and soup_socket_listen() directly. - * - * Returns: a new #SoupSocket, or NULL if there was a failure. - **/ -SoupSocket * -soup_socket_server_new (SoupAddress *local_addr, gpointer ssl_creds, - SoupSocketListenerCallback callback, - gpointer user_data) +gboolean +soup_socket_is_ssl (SoupSocket *sock) { - SoupSocket *sock; - SoupSocketPrivate *priv; - - g_return_val_if_fail (SOUP_IS_ADDRESS (local_addr), NULL); - - sock = g_object_new (SOUP_TYPE_SOCKET, - SOUP_SOCKET_SSL_CREDENTIALS, ssl_creds, - NULL); - priv = SOUP_SOCKET_GET_PRIVATE (sock); - if (!soup_socket_listen (sock, local_addr)) { - g_object_unref (sock); - return NULL; - } - - if (callback) { - g_signal_connect (sock, "new_connection", - G_CALLBACK (callback), user_data); - } + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - return sock; + return priv->ssl_creds != NULL; } - /** * soup_socket_disconnect: * @sock: a #SoupSocket @@ -952,19 +892,18 @@ soup_socket_disconnect (SoupSocket *sock) int sockfd; /* Another thread is currently doing IO, so - * we can't close the iochannel. So just kick - * the file descriptor out from under it. + * we can't close the iochannel. So just shutdown + * the file descriptor to force the I/O to fail. + * (It will actually be closed when the socket is + * destroyed.) */ - sockfd = priv->sockfd; priv->sockfd = -1; + if (sockfd == -1) already_disconnected = TRUE; - else { - g_io_channel_set_close_on_unref (priv->iochannel, - FALSE); - SOUP_CLOSE_SOCKET (sockfd); - } + else + shutdown (sockfd, SHUT_RDWR); } if (already_disconnected) @@ -1078,41 +1017,38 @@ socket_read_watch (GIOChannel *chan, GIOCondition cond, gpointer user_data) } static SoupSocketIOStatus -read_from_network (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) +read_from_network (SoupSocket *sock, gpointer buffer, gsize len, + gsize *nread, GError **error) { SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); GIOStatus status; GIOCondition cond = G_IO_IN; - GError *err = NULL; + GError *my_err = NULL; if (!priv->iochannel) return SOUP_SOCKET_EOF; status = g_io_channel_read_chars (priv->iochannel, - buffer, len, nread, &err); - if (err) { - if (err->domain == SOUP_SSL_ERROR && - err->code == SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE) + buffer, len, nread, &my_err); + if (my_err) { + if (my_err->domain == SOUP_SSL_ERROR && + my_err->code == SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE) cond = G_IO_OUT; - g_object_set_data_full (G_OBJECT (sock), - "SoupSocket-last_error", - err, (GDestroyNotify)g_error_free); - } else { - g_object_set_data (G_OBJECT (sock), - "SoupSocket-last_error", - NULL); + g_propagate_error (error, my_err); } switch (status) { case G_IO_STATUS_NORMAL: case G_IO_STATUS_AGAIN: - if (*nread > 0) + if (*nread > 0) { + g_clear_error (error); return SOUP_SOCKET_OK; + } - /* If the connection/session is sync and we get - EAGAIN or EWOULDBLOCK, then it will be socket timeout - and should be treated as an error condition. - */ + /* If the socket is sync and we get EAGAIN, then it is + * a socket timeout and should be treated as an error + * condition. + */ if (!priv->non_blocking) return SOUP_SOCKET_ERROR; @@ -1123,9 +1059,11 @@ read_from_network (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) cond | G_IO_HUP | G_IO_ERR, socket_read_watch, sock); } + g_clear_error (error); return SOUP_SOCKET_WOULD_BLOCK; case G_IO_STATUS_EOF: + g_clear_error (error); return SOUP_SOCKET_EOF; default: @@ -1155,11 +1093,23 @@ read_from_buf (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) } /** + * SoupSocketIOStatus: + * @SOUP_SOCKET_OK: Success + * @SOUP_SOCKET_WOULD_BLOCK: Cannot read/write any more at this time + * @SOUP_SOCKET_EOF: End of file + * @SOUP_SOCKET_ERROR: Other error + * + * Return value from the #SoupSocket IO methods. + **/ + +/** * soup_socket_read: * @sock: the socket * @buffer: buffer to read into * @len: size of @buffer in bytes * @nread: on return, the number of bytes read into @buffer + * @cancellable: a #GCancellable, or %NULL + * @error: error pointer * * Attempts to read up to @len bytes from @sock into @buffer. If some * data is successfully read, soup_socket_read() will return @@ -1175,10 +1125,12 @@ read_from_buf (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) * * Return value: a #SoupSocketIOStatus, as described above (or * %SOUP_SOCKET_EOF if the socket is no longer connected, or - * %SOUP_SOCKET_ERROR on any other error). + * %SOUP_SOCKET_ERROR on any other error, in which case @error will + * also be set). **/ SoupSocketIOStatus -soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) +soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, + gsize *nread, GCancellable *cancellable, GError **error) { SoupSocketPrivate *priv; SoupSocketIOStatus status; @@ -1190,7 +1142,7 @@ soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) if (priv->read_buf) status = read_from_buf (sock, buffer, len, nread); else - status = read_from_network (sock, buffer, len, nread); + status = read_from_network (sock, buffer, len, nread, error); g_mutex_unlock (priv->iolock); return status; @@ -1206,6 +1158,8 @@ soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) * @nread: on return, the number of bytes read into @buffer * @got_boundary: on return, whether or not the data in @buffer * ends with the boundary string + * @cancellable: a #GCancellable, or %NULL + * @error: error pointer * * Like soup_socket_read(), but reads no further than the first * occurrence of @boundary. (If the boundary is found, it will be @@ -1217,7 +1171,8 @@ soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, gsize *nread) SoupSocketIOStatus soup_socket_read_until (SoupSocket *sock, gpointer buffer, gsize len, gconstpointer boundary, gsize boundary_len, - gsize *nread, gboolean *got_boundary) + gsize *nread, gboolean *got_boundary, + GCancellable *cancellable, GError **error) { SoupSocketPrivate *priv; SoupSocketIOStatus status; @@ -1242,7 +1197,7 @@ soup_socket_read_until (SoupSocket *sock, gpointer buffer, gsize len, g_byte_array_set_size (read_buf, len); status = read_from_network (sock, read_buf->data + prev_len, - len - prev_len, nread); + len - prev_len, nread, error); read_buf->len = prev_len + *nread; if (status != SOUP_SOCKET_OK) { @@ -1294,6 +1249,8 @@ socket_write_watch (GIOChannel *chan, GIOCondition cond, gpointer user_data) * @buffer: data to write * @len: size of @buffer, in bytes * @nwrote: on return, number of bytes written + * @cancellable: a #GCancellable, or %NULL + * @error: error pointer * * Attempts to write @len bytes from @buffer to @sock. If some data is * successfully written, the resturn status will be @@ -1307,11 +1264,13 @@ socket_write_watch (GIOChannel *chan, GIOCondition cond, gpointer user_data) * %SOUP_SOCKET_WOULD_BLOCK.) * * Return value: a #SoupSocketIOStatus, as described above (or - * %SOUP_SOCKET_EOF or %SOUP_SOCKET_ERROR). + * %SOUP_SOCKET_EOF or %SOUP_SOCKET_ERROR. @error will be set if the + * return value is %SOUP_SOCKET_ERROR.) **/ SoupSocketIOStatus soup_socket_write (SoupSocket *sock, gconstpointer buffer, - gsize len, gsize *nwrote) + gsize len, gsize *nwrote, + GCancellable *cancellable, GError **error) { SoupSocketPrivate *priv; GIOStatus status; @@ -1319,7 +1278,7 @@ soup_socket_write (SoupSocket *sock, gconstpointer buffer, gpointer pipe_handler; #endif GIOCondition cond = G_IO_OUT; - GError *err = NULL; + GError *my_err = NULL; g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_SOCKET_ERROR); priv = SOUP_SOCKET_GET_PRIVATE (sock); @@ -1339,27 +1298,20 @@ soup_socket_write (SoupSocket *sock, gconstpointer buffer, pipe_handler = signal (SIGPIPE, SIG_IGN); #endif status = g_io_channel_write_chars (priv->iochannel, - buffer, len, nwrote, &err); + buffer, len, nwrote, &my_err); #ifdef SIGPIPE signal (SIGPIPE, pipe_handler); #endif - if (err) { - if (err->domain == SOUP_SSL_ERROR && - err->code == SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ) + if (my_err) { + if (my_err->domain == SOUP_SSL_ERROR && + my_err->code == SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ) cond = G_IO_IN; - g_object_set_data_full (G_OBJECT (sock), - "SoupSocket-last_error", - err, (GDestroyNotify)g_error_free); - } else { - g_object_set_data (G_OBJECT (sock), - "SoupSocket-last_error", - NULL); + g_propagate_error (error, my_err); } - /* If the connection/session is sync and we get - EAGAIN or EWOULDBLOCK, then it will be socket timeout - and should be treated as an error condition. - */ + /* If the socket is sync and we get EAGAIN, then it is a + * socket timeout and should be treated as an error condition. + */ if (!priv->non_blocking && status == G_IO_STATUS_AGAIN) { g_mutex_unlock (priv->iolock); return SOUP_SOCKET_ERROR; @@ -1370,6 +1322,8 @@ soup_socket_write (SoupSocket *sock, gconstpointer buffer, return SOUP_SOCKET_ERROR; } + g_clear_error (error); + if (*nwrote) { g_mutex_unlock (priv->iolock); return SOUP_SOCKET_OK; diff --git a/libsoup/soup-socket.h b/libsoup/soup-socket.h index 5d60080..0cdac17 100644 --- a/libsoup/soup-socket.h +++ b/libsoup/soup-socket.h @@ -7,6 +7,7 @@ #define SOUP_SOCKET_H 1 #include +#include G_BEGIN_DECLS @@ -26,91 +27,59 @@ typedef struct { GObjectClass parent_class; /* signals */ - void (*connect_result) (SoupSocket *, guint); void (*readable) (SoupSocket *); void (*writable) (SoupSocket *); void (*disconnected) (SoupSocket *); void (*new_connection) (SoupSocket *, SoupSocket *); + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); } SoupSocketClass; +#define SOUP_SOCKET_LOCAL_ADDRESS "local-address" +#define SOUP_SOCKET_REMOTE_ADDRESS "remote-address" #define SOUP_SOCKET_FLAG_NONBLOCKING "non-blocking" -#define SOUP_SOCKET_FLAG_NODELAY "nodelay" -#define SOUP_SOCKET_FLAG_REUSEADDR "reuseaddr" -#define SOUP_SOCKET_FLAG_CLOEXEC "cloexec" #define SOUP_SOCKET_IS_SERVER "is-server" #define SOUP_SOCKET_SSL_CREDENTIALS "ssl-creds" #define SOUP_SOCKET_ASYNC_CONTEXT "async-context" #define SOUP_SOCKET_TIMEOUT "timeout" -/** - * SoupSocketCallback: - * @sock: the #SoupSocket - * @status: an HTTP status code indicating success or failure - * @user_data: the data passed to soup_socket_client_new_async() - * - * The callback function passed to soup_socket_client_new_async(). - **/ typedef void (*SoupSocketCallback) (SoupSocket *sock, guint status, gpointer user_data); -/** - * SoupSocketListenerCallback: - * @listener: the listening #SoupSocket - * @sock: the newly-received #SoupSocket - * @user_data: the data passed to soup_socket_server_new(). - * - * The callback function passed to soup_socket_server_new(), which - * receives new connections. - **/ -typedef void (*SoupSocketListenerCallback) (SoupSocket *listener, - SoupSocket *sock, - gpointer user_data); - GType soup_socket_get_type (void); SoupSocket *soup_socket_new (const char *optname1, ...) G_GNUC_NULL_TERMINATED; -guint soup_socket_connect (SoupSocket *sock, - SoupAddress *remote_addr); -gboolean soup_socket_listen (SoupSocket *sock, - SoupAddress *local_addr); -gboolean soup_socket_start_ssl (SoupSocket *sock); +void soup_socket_connect_async (SoupSocket *sock, + GCancellable *cancellable, + SoupSocketCallback callback, + gpointer user_data); +guint soup_socket_connect_sync (SoupSocket *sock, + GCancellable *cancellable); + +gboolean soup_socket_listen (SoupSocket *sock); + +gboolean soup_socket_start_ssl (SoupSocket *sock, + GCancellable *cancellable); gboolean soup_socket_start_proxy_ssl (SoupSocket *sock, - const char *ssl_host); + const char *ssl_host, + GCancellable *cancellable); +gboolean soup_socket_is_ssl (SoupSocket *sock); void soup_socket_disconnect (SoupSocket *sock); gboolean soup_socket_is_connected (SoupSocket *sock); -SoupSocket *soup_socket_client_new_async (const char *hostname, - guint port, - gpointer ssl_creds, - SoupSocketCallback callback, - gpointer user_data); -SoupSocket *soup_socket_client_new_sync (const char *hostname, - guint port, - gpointer ssl_creds, - guint *status_ret); -SoupSocket *soup_socket_server_new (SoupAddress *local_addr, - gpointer ssl_creds, - SoupSocketListenerCallback callback, - gpointer user_data); - SoupAddress *soup_socket_get_local_address (SoupSocket *sock); SoupAddress *soup_socket_get_remote_address (SoupSocket *sock); -/** - * SoupSocketIOStatus: - * @SOUP_SOCKET_OK: Success - * @SOUP_SOCKET_WOULD_BLOCK: Cannot read/write any more at this time - * @SOUP_SOCKET_EOF: End of file - * @SOUP_SOCKET_ERROR: Other error - * - * Return value from the #SoupSocket IO methods. - **/ typedef enum { SOUP_SOCKET_OK, SOUP_SOCKET_WOULD_BLOCK, @@ -121,19 +90,25 @@ typedef enum { SoupSocketIOStatus soup_socket_read (SoupSocket *sock, gpointer buffer, gsize len, - gsize *nread); + gsize *nread, + GCancellable *cancellable, + GError **error); SoupSocketIOStatus soup_socket_read_until (SoupSocket *sock, gpointer buffer, gsize len, gconstpointer boundary, gsize boundary_len, gsize *nread, - gboolean *got_boundary); + gboolean *got_boundary, + GCancellable *cancellable, + GError **error); SoupSocketIOStatus soup_socket_write (SoupSocket *sock, gconstpointer buffer, gsize len, - gsize *nwrote); + gsize *nwrote, + GCancellable *cancellable, + GError **error); G_END_DECLS diff --git a/libsoup/soup-ssl.h b/libsoup/soup-ssl.h index e0b2b1c..37f6e41 100644 --- a/libsoup/soup-ssl.h +++ b/libsoup/soup-ssl.h @@ -8,13 +8,6 @@ #include -/** - * SoupSSLType: - * @SOUP_SSL_TYPE_CLIENT: the client side of an SSL connection - * @SOUP_SSL_TYPE_SERVER: the server side of an SSL connection - * - * What kind of SSL connection this is. - **/ typedef enum { SOUP_SSL_TYPE_CLIENT = 0, SOUP_SSL_TYPE_SERVER @@ -34,14 +27,4 @@ GIOChannel *soup_ssl_wrap_iochannel (GIOChannel *sock, const char *remote_host, SoupSSLCredentials *creds); -#define SOUP_SSL_ERROR soup_ssl_error_quark() - -GQuark soup_ssl_error_quark (void); - -typedef enum { - SOUP_SSL_ERROR_HANDSHAKE_NEEDS_READ, - SOUP_SSL_ERROR_HANDSHAKE_NEEDS_WRITE, - SOUP_SSL_ERROR_CERTIFICATE, -} SoupSocketError; - #endif /* SOUP_SSL_H */ diff --git a/libsoup/soup-status.c b/libsoup/soup-status.c index 923e68e..923b72a 100644 --- a/libsoup/soup-status.c +++ b/libsoup/soup-status.c @@ -9,6 +9,145 @@ #include "soup-status.h" +/** + * SECTION:soup-status + * @short_description: HTTP (and libsoup) status codes + * + **/ + +/** + * SOUP_STATUS_IS_TRANSPORT_ERROR: + * @status: a status code + * + * Tests if @status is a libsoup transport error. + * + * Return value: %TRUE or %FALSE + **/ +/** + * SOUP_STATUS_IS_INFORMATIONAL: + * @status: an HTTP status code + * + * Tests if @status is an Informational (1xx) response. + * + * Return value: %TRUE or %FALSE + **/ +/** + * SOUP_STATUS_IS_SUCCESSFUL: + * @status: an HTTP status code + * + * Tests if @status is a Successful (2xx) response. + * + * Return value: %TRUE or %FALSE + **/ +/** + * SOUP_STATUS_IS_REDIRECTION: + * @status: an HTTP status code + * + * Tests if @status is a Redirection (3xx) response. + * + * Return value: %TRUE or %FALSE + **/ +/** + * SOUP_STATUS_IS_CLIENT_ERROR: + * @status: an HTTP status code + * + * Tests if @status is a Client Error (4xx) response. + * + * Return value: %TRUE or %FALSE + **/ +/** + * SOUP_STATUS_IS_SERVER_ERROR: + * @status: an HTTP status code + * + * Tests if @status is a Server Error (5xx) response. + * + * Return value: %TRUE or %FALSE + **/ + +/** + * SoupKnownStatusCode: + * @SOUP_STATUS_NONE: No status available. (Eg, the message has not + * been sent yet) + * @SOUP_STATUS_CANCELLED: Message was cancelled locally + * @SOUP_STATUS_CANT_RESOLVE: Unable to resolve destination host name + * @SOUP_STATUS_CANT_RESOLVE_PROXY: Unable to resolve proxy host name + * @SOUP_STATUS_CANT_CONNECT: Unable to connect to remote host + * @SOUP_STATUS_CANT_CONNECT_PROXY: Unable to connect to proxy + * @SOUP_STATUS_SSL_FAILED: SSL negotiation failed + * @SOUP_STATUS_IO_ERROR: A network error occurred, or the other end + * closed the connection unexpectedly + * @SOUP_STATUS_MALFORMED: Malformed data (usually a programmer error) + * @SOUP_STATUS_TRY_AGAIN: Try again. (Only returned in certain + * specifically documented cases) + * @SOUP_STATUS_CONTINUE: 100 Continue (HTTP) + * @SOUP_STATUS_SWITCHING_PROTOCOLS: 101 Switching Protocols (HTTP) + * @SOUP_STATUS_PROCESSING: 102 Processing (WebDAV) + * @SOUP_STATUS_OK: 200 Success (HTTP). Also used by many lower-level + * soup routines to indicate success. + * @SOUP_STATUS_CREATED: 201 Created (HTTP) + * @SOUP_STATUS_ACCEPTED: 202 Accepted (HTTP) + * @SOUP_STATUS_NON_AUTHORITATIVE: 203 Non-Authoritative Information + * (HTTP) + * @SOUP_STATUS_NO_CONTENT: 204 No Content (HTTP) + * @SOUP_STATUS_RESET_CONTENT: 205 Reset Content (HTTP) + * @SOUP_STATUS_PARTIAL_CONTENT: 206 Partial Content (HTTP) + * @SOUP_STATUS_MULTI_STATUS: 207 Multi-Status (WebDAV) + * @SOUP_STATUS_MULTIPLE_CHOICES: 300 Multiple Choices (HTTP) + * @SOUP_STATUS_MOVED_PERMANENTLY: 301 Moved Permanently (HTTP) + * @SOUP_STATUS_FOUND: 302 Found (HTTP) + * @SOUP_STATUS_MOVED_TEMPORARILY: 302 Moved Temporarily (old name, + * RFC 2068) + * @SOUP_STATUS_SEE_OTHER: 303 See Other (HTTP) + * @SOUP_STATUS_NOT_MODIFIED: 304 Not Modified (HTTP) + * @SOUP_STATUS_USE_PROXY: 305 Use Proxy (HTTP) + * @SOUP_STATUS_NOT_APPEARING_IN_THIS_PROTOCOL: 306 [Unused] (HTTP) + * @SOUP_STATUS_TEMPORARY_REDIRECT: 307 Temporary Redirect (HTTP) + * @SOUP_STATUS_BAD_REQUEST: 400 Bad Request (HTTP) + * @SOUP_STATUS_UNAUTHORIZED: 401 Unauthorized (HTTP) + * @SOUP_STATUS_PAYMENT_REQUIRED: 402 Payment Required (HTTP) + * @SOUP_STATUS_FORBIDDEN: 403 Forbidden (HTTP) + * @SOUP_STATUS_NOT_FOUND: 404 Not Found (HTTP) + * @SOUP_STATUS_METHOD_NOT_ALLOWED: 405 Method Not Allowed (HTTP) + * @SOUP_STATUS_NOT_ACCEPTABLE: 406 Not Acceptable (HTTP) + * @SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407 Proxy Authentication + * Required (HTTP) + * @SOUP_STATUS_PROXY_UNAUTHORIZED: shorter alias for + * %SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED + * @SOUP_STATUS_REQUEST_TIMEOUT: 408 Request Timeout (HTTP) + * @SOUP_STATUS_CONFLICT: 409 Conflict (HTTP) + * @SOUP_STATUS_GONE: 410 Gone (HTTP) + * @SOUP_STATUS_LENGTH_REQUIRED: 411 Length Required (HTTP) + * @SOUP_STATUS_PRECONDITION_FAILED: 412 Precondition Failed (HTTP) + * @SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE: 413 Request Entity Too Large + * (HTTP) + * @SOUP_STATUS_REQUEST_URI_TOO_LONG: 414 Request-URI Too Long (HTTP) + * @SOUP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415 Unsupported Media Type + * (HTTP) + * @SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE: 416 Requested Range + * Not Satisfiable (HTTP) + * @SOUP_STATUS_INVALID_RANGE: shorter alias for + * %SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE + * @SOUP_STATUS_EXPECTATION_FAILED: 417 Expectation Failed (HTTP) + * @SOUP_STATUS_UNPROCESSABLE_ENTITY: 422 Unprocessable Entity + * (WebDAV) + * @SOUP_STATUS_LOCKED: 423 Locked (WebDAV) + * @SOUP_STATUS_FAILED_DEPENDENCY: 424 Failed Dependency (WebDAV) + * @SOUP_STATUS_INTERNAL_SERVER_ERROR: 500 Internal Server Error + * (HTTP) + * @SOUP_STATUS_NOT_IMPLEMENTED: 501 Not Implemented (HTTP) + * @SOUP_STATUS_BAD_GATEWAY: 502 Bad Gateway (HTTP) + * @SOUP_STATUS_SERVICE_UNAVAILABLE: 503 Service Unavailable (HTTP) + * @SOUP_STATUS_GATEWAY_TIMEOUT: 504 Gateway Timeout (HTTP) + * @SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505 HTTP Version Not + * Supported (HTTP) + * @SOUP_STATUS_INSUFFICIENT_STORAGE: 507 Insufficient Storage + * (WebDAV) + * @SOUP_STATUS_NOT_EXTENDED: 510 Not Extended (RFC 2774) + * + * These represent the known HTTP status code values, plus various + * network and internal errors. + **/ + static struct { guint code; const char *phrase; @@ -90,6 +229,10 @@ static struct { * * Looks up the stock HTTP description of @status_code. * + * You should not need to use this; if you are interested in the + * textual description for the %status_code of a given #SoupMessage, + * just look at the message's %reason_phrase. + * * Return value: the (English) description of @status_code **/ const char * @@ -104,3 +247,13 @@ soup_status_get_phrase (guint status_code) return "Unknown Error"; } + + +GQuark +soup_http_error_quark (void) +{ + static GQuark error; + if (!error) + error = g_quark_from_static_string ("soup_http_error_quark"); + return error; +} diff --git a/libsoup/soup-status.h b/libsoup/soup-status.h index 507d690..983250b 100644 --- a/libsoup/soup-status.h +++ b/libsoup/soup-status.h @@ -12,76 +12,6 @@ G_BEGIN_DECLS -/** - * SoupStatusClass: - * @SOUP_STATUS_CLASS_TRANSPORT_ERROR: Network or Soup-level error - * @SOUP_STATUS_CLASS_INFORMATIONAL: HTTP 1xx response providing - * partial information about the state of a request - * @SOUP_STATUS_CLASS_SUCCESS: HTTP 2xx successful response - * @SOUP_STATUS_CLASS_REDIRECT: HTTP 3xx redirection response - * @SOUP_STATUS_CLASS_CLIENT_ERROR: HTTP 4xx client error response - * @SOUP_STATUS_CLASS_SERVER_ERROR: HTTP 5xx server error response - * - * The classes of HTTP and Soup status codes - **/ -typedef enum { - SOUP_STATUS_CLASS_TRANSPORT_ERROR = 0, - SOUP_STATUS_CLASS_INFORMATIONAL, - SOUP_STATUS_CLASS_SUCCESS, - SOUP_STATUS_CLASS_REDIRECT, - SOUP_STATUS_CLASS_CLIENT_ERROR, - SOUP_STATUS_CLASS_SERVER_ERROR -} SoupStatusClass; - -/** - * SOUP_STATUS_IS_TRANSPORT_ERROR: - * @status: a status code - * - * Tests if @status is a libsoup transport error. - * - * Return value: %TRUE or %FALSE - **/ -/** - * SOUP_STATUS_IS_INFORMATIONAL: - * @status: an HTTP status code - * - * Tests if @status is an Informational (1xx) response. - * - * Return value: %TRUE or %FALSE - **/ -/** - * SOUP_STATUS_IS_SUCCESSFUL: - * @status: an HTTP status code - * - * Tests if @status is a Successful (2xx) response. - * - * Return value: %TRUE or %FALSE - **/ -/** - * SOUP_STATUS_IS_REDIRECTION: - * @status: an HTTP status code - * - * Tests if @status is a Redirection (3xx) response. - * - * Return value: %TRUE or %FALSE - **/ -/** - * SOUP_STATUS_IS_CLIENT_ERROR: - * @status: an HTTP status code - * - * Tests if @status is a Client Error (4xx) response. - * - * Return value: %TRUE or %FALSE - **/ -/** - * SOUP_STATUS_IS_SERVER_ERROR: - * @status: an HTTP status code - * - * Tests if @status is a Server Error (5xx) response. - * - * Return value: %TRUE or %FALSE - **/ - #define SOUP_STATUS_IS_TRANSPORT_ERROR(status) ((status) > 0 && (status) < 100) #define SOUP_STATUS_IS_INFORMATIONAL(status) ((status) >= 100 && (status) < 200) #define SOUP_STATUS_IS_SUCCESSFUL(status) ((status) >= 200 && (status) < 300) @@ -89,89 +19,6 @@ typedef enum { #define SOUP_STATUS_IS_CLIENT_ERROR(status) ((status) >= 400 && (status) < 500) #define SOUP_STATUS_IS_SERVER_ERROR(status) ((status) >= 500 && (status) < 600) -/** - * SoupKnownStatusCode: - * @SOUP_STATUS_NONE: No status available. (Eg, the message has not - * been sent yet) - * @SOUP_STATUS_CANCELLED: Message was cancelled locally - * @SOUP_STATUS_CANT_RESOLVE: Unable to resolve destination host name - * @SOUP_STATUS_CANT_RESOLVE_PROXY: Unable to resolve proxy host name - * @SOUP_STATUS_CANT_CONNECT: Unable to connect to remote host - * @SOUP_STATUS_CANT_CONNECT_PROXY: Unable to connect to proxy - * @SOUP_STATUS_SSL_FAILED: SSL negotiation failed - * @SOUP_STATUS_IO_ERROR: A network error occurred, or the other end - * closed the connection unexpectedly - * @SOUP_STATUS_MALFORMED: Malformed data (usually a programmer error) - * @SOUP_STATUS_TRY_AGAIN: Try again. (Only returned in certain - * specifically documented cases) - * @SOUP_STATUS_CONTINUE: 100 Continue (HTTP) - * @SOUP_STATUS_SWITCHING_PROTOCOLS: 101 Switching Protocols (HTTP) - * @SOUP_STATUS_PROCESSING: 102 Processing (WebDAV) - * @SOUP_STATUS_OK: 200 Success (HTTP). Also used by many lower-level - * soup routines to indicate success. - * @SOUP_STATUS_CREATED: 201 Created (HTTP) - * @SOUP_STATUS_ACCEPTED: 202 Accepted (HTTP) - * @SOUP_STATUS_NON_AUTHORITATIVE: 203 Non-Authoritative Information - * (HTTP) - * @SOUP_STATUS_NO_CONTENT: 204 No Content (HTTP) - * @SOUP_STATUS_RESET_CONTENT: 205 Reset Content (HTTP) - * @SOUP_STATUS_PARTIAL_CONTENT: 206 Partial Content (HTTP) - * @SOUP_STATUS_MULTI_STATUS: 207 Multi-Status (WebDAV) - * @SOUP_STATUS_MULTIPLE_CHOICES: 300 Multiple Choices (HTTP) - * @SOUP_STATUS_MOVED_PERMANENTLY: 301 Moved Permanently (HTTP) - * @SOUP_STATUS_FOUND: 302 Found (HTTP) - * @SOUP_STATUS_MOVED_TEMPORARILY: 302 Moved Temporarily (old name, - * RFC 2068) - * @SOUP_STATUS_SEE_OTHER: 303 See Other (HTTP) - * @SOUP_STATUS_NOT_MODIFIED: 304 Not Modified (HTTP) - * @SOUP_STATUS_USE_PROXY: 305 Use Proxy (HTTP) - * @SOUP_STATUS_NOT_APPEARING_IN_THIS_PROTOCOL: 306 [Unused] (HTTP) - * @SOUP_STATUS_TEMPORARY_REDIRECT: 307 Temporary Redirect (HTTP) - * @SOUP_STATUS_BAD_REQUEST: 400 Bad Request (HTTP) - * @SOUP_STATUS_UNAUTHORIZED: 401 Unauthorized (HTTP) - * @SOUP_STATUS_PAYMENT_REQUIRED: 402 Payment Required (HTTP) - * @SOUP_STATUS_FORBIDDEN: 403 Forbidden (HTTP) - * @SOUP_STATUS_NOT_FOUND: 404 Not Found (HTTP) - * @SOUP_STATUS_METHOD_NOT_ALLOWED: 405 Method Not Allowed (HTTP) - * @SOUP_STATUS_NOT_ACCEPTABLE: 406 Not Acceptable (HTTP) - * @SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407 Proxy Authentication - * Required (HTTP) - * @SOUP_STATUS_PROXY_UNAUTHORIZED: shorter alias for - * %SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED - * @SOUP_STATUS_REQUEST_TIMEOUT: 408 Request Timeout (HTTP) - * @SOUP_STATUS_CONFLICT: 409 Conflict (HTTP) - * @SOUP_STATUS_GONE: 410 Gone (HTTP) - * @SOUP_STATUS_LENGTH_REQUIRED: 411 Length Required (HTTP) - * @SOUP_STATUS_PRECONDITION_FAILED: 412 Precondition Failed (HTTP) - * @SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE: 413 Request Entity Too Large - * (HTTP) - * @SOUP_STATUS_REQUEST_URI_TOO_LONG: 414 Request-URI Too Long (HTTP) - * @SOUP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415 Unsupported Media Type - * (HTTP) - * @SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE: 416 Requested Range - * Not Satisfiable (HTTP) - * @SOUP_STATUS_INVALID_RANGE: shorter alias for - * %SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE - * @SOUP_STATUS_EXPECTATION_FAILED: 417 Expectation Failed (HTTP) - * @SOUP_STATUS_UNPROCESSABLE_ENTITY: 422 Unprocessable Entity - * (WebDAV) - * @SOUP_STATUS_LOCKED: 423 Locked (WebDAV) - * @SOUP_STATUS_FAILED_DEPENDENCY: 424 Failed Dependency (WebDAV) - * @SOUP_STATUS_INTERNAL_SERVER_ERROR: 500 Internal Server Error - * (HTTP) - * @SOUP_STATUS_NOT_IMPLEMENTED: 501 Not Implemented (HTTP) - * @SOUP_STATUS_BAD_GATEWAY: 502 Bad Gateway (HTTP) - * @SOUP_STATUS_SERVICE_UNAVAILABLE: 503 Service Unavailable (HTTP) - * @SOUP_STATUS_GATEWAY_TIMEOUT: 504 Gateway Timeout (HTTP) - * @SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505 HTTP Version Not - * Supported (HTTP) - * @SOUP_STATUS_INSUFFICIENT_STORAGE: 507 Insufficient Storage - * (WebDAV) - * @SOUP_STATUS_NOT_EXTENDED: 510 Not Extended (RFC 2774) - * - * These represent the known HTTP status code values, plus various - * network and internal errors. - **/ typedef enum { SOUP_STATUS_NONE, @@ -246,6 +93,9 @@ typedef enum { const char *soup_status_get_phrase (guint status_code); +#define SOUP_HTTP_ERROR soup_http_error_quark() +GQuark soup_http_error_quark (void); + G_END_DECLS #endif /* SOUP_STATUS_H */ diff --git a/libsoup/soup-types.h b/libsoup/soup-types.h index fa5a400..951dd96 100644 --- a/libsoup/soup-types.h +++ b/libsoup/soup-types.h @@ -14,42 +14,15 @@ G_BEGIN_DECLS typedef struct SoupAddress SoupAddress; -typedef struct SoupConnection SoupConnection; +typedef struct SoupAuth SoupAuth; +typedef struct SoupAuthDomain SoupAuthDomain; typedef struct SoupMessage SoupMessage; -typedef struct SoupMessageFilter SoupMessageFilter; typedef struct SoupServer SoupServer; -typedef union SoupServerAuth SoupServerAuth; -typedef struct SoupServerAuthContext SoupServerAuthContext; -typedef struct SoupServerMessage SoupServerMessage; typedef struct SoupSession SoupSession; typedef struct SoupSessionAsync SoupSessionAsync; typedef struct SoupSessionSync SoupSessionSync; typedef struct SoupSocket SoupSocket; -typedef struct SoupUri SoupUri; - -#define SOUP_MAKE_INTERFACE(type_name,TypeName,base_init) \ -GType type_name##_get_type(void)\ -{\ - static GType type = 0; \ - if (!type){ \ - static GTypeInfo const object_info = { \ - sizeof (TypeName##Class), \ - \ - (GBaseInitFunc) base_init, \ - (GBaseFinalizeFunc) NULL, \ - \ - (GClassInitFunc) NULL, \ - (GClassFinalizeFunc) NULL, \ - NULL, /* class_data */ \ - \ - 0, \ - 0, /* n_preallocs */ \ - (GInstanceInitFunc) NULL, \ - }; \ - type = g_type_register_static (G_TYPE_INTERFACE, #TypeName, &object_info, 0); \ - } \ - return type; \ -} +typedef struct SoupURI SoupURI; G_END_DECLS diff --git a/libsoup/soup-uri.c b/libsoup/soup-uri.c index ecbf6a9..b9569c7 100644 --- a/libsoup/soup-uri.c +++ b/libsoup/soup-uri.c @@ -10,33 +10,106 @@ #include #include "soup-uri.h" +#include "soup-form.h" +#include "soup-misc.h" -static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars); +/** + * SECTION:soup-uri + * @short_description: URIs + * + * A #SoupURI represents a (parsed) URI. + * + * Many applications will not need to use #SoupURI directly at all; on + * the client side, soup_message_new() takes a stringified URI, and on + * the server side, the path and query components are provided for you + * in the server callback. + **/ -static inline SoupProtocol -soup_uri_get_protocol (const char *proto, int len) -{ - char proto_buf[128]; +/** + * SoupURI: + * @scheme: the URI scheme (eg, "http") + * @user: a username, or %NULL + * @password: a password, or %NULL + * @host: the hostname or IP address + * @port: the port number on @host + * @path: the path on @host + * @query: a query for @path, or %NULL + * @fragment: a fragment identifier within @path, or %NULL + * + * A #SoupURI represents a (parsed) URI. #SoupURI supports RFC 3986 + * (URI Generic Syntax), and can parse any valid URI. However, libsoup + * only uses "http" and "https" URIs internally. + * + * @scheme will always be set in any URI. It is an interned string and + * is always all lowercase. (If you parse a URI with a non-lowercase + * scheme, it will be converted to lowercase.) The macros + * %SOUP_URI_SCHEME_HTTP and %SOUP_URI_SCHEME_HTTPS provide the + * interned values for "http" and "https" and can be compared against + * URI @scheme values. + * + * @user and @password are parsed as defined in the older URI specs + * (ie, separated by a colon; RFC 3986 only talks about a single + * "userinfo" field). Note that @password is not included in the + * output of soup_uri_to_string(). libsoup does not normally use these + * fields; authentication is handled via #SoupSession signals. + * + * @host contains the hostname, and @port the port specified in the + * URI. If the URI doesn't contain a hostname, @host will be %NULL, + * and if it doesn't specify a port, @port may be 0. However, for + * "http" and "https" URIs, @host is guaranteed to be non-%NULL + * (trying to parse an http URI with no @host will return %NULL), and + * @port will always be non-0 (because libsoup knows the default value + * to use when it is not specified in the URI). + * + * @path is always non-%NULL. For http/https URIs, @path will never be + * an empty string either; if the input URI has no path, the parsed + * #SoupURI will have a @path of "/". + * + * @query and @fragment are optional for all URI types. + * soup_form_decode_urlencoded() may be useful for parsing @query. + * + * Note that @path, @query, and @fragment may contain + * %-encoded characters. soup_uri_new() calls + * soup_uri_normalize() on them, but not soup_uri_decode(). This is + * necessary to ensure that soup_uri_to_string() will generate a URI + * that has exactly the same meaning as the original. (In theory, + * #SoupURI should leave @user, @password, and @host partially-encoded + * as well, but this would be more annoying than useful.) + **/ - g_return_val_if_fail (len < sizeof (proto_buf) - 1, 0); +static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars); +static char *uri_decoded_copy (const char *str, int length); +static char *uri_normalized_copy (const char *str, int length, const char *unescape_extra); - memcpy (proto_buf, proto, len); - proto_buf[len] = '\0'; - return g_quark_from_string (proto_buf); -} +static const char *http_scheme, *https_scheme; static inline const char * -soup_protocol_name (SoupProtocol proto) +soup_uri_get_scheme (const char *scheme, int len) { - return g_quark_to_string (proto); + if (len == 4 && !strncmp (scheme, "http", 4)) { + if (G_UNLIKELY (!http_scheme)) + http_scheme = g_intern_static_string ("http"); + return http_scheme; + } else if (len == 5 && !strncmp (scheme, "https", 5)) { + if (G_UNLIKELY (!https_scheme)) + https_scheme = g_intern_static_string ("https"); + return https_scheme; + } else { + char *lower_scheme; + + lower_scheme = g_ascii_strdown (scheme, len); + scheme = g_intern_string (lower_scheme); + g_free (lower_scheme); + return scheme; + } } static inline guint -soup_protocol_default_port (SoupProtocol proto) +soup_scheme_default_port (const char *scheme) { - if (proto == SOUP_PROTOCOL_HTTP) + if (scheme == http_scheme) return 80; - else if (proto == SOUP_PROTOCOL_HTTPS) + else if (scheme == https_scheme) return 443; else return 0; @@ -49,38 +122,43 @@ soup_protocol_default_port (SoupProtocol proto) * * Parses @uri_string relative to @base. * - * Return value: a parsed #SoupUri. + * Return value: a parsed #SoupURI. **/ -SoupUri * -soup_uri_new_with_base (const SoupUri *base, const char *uri_string) +SoupURI * +soup_uri_new_with_base (SoupURI *base, const char *uri_string) { - SoupUri *uri; - const char *end, *hash, *colon, *at, *slash, *question; - const char *p; + SoupURI *uri; + const char *end, *hash, *colon, *at, *path, *question; + const char *p, *hostend; + gboolean remove_dot_segments = TRUE; - uri = g_new0 (SoupUri, 1); + uri = g_slice_new0 (SoupURI); - /* See RFC2396 for details. IF YOU CHANGE ANYTHING IN THIS + /* See RFC 3986 for details. IF YOU CHANGE ANYTHING IN THIS * FUNCTION, RUN tests/uri-parsing AFTERWARDS. */ /* Find fragment. */ end = hash = strchr (uri_string, '#'); if (hash && hash[1]) { - uri->fragment = g_strdup (hash + 1); - soup_uri_decode (uri->fragment); + uri->fragment = uri_normalized_copy (hash + 1, strlen (hash + 1), + NULL); + if (!uri->fragment) { + soup_uri_free (uri); + return NULL; + } } else end = uri_string + strlen (uri_string); - /* Find protocol: initial [a-z+.-]* substring until ":" */ + /* Find scheme: initial [a-z+.-]* substring until ":" */ p = uri_string; - while (p < end && (isalnum ((unsigned char)*p) || + while (p < end && (g_ascii_isalnum (*p) || *p == '.' || *p == '+' || *p == '-')) p++; if (p > uri_string && *p == ':') { - uri->protocol = soup_uri_get_protocol (uri_string, p - uri_string); - if (!uri->protocol) { + uri->scheme = soup_uri_get_scheme (uri_string, p - uri_string); + if (!uri->scheme) { soup_uri_free (uri); return NULL; } @@ -94,75 +172,108 @@ soup_uri_new_with_base (const SoupUri *base, const char *uri_string) if (strncmp (uri_string, "//", 2) == 0) { uri_string += 2; - slash = uri_string + strcspn (uri_string, "/#"); + path = uri_string + strcspn (uri_string, "/?#"); at = strchr (uri_string, '@'); - if (at && at < slash) { + if (at && at < path) { colon = strchr (uri_string, ':'); if (colon && colon < at) { - uri->passwd = g_strndup (colon + 1, - at - colon - 1); - soup_uri_decode (uri->passwd); + uri->password = uri_decoded_copy (colon + 1, + at - colon - 1); + if (!uri->password) { + soup_uri_free (uri); + return NULL; + } } else { - uri->passwd = NULL; + uri->password = NULL; colon = at; } - uri->user = g_strndup (uri_string, colon - uri_string); - soup_uri_decode (uri->user); + uri->user = uri_decoded_copy (uri_string, + colon - uri_string); + if (!uri->user) { + soup_uri_free (uri); + return NULL; + } uri_string = at + 1; } else - uri->user = uri->passwd = NULL; + uri->user = uri->password = NULL; /* Find host and port. */ - colon = strchr (uri_string, ':'); - if (colon && colon < slash) { - uri->host = g_strndup (uri_string, colon - uri_string); - uri->port = strtoul (colon + 1, NULL, 10); + if (*uri_string == '[') { + uri_string++; + hostend = strchr (uri_string, ']'); + if (!hostend || hostend > path) { + soup_uri_free (uri); + return NULL; + } + if (*(hostend + 1) == ':') + colon = hostend + 1; + else + colon = NULL; } else { - uri->host = g_strndup (uri_string, slash - uri_string); - soup_uri_decode (uri->host); + colon = memchr (uri_string, ':', path - uri_string); + hostend = colon ? colon : path; } - uri_string = slash; + uri->host = uri_decoded_copy (uri_string, hostend - uri_string); + if (!uri->host) { + soup_uri_free (uri); + return NULL; + } + + if (colon && colon != path - 1) { + char *portend; + uri->port = strtoul (colon + 1, &portend, 10); + if (portend != (char *)path) { + soup_uri_free (uri); + return NULL; + } + } + + uri_string = path; } /* Find query */ question = memchr (uri_string, '?', end - uri_string); if (question) { if (question[1]) { - uri->query = g_strndup (question + 1, - end - (question + 1)); - soup_uri_decode (uri->query); + uri->query = uri_normalized_copy (question + 1, + end - (question + 1), + NULL); + if (!uri->query) { + soup_uri_free (uri); + return NULL; + } } end = question; } if (end != uri_string) { - uri->path = g_strndup (uri_string, end - uri_string); - soup_uri_decode (uri->path); + uri->path = uri_normalized_copy (uri_string, end - uri_string, + NULL); + if (!uri->path) { + soup_uri_free (uri); + return NULL; + } } - /* Apply base URI. Again, this is spelled out in RFC 2396. */ - if (base && !uri->protocol && uri->host) - uri->protocol = base->protocol; - else if (base && !uri->protocol) { - uri->protocol = base->protocol; + /* Apply base URI. Again, this is spelled out in RFC 3986. */ + if (base && !uri->scheme && uri->host) + uri->scheme = base->scheme; + else if (base && !uri->scheme) { + uri->scheme = base->scheme; uri->user = g_strdup (base->user); - uri->passwd = g_strdup (base->passwd); + uri->password = g_strdup (base->password); uri->host = g_strdup (base->host); uri->port = base->port; if (!uri->path) { - if (uri->query) - uri->path = g_strdup (""); - else { - uri->path = g_strdup (base->path); + uri->path = g_strdup (base->path); + if (!uri->query) uri->query = g_strdup (base->query); - } - } - - if (*uri->path != '/') { - char *newpath, *last, *p, *q; + remove_dot_segments = FALSE; + } else if (*uri->path != '/') { + char *newpath, *last; last = strrchr (base->path, '/'); if (last) { @@ -173,58 +284,72 @@ soup_uri_new_with_base (const SoupUri *base, const char *uri_string) } else newpath = g_strdup_printf ("/%s", uri->path); - /* Remove "./" where "." is a complete segment. */ - for (p = newpath + 1; *p; ) { - if (*(p - 1) == '/' && - *p == '.' && *(p + 1) == '/') - memmove (p, p + 2, strlen (p + 2) + 1); - else - p++; - } - /* Remove "." at end. */ - if (p > newpath + 2 && - *(p - 1) == '.' && *(p - 2) == '/') - *(p - 1) = '\0'; - /* Remove "/../" where != ".." */ - for (p = newpath + 1; *p; ) { - if (!strncmp (p, "../", 3)) { - p += 3; - continue; - } - q = strchr (p + 1, '/'); - if (!q) - break; - if (strncmp (q, "/../", 4) != 0) { - p = q + 1; - continue; - } - memmove (p, q + 4, strlen (q + 4) + 1); - p = newpath + 1; - } - /* Remove "/.." at end where != ".." */ - q = strrchr (newpath, '/'); - if (q && !strcmp (q, "/..")) { - p = q - 1; - while (p > newpath && *p != '/') - p--; - if (strncmp (p, "/../", 4) != 0) - *(p + 1) = 0; - } - g_free (uri->path); uri->path = newpath; } } - /* Sanity check */ - if ((uri->protocol == SOUP_PROTOCOL_HTTP || - uri->protocol == SOUP_PROTOCOL_HTTPS) && !uri->host) { - soup_uri_free (uri); - return NULL; + if (remove_dot_segments && uri->path && *uri->path) { + char *p = uri->path, *q; + + /* Remove "./" where "." is a complete segment. */ + for (p = uri->path + 1; *p; ) { + if (*(p - 1) == '/' && + *p == '.' && *(p + 1) == '/') + memmove (p, p + 2, strlen (p + 2) + 1); + else + p++; + } + /* Remove "." at end. */ + if (p > uri->path + 2 && + *(p - 1) == '.' && *(p - 2) == '/') + *(p - 1) = '\0'; + + /* Remove "/../" where != ".." */ + for (p = uri->path + 1; *p; ) { + if (!strncmp (p, "../", 3)) { + p += 3; + continue; + } + q = strchr (p + 1, '/'); + if (!q) + break; + if (strncmp (q, "/../", 4) != 0) { + p = q + 1; + continue; + } + memmove (p, q + 4, strlen (q + 4) + 1); + p = uri->path + 1; + } + /* Remove "/.." at end where != ".." */ + q = strrchr (uri->path, '/'); + if (q && !strcmp (q, "/..")) { + p = q - 1; + while (p > uri->path && *p != '/') + p--; + if (strncmp (p, "/../", 4) != 0) + *(p + 1) = 0; + } + + /* Remove extraneous initial "/.."s */ + while (!strncmp (uri->path, "/../", 4)) + memmove (uri->path, uri->path + 3, strlen (uri->path) - 2); + if (!strcmp (uri->path, "/..")) + uri->path[1] = '\0'; + } + + /* HTTP-specific stuff */ + if (uri->scheme == http_scheme || uri->scheme == https_scheme) { + if (!uri->host) { + soup_uri_free (uri); + return NULL; + } + if (!uri->path) + uri->path = g_strdup ("/"); } if (!uri->port) - uri->port = soup_protocol_default_port (uri->protocol); + uri->port = soup_scheme_default_port (uri->scheme); if (!uri->path) uri->path = g_strdup (""); @@ -237,17 +362,23 @@ soup_uri_new_with_base (const SoupUri *base, const char *uri_string) * * Parses an absolute URI. * - * Return value: a #SoupUri, or %NULL. + * You can also pass %NULL for @uri_string if you want to get back an + * "empty" #SoupURI that you can fill in by hand. + * + * Return value: a #SoupURI, or %NULL. **/ -SoupUri * +SoupURI * soup_uri_new (const char *uri_string) { - SoupUri *uri; + SoupURI *uri; + + if (!uri_string) + return g_slice_new0 (SoupURI); uri = soup_uri_new_with_base (NULL, uri_string); if (!uri) return NULL; - if (!uri->protocol) { + if (!uri->scheme) { soup_uri_free (uri); return NULL; } @@ -256,31 +387,24 @@ soup_uri_new (const char *uri_string) } -static inline void -append_uri (GString *str, const char *in, const char *extra_enc_chars, - gboolean pre_encoded) -{ - if (pre_encoded) - g_string_append (str, in); - else - append_uri_encoded (str, in, extra_enc_chars); -} - /** * soup_uri_to_string: - * @uri: a #SoupUri - * @just_path: if %TRUE, output just the path and query portions + * @uri: a #SoupURI + * @just_path_and_query: if %TRUE, output just the path and query portions * * Returns a string representing @uri. * + * If @just_path_and_query is %TRUE, this concatenates the path and query + * together. That is, it constructs the string that would be needed in + * the Request-Line of an HTTP request for @uri. + * * Return value: a string representing @uri, which the caller must free. **/ char * -soup_uri_to_string (const SoupUri *uri, gboolean just_path) +soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query) { GString *str; char *return_result; - gboolean pre_encoded = uri->broken_encoding; /* IF YOU CHANGE ANYTHING IN THIS FUNCTION, RUN * tests/uri-parsing AFTERWARD. @@ -288,33 +412,36 @@ soup_uri_to_string (const SoupUri *uri, gboolean just_path) str = g_string_sized_new (20); - if (uri->protocol && !just_path) - g_string_sprintfa (str, "%s:", soup_protocol_name (uri->protocol)); - if (uri->host && !just_path) { + if (uri->scheme && !just_path_and_query) + g_string_sprintfa (str, "%s:", uri->scheme); + if (uri->host && !just_path_and_query) { g_string_append (str, "//"); if (uri->user) { - append_uri (str, uri->user, ":;@/", pre_encoded); + append_uri_encoded (str, uri->user, ":;@?/"); g_string_append_c (str, '@'); } - append_uri (str, uri->host, ":/", pre_encoded); - if (uri->port && uri->port != soup_protocol_default_port (uri->protocol)) + if (strchr (uri->host, ':')) { + g_string_append_c (str, '['); + g_string_append (str, uri->host); + g_string_append_c (str, ']'); + } else + append_uri_encoded (str, uri->host, ":/"); + if (uri->port && uri->port != soup_scheme_default_port (uri->scheme)) g_string_append_printf (str, ":%d", uri->port); if (!uri->path && (uri->query || uri->fragment)) g_string_append_c (str, '/'); } if (uri->path && *uri->path) - append_uri (str, uri->path, "?", pre_encoded); - else if (just_path) - g_string_append_c (str, '/'); + g_string_append (str, uri->path); if (uri->query) { g_string_append_c (str, '?'); - append_uri (str, uri->query, NULL, pre_encoded); + g_string_append (str, uri->query); } - if (uri->fragment && !just_path) { + if (uri->fragment && !just_path_and_query) { g_string_append_c (str, '#'); - append_uri (str, uri->fragment, NULL, pre_encoded); + g_string_append (str, uri->fragment); } return_result = str->str; @@ -325,89 +452,105 @@ soup_uri_to_string (const SoupUri *uri, gboolean just_path) /** * soup_uri_copy: - * @uri: a #SoupUri + * @uri: a #SoupURI * * Copies @uri * * Return value: a copy of @uri, which must be freed with soup_uri_free() **/ -SoupUri * -soup_uri_copy (const SoupUri *uri) +SoupURI * +soup_uri_copy (SoupURI *uri) { - SoupUri *dup; + SoupURI *dup; g_return_val_if_fail (uri != NULL, NULL); - dup = g_new0 (SoupUri, 1); - dup->protocol = uri->protocol; + dup = g_slice_new0 (SoupURI); + dup->scheme = uri->scheme; dup->user = g_strdup (uri->user); - dup->passwd = g_strdup (uri->passwd); + dup->password = g_strdup (uri->password); dup->host = g_strdup (uri->host); dup->port = uri->port; dup->path = g_strdup (uri->path); dup->query = g_strdup (uri->query); dup->fragment = g_strdup (uri->fragment); - dup->broken_encoding = uri->broken_encoding; - return dup; } -/** - * soup_uri_copy_root: - * @uri: a #SoupUri - * - * Copies the protocol, host, and port of @uri into a new #SoupUri - * (all other fields in the new URI will be empty.) - * - * Return value: a partial copy of @uri, which must be freed with - * soup_uri_free() - **/ -SoupUri * -soup_uri_copy_root (const SoupUri *uri) +/* Temporarily still used by SoupSession, but no longer public */ +SoupURI *soup_uri_copy_root (SoupURI *uri); +gboolean soup_uri_host_equal (gconstpointer v1, gconstpointer v2); +guint soup_uri_host_hash (gconstpointer key); + +SoupURI * +soup_uri_copy_root (SoupURI *uri) { - SoupUri *dup; + SoupURI *dup; g_return_val_if_fail (uri != NULL, NULL); - dup = g_new0 (SoupUri, 1); - dup->protocol = uri->protocol; - dup->host = g_strdup (uri->host); - dup->port = uri->port; + dup = g_slice_new0 (SoupURI); + dup->scheme = uri->scheme; + dup->host = g_strdup (uri->host); + dup->port = uri->port; return dup; } +guint +soup_uri_host_hash (gconstpointer key) +{ + const SoupURI *uri = key; + + return GPOINTER_TO_UINT (uri->scheme) + uri->port + + soup_str_case_hash (uri->host); +} + +gboolean +soup_uri_host_equal (gconstpointer v1, gconstpointer v2) +{ + const SoupURI *one = v1; + const SoupURI *two = v2; + + if (one->scheme != two->scheme) + return FALSE; + if (one->port != two->port) + return FALSE; + + return g_ascii_strcasecmp (one->host, two->host) == 0; +} + static inline gboolean -parts_equal (const char *one, const char *two) +parts_equal (const char *one, const char *two, gboolean insensitive) { if (!one && !two) return TRUE; if (!one || !two) return FALSE; - return !strcmp (one, two); + return insensitive ? !g_ascii_strcasecmp (one, two) : !strcmp (one, two); } /** * soup_uri_equal: - * @uri1: a #SoupUri - * @uri2: another #SoupUri + * @uri1: a #SoupURI + * @uri2: another #SoupURI * * Tests whether or not @uri1 and @uri2 are equal in all parts * * Return value: %TRUE or %FALSE **/ gboolean -soup_uri_equal (const SoupUri *uri1, const SoupUri *uri2) +soup_uri_equal (SoupURI *uri1, SoupURI *uri2) { - if (uri1->protocol != uri2->protocol || - uri1->port != uri2->port || - !parts_equal (uri1->user, uri2->user) || - !parts_equal (uri1->passwd, uri2->passwd) || - !parts_equal (uri1->host, uri2->host) || - !parts_equal (uri1->path, uri2->path) || - !parts_equal (uri1->query, uri2->query) || - !parts_equal (uri1->fragment, uri2->fragment)) + if (uri1->scheme != uri2->scheme || + uri1->port != uri2->port || + !parts_equal (uri1->user, uri2->user, FALSE) || + !parts_equal (uri1->password, uri2->password, FALSE) || + !parts_equal (uri1->host, uri2->host, TRUE) || + !parts_equal (uri1->path, uri2->path, FALSE) || + !parts_equal (uri1->query, uri2->query, FALSE) || + !parts_equal (uri1->fragment, uri2->fragment, FALSE)) return FALSE; return TRUE; @@ -415,35 +558,39 @@ soup_uri_equal (const SoupUri *uri1, const SoupUri *uri2) /** * soup_uri_free: - * @uri: a #SoupUri + * @uri: a #SoupURI * * Frees @uri. **/ void -soup_uri_free (SoupUri *uri) +soup_uri_free (SoupURI *uri) { g_return_if_fail (uri != NULL); g_free (uri->user); - g_free (uri->passwd); + g_free (uri->password); g_free (uri->host); g_free (uri->path); g_free (uri->query); g_free (uri->fragment); - g_free (uri); + g_slice_free (SoupURI, uri); } -/* From RFC 2396 2.4.3, the characters that should always be encoded */ +/* From RFC 3986 */ +#define SOUP_URI_UNRESERVED 0 +#define SOUP_URI_PCT_ENCODED 1 +#define SOUP_URI_GEN_DELIMS 2 +#define SOUP_URI_SUB_DELIMS 4 static const char uri_encoded_char[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00 - 0x0f */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10 - 0x1f */ - 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ' ' - '/' */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, /* '0' - '?' */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* '@' - 'O' */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, /* 'P' - '_' */ - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* '`' - 'o' */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* 'p' - 0x7f */ + 1, 4, 1, 2, 4, 1, 4, 4, 4, 4, 4, 4, 4, 0, 0, 2, /* !"#$%&'()*+,-./ */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 1, 4, 1, 2, /* 0123456789:;<=>? */ + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* @ABCDEFGHIJKLMNO */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 2, 1, 0, /* PQRSTUVWXYZ[\]^_ */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* `abcdefghijklmno */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* pqrstuvwxyz{|}~ */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -460,9 +607,9 @@ append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars) const unsigned char *s = (const unsigned char *)in; while (*s) { - if (uri_encoded_char[*s] || + if ((uri_encoded_char[*s] & (SOUP_URI_PCT_ENCODED | SOUP_URI_GEN_DELIMS)) || (extra_enc_chars && strchr (extra_enc_chars, *s))) - g_string_append_printf (str, "%%%02x", (int)*s++); + g_string_append_printf (str, "%%%02X", (int)*s++); else g_string_append_c (str, *s++); } @@ -471,11 +618,11 @@ append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars) /** * soup_uri_encode: * @part: a URI part - * @escape_extra: additional characters beyond " \"%#<>{}|\^[]`" - * to escape (or %NULL) + * @escape_extra: additional reserved characters to escape (or %NULL) * - * This %-encodes the given URI part and returns the escaped version - * in allocated memory, which the caller must free when it is done. + * This %-encodes the given URI part and returns the escaped + * version in allocated memory, which the caller must free when it is + * done. * * Return value: the encoded URI part **/ @@ -493,42 +640,284 @@ soup_uri_encode (const char *part, const char *escape_extra) return encoded; } +#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10) +#define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2])) + +char * +uri_decoded_copy (const char *part, int length) +{ + unsigned char *s, *d; + char *decoded = g_strndup (part, length); + + s = d = (unsigned char *)decoded; + do { + if (*s == '%') { + if (!g_ascii_isxdigit (s[1]) || + !g_ascii_isxdigit (s[2])) { + g_free (decoded); + return NULL; + } + *d++ = HEXCHAR (s); + s += 2; + } else + *d++ = *s; + } while (*s++); + + return decoded; +} + /** * soup_uri_decode: * @part: a URI part * - * %-decodes the passed-in URI *in place*. The decoded version is - * never longer than the encoded version, so there does not need to - * be any additional space at the end of the string. + * Fully %-decodes @part. + * + * Return value: the decoded URI part, or %NULL if an invalid percent + * code was encountered. */ -void -soup_uri_decode (char *part) +char * +soup_uri_decode (const char *part) { - unsigned char *s, *d; + return uri_decoded_copy (part, strlen (part)); +} -#define XDIGIT(c) ((c) <= '9' ? (c) - '0' : ((c) & 0x4F) - 'A' + 10) +char * +uri_normalized_copy (const char *part, int length, const char *unescape_extra) +{ + unsigned char *s, *d, c; + char *normalized = g_strndup (part, length); - s = d = (unsigned char *)part; + s = d = (unsigned char *)normalized; do { - if (*s == '%' && s[1] && s[2]) { - *d++ = (XDIGIT (s[1]) << 4) + XDIGIT (s[2]); - s += 2; + if (*s == '%') { + if (!g_ascii_isxdigit (s[1]) || + !g_ascii_isxdigit (s[2])) { + g_free (normalized); + return NULL; + } + + c = HEXCHAR (s); + if (uri_encoded_char[c] == SOUP_URI_UNRESERVED || + (unescape_extra && strchr (unescape_extra, c))) { + *d++ = c; + s += 2; + } else { + *d++ = *s++; + *d++ = g_ascii_toupper (*s++); + *d++ = g_ascii_toupper (*s); + } } else *d++ = *s; } while (*s++); + + return normalized; } /** + * soup_uri_normalize: + * @part: a URI part + * @unescape_extra: reserved characters to unescape (or %NULL) + * + * %-decodes any "unreserved" characters (or characters in + * @unescape_extra) in @part. + * + * "Unreserved" characters are those that are not allowed to be used + * for punctuation according to the URI spec. For example, letters are + * unreserved, so soup_uri_normalize() will turn + * http://example.com/foo/b%61r into + * http://example.com/foo/bar, which is guaranteed + * to mean the same thing. However, "/" is "reserved", so + * http://example.com/foo%2Fbar would not + * be changed, because it might mean something different to the + * server. + * + * Return value: the normalized URI part, or %NULL if an invalid percent + * code was encountered. + */ +char * +soup_uri_normalize (const char *part, const char *unescape_extra) +{ + return uri_normalized_copy (part, strlen (part), unescape_extra); +} + + +/** * soup_uri_uses_default_port: - * @uri: a #SoupUri + * @uri: a #SoupURI * - * Tests if @uri uses the default port for its protocol. (Eg, 80 for - * http.) + * Tests if @uri uses the default port for its scheme. (Eg, 80 for + * http.) (This only works for http and https; libsoup does not know + * the default ports of other protocols.) * * Return value: %TRUE or %FALSE **/ gboolean -soup_uri_uses_default_port (const SoupUri *uri) +soup_uri_uses_default_port (SoupURI *uri) +{ + g_return_val_if_fail (uri->scheme == http_scheme || + uri->scheme == https_scheme, FALSE); + + return uri->port == soup_scheme_default_port (uri->scheme); +} + +/** + * SOUP_URI_SCHEME_HTTP: + * + * "http" as an interned string. This can be compared directly against + * the value of a #SoupURI's scheme + **/ + +/** + * SOUP_URI_SCHEME_HTTPS: + * + * "https" as an interned string. This can be compared directly + * against the value of a #SoupURI's scheme + **/ + +/** + * soup_uri_set_scheme: + * @uri: a #SoupURI + * @scheme: the URI scheme + * + * Sets @uri's scheme to @scheme. This will also set @uri's port to + * the default port for @scheme, if known. + **/ +void +soup_uri_set_scheme (SoupURI *uri, const char *scheme) +{ + uri->scheme = soup_uri_get_scheme (scheme, strlen (scheme)); + uri->port = soup_scheme_default_port (uri->scheme); +} + +/** + * soup_uri_set_user: + * @uri: a #SoupURI + * @user: the username, or %NULL + * + * Sets @uri's user to @user. + **/ +void +soup_uri_set_user (SoupURI *uri, const char *user) +{ + g_free (uri->user); + uri->user = g_strdup (user); +} + +/** + * soup_uri_set_password: + * @uri: a #SoupURI + * @password: the password, or %NULL + * + * Sets @uri's password to @password. + **/ +void +soup_uri_set_password (SoupURI *uri, const char *password) +{ + g_free (uri->password); + uri->password = g_strdup (password); +} + +/** + * soup_uri_set_host: + * @uri: a #SoupURI + * @host: the hostname or IP address, or %NULL + * + * Sets @uri's host to @host. + * + * If @host is an IPv6 IP address, it should not include the brackets + * required by the URI syntax; they will be added automatically when + * converting @uri to a string. + **/ +void +soup_uri_set_host (SoupURI *uri, const char *host) { - return uri->port == soup_protocol_default_port (uri->protocol); + g_free (uri->host); + uri->host = g_strdup (host); +} + +/** + * soup_uri_set_port: + * @uri: a #SoupURI + * @port: the port, or 0 + * + * Sets @uri's port to @port. If @port is 0, @uri will not have an + * explicitly-specified port. + **/ +void +soup_uri_set_port (SoupURI *uri, guint port) +{ + uri->port = port; +} + +/** + * soup_uri_set_path: + * @uri: a #SoupURI + * @path: the path + * + * Sets @uri's path to @path. + **/ +void +soup_uri_set_path (SoupURI *uri, const char *path) +{ + g_free (uri->path); + uri->path = g_strdup (path); +} + +/** + * soup_uri_set_query: + * @uri: a #SoupURI + * @query: the query + * + * Sets @uri's query to @query. + **/ +void +soup_uri_set_query (SoupURI *uri, const char *query) +{ + g_free (uri->query); + uri->query = g_strdup (query); +} + +/** + * soup_uri_set_query_from_form: + * @uri: a #SoupURI + * @form: a #GHashTable containing HTML form information + * + * Sets @uri's query to the result of encoding @form according to the + * HTML form rules. See soup_form_encode_urlencoded() for more + * information. + **/ +void +soup_uri_set_query_from_form (SoupURI *uri, GHashTable *form) +{ + g_free (uri->query); + uri->query = soup_form_encode_urlencoded (form); +} + +/** + * soup_uri_set_fragment: + * @uri: a #SoupURI + * @fragment: the fragment + * + * Sets @uri's fragment to @fragment. + **/ +void +soup_uri_set_fragment (SoupURI *uri, const char *fragment) +{ + g_free (uri->fragment); + uri->fragment = g_strdup (fragment); +} + + +GType +soup_uri_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) { + type = g_boxed_type_register_static ( + g_intern_static_string ("SoupURI"), + (GBoxedCopyFunc)soup_uri_copy, + (GBoxedFreeFunc)soup_uri_free); + } + return type; } diff --git a/libsoup/soup-uri.h b/libsoup/soup-uri.h index c9c9e78..e939b1b 100644 --- a/libsoup/soup-uri.h +++ b/libsoup/soup-uri.h @@ -1,5 +1,4 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* url-util.h : utility functions to parse URLs */ /* * Copyright 1999-2002 Ximian, Inc. @@ -13,66 +12,67 @@ G_BEGIN_DECLS -/** - * SoupProtocol: - * - * #GQuark is used for SoupProtocol so that the protocol of a #SoupUri - * can be tested quickly. - **/ -typedef GQuark SoupProtocol; +struct SoupURI { + const char *scheme; -/** - * SOUP_PROTOCOL_HTTP: - * - * This returns the #SoupProtocol value for "http". - **/ -#define SOUP_PROTOCOL_HTTP (g_quark_from_static_string ("http")) + char *user; + char *password; -/** - * SOUP_PROTOCOL_HTTPS: - * - * This returns the #SoupProtocol value for "https". -**/ -#define SOUP_PROTOCOL_HTTPS (g_quark_from_static_string ("https")) + char *host; + guint port; -struct SoupUri { - SoupProtocol protocol; + char *path; + char *query; - char *user; - char *passwd; - - char *host; - guint port; - - char *path; - char *query; - - char *fragment; - - /* Don't use this */ - gboolean broken_encoding; + char *fragment; }; -SoupUri *soup_uri_new_with_base (const SoupUri *base, - const char *uri_string); -SoupUri *soup_uri_new (const char *uri_string); - -char *soup_uri_to_string (const SoupUri *uri, - gboolean just_path); - -SoupUri *soup_uri_copy (const SoupUri *uri); -SoupUri *soup_uri_copy_root (const SoupUri *uri); - -gboolean soup_uri_equal (const SoupUri *uri1, - const SoupUri *uri2); - -void soup_uri_free (SoupUri *uri); - -char *soup_uri_encode (const char *part, - const char *escape_extra); -void soup_uri_decode (char *part); - -gboolean soup_uri_uses_default_port (const SoupUri *uri); +GType soup_uri_get_type (void); +#define SOUP_TYPE_URI (soup_uri_get_type ()) + +#define SOUP_URI_SCHEME_HTTP (g_intern_static_string ("http")) +#define SOUP_URI_SCHEME_HTTPS (g_intern_static_string ("https")) + +SoupURI *soup_uri_new_with_base (SoupURI *base, + const char *uri_string); +SoupURI *soup_uri_new (const char *uri_string); + +char *soup_uri_to_string (SoupURI *uri, + gboolean just_path_and_query); + +SoupURI *soup_uri_copy (SoupURI *uri); + +gboolean soup_uri_equal (SoupURI *uri1, + SoupURI *uri2); + +void soup_uri_free (SoupURI *uri); + +char *soup_uri_encode (const char *part, + const char *escape_extra); +char *soup_uri_decode (const char *part); +char *soup_uri_normalize (const char *part, + const char *unescape_extra); + +gboolean soup_uri_uses_default_port (SoupURI *uri); + +void soup_uri_set_scheme (SoupURI *uri, + const char *scheme); +void soup_uri_set_user (SoupURI *uri, + const char *user); +void soup_uri_set_password (SoupURI *uri, + const char *password); +void soup_uri_set_host (SoupURI *uri, + const char *host); +void soup_uri_set_port (SoupURI *uri, + guint port); +void soup_uri_set_path (SoupURI *uri, + const char *path); +void soup_uri_set_query (SoupURI *uri, + const char *query); +void soup_uri_set_query_from_form (SoupURI *uri, + GHashTable *form); +void soup_uri_set_fragment (SoupURI *uri, + const char *fragment); G_END_DECLS diff --git a/libsoup/soup-value-utils.c b/libsoup/soup-value-utils.c new file mode 100644 index 0000000..af214f3 --- /dev/null +++ b/libsoup/soup-value-utils.c @@ -0,0 +1,292 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-type-utils.c: GValue and GType-related utilities + * + * Copyright (C) 2007 Red Hat, Inc. + */ + +#include "soup-value-utils.h" + +#include + +/** + * SECTION:soup-value-utils + * @short_description: #GValue utilities + * + **/ + +/** + * SOUP_VALUE_SETV: + * @val: a #GValue + * @type: a #GType + * @args: #va_list pointing to a value of type @type + * + * Copies an argument of type @type from @args into @val. @val will + * point directly to the value in @args rather than copying it, so you + * must g_value_copy() it if you want it to remain valid. + **/ + +/** + * SOUP_VALUE_GETV: + * @val: a #GValue + * @type: a #GType + * @args: #va_list pointing to a value of type pointer-to-@type + * + * Extracts a value of type @type from @val into @args. The return + * value will point to the same data as @val rather than being a copy + * of it. + **/ + +static void +soup_value_hash_value_free (gpointer val) +{ + g_value_unset (val); + g_free (val); +} + +/** + * soup_value_hash_new: + * + * Creates a #GHashTable whose keys are strings and whose values + * are #GValue. + **/ +GHashTable * +soup_value_hash_new (void) +{ + return g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, soup_value_hash_value_free); +} + +/** + * soup_value_hash_insert_value: + * @hash: a value hash + * @key: the key + * @value: a value + * + * Inserts @value into @hash. (Unlike with g_hash_table_insert(), both + * the key and the value are copied). + **/ +void +soup_value_hash_insert_value (GHashTable *hash, const char *key, GValue *value) +{ + GValue *copy = g_new0 (GValue, 1); + + g_value_init (copy, G_VALUE_TYPE (value)); + g_value_copy (value, copy); + g_hash_table_insert (hash, g_strdup (key), copy); +} + +/** + * soup_value_hash_insert: + * @hash: a value hash + * @key: the key + * @type: a #GType + * @...: a value of type @type + * + * Inserts the provided value of type @type into @hash. (Unlike with + * g_hash_table_insert(), both the key and the value are copied). + **/ +void +soup_value_hash_insert (GHashTable *hash, const char *key, GType type, ...) +{ + va_list args; + GValue val; + + va_start (args, type); + SOUP_VALUE_SETV (&val, type, args); + va_end (args); + soup_value_hash_insert_value (hash, key, &val); +} + +/** + * soup_value_hash_lookup: + * @hash: a value hash + * @key: the key to look up + * @type: a #GType + * @...: a value of type pointer-to-@type + * + * Looks up @key in @hash and stores its value into the provided + * location. + * + * Return value: %TRUE if @hash contained a value with key @key and + * type @type, %FALSE if not. + **/ +gboolean +soup_value_hash_lookup (GHashTable *hash, const char *key, GType type, ...) +{ + va_list args; + GValue *value; + + value = g_hash_table_lookup (hash, key); + if (!value || !G_VALUE_HOLDS (value, type)) + return FALSE; + + va_start (args, type); + SOUP_VALUE_GETV (value, type, args); + va_end (args); + + return TRUE; +} + + +/** + * soup_value_array_from_args: + * @args: arguments to create a #GValueArray from + * + * Creates a #GValueArray from the provided arguments, which must + * consist of pairs of a #GType and a value of that type, terminated + * by %G_TYPE_INVALID. (The array will contain copies of the provided + * data rather than pointing to the passed-in data directly.) + * + * Return value: a new #GValueArray, or %NULL if an error occurred. + **/ +GValueArray * +soup_value_array_from_args (va_list args) +{ + GValueArray *array; + GType type; + GValue val; + + array = g_value_array_new (1); + while ((type = va_arg (args, GType)) != G_TYPE_INVALID) { + SOUP_VALUE_SETV (&val, type, args); + g_value_array_append (array, &val); + } + return array; +} + +/** + * soup_value_array_to_args: + * @array: a #GValueArray + * @args: arguments to extract @array into + * + * Extracts a #GValueArray into the provided arguments, which must + * consist of pairs of a #GType and a value of pointer-to-that-type, + * terminated by %G_TYPE_INVALID. The returned values will point to the + * same memory as the values in the array. + * + * Return value: success or failure + **/ +gboolean +soup_value_array_to_args (GValueArray *array, va_list args) +{ + GType type; + GValue *value; + int i; + + for (i = 0; i < array->n_values; i++) { + type = va_arg (args, GType); + if (type == G_TYPE_INVALID) + return FALSE; + value = g_value_array_get_nth (array, i); + if (!G_VALUE_HOLDS (value, type)) + return FALSE; + SOUP_VALUE_GETV (value, type, args); + } + return TRUE; +} + +/** + * soup_value_array_insert: + * @array: a #GValueArray + * @index_: the index to insert at + * @type: a #GType + * @...: a value of type @type + * + * Inserts the provided value of type @type into @array as with + * g_value_array_insert(). (The provided data is copied rather than + * being inserted directly.) + **/ +void +soup_value_array_insert (GValueArray *array, guint index_, GType type, ...) +{ + va_list args; + GValue val; + + va_start (args, type); + SOUP_VALUE_SETV (&val, type, args); + va_end (args); + g_value_array_insert (array, index_, &val); +} + +/** + * soup_value_array_append: + * @array: a #GValueArray + * @type: a #GType + * @...: a value of type @type + * + * Appends the provided value of type @type to @array as with + * g_value_array_append(). (The provided data is copied rather than + * being inserted directly.) + **/ +void +soup_value_array_append (GValueArray *array, GType type, ...) +{ + va_list args; + GValue val; + + va_start (args, type); + SOUP_VALUE_SETV (&val, type, args); + va_end (args); + g_value_array_append (array, &val); +} + +/** + * soup_value_array_get_nth: + * @array: a #GValueArray + * @index_: the index to look up + * @type: a #GType + * @...: a value of type pointer-to-@type + * + * Gets the @index_ element of @array and stores its value into the + * provided location. + * + * Return value: %TRUE if @array contained a value with index @index_ + * and type @type, %FALSE if not. + **/ +gboolean +soup_value_array_get_nth (GValueArray *array, guint index_, GType type, ...) +{ + GValue *value; + va_list args; + + value = g_value_array_get_nth (array, index_); + if (!value || !G_VALUE_HOLDS (value, type)) + return FALSE; + + va_start (args, type); + SOUP_VALUE_GETV (value, type, args); + va_end (args); + return TRUE; +} + + +static GByteArray * +soup_byte_array_copy (GByteArray *ba) +{ + GByteArray *copy; + + copy = g_byte_array_sized_new (ba->len); + g_byte_array_append (copy, ba->data, ba->len); + return copy; +} + +static void +soup_byte_array_free (GByteArray *ba) +{ + g_byte_array_free (ba, TRUE); +} + +GType +soup_byte_array_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static ( + g_intern_static_string ("GByteArray"), + (GBoxedCopyFunc)soup_byte_array_copy, + (GBoxedFreeFunc)soup_byte_array_free); + } + return type; +} diff --git a/libsoup/soup-value-utils.h b/libsoup/soup-value-utils.h new file mode 100644 index 0000000..19f7cce --- /dev/null +++ b/libsoup/soup-value-utils.h @@ -0,0 +1,69 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Red Hat, Inc. + */ + +#ifndef SOUP_VALUE_UTILS_H +#define SOUP_VALUE_UTILS_H 1 + +#include +#include + +G_BEGIN_DECLS + +#define SOUP_VALUE_SETV(val, type, args) \ +G_STMT_START { \ + char *error = NULL; \ + \ + memset (val, 0, sizeof (GValue)); \ + g_value_init (val, type); \ + G_VALUE_COLLECT (val, args, G_VALUE_NOCOPY_CONTENTS, &error); \ + if (error) \ + g_free (error); \ +} G_STMT_END + +#define SOUP_VALUE_GETV(val, type, args) \ +G_STMT_START { \ + char *error = NULL; \ + \ + G_VALUE_LCOPY (val, args, G_VALUE_NOCOPY_CONTENTS, &error); \ + if (error) \ + g_free (error); \ +} G_STMT_END + +GHashTable *soup_value_hash_new (void); +void soup_value_hash_insert_value (GHashTable *hash, + const char *key, + GValue *value); +void soup_value_hash_insert (GHashTable *hash, + const char *key, + GType type, + ...); +gboolean soup_value_hash_lookup (GHashTable *hash, + const char *key, + GType type, + ...); + +GValueArray *soup_value_array_from_args (va_list args); +gboolean soup_value_array_to_args (GValueArray *array, + va_list args); + +void soup_value_array_insert (GValueArray *array, + guint index_, + GType type, + ...); +void soup_value_array_append (GValueArray *array, + GType type, + ...); +gboolean soup_value_array_get_nth (GValueArray *array, + guint index_, + GType type, + ...); + + +GType soup_byte_array_get_type (void); +#define SOUP_TYPE_BYTE_ARRAY (soup_byte_array_get_type ()) + +G_END_DECLS + +#endif /* SOUP_VALUE_UTILS_H */ diff --git a/libsoup/soup-xmlrpc-message.c b/libsoup/soup-xmlrpc-message.c deleted file mode 100644 index b2954b9..0000000 --- a/libsoup/soup-xmlrpc-message.c +++ /dev/null @@ -1,402 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-xmlrpc-message.c: XMLRPC request message - * - * Copyright (C) 2003, Novell, Inc. - * Copyright (C) 2004, Mariano Suarez-Alvarez - * Copyright (C) 2004, Fernando Herrera - * Copyright (C) 2005, Jeff Bailey - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include - -#include "soup-date.h" -#include "soup-misc.h" -#include "soup-xmlrpc-message.h" - -G_DEFINE_TYPE (SoupXmlrpcMessage, soup_xmlrpc_message, SOUP_TYPE_MESSAGE) - -typedef struct { - xmlDocPtr doc; - xmlNodePtr last_node; -} SoupXmlrpcMessagePrivate; -#define SOUP_XMLRPC_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_XMLRPC_MESSAGE, SoupXmlrpcMessagePrivate)) - -static void soup_xmlrpc_message_end_element (SoupXmlrpcMessage *msg); - -static void -finalize (GObject *object) -{ - SoupXmlrpcMessagePrivate *priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (object); - - if (priv->doc) - xmlFreeDoc (priv->doc); - - G_OBJECT_CLASS (soup_xmlrpc_message_parent_class)->finalize (object); -} - -static void -soup_xmlrpc_message_class_init (SoupXmlrpcMessageClass *soup_xmlrpc_message_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (soup_xmlrpc_message_class); - - g_type_class_add_private (soup_xmlrpc_message_class, sizeof (SoupXmlrpcMessagePrivate)); - - object_class->finalize = finalize; -} - -static void -soup_xmlrpc_message_init (SoupXmlrpcMessage *msg) -{ - SoupXmlrpcMessagePrivate *priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - priv->doc = xmlNewDoc ((const xmlChar *)"1.0"); - priv->doc->standalone = FALSE; - priv->doc->encoding = xmlCharStrdup ("UTF-8"); -} - - -SoupXmlrpcMessage * -soup_xmlrpc_message_new (const char *uri_string) -{ - SoupXmlrpcMessage *msg; - SoupUri *uri; - - uri = soup_uri_new (uri_string); - if (!uri) - return NULL; - - msg = soup_xmlrpc_message_new_from_uri (uri); - - soup_uri_free (uri); - - return msg; -} - -SoupXmlrpcMessage * -soup_xmlrpc_message_new_from_uri (const SoupUri *uri) -{ - SoupXmlrpcMessage *msg; - - msg = g_object_new (SOUP_TYPE_XMLRPC_MESSAGE, NULL); - SOUP_MESSAGE (msg)->method = SOUP_METHOD_POST; - soup_message_set_uri (SOUP_MESSAGE (msg), uri); - - return msg; -} - -void -soup_xmlrpc_message_start_call (SoupXmlrpcMessage *msg, const char *method_name) -{ - SoupXmlrpcMessagePrivate *priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - xmlNodePtr root; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - - root = xmlNewDocNode (priv->doc, NULL, (const xmlChar *)"methodCall", NULL); - xmlDocSetRootElement (priv->doc, root); - - xmlNewChild (root, NULL, (const xmlChar *)"methodName", (const xmlChar *)method_name); - - priv->last_node = root; - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"params", NULL); -} - -void -soup_xmlrpc_message_end_call (SoupXmlrpcMessage *msg) -{ - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - - soup_xmlrpc_message_end_element (msg); - soup_xmlrpc_message_end_element (msg); - soup_xmlrpc_message_end_element (msg); -} - -void -soup_xmlrpc_message_start_param (SoupXmlrpcMessage *msg) -{ - SoupXmlrpcMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"param", NULL); -} - -void -soup_xmlrpc_message_end_param (SoupXmlrpcMessage *msg) -{ - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - - soup_xmlrpc_message_end_element (msg); -} - -void -soup_xmlrpc_message_write_int (SoupXmlrpcMessage *msg, long i) -{ - SoupXmlrpcMessagePrivate *priv; - char *str; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - str = g_strdup_printf ("%ld", i); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"value", NULL); - xmlNewTextChild (priv->last_node, NULL, (const xmlChar *)"i4", (xmlChar *)str); - soup_xmlrpc_message_end_element (msg); - - g_free (str); -} - -void -soup_xmlrpc_message_write_boolean (SoupXmlrpcMessage *msg, gboolean b) -{ - SoupXmlrpcMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"value", NULL); - xmlNewChild (priv->last_node, NULL, (const xmlChar *)"boolean", (const xmlChar*)(b ? "1" : "0")); - soup_xmlrpc_message_end_element (msg); -} - -void -soup_xmlrpc_message_write_string (SoupXmlrpcMessage *msg, const char *str) -{ - SoupXmlrpcMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"value", NULL); - xmlNewTextChild (priv->last_node, NULL, (const xmlChar *)"string", (const xmlChar *)str); - soup_xmlrpc_message_end_element (msg); -} - -void -soup_xmlrpc_message_write_double (SoupXmlrpcMessage *msg, double d) -{ - SoupXmlrpcMessagePrivate *priv; - char *str; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - str = g_strdup_printf ("%f", d); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"value", NULL); - xmlNewTextChild (priv->last_node, NULL, (const xmlChar *)"double", (xmlChar *)str); - soup_xmlrpc_message_end_element (msg); - - g_free (str); -} - -void -soup_xmlrpc_message_write_datetime (SoupXmlrpcMessage *msg, const time_t timeval) -{ - SoupXmlrpcMessagePrivate *priv; - struct tm time; - char str[128]; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - soup_gmtime (&timeval, &time); - strftime (str, 128, "%Y%m%dT%H:%M:%S", &time); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"value", NULL); - xmlNewTextChild (priv->last_node, NULL, (const xmlChar *)"dateTime.iso8601", (xmlChar *)str); - soup_xmlrpc_message_end_element (msg); -} - -void -soup_xmlrpc_message_write_base64 (SoupXmlrpcMessage *msg, gconstpointer buf, int len) -{ - SoupXmlrpcMessagePrivate *priv; - char *str; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - str = g_base64_encode (buf, len); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"value", NULL); - xmlNewTextChild (priv->last_node, NULL, (const xmlChar *)"base64", (xmlChar *)str); - soup_xmlrpc_message_end_element (msg); - - g_free (str); -} - -void -soup_xmlrpc_message_start_struct (SoupXmlrpcMessage *msg) -{ - SoupXmlrpcMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"value", NULL); - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"struct", NULL); -} - -void -soup_xmlrpc_message_end_struct (SoupXmlrpcMessage *msg) -{ - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - - soup_xmlrpc_message_end_element (msg); - soup_xmlrpc_message_end_element (msg); -} - -void -soup_xmlrpc_message_start_member (SoupXmlrpcMessage *msg, const char *name) -{ - SoupXmlrpcMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"member", NULL); - xmlNewChild (priv->last_node, NULL, (const xmlChar *)"name", (const xmlChar *)name); -} - -void -soup_xmlrpc_message_end_member (SoupXmlrpcMessage *msg) -{ - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - - soup_xmlrpc_message_end_element (msg); -} - -void -soup_xmlrpc_message_start_array (SoupXmlrpcMessage *msg) -{ - SoupXmlrpcMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"value", NULL); - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"array", NULL); - priv->last_node = xmlNewChild (priv->last_node, NULL, (const xmlChar *)"data", NULL); -} - -void -soup_xmlrpc_message_end_array (SoupXmlrpcMessage *msg) -{ - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - - soup_xmlrpc_message_end_element (msg); - soup_xmlrpc_message_end_element (msg); - soup_xmlrpc_message_end_element (msg); -} - -static void -soup_xmlrpc_message_end_element (SoupXmlrpcMessage *msg) -{ - SoupXmlrpcMessagePrivate *priv; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - priv->last_node = priv->last_node->parent; -} - -xmlChar * -soup_xmlrpc_message_to_string (SoupXmlrpcMessage *msg) -{ - SoupXmlrpcMessagePrivate *priv; - xmlChar *body; - int len; - - g_return_val_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg), NULL); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - xmlDocDumpMemory (priv->doc, &body, &len); - - return body; -} - -gboolean -soup_xmlrpc_message_from_string (SoupXmlrpcMessage *message, const char *xmlstr) -{ - SoupXmlrpcMessagePrivate *priv; - xmlDocPtr newdoc; - xmlNodePtr body; - - g_return_val_if_fail (SOUP_IS_XMLRPC_MESSAGE (message), FALSE); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (message); - g_return_val_if_fail (xmlstr != NULL, FALSE); - - newdoc = xmlParseMemory (xmlstr, strlen (xmlstr)); - if (!newdoc) - return FALSE; - - body = xmlDocGetRootElement (newdoc); - if (!body || strcmp ((const char *)body->name, "methodCall")) - goto bad; - - body = soup_xml_real_node (body->children); - if (!body || strcmp ((const char *)body->name, "methodName")) - goto bad; - - body = soup_xml_real_node (body->next); - if (!body || strcmp ((const char *)body->name, "params")) - goto bad; - - body = xmlGetLastChild (body); - if (!body) - goto bad; - - /* body should be pointing by now to the last param */ - xmlFreeDoc (priv->doc); - priv->doc = newdoc; - priv->last_node = body; - - return TRUE; - -bad: - xmlFreeDoc (newdoc); - return FALSE; -} - -void -soup_xmlrpc_message_persist (SoupXmlrpcMessage *msg) -{ - SoupXmlrpcMessagePrivate *priv; - xmlChar *body; - int len; - - g_return_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg)); - priv = SOUP_XMLRPC_MESSAGE_GET_PRIVATE (msg); - - xmlDocDumpMemory (priv->doc, &body, &len); - - soup_message_set_request (SOUP_MESSAGE (msg), "text/xml", - SOUP_BUFFER_SYSTEM_OWNED, (char *)body, len); -} - -SoupXmlrpcResponse * -soup_xmlrpc_message_parse_response (SoupXmlrpcMessage *msg) -{ - char *str; - SoupXmlrpcResponse *response; - - g_return_val_if_fail (SOUP_IS_XMLRPC_MESSAGE (msg), NULL); - - str = g_malloc0 (SOUP_MESSAGE (msg)->response.length + 1); - strncpy (str, SOUP_MESSAGE (msg)->response.body, SOUP_MESSAGE (msg)->response.length); - - response = soup_xmlrpc_response_new_from_string (str); - g_free (str); - - return response; -} diff --git a/libsoup/soup-xmlrpc-message.h b/libsoup/soup-xmlrpc-message.h deleted file mode 100644 index 64cc132..0000000 --- a/libsoup/soup-xmlrpc-message.h +++ /dev/null @@ -1,80 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * FIXME Copyright - */ - -#ifndef SOUP_XMLRPC_MESSAGE_H -#define SOUP_XMLRPC_MESSAGE_H - -#include -#include -#include -#include - -#include "soup-xmlrpc-response.h" - -G_BEGIN_DECLS - -#define SOUP_TYPE_XMLRPC_MESSAGE (soup_xmlrpc_message_get_type ()) -#define SOUP_XMLRPC_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_XMLRPC_MESSAGE, SoupXmlrpcMessage)) -#define SOUP_XMLRPC_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_XMLRPC_MESSAGE, SoupXmlrpcMessageClass)) -#define SOUP_IS_XMLRPC_MESSAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_XMLRPC_MESSAGE)) -#define SOUP_IS_XMLRPC_MESSAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_XMLRPC_MESSAGE)) -#define SOUP_XMLRPC_MESSAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_XMLRPC_MESSAGE, SoupXmlrpcMessageClass)) - -typedef struct { - SoupMessage parent; - -} SoupXmlrpcMessage; - -typedef struct { - SoupMessageClass parent_class; -} SoupXmlrpcMessageClass; - -GType soup_xmlrpc_message_get_type (void); - -SoupXmlrpcMessage *soup_xmlrpc_message_new (const char *uri_string); -SoupXmlrpcMessage *soup_xmlrpc_message_new_from_uri (const SoupUri *uri); - -void soup_xmlrpc_message_start_call (SoupXmlrpcMessage *msg, - const char *method_name); -void soup_xmlrpc_message_end_call (SoupXmlrpcMessage *msg); - -void soup_xmlrpc_message_start_param (SoupXmlrpcMessage *msg); -void soup_xmlrpc_message_end_param (SoupXmlrpcMessage *msg); - -void soup_xmlrpc_message_write_int (SoupXmlrpcMessage *msg, - long i); -void soup_xmlrpc_message_write_boolean (SoupXmlrpcMessage *msg, - gboolean b); -void soup_xmlrpc_message_write_string (SoupXmlrpcMessage *msg, - const char *str); -void soup_xmlrpc_message_write_double (SoupXmlrpcMessage *msg, - double d); -void soup_xmlrpc_message_write_datetime (SoupXmlrpcMessage *msg, - const time_t timeval); -void soup_xmlrpc_message_write_base64 (SoupXmlrpcMessage *msg, - gconstpointer buf, - int len); - -void soup_xmlrpc_message_start_struct (SoupXmlrpcMessage *msg); -void soup_xmlrpc_message_end_struct (SoupXmlrpcMessage *msg); - -void soup_xmlrpc_message_start_member (SoupXmlrpcMessage *msg, - const char *name); -void soup_xmlrpc_message_end_member (SoupXmlrpcMessage *msg); - -void soup_xmlrpc_message_start_array (SoupXmlrpcMessage *msg); -void soup_xmlrpc_message_end_array (SoupXmlrpcMessage *msg); - -gboolean soup_xmlrpc_message_from_string (SoupXmlrpcMessage *message, - const char *xmlstr); - -xmlChar *soup_xmlrpc_message_to_string (SoupXmlrpcMessage *msg); -void soup_xmlrpc_message_persist (SoupXmlrpcMessage *msg); - -SoupXmlrpcResponse *soup_xmlrpc_message_parse_response (SoupXmlrpcMessage *msg); - -G_END_DECLS - -#endif diff --git a/libsoup/soup-xmlrpc-response.c b/libsoup/soup-xmlrpc-response.c deleted file mode 100644 index ab0f82c..0000000 --- a/libsoup/soup-xmlrpc-response.c +++ /dev/null @@ -1,649 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-xmlrpc-response.c: XMLRPC response message - * - * Copyright (C) 2003, Novell, Inc. - * Copyright (C) 2004, Mariano Suarez-Alvarez - * Copyright (C) 2004, Fernando Herrera - * Copyright (C) 2005, Jeff Bailey - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#include -#include -#include -#include - -#include -#include - -#include "soup-date.h" -#include "soup-misc.h" -#include "soup-xmlrpc-response.h" - - -G_DEFINE_TYPE (SoupXmlrpcResponse, soup_xmlrpc_response, G_TYPE_OBJECT) - -typedef struct { - xmlDocPtr doc; - gboolean fault; - xmlNodePtr value; -} SoupXmlrpcResponsePrivate; -#define SOUP_XMLRPC_RESPONSE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_XMLRPC_RESPONSE, SoupXmlrpcResponsePrivate)) - -static void -finalize (GObject *object) -{ - SoupXmlrpcResponsePrivate *priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (object); - - if (priv->doc) - xmlFreeDoc (priv->doc); - - G_OBJECT_CLASS (soup_xmlrpc_response_parent_class)->finalize (object); -} - -static void -soup_xmlrpc_response_class_init (SoupXmlrpcResponseClass *soup_xmlrpc_response_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (soup_xmlrpc_response_class); - - g_type_class_add_private (soup_xmlrpc_response_class, sizeof (SoupXmlrpcResponsePrivate)); - - object_class->finalize = finalize; -} - -static void -soup_xmlrpc_response_init (SoupXmlrpcResponse *response) -{ - SoupXmlrpcResponsePrivate *priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (response); - - priv->doc = xmlNewDoc ((const xmlChar *)"1.0"); - priv->fault = FALSE; -} - - -SoupXmlrpcResponse * -soup_xmlrpc_response_new (void) -{ - SoupXmlrpcResponse *response; - - response = g_object_new (SOUP_TYPE_XMLRPC_RESPONSE, NULL); - return response; -} - -SoupXmlrpcResponse * -soup_xmlrpc_response_new_from_string (const char *xmlstr) -{ - SoupXmlrpcResponse *response; - - g_return_val_if_fail (xmlstr != NULL, NULL); - - response = g_object_new (SOUP_TYPE_XMLRPC_RESPONSE, NULL); - - if (!soup_xmlrpc_response_from_string (response, xmlstr)) { - g_object_unref (response); - return NULL; - } - - return response; -} - -static xmlNode * -exactly_one_child (xmlNode *node) -{ - xmlNode *child; - - child = soup_xml_real_node (node->children); - if (!child || soup_xml_real_node (child->next)) - return NULL; - - return child; -} - -gboolean -soup_xmlrpc_response_from_string (SoupXmlrpcResponse *response, const char *xmlstr) -{ - SoupXmlrpcResponsePrivate *priv; - xmlDocPtr newdoc; - xmlNodePtr body; - gboolean fault = TRUE; - - g_return_val_if_fail (SOUP_IS_XMLRPC_RESPONSE (response), FALSE); - priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (response); - g_return_val_if_fail (xmlstr != NULL, FALSE); - - newdoc = xmlParseMemory (xmlstr, strlen (xmlstr)); - if (!newdoc) - goto very_bad; - - body = xmlDocGetRootElement (newdoc); - if (!body || strcmp ((const char *)body->name, "methodResponse")) - goto bad; - - body = exactly_one_child (body); - if (!body) - goto bad; - - if (strcmp ((const char *)body->name, "params") == 0) { - fault = FALSE; - body = exactly_one_child (body); - if (!body || strcmp ((const char *)body->name, "param")) - goto bad; - } else if (strcmp ((const char *)body->name, "fault") != 0) - goto bad; - - body = exactly_one_child (body); - if (!body || strcmp ((const char *)body->name, "value")) - goto bad; - - /* body should be pointing by now to the struct of a fault, or the value of a - * normal response - */ - - xmlFreeDoc (priv->doc); - priv->doc = newdoc; - priv->value = body; - priv->fault = fault; - - return TRUE; - -bad: - xmlFreeDoc (newdoc); -very_bad: - return FALSE; -} - -xmlChar * -soup_xmlrpc_response_to_string (SoupXmlrpcResponse *response) -{ - SoupXmlrpcResponsePrivate *priv; - xmlChar *str; - int size; - - g_return_val_if_fail (SOUP_IS_XMLRPC_RESPONSE (response), FALSE); - priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (response); - - xmlDocDumpMemoryEnc (priv->doc, &str, &size, "UTF-8"); - - return str; -} - -gboolean -soup_xmlrpc_response_is_fault (SoupXmlrpcResponse *response) -{ - SoupXmlrpcResponsePrivate *priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (response); - - return priv->fault; -} - -SoupXmlrpcValue * -soup_xmlrpc_response_get_value (SoupXmlrpcResponse *response) -{ - SoupXmlrpcResponsePrivate *priv; - g_return_val_if_fail (SOUP_IS_XMLRPC_RESPONSE (response), FALSE); - priv = SOUP_XMLRPC_RESPONSE_GET_PRIVATE (response); - - return (SoupXmlrpcValue*) priv->value; -} - -SoupXmlrpcValueType -soup_xmlrpc_value_get_type (SoupXmlrpcValue *value) -{ - xmlNode *xml; - - xml = (xmlNode *) value; - - if (strcmp ((const char *)xml->name, "value")) - return SOUP_XMLRPC_VALUE_TYPE_BAD; - - xml = exactly_one_child (xml); - if (!xml) - return SOUP_XMLRPC_VALUE_TYPE_BAD; - - if (xml->type == XML_TEXT_NODE) - return SOUP_XMLRPC_VALUE_TYPE_STRING; - else if (xml->type != XML_ELEMENT_NODE) - return SOUP_XMLRPC_VALUE_TYPE_BAD; - - if (strcmp ((const char *)xml->name, "i4") == 0 || strcmp ((const char *)xml->name, "int") == 0) - return SOUP_XMLRPC_VALUE_TYPE_INT; - else if (strcmp ((const char *)xml->name, "boolean") == 0) - return SOUP_XMLRPC_VALUE_TYPE_BOOLEAN; - else if (strcmp ((const char *)xml->name, "string") == 0) - return SOUP_XMLRPC_VALUE_TYPE_STRING; - else if (strcmp ((const char *)xml->name, "double") == 0) - return SOUP_XMLRPC_VALUE_TYPE_DOUBLE; - else if (strcmp ((const char *)xml->name, "dateTime.iso8601") == 0) - return SOUP_XMLRPC_VALUE_TYPE_DATETIME; - else if (strcmp ((const char *)xml->name, "base64") == 0) - return SOUP_XMLRPC_VALUE_TYPE_BASE64; - else if (strcmp ((const char *)xml->name, "struct") == 0) - return SOUP_XMLRPC_VALUE_TYPE_STRUCT; - else if (strcmp ((const char *)xml->name, "array") == 0) - return SOUP_XMLRPC_VALUE_TYPE_ARRAY; - else - return SOUP_XMLRPC_VALUE_TYPE_BAD; -} - -gboolean -soup_xmlrpc_value_get_int (SoupXmlrpcValue *value, long *i) -{ - xmlNode *xml; - xmlChar *content; - char *tail; - gboolean ok; - - xml = (xmlNode *) value; - - if (strcmp ((const char *)xml->name, "value")) - return FALSE; - xml = exactly_one_child (xml); - if (!xml || (strcmp ((const char *)xml->name, "int") && strcmp ((const char *)xml->name, "i4"))) - return FALSE; - - /* FIXME this should be exactly one text node */ - content = xmlNodeGetContent (xml); - *i = strtol ((char *)content, &tail, 10); - ok = (*tail == '\0'); - xmlFree (content); - - return ok; -} - -gboolean -soup_xmlrpc_value_get_double (SoupXmlrpcValue *value, double *d) -{ - xmlNode *xml; - xmlChar *content; - char *tail; - gboolean ok; - - xml = (xmlNode *) value; - - if (strcmp ((const char *)xml->name, "value")) - return FALSE; - xml = exactly_one_child (xml); - if (!xml || (strcmp ((const char *)xml->name, "double"))) - return FALSE; - - /* FIXME this should be exactly one text node */ - content = xmlNodeGetContent (xml); - *d = g_ascii_strtod ((char *)content, &tail); - ok = (*tail == '\0'); - xmlFree (content); - - return ok; -} - -gboolean -soup_xmlrpc_value_get_boolean (SoupXmlrpcValue *value, gboolean *b) -{ - xmlNode *xml; - xmlChar *content; - char *tail; - gboolean ok; - int i; - - xml = (xmlNode *) value; - - if (strcmp ((const char *)xml->name, "value")) - return FALSE; - xml = exactly_one_child (xml); - if (!xml || strcmp ((const char *)xml->name, "boolean")) - return FALSE; - - content = xmlNodeGetContent (xml); - i = strtol ((char *)content, &tail, 10); - *b = (i == 1); - ok = (*tail == '\0'); - xmlFree (content); - - return ok; -} - -gboolean -soup_xmlrpc_value_get_string (SoupXmlrpcValue *value, char **str) -{ - xmlNode *xml; - xmlChar *content; - - xml = (xmlNode *) value; - - if (strcmp ((const char *)xml->name, "value")) - return FALSE; - xml = exactly_one_child (xml); - if (!xml) - return FALSE; - if (xml->type == XML_ELEMENT_NODE) { - if (strcmp ((char *)xml->name, "string")) - return FALSE; - } else if (xml->type != XML_TEXT_NODE) - return FALSE; - - content = xmlNodeGetContent (xml); - *str = content ? g_strdup ((char *)content) : g_strdup (""); - xmlFree (content); - - return TRUE; -} - -gboolean -soup_xmlrpc_value_get_datetime (SoupXmlrpcValue *value, time_t *timeval) -{ - xmlNode *xml; - xmlChar *content; - - xml = (xmlNode *) value; - - if (strcmp ((const char *)xml->name, "value")) - return FALSE; - xml = exactly_one_child (xml); - if (!xml || (strcmp ((const char *)xml->name, "dateTime.iso8601"))) - return FALSE; - - /* FIXME this should be exactly one text node */ - content = xmlNodeGetContent (xml); - if (xmlStrlen (content) != 17) { - xmlFree (content); - return FALSE; - } - - *timeval = soup_date_iso8601_parse ((char *)content); - xmlFree (content); - return TRUE; -} - -gboolean -soup_xmlrpc_value_get_base64 (SoupXmlrpcValue *value, GByteArray **data) -{ - xmlNode *xml; - xmlChar *content; - guchar *decoded; - gsize len; - - xml = (xmlNode *) value; - if (strcmp ((const char *)xml->name, "value")) - return FALSE; - xml = exactly_one_child (xml); - if (!xml || strcmp ((const char *)xml->name, "base64")) - return FALSE; - - content = xmlNodeGetContent (xml); - decoded = g_base64_decode ((const char *)content, &len); - xmlFree (content); - - *data = g_byte_array_new (); - g_byte_array_append (*data, decoded, len); - g_free (decoded); - - return TRUE; -} - - -gboolean -soup_xmlrpc_value_get_struct (SoupXmlrpcValue *value, GHashTable **table) -{ - xmlNode *xml; - GHashTable *t; - - xml = (xmlNode *) value; - - if (strcmp ((const char *)xml->name, "value")) - return FALSE; - xml = exactly_one_child (xml); - - if (!xml || strcmp ((const char *)xml->name, "struct")) - return FALSE; - - t = g_hash_table_new_full (g_str_hash, g_str_equal, xmlFree, NULL); - - for (xml = soup_xml_real_node (xml->children); - xml; - xml = soup_xml_real_node (xml->next)) { - xmlChar *name; - xmlNode *val, *cur; - - if (strcmp ((const char *)xml->name, "member") || - !xml->children) - goto bad; - - name = NULL; - val = NULL; - - for (cur = soup_xml_real_node (xml->children); - cur; - cur = soup_xml_real_node (cur->next)) { - if (strcmp((const char *)cur->name, "name") == 0) { - if (name) - goto local_bad; - name = xmlNodeGetContent (cur); - } else if (strcmp ((const char *)cur->name, "value") == 0) - val = cur; - else - goto local_bad; - - continue; -local_bad: - if (name) - xmlFree (name); - goto bad; - } - - if (!name || !val) { - if (name) - xmlFree (name); - goto bad; - } - g_hash_table_insert (t, name, val); - } - - *table = t; - return TRUE; - -bad: - g_hash_table_destroy (t); - return FALSE; -} - -gboolean -soup_xmlrpc_value_array_get_iterator (SoupXmlrpcValue *value, SoupXmlrpcValueArrayIterator **iter) -{ - xmlNode *xml, *array, *data; - - xml = (xmlNode *) value; - - array = soup_xml_real_node (xml->children); - if (!array || strcmp((const char *)array->name, "array") != 0 || - soup_xml_real_node (array->next)) - return FALSE; - - data = soup_xml_real_node (array->children); - if (!data || strcmp((const char *)data->name, "data") != 0 || - soup_xml_real_node (data->next)) - return FALSE; - - *iter = (SoupXmlrpcValueArrayIterator *) soup_xml_real_node (data->children); - return TRUE; -} - - -SoupXmlrpcValueArrayIterator * -soup_xmlrpc_value_array_iterator_prev (SoupXmlrpcValueArrayIterator *iter) -{ - xmlNode *xml, *prev; - - xml = (xmlNode *) iter; - - prev = xml->prev; - while (prev != soup_xml_real_node (prev)) - prev = prev->prev; - - return (SoupXmlrpcValueArrayIterator *) prev; -} - -SoupXmlrpcValueArrayIterator * -soup_xmlrpc_value_array_iterator_next (SoupXmlrpcValueArrayIterator *iter) -{ - xmlNode *xml; - - xml = (xmlNode *) iter; - - return (SoupXmlrpcValueArrayIterator *) soup_xml_real_node (xml->next); -} - -gboolean -soup_xmlrpc_value_array_iterator_get_value (SoupXmlrpcValueArrayIterator *iter, - SoupXmlrpcValue **value) -{ - *value = (SoupXmlrpcValue *) iter; - - return TRUE; -} - -static void -indent (int d) -{ - while (d--) - g_printerr (" "); -} - -static void -soup_xmlrpc_value_dump_internal (SoupXmlrpcValue *value, int d); - -static void -soup_xmlrpc_value_dump_struct_member (const char *name, SoupXmlrpcValue *value, gpointer d) -{ - indent (GPOINTER_TO_INT (d)); - g_printerr ("MEMBER: %s\n", name); - soup_xmlrpc_value_dump_internal (value, GPOINTER_TO_INT (d)); -} - -static void -soup_xmlrpc_value_dump_array_element (const int i, SoupXmlrpcValue *value, gpointer d) -{ - indent (GPOINTER_TO_INT (d)); - g_printerr ("ELEMENT: %d\n", i); - soup_xmlrpc_value_dump_internal (value, GPOINTER_TO_INT (d)); -} - -static void -soup_xmlrpc_value_dump_internal (SoupXmlrpcValue *value, int d) -{ - long i; - gboolean b; - char *str; - double f; - time_t timeval; - GByteArray *base64; - GHashTable *hash; - SoupXmlrpcValueArrayIterator *iter; - - g_printerr ("\n\n[%s]\n", ((xmlNode*)value)->name); - switch (soup_xmlrpc_value_get_type (value)) { - - case SOUP_XMLRPC_VALUE_TYPE_BAD: - indent (d); - g_printerr ("BAD\n"); - break; - - case SOUP_XMLRPC_VALUE_TYPE_INT: - indent (d); - if (!soup_xmlrpc_value_get_int (value, &i)) - g_printerr ("BAD INT\n"); - else - g_printerr ("INT: %ld\n", i); - break; - - case SOUP_XMLRPC_VALUE_TYPE_BOOLEAN: - indent (d); - if (!soup_xmlrpc_value_get_boolean (value, &b)) - g_printerr ("BAD BOOLEAN\n"); - else - g_printerr ("BOOLEAN: %s\n", b ? "true" : "false"); - break; - - case SOUP_XMLRPC_VALUE_TYPE_STRING: - indent (d); - if (!soup_xmlrpc_value_get_string (value, &str)) - g_printerr ("BAD STRING\n"); - else { - g_printerr ("STRING: \"%s\"\n", str); - g_free (str); - } - break; - - case SOUP_XMLRPC_VALUE_TYPE_DOUBLE: - indent (d); - if (!soup_xmlrpc_value_get_double (value, &f)) - g_printerr ("BAD DOUBLE\n"); - else - g_printerr ("DOUBLE: %f\n", f); - break; - - case SOUP_XMLRPC_VALUE_TYPE_DATETIME: - indent (d); - if (!soup_xmlrpc_value_get_datetime (value, &timeval)) - g_printerr ("BAD DATETIME\n"); - else - g_printerr ("DATETIME: %s\n", asctime (gmtime (&timeval))); - break; - - case SOUP_XMLRPC_VALUE_TYPE_BASE64: - indent (d); - if (!soup_xmlrpc_value_get_base64 (value, &base64)) - g_printerr ("BAD BASE64\n"); - else { - GString *hex = g_string_new (NULL); - int i; - - for (i = 0; i < base64->len; i++) - g_string_append_printf (hex, "%02x", base64->data[i]); - - g_printerr ("BASE64: %s\n", hex->str); - g_string_free (hex, TRUE); - g_byte_array_free (base64, TRUE); - } - - break; - - case SOUP_XMLRPC_VALUE_TYPE_STRUCT: - indent (d); - if (!soup_xmlrpc_value_get_struct (value, &hash)) - g_printerr ("BAD STRUCT\n"); - else { - g_printerr ("STRUCT\n"); - g_hash_table_foreach (hash, (GHFunc) soup_xmlrpc_value_dump_struct_member, - GINT_TO_POINTER (d+1)); - g_hash_table_destroy (hash); - } - break; - - case SOUP_XMLRPC_VALUE_TYPE_ARRAY: - indent (d); - if (!soup_xmlrpc_value_array_get_iterator (value, &iter)) - g_printerr ("BAD ARRAY\n"); - else { - SoupXmlrpcValue *evalue; - int i = 0; - g_printerr ("ARRAY\n"); - while (iter != NULL) { - soup_xmlrpc_value_array_iterator_get_value (iter, &evalue); - soup_xmlrpc_value_dump_array_element (i, evalue, GINT_TO_POINTER (d+1)); - iter = soup_xmlrpc_value_array_iterator_next (iter); - i++; - } - } - break; - } - -} - -void -soup_xmlrpc_value_dump (SoupXmlrpcValue *value) -{ - soup_xmlrpc_value_dump_internal (value, 0); -} - diff --git a/libsoup/soup-xmlrpc-response.h b/libsoup/soup-xmlrpc-response.h deleted file mode 100644 index f2207c7..0000000 --- a/libsoup/soup-xmlrpc-response.h +++ /dev/null @@ -1,88 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * FIXME Copyright - */ - -#ifndef SOUP_XMLRPC_RESPONSE_H -#define SOUP_XMLRPC_RESPONSE_H - -#include -#include - -G_BEGIN_DECLS - -#define SOUP_TYPE_XMLRPC_RESPONSE (soup_xmlrpc_response_get_type ()) -#define SOUP_XMLRPC_RESPONSE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_XMLRPC_RESPONSE, SoupXmlrpcResponse)) -#define SOUP_XMLRPC_RESPONSE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_XMLRPC_RESPONSE, SoupXmlrpcResponseClass)) -#define SOUP_IS_XMLRPC_RESPONSE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_XMLRPC_RESPONSE)) -#define SOUP_IS_XMLRPC_RESPONSE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_XMLRPC_RESPONSE)) -#define SOUP_XMLRPC_RESPONSE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_XMLRPC_RESPONSE, SoupXmlrpcResponseClass)) - -typedef struct { - GObject parent; - -} SoupXmlrpcResponse; - -typedef struct { - GObjectClass parent_class; -} SoupXmlrpcResponseClass; - -GType soup_xmlrpc_response_get_type (void); - -SoupXmlrpcResponse *soup_xmlrpc_response_new (void); -SoupXmlrpcResponse *soup_xmlrpc_response_new_from_string (const char *xmlstr); - -gboolean soup_xmlrpc_response_from_string (SoupXmlrpcResponse *response, - const char *xmlstr); -xmlChar *soup_xmlrpc_response_to_string (SoupXmlrpcResponse *response); - -typedef xmlNode *SoupXmlrpcValue; - -typedef enum { - SOUP_XMLRPC_VALUE_TYPE_BAD, - SOUP_XMLRPC_VALUE_TYPE_INT, - SOUP_XMLRPC_VALUE_TYPE_BOOLEAN, - SOUP_XMLRPC_VALUE_TYPE_STRING, - SOUP_XMLRPC_VALUE_TYPE_DOUBLE, - SOUP_XMLRPC_VALUE_TYPE_DATETIME, - SOUP_XMLRPC_VALUE_TYPE_BASE64, - SOUP_XMLRPC_VALUE_TYPE_STRUCT, - SOUP_XMLRPC_VALUE_TYPE_ARRAY -} SoupXmlrpcValueType; - -gboolean soup_xmlrpc_response_is_fault (SoupXmlrpcResponse *response); -SoupXmlrpcValue *soup_xmlrpc_response_get_value (SoupXmlrpcResponse *response); -SoupXmlrpcValueType soup_xmlrpc_value_get_type (SoupXmlrpcValue *value); - -gboolean soup_xmlrpc_value_get_int (SoupXmlrpcValue *value, - long *i); -gboolean soup_xmlrpc_value_get_double (SoupXmlrpcValue *value, - double *b); -gboolean soup_xmlrpc_value_get_boolean (SoupXmlrpcValue *value, - gboolean *b); -gboolean soup_xmlrpc_value_get_string (SoupXmlrpcValue *value, - char **str); -gboolean soup_xmlrpc_value_get_datetime (SoupXmlrpcValue *value, - time_t *timeval); -gboolean soup_xmlrpc_value_get_base64 (SoupXmlrpcValue *value, - GByteArray **data); - -gboolean soup_xmlrpc_value_get_struct (SoupXmlrpcValue *value, - GHashTable **table); - - -typedef xmlNodePtr SoupXmlrpcValueArrayIterator; - -gboolean soup_xmlrpc_value_array_get_iterator (SoupXmlrpcValue *value, - SoupXmlrpcValueArrayIterator **iter); - -SoupXmlrpcValueArrayIterator *soup_xmlrpc_value_array_iterator_prev (SoupXmlrpcValueArrayIterator *iter); -SoupXmlrpcValueArrayIterator *soup_xmlrpc_value_array_iterator_next (SoupXmlrpcValueArrayIterator *iter); -gboolean soup_xmlrpc_value_array_iterator_get_value (SoupXmlrpcValueArrayIterator *iter, - SoupXmlrpcValue **value); - -void soup_xmlrpc_value_dump (SoupXmlrpcValue *value); - -G_END_DECLS - -#endif diff --git a/libsoup/soup-xmlrpc.c b/libsoup/soup-xmlrpc.c new file mode 100644 index 0000000..9ebfe83 --- /dev/null +++ b/libsoup/soup-xmlrpc.c @@ -0,0 +1,796 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-xmlrpc.c: XML-RPC parser/generator + * + * Copyright (C) 2007 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "soup-xmlrpc.h" +#include "soup-value-utils.h" +#include "soup-date.h" +#include "soup-message.h" +#include "soup-misc.h" +#include "soup-session.h" + +/** + * SECTION:soup-xmlrpc + * @short_description: XML-RPC support + * + **/ + +static xmlNode *find_real_node (xmlNode *node); + +static gboolean insert_value (xmlNode *parent, GValue *value); + +static void +insert_member (gpointer name, gpointer value, gpointer data) +{ + xmlNode *member, **struct_node = data; + + if (!*struct_node) + return; + + member = xmlNewChild (*struct_node, NULL, + (const xmlChar *)"member", NULL); + xmlNewTextChild (member, NULL, + (const xmlChar *)"name", (const xmlChar *)name); + if (!insert_value (member, value)) { + xmlFreeNode (*struct_node); + *struct_node = NULL; + } +} + +static gboolean +insert_value (xmlNode *parent, GValue *value) +{ + GType type = G_VALUE_TYPE (value); + xmlNode *xvalue; + char buf[128]; + + xvalue = xmlNewChild (parent, NULL, (const xmlChar *)"value", NULL); + + if (type == G_TYPE_INT) { + snprintf (buf, sizeof (buf), "%d", g_value_get_int (value)); + xmlNewChild (xvalue, NULL, + (const xmlChar *)"int", + (const xmlChar *)buf); + } else if (type == G_TYPE_BOOLEAN) { + snprintf (buf, sizeof (buf), "%d", g_value_get_boolean (value)); + xmlNewChild (xvalue, NULL, + (const xmlChar *)"boolean", + (const xmlChar *)buf); + } else if (type == G_TYPE_STRING) { + xmlNewTextChild (xvalue, NULL, + (const xmlChar *)"string", + (const xmlChar *)g_value_get_string (value)); + } else if (type == G_TYPE_DOUBLE) { + g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value)); + xmlNewChild (xvalue, NULL, + (const xmlChar *)"double", + (const xmlChar *)buf); + } else if (type == SOUP_TYPE_DATE) { + SoupDate *date = g_value_get_boxed (value); + char *timestamp = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC); + xmlNewChild (xvalue, NULL, + (const xmlChar *)"dateTime.iso8601", + (const xmlChar *)timestamp); + g_free (timestamp); + } else if (type == SOUP_TYPE_BYTE_ARRAY) { + GByteArray *ba = g_value_get_boxed (value); + char *encoded; + + encoded = g_base64_encode (ba->data, ba->len); + xmlNewChild (xvalue, NULL, + (const xmlChar *)"base64", + (const xmlChar *)encoded); + g_free (encoded); + } else if (type == G_TYPE_HASH_TABLE) { + GHashTable *hash = g_value_get_boxed (value); + xmlNode *struct_node; + + struct_node = xmlNewChild (xvalue, NULL, + (const xmlChar *)"struct", NULL); + g_hash_table_foreach (hash, insert_member, &struct_node); + if (!struct_node) + return FALSE; + } else if (type == G_TYPE_VALUE_ARRAY) { + GValueArray *va = g_value_get_boxed (value); + xmlNode *node; + int i; + + node = xmlNewChild (xvalue, NULL, + (const xmlChar *)"array", NULL); + node = xmlNewChild (node, NULL, + (const xmlChar *)"data", NULL); + for (i = 0; i < va->n_values; i++) { + if (!insert_value (node, &va->values[i])) + return FALSE; + } + } else + return FALSE; + + return TRUE; +} + +/** + * soup_xmlrpc_build_method_call: + * @method_name: the name of the XML-RPC method + * @params: arguments to @method + * @n_params: length of @params + * + * This creates an XML-RPC methodCall and returns it as a string. + * This is the low-level method that soup_xmlrpc_request_new() and + * soup_xmlrpc_call() are built on. + * + * @params is an array of #GValue representing the parameters to + * @method. (It is *not* a #GValueArray, although if you have a + * #GValueArray, you can just pass its %values and %n_values fields.) + * + * The correspondence between glib types and XML-RPC types is: + * + * int: #int (%G_TYPE_INT) + * boolean: #gboolean (%G_TYPE_BOOLEAN) + * string: #char* (%G_TYPE_STRING) + * double: #double (%G_TYPE_DOUBLE) + * datetime.iso8601: #SoupDate (%SOUP_TYPE_DATE) + * base64: #GByteArray (%SOUP_TYPE_BYTE_ARRAY) + * struct: #GHashTable (%G_TYPE_HASH_TABLE) + * array: #GValueArray (%G_TYPE_VALUE_ARRAY) + * + * For structs, use a #GHashTable that maps strings to #GValue; + * soup_value_hash_new() and related methods can help with this. + * + * Return value: the text of the methodCall, or %NULL on error + **/ +char * +soup_xmlrpc_build_method_call (const char *method_name, + GValue *params, int n_params) +{ + xmlDoc *doc; + xmlNode *node, *param; + xmlChar *xmlbody; + int i, len; + char *body; + + doc = xmlNewDoc ((const xmlChar *)"1.0"); + doc->standalone = FALSE; + doc->encoding = xmlCharStrdup ("UTF-8"); + + node = xmlNewDocNode (doc, NULL, (const xmlChar *)"methodCall", NULL); + xmlDocSetRootElement (doc, node); + xmlNewChild (node, NULL, (const xmlChar *)"methodName", + (const xmlChar *)method_name); + + node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL); + for (i = 0; i < n_params; i++) { + param = xmlNewChild (node, NULL, + (const xmlChar *)"param", NULL); + if (!insert_value (param, ¶ms[i])) { + xmlFreeDoc (doc); + return NULL; + } + } + + xmlDocDumpMemory (doc, &xmlbody, &len); + body = g_strndup ((char *)xmlbody, len); + xmlFree (xmlbody); + xmlFreeDoc (doc); + return body; +} + +static SoupMessage * +soup_xmlrpc_request_newv (const char *uri, const char *method_name, va_list args) +{ + SoupMessage *msg; + GValueArray *params; + char *body; + + params = soup_value_array_from_args (args); + if (!params) + return NULL; + + body = soup_xmlrpc_build_method_call (method_name, params->values, + params->n_values); + g_value_array_free (params); + if (!body) + return NULL; + + msg = soup_message_new ("POST", uri); + soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE, + body, strlen (body)); + return msg; +} + +/** + * soup_xmlrpc_request_new: + * @uri: URI of the XML-RPC service + * @method_name: the name of the XML-RPC method to invoke at @uri + * @...: parameters for @method + * + * Creates an XML-RPC methodCall and returns a #SoupMessage, ready + * to send, for that method call. + * + * The parameters are passed as type/value pairs; ie, first a #GType, + * and then a value of the appropriate type, finally terminated by + * %G_TYPE_INVALID. + * + * Return value: a #SoupMessage encoding the indicated XML-RPC + * request. + **/ +SoupMessage * +soup_xmlrpc_request_new (const char *uri, const char *method_name, ...) +{ + SoupMessage *msg; + va_list args; + + va_start (args, method_name); + msg = soup_xmlrpc_request_newv (uri, method_name, args); + va_end (args); + return msg; +} + +/** + * soup_xmlrpc_build_method_response: + * @value: the return value + * + * This creates a (successful) XML-RPC methodResponse and returns it + * as a string. To create a fault response, use + * soup_xmlrpc_build_fault(). + * + * The glib type to XML-RPC type mapping is as with + * soup_xmlrpc_build_method_call(), qv. + * + * Return value: the text of the methodResponse, or %NULL on error + **/ +char * +soup_xmlrpc_build_method_response (GValue *value) +{ + xmlDoc *doc; + xmlNode *node; + xmlChar *xmlbody; + char *body; + int len; + + doc = xmlNewDoc ((const xmlChar *)"1.0"); + doc->standalone = FALSE; + doc->encoding = xmlCharStrdup ("UTF-8"); + + node = xmlNewDocNode (doc, NULL, + (const xmlChar *)"methodResponse", NULL); + xmlDocSetRootElement (doc, node); + + node = xmlNewChild (node, NULL, (const xmlChar *)"params", NULL); + node = xmlNewChild (node, NULL, (const xmlChar *)"param", NULL); + if (!insert_value (node, value)) { + xmlFreeDoc (doc); + return FALSE; + } + + xmlDocDumpMemory (doc, &xmlbody, &len); + body = g_strndup ((char *)xmlbody, len); + xmlFree (xmlbody); + xmlFreeDoc (doc); + return body; +} + +static char * +soup_xmlrpc_build_faultv (int fault_code, const char *fault_format, va_list args) +{ + xmlDoc *doc; + xmlNode *node, *member; + GValue value; + xmlChar *xmlbody; + char *fault_string, *body; + int len; + + fault_string = g_strdup_vprintf (fault_format, args); + + doc = xmlNewDoc ((const xmlChar *)"1.0"); + doc->standalone = FALSE; + doc->encoding = xmlCharStrdup ("UTF-8"); + + node = xmlNewDocNode (doc, NULL, + (const xmlChar *)"methodResponse", NULL); + xmlDocSetRootElement (doc, node); + node = xmlNewDocNode (doc, NULL, (const xmlChar *)"fault", NULL); + node = xmlNewDocNode (doc, NULL, (const xmlChar *)"value", NULL); + node = xmlNewDocNode (doc, NULL, (const xmlChar *)"struct", NULL); + + memset (&value, 0, sizeof (value)); + + member = xmlNewDocNode (doc, NULL, (const xmlChar *)"member", NULL); + xmlNewDocNode (doc, NULL, + (const xmlChar *)"name", (const xmlChar *)"faultCode"); + g_value_init (&value, G_TYPE_INT); + g_value_set_int (&value, fault_code); + insert_value (member, &value); + g_value_unset (&value); + + member = xmlNewDocNode (doc, NULL, (const xmlChar *)"member", NULL); + xmlNewDocNode (doc, NULL, + (const xmlChar *)"name", (const xmlChar *)"faultString"); + g_value_init (&value, G_TYPE_STRING); + g_value_take_string (&value, fault_string); + insert_value (member, &value); + g_value_unset (&value); + + xmlDocDumpMemory (doc, &xmlbody, &len); + body = g_strndup ((char *)xmlbody, len); + xmlFree (xmlbody); + xmlFreeDoc (doc); + + return body; +} + +/** + * soup_xmlrpc_build_fault: + * @fault_code: the fault code + * @fault_format: a printf()-style format string + * @...: the parameters to @fault_format + * + * This creates an XML-RPC fault response and returns it as a string. + * (To create a successful response, use + * soup_xmlrpc_build_method_response().) + * + * Return value: the text of the fault + **/ +char * +soup_xmlrpc_build_fault (int fault_code, const char *fault_format, ...) +{ + va_list args; + char *body; + + va_start (args, fault_format); + body = soup_xmlrpc_build_faultv (fault_code, fault_format, args); + va_end (args); + return body; +} + +/** + * soup_xmlrpc_set_response: + * @msg: an XML-RPC request + * @type: the type of the response value + * @...: the response value + * + * Sets the status code and response body of @msg to indicate a + * successful XML-RPC call, with a return value given by @type and the + * following varargs argument, of the type indicated by @type. + **/ +void +soup_xmlrpc_set_response (SoupMessage *msg, GType type, ...) +{ + va_list args; + GValue value; + char *body; + + va_start (args, type); + SOUP_VALUE_GETV (&value, type, args); + va_end (args); + + body = soup_xmlrpc_build_method_response (&value); + g_value_unset (&value); + soup_message_set_status (msg, SOUP_STATUS_OK); + soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE, + body, strlen (body)); +} + +/** + * soup_xmlrpc_set_fault: + * @msg: an XML-RPC request + * @fault_code: the fault code + * @fault_format: a printf()-style format string + * @...: the parameters to @fault_format + * + * Sets the status code and response body of @msg to indicate an + * unsuccessful XML-RPC call, with the error described by @fault_code + * and @fault_format. + **/ +void +soup_xmlrpc_set_fault (SoupMessage *msg, int fault_code, + const char *fault_format, ...) +{ + va_list args; + char *body; + + va_start (args, fault_format); + body = soup_xmlrpc_build_faultv (fault_code, fault_format, args); + va_end (args); + + soup_message_set_status (msg, SOUP_STATUS_OK); + soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE, + body, strlen (body)); +} + + + +static gboolean +parse_value (xmlNode *xmlvalue, GValue *value) +{ + xmlNode *typenode; + const char *typename; + xmlChar *content; + + memset (value, 0, sizeof (GValue)); + + typenode = find_real_node (xmlvalue->children); + if (!typenode) { + /* If no type node, it's a string */ + content = xmlNodeGetContent (typenode); + g_value_init (value, G_TYPE_STRING); + g_value_set_string (value, (char *)content); + xmlFree (content); + return TRUE; + } + + typename = (const char *)typenode->name; + + if (!strcmp (typename, "i4") || !strcmp (typename, "int")) { + content = xmlNodeGetContent (typenode); + g_value_init (value, G_TYPE_INT); + g_value_set_int (value, atoi ((char *)content)); + xmlFree (content); + } else if (!strcmp (typename, "boolean")) { + content = xmlNodeGetContent (typenode); + g_value_init (value, G_TYPE_BOOLEAN); + g_value_set_boolean (value, atoi ((char *)content)); + xmlFree (content); + } else if (!strcmp (typename, "string")) { + content = xmlNodeGetContent (typenode); + g_value_init (value, G_TYPE_STRING); + g_value_set_string (value, (char *)content); + xmlFree (content); + } else if (!strcmp (typename, "double")) { + content = xmlNodeGetContent (typenode); + g_value_init (value, G_TYPE_DOUBLE); + g_value_set_double (value, g_ascii_strtod ((char *)content, NULL)); + xmlFree (content); + } else if (!strcmp (typename, "dateTime.iso8601")) { + content = xmlNodeGetContent (typenode); + g_value_init (value, SOUP_TYPE_DATE); + g_value_take_boxed (value, soup_date_new_from_string ((char *)content)); + xmlFree (content); + } else if (!strcmp (typename, "base64")) { + GByteArray *ba; + guchar *decoded; + gsize len; + + content = xmlNodeGetContent (typenode); + decoded = g_base64_decode ((char *)content, &len); + ba = g_byte_array_sized_new (len); + g_byte_array_append (ba, decoded, len); + g_free (decoded); + xmlFree (content); + g_value_init (value, SOUP_TYPE_BYTE_ARRAY); + g_value_take_boxed (value, ba); + } else if (!strcmp (typename, "struct")) { + xmlNode *member, *child, *mname, *mxval; + GHashTable *hash; + GValue mgval; + + hash = soup_value_hash_new (); + for (member = find_real_node (typenode->children); + member; + member = find_real_node (member->next)) { + if (strcmp ((const char *)member->name, "member") != 0) { + g_hash_table_destroy (hash); + return FALSE; + } + mname = mxval = NULL; + memset (&mgval, 0, sizeof (mgval)); + + for (child = find_real_node (member->children); + child; + child = find_real_node (child->next)) { + if (!strcmp ((const char *)child->name, "name")) + mname = child; + else if (!strcmp ((const char *)child->name, "value")) + mxval = child; + else + break; + } + + if (!mname || !mxval || !parse_value (mxval, &mgval)) { + g_hash_table_destroy (hash); + return FALSE; + } + + content = xmlNodeGetContent (mname); + soup_value_hash_insert_value (hash, (char *)content, &mgval); + xmlFree (content); + } + g_value_init (value, G_TYPE_HASH_TABLE); + g_value_take_boxed (value, hash); + } else if (!strcmp (typename, "array")) { + xmlNode *data, *xval; + GValueArray *array; + GValue gval; + + data = find_real_node (typenode->children); + if (!data || strcmp ((const char *)data->name, "data") != 0) + return FALSE; + + array = g_value_array_new (1); + for (xval = find_real_node (data->children); + xval; + xval = find_real_node (xval->next)) { + memset (&gval, 0, sizeof (gval)); + if (strcmp ((const char *)xval->name, "value") != 0 || + !parse_value (xval, &gval)) { + g_value_array_free (array); + return FALSE; + } + + g_value_array_append (array, &gval); + g_value_unset (&gval); + } + g_value_init (value, G_TYPE_VALUE_ARRAY); + g_value_take_boxed (value, array); + } else + return FALSE; + + return TRUE; +} + +/** + * soup_xmlrpc_parse_method_call: + * @method_call: the XML-RPC methodCall string + * @length: the length of @method_call, or -1 if it is NUL-terminated + * @method_name: on return, the methodName from @method_call + * @params: on return, the parameters from @method_call + * + * Parses @method_call to get the name and parameters, and returns the + * parameter values in a #GValueArray; see also + * soup_xmlrpc_extract_method_call(), which is more convenient if you + * know in advance what the types of the parameters will be. + * + * Return value: success or failure. + **/ +gboolean +soup_xmlrpc_parse_method_call (const char *method_call, int length, + char **method_name, GValueArray **params) +{ + xmlDoc *doc; + xmlNode *node, *param, *xval; + xmlChar *xmlMethodName = NULL; + gboolean success = FALSE; + GValue value; + + doc = xmlParseMemory (method_call, + length == -1 ? strlen (method_call) : length); + if (!doc) + return FALSE; + + node = xmlDocGetRootElement (doc); + if (!node || strcmp ((const char *)node->name, "methodCall") != 0) + goto fail; + + node = find_real_node (node->children); + if (!node || strcmp ((const char *)node->name, "methodName") != 0) + goto fail; + xmlMethodName = xmlNodeGetContent (node); + + node = find_real_node (node->next); + if (!node || strcmp ((const char *)node->name, "params") != 0) + goto fail; + + *params = g_value_array_new (1); + param = find_real_node (node->children); + while (param && !strcmp ((const char *)param->name, "param")) { + xval = find_real_node (param->children); + if (!xval || !strcmp ((const char *)xval->name, "value") || + !parse_value (xval, &value)) { + g_value_array_free (*params); + goto fail; + } + g_value_array_append (*params, &value); + g_value_unset (&value); + } + + success = TRUE; + *method_name = g_strdup ((char *)xmlMethodName); + +fail: + xmlFreeDoc (doc); + if (xmlMethodName) + xmlFree (xmlMethodName); + return success; +} + +/** + * soup_xmlrpc_extract_method_call: + * @method_call: the XML-RPC methodCall string + * @length: the length of @method_call, or -1 if it is NUL-terminated + * @method_name: on return, the methodName from @method_call + * @...: return types and locations for parameters + * + * Parses @method_call to get the name and parameters, and puts + * the parameters into variables of the appropriate types. + * + * The parameters are handled similarly to + * @soup_xmlrpc_build_method_call, with pairs of types and values, + * terminated by %G_TYPE_INVALID, except that values are pointers to + * variables of the indicated type, rather than values of the type. + * + * See also soup_xmlrpc_parse_method_call(), which can be used if + * you don't know the types of the parameters. + * + * Return value: success or failure. + **/ +gboolean +soup_xmlrpc_extract_method_call (const char *method_call, int length, + char **method_name, ...) +{ + GValueArray *params; + gboolean success; + va_list args; + + if (!soup_xmlrpc_parse_method_call (method_call, length, + method_name, ¶ms)) + return FALSE; + + va_start (args, method_name); + success = soup_value_array_to_args (params, args); + va_end (args); + + g_value_array_free (params); + return success; +} + +/** + * soup_xmlrpc_parse_method_response: + * @method_response: the XML-RPC methodResponse string + * @length: the length of @method_response, or -1 if it is NUL-terminated + * @value: on return, the return value from @method_call + * @error: error return value + * + * Parses @method_response and returns the return value in @value. If + * @method_response is a fault, @value will be unchanged, and @error + * will be set to an error of type %SOUP_XMLRPC_FAULT, with the error + * #code containing the fault code, and the error #message containing + * the fault string. (If @method_response cannot be parsed at all, + * soup_xmlrpc_parse_method_response() will return %FALSE, but @error + * will be unset.) + * + * Return value: %TRUE if a return value was parsed, %FALSE if the + * response could not be parsed, or contained a fault. + **/ +gboolean +soup_xmlrpc_parse_method_response (const char *method_response, int length, + GValue *value, GError **error) +{ + xmlDoc *doc; + xmlNode *node; + gboolean success = FALSE; + + doc = xmlParseMemory (method_response, + length == -1 ? strlen (method_response) : length); + if (!doc) + return FALSE; + + node = xmlDocGetRootElement (doc); + if (!node || strcmp ((const char *)node->name, "methodResponse") != 0) + goto fail; + + node = find_real_node (node->children); + if (!node) + goto fail; + + if (!strcmp ((const char *)node->name, "fault") && error) { + int fault_code = -1; + xmlChar *fault_string = NULL; + + for (node = find_real_node (node->children); + node; + node = find_real_node (node->next)) { + if (!strcmp ((const char *)node->name, "faultCode")) { + xmlChar *content = xmlNodeGetContent (node); + fault_code = atoi ((char *)content); + xmlFree (content); + } else if (!strcmp ((const char *)node->name, "faultString")) { + fault_string = xmlNodeGetContent (node); + } else { + if (fault_string) + xmlFree (fault_string); + goto fail; + } + } + if (fault_code != -1 && fault_string != NULL) { + g_set_error (error, SOUP_XMLRPC_FAULT, + fault_code, "%s", fault_string); + } + if (fault_string) + xmlFree (fault_string); + } else if (!strcmp ((const char *)node->name, "params")) { + node = find_real_node (node->children); + if (!node || strcmp ((const char *)node->name, "param") != 0) + goto fail; + node = find_real_node (node->children); + if (!node || strcmp ((const char *)node->name, "value") != 0) + goto fail; + if (!parse_value (node, value)) + goto fail; + success = TRUE; + } + +fail: + xmlFreeDoc (doc); + return success; +} + +/** + * soup_xmlrpc_extract_method_response: + * @method_response: the XML-RPC methodResponse string + * @length: the length of @method_response, or -1 if it is NUL-terminated + * @error: error return value + * @type: the expected type of the return value + * @...: location for return value + * + * Parses @method_response and extracts the return value into + * a variable of the correct type. + * + * If @method_response is a fault, the return value will be unset, + * and @error will be set to an error of type %SOUP_XMLRPC_FAULT, with + * the error #code containing the fault code, and the error #message + * containing the fault string. (If @method_response cannot be parsed + * at all, soup_xmlrpc_extract_method_response() will return %FALSE, + * but @error will be unset.) + * + * Return value: %TRUE if a return value was parsed, %FALSE if the + * response was of the wrong type, or contained a fault. + **/ +gboolean +soup_xmlrpc_extract_method_response (const char *method_response, int length, + GError **error, GType type, ...) +{ + GValue value; + va_list args; + + if (!soup_xmlrpc_parse_method_response (method_response, length, + &value, error)) + return FALSE; + if (!G_VALUE_HOLDS (&value, type)) + return FALSE; + + va_start (args, type); + SOUP_VALUE_GETV (&value, type, args); + va_end (args); + + return TRUE; +} + + +GQuark +soup_xmlrpc_error_quark (void) +{ + static GQuark error; + if (!error) + error = g_quark_from_static_string ("soup_xmlrpc_error_quark"); + return error; +} + +GQuark +soup_xmlrpc_fault_quark (void) +{ + static GQuark error; + if (!error) + error = g_quark_from_static_string ("soup_xmlrpc_fault_quark"); + return error; +} + +static xmlNode * +find_real_node (xmlNode *node) +{ + while (node && (node->type == XML_COMMENT_NODE || + xmlIsBlankNode (node))) + node = node->next; + return node; +} diff --git a/libsoup/soup-xmlrpc.h b/libsoup/soup-xmlrpc.h new file mode 100644 index 0000000..faf84f6 --- /dev/null +++ b/libsoup/soup-xmlrpc.h @@ -0,0 +1,67 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Red Hat, Inc. + */ + +#ifndef SOUP_XMLRPC_H +#define SOUP_XMLRPC_H 1 + +#include + +G_BEGIN_DECLS + +/* XML-RPC client */ +char *soup_xmlrpc_build_method_call (const char *method_name, + GValue *params, + int n_params); +SoupMessage *soup_xmlrpc_request_new (const char *uri, + const char *method_name, + ...); +gboolean soup_xmlrpc_parse_method_response (const char *method_response, + int length, + GValue *value, + GError **error); +gboolean soup_xmlrpc_extract_method_response (const char *method_response, + int length, + GError **error, + GType type, + ...); + +/* XML-RPC server */ +gboolean soup_xmlrpc_parse_method_call (const char *method_call, + int length, + char **method_name, + GValueArray **params); +gboolean soup_xmlrpc_extract_method_call (const char *method_call, + int length, + char **method_name, + ...); +char *soup_xmlrpc_build_method_response (GValue *value); +char *soup_xmlrpc_build_fault (int fault_code, + const char *fault_format, + ...); +void soup_xmlrpc_set_response (SoupMessage *msg, + GType type, + ...); +void soup_xmlrpc_set_fault (SoupMessage *msg, + int fault_code, + const char *fault_format, + ...); + + +/* Errors */ +#define SOUP_XMLRPC_ERROR soup_xmlrpc_error_quark() +GQuark soup_xmlrpc_error_quark (void); + +typedef enum { + SOUP_XMLRPC_ERROR_ARGUMENTS, + SOUP_XMLRPC_ERROR_RETVAL +} SoupXMLRPCError; + +#define SOUP_XMLRPC_FAULT soup_xmlrpc_fault_quark() +GQuark soup_xmlrpc_fault_quark (void); + + +G_END_DECLS + +#endif /* SOUP_XMLRPC_H */ diff --git a/libsoup/soup.h b/libsoup/soup.h index da67d87..6ed7828 100644 --- a/libsoup/soup.h +++ b/libsoup/soup.h @@ -10,12 +10,27 @@ extern "C" { #endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include #include #include #include +#include #include +#include +#include #ifdef __cplusplus } diff --git a/tests/Makefile.am b/tests/Makefile.am index 9929507..3f9ce55 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -9,42 +9,48 @@ LIBS = $(top_builddir)/libsoup/libsoup-$(SOUP_API_VERSION).la noinst_PROGRAMS = \ context-test \ + continue-test \ date \ - dict \ dns \ get \ getbug \ header-parsing \ ntlm-test \ - revserver \ simple-httpd \ simple-proxy \ uri-parsing \ + $(CURL_TESTS) \ $(APACHE_TESTS) \ $(SSL_TESTS) \ $(XMLRPC_TESTS) -auth_test_SOURCES = auth-test.c apache-wrapper.c apache-wrapper.h -context_test_SOURCES = context-test.c -date_SOURCES = date.c -dict_SOURCES = dict.c +TEST_SRCS = test-utils.c test-utils.h + +auth_test_SOURCES = auth-test.c $(TEST_SRCS) +context_test_SOURCES = context-test.c $(TEST_SRCS) +continue_test_SOURCES = continue-test.c $(TEST_SRCS) +date_SOURCES = date.c $(TEST_SRCS) dns_SOURCES = dns.c get_SOURCES = get.c getbug_SOURCES = getbug.c -header_parsing_SOURCES = header-parsing.c -ntlm_test_SOURCES = ntlm-test.c -proxy_test_SOURCES = proxy-test.c apache-wrapper.c apache-wrapper.h -pull_api_SOURCES = pull-api.c apache-wrapper.c apache-wrapper.h -revserver_SOURCES = revserver.c +header_parsing_SOURCES = header-parsing.c $(TEST_SRCS) +ntlm_test_SOURCES = ntlm-test.c $(TEST_SRCS) +proxy_test_SOURCES = proxy-test.c $(TEST_SRCS) +pull_api_SOURCES = pull-api.c $(TEST_SRCS) +query_test_SOURCES = query-test.c $(TEST_SRCS) +server_auth_test_SOURCES = server-auth-test.c $(TEST_SRCS) simple_httpd_SOURCES = simple-httpd.c simple_proxy_SOURCES = simple-proxy.c -ssl_test_SOURCES = ssl-test.c -uri_parsing_SOURCES = uri-parsing.c -xmlrpc_test_SOURCES = xmlrpc-test.c apache-wrapper.c apache-wrapper.h +ssl_test_SOURCES = ssl-test.c $(TEST_SRCS) +uri_parsing_SOURCES = uri-parsing.c $(TEST_SRCS) +xmlrpc_test_SOURCES = xmlrpc-test.c $(TEST_SRCS) if HAVE_APACHE APACHE_TESTS = auth-test proxy-test pull-api endif +if HAVE_CURL +CURL_TESTS = query-test server-auth-test +endif if HAVE_SSL SSL_TESTS = ssl-test endif @@ -52,7 +58,17 @@ if HAVE_XMLRPC_EPI_PHP XMLRPC_TESTS = xmlrpc-test endif -TESTS = context-test date header-parsing uri-parsing ntlm-test $(APACHE_TESTS) $(SSL_TESTS) $(XMLRPC_TESTS) +TESTS = \ + context-test \ + continue-test \ + date \ + header-parsing \ + uri-parsing \ + ntlm-test \ + $(APACHE_TESTS) \ + $(CURL_TESTS) \ + $(SSL_TESTS) \ + $(XMLRPC_TESTS) EXTRA_DIST = \ libsoup.supp \ diff --git a/tests/apache-wrapper.c b/tests/apache-wrapper.c deleted file mode 100644 index bc859ae..0000000 --- a/tests/apache-wrapper.c +++ /dev/null @@ -1,69 +0,0 @@ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#ifdef HAVE_APACHE - -#include -#include - -#include "apache-wrapper.h" - -static gboolean -apache_cmd (char *cmd) -{ - char *argv[8]; - char *cwd, *conf; - int status; - gboolean ok; - - cwd = g_get_current_dir (); - conf = g_build_filename (cwd, "httpd.conf", NULL); - - argv[0] = APACHE_HTTPD; - argv[1] = "-d"; - argv[2] = cwd; - argv[3] = "-f"; - argv[4] = conf; - argv[5] = "-k"; - argv[6] = cmd; - argv[7] = NULL; - - ok = g_spawn_sync (cwd, argv, NULL, 0, NULL, NULL, - NULL, NULL, &status, NULL); - if (ok) - ok = (status == 0); - - g_free (cwd); - g_free (conf); - - return ok; -} - -gboolean -apache_init (void) -{ - return apache_cmd ("start"); -} - -void -apache_cleanup (void) -{ - pid_t pid; - char *contents; - - if (g_file_get_contents ("httpd.pid", &contents, NULL, NULL)) { - pid = strtoul (contents, NULL, 10); - g_free (contents); - } else - pid = 0; - - apache_cmd ("graceful-stop"); - - if (pid) { - while (kill (pid, 0) == 0) - g_usleep (100); - } -} - -#endif /* HAVE_APACHE */ diff --git a/tests/apache-wrapper.h b/tests/apache-wrapper.h deleted file mode 100644 index 58302a9..0000000 --- a/tests/apache-wrapper.h +++ /dev/null @@ -1,4 +0,0 @@ -#include - -gboolean apache_init (void); -void apache_cleanup (void); diff --git a/tests/auth-test.c b/tests/auth-test.c index cf4473d..4ef5e3c 100644 --- a/tests/auth-test.c +++ b/tests/auth-test.c @@ -12,24 +12,9 @@ #include "libsoup/soup-auth.h" #include "libsoup/soup-session.h" -#include "apache-wrapper.h" +#include "test-utils.h" GMainLoop *loop; -int errors = 0; -gboolean debug = FALSE; - -static void -dprintf (const char *format, ...) -{ - va_list args; - - if (!debug) - return; - - va_start (args, format); - vprintf (format, args); - va_end (args); -} typedef struct { /* Explanation of what you should see */ @@ -181,16 +166,16 @@ identify_auth (SoupMessage *msg) const char *header; int num; - header = soup_message_get_header (msg->request_headers, - "Authorization"); + header = soup_message_headers_get (msg->request_headers, + "Authorization"); if (!header) return 0; if (!g_ascii_strncasecmp (header, "Basic ", 6)) { char *token; - int len; + gsize len; - token = soup_base64_decode (header + 6, &len); + token = (char *)g_base64_decode (header + 6, &len); num = token[len - 1] - '0'; g_free (token); } else { @@ -216,46 +201,45 @@ handler (SoupMessage *msg, gpointer data) auth = identify_auth (msg); - dprintf (" %d %s (using %s)\n", msg->status_code, msg->reason_phrase, - auths[auth]); + debug_printf (1, " %d %s (using %s)\n", + msg->status_code, msg->reason_phrase, + auths[auth]); if (*expected) { exp = *expected - '0'; if (auth != exp) { - dprintf (" expected %s!\n", auths[exp]); + debug_printf (1, " expected %s!\n", auths[exp]); errors++; } memmove (expected, expected + 1, strlen (expected)); } else { - dprintf (" expected to be finished\n"); + debug_printf (1, " expected to be finished\n"); errors++; } } static void authenticate (SoupSession *session, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password, gpointer data) + SoupAuth *auth, gboolean retrying, gpointer data) { int *i = data; + char *username, *password; + char num; - if (tests[*i].provided[0]) { - *username = g_strdup_printf ("user%c", tests[*i].provided[0]); - *password = g_strdup_printf ("realm%c", tests[*i].provided[0]); - } -} - -static void -reauthenticate (SoupSession *session, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password, gpointer data) -{ - int *i = data; + if (!tests[*i].provided[0]) + return; + if (retrying) { + if (!tests[*i].provided[1]) + return; + num = tests[*i].provided[1]; + } else + num = tests[*i].provided[0]; - if (tests[*i].provided[0] && tests[*i].provided[1]) { - *username = g_strdup_printf ("user%c", tests[*i].provided[1]); - *password = g_strdup_printf ("realm%c", tests[*i].provided[1]); - } + username = g_strdup_printf ("user%c", num); + password = g_strdup_printf ("realm%c", num); + soup_auth_authenticate (auth, username, password); + g_free (username); + g_free (password); } static void @@ -266,46 +250,44 @@ bug271540_sent (SoupMessage *msg, gpointer data) int auth = identify_auth (msg); if (!*authenticated && auth) { - dprintf (" using auth on message %d before authenticating!!??\n", n); + debug_printf (1, " using auth on message %d before authenticating!!??\n", n); errors++; } else if (*authenticated && !auth) { - dprintf (" sent unauthenticated message %d after authenticating!\n", n); + debug_printf (1, " sent unauthenticated message %d after authenticating!\n", n); errors++; } } static void bug271540_authenticate (SoupSession *session, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password, gpointer data) + SoupAuth *auth, gboolean retrying, gpointer data) { int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#")); gboolean *authenticated = data; - if (strcmp (auth_type, "Basic") != 0 || - strcmp (auth_realm, "realm1") != 0) + if (strcmp (soup_auth_get_scheme_name (auth), "Basic") != 0 || + strcmp (soup_auth_get_realm (auth), "realm1") != 0) return; if (!*authenticated) { - dprintf (" authenticating message %d\n", n); - *username = g_strdup ("user1"); - *password = g_strdup ("realm1"); + debug_printf (1, " authenticating message %d\n", n); + soup_auth_authenticate (auth, "user1", "realm1"); *authenticated = TRUE; } else { - dprintf (" asked to authenticate message %d after authenticating!\n", n); + debug_printf (1, " asked to authenticate message %d after authenticating!\n", n); errors++; } } static void -bug271540_finished (SoupMessage *msg, gpointer data) +bug271540_finished (SoupSession *session, SoupMessage *msg, gpointer data) { int *left = data; int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#")); if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - dprintf (" got status '%d %s' on message %d!\n", - msg->status_code, msg->reason_phrase, n); + debug_printf (1, " got status '%d %s' on message %d!\n", + msg->status_code, msg->reason_phrase, n); errors++; } @@ -314,6 +296,62 @@ bug271540_finished (SoupMessage *msg, gpointer data) g_main_loop_quit (loop); } +static void +digest_nonce_authenticate (SoupSession *session, SoupMessage *msg, + SoupAuth *auth, gboolean retrying, gpointer data) +{ + if (retrying) + return; + + if (strcmp (soup_auth_get_scheme_name (auth), "Digest") != 0 || + strcmp (soup_auth_get_realm (auth), "realm1") != 0) + return; + + soup_auth_authenticate (auth, "user1", "realm1"); +} + +static void +digest_nonce_unauthorized (SoupMessage *msg, gpointer data) +{ + gboolean *got_401 = data; + *got_401 = TRUE; +} + +static void +do_digest_nonce_test (SoupSession *session, + const char *nth, const char *uri, + gboolean expect_401, gboolean expect_signal) +{ + SoupMessage *msg; + gboolean got_401; + + msg = soup_message_new (SOUP_METHOD_GET, uri); + if (expect_signal) { + g_signal_connect (session, "authenticate", + G_CALLBACK (digest_nonce_authenticate), + NULL); + } + soup_message_add_status_code_handler (msg, "got_headers", + SOUP_STATUS_UNAUTHORIZED, + G_CALLBACK (digest_nonce_unauthorized), + &got_401); + got_401 = FALSE; + soup_session_send_message (session, msg); + if (got_401 != expect_401) { + debug_printf (1, " %s request %s a 401 Unauthorized!\n", nth, + got_401 ? "got" : "did not get"); + errors++; + } + if (msg->status_code != SOUP_STATUS_OK) { + debug_printf (1, " %s request got status %d %s!\n", nth, + msg->status_code, msg->reason_phrase); + errors++; + } + if (errors == 0) + debug_printf (1, " %s request succeeded\n", nth); + g_object_unref (msg); +} + int main (int argc, char **argv) { @@ -321,39 +359,22 @@ main (int argc, char **argv) SoupMessage *msg; char *base_uri, *uri, *expected; gboolean authenticated; - int i, opt; - - g_type_init (); - g_thread_init (NULL); - - while ((opt = getopt (argc, argv, "d")) != -1) { - switch (opt) { - case 'd': - debug = TRUE; - break; - default: - fprintf (stderr, "Usage: %s [-d]\n", argv[0]); - return 1; - } - } + int i; + + test_init (argc, argv, NULL); + apache_init (); - if (!apache_init ()) { - fprintf (stderr, "Could not start apache\n"); - return 1; - } base_uri = "http://localhost:47524/"; - session = soup_session_async_new (); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), &i); - g_signal_connect (session, "reauthenticate", - G_CALLBACK (reauthenticate), &i); for (i = 0; i < ntests; i++) { - dprintf ("Test %d: %s\n", i + 1, tests[i].explanation); + debug_printf (1, "Test %d: %s\n", i + 1, tests[i].explanation); uri = g_strconcat (base_uri, tests[i].url, NULL); - dprintf (" GET %s\n", uri); + debug_printf (1, " GET %s\n", uri); msg = soup_message_new (SOUP_METHOD_GET, uri); g_free (uri); @@ -364,39 +385,41 @@ main (int argc, char **argv) expected = g_strdup (tests[i].expected); soup_message_add_status_code_handler ( - msg, SOUP_STATUS_UNAUTHORIZED, - SOUP_HANDLER_PRE_BODY, handler, expected); + msg, "got_headers", SOUP_STATUS_UNAUTHORIZED, + G_CALLBACK (handler), expected); soup_message_add_status_code_handler ( - msg, SOUP_STATUS_OK, SOUP_HANDLER_PRE_BODY, - handler, expected); + msg, "got_headers", SOUP_STATUS_OK, + G_CALLBACK (handler), expected); soup_session_send_message (session, msg); if (msg->status_code != SOUP_STATUS_UNAUTHORIZED && msg->status_code != SOUP_STATUS_OK) { - dprintf (" %d %s !\n", msg->status_code, - msg->reason_phrase); + debug_printf (1, " %d %s !\n", msg->status_code, + msg->reason_phrase); errors++; } if (*expected) { - dprintf (" expected %d more round(s)\n", - (int)strlen (expected)); + debug_printf (1, " expected %d more round(s)\n", + (int)strlen (expected)); errors++; } g_free (expected); - if (msg->status_code != tests[i].final_status) - dprintf (" expected %d\n", tests[i].final_status); + if (msg->status_code != tests[i].final_status) { + debug_printf (1, " expected %d\n", + tests[i].final_status); + } - dprintf ("\n"); + debug_printf (1, "\n"); g_object_unref (msg); } soup_session_abort (session); g_object_unref (session); - /* And now for a regression test */ + /* And now for some regression tests */ - dprintf ("Regression test for bug 271540:\n"); - session = soup_session_async_new (); + debug_printf (1, "Testing pipelined auth (bug 271540):\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); authenticated = FALSE; g_signal_connect (session, "authenticate", @@ -416,19 +439,100 @@ main (int argc, char **argv) loop = g_main_loop_new (NULL, TRUE); g_main_loop_run (loop); + + debug_printf (1, "\nTesting digest nonce expiration:\n"); + + /* We test two different things here: + * + * 1. If we get a 401 response with + * "WWW-Authenticate: Digest stale=true...", we should + * retry and succeed *without* the session asking for a + * password again. + * + * 2. If we get a successful response with + * "Authentication-Info: nextnonce=...", we should update + * the nonce automatically so as to avoid getting a + * stale nonce error on the next request. + * + * In our Apache config, /Digest/realm1 and + * /Digest/realm1/expire are set up to use the same auth info, + * but only the latter has an AuthDigestNonceLifetime (of 2 + * seconds). The way nonces work in Apache, a nonce received + * from /Digest/realm1 will still expire in + * /Digest/realm1/expire, but it won't issue a nextnonce for a + * request in /Digest/realm1. This lets us test both + * behaviors. + * + * The expected conversation is: + * + * First message + * GET /Digest/realm1 + * + * 401 Unauthorized + * WWW-Authenticate: Digest nonce=A + * + * [emit 'authenticate'] + * + * GET /Digest/realm1 + * Authorization: Digest nonce=A + * + * 200 OK + * [No Authentication-Info] + * + * [sleep 2 seconds: nonce A is no longer valid, but we have no + * way of knowing that] + * + * Second message + * GET /Digest/realm1/expire/ + * Authorization: Digest nonce=A + * + * 401 Unauthorized + * WWW-Authenticate: Digest stale=true nonce=B + * + * GET /Digest/realm1/expire/ + * Authorization: Digest nonce=B + * + * 200 OK + * Authentication-Info: nextnonce=C + * + * [sleep 1 second] + * + * Third message + * GET /Digest/realm1/expire/ + * Authorization: Digest nonce=C + * [nonce=B would work here too] + * + * 200 OK + * Authentication-Info: nextnonce=D + * + * [sleep 1 second; nonces B and C are no longer valid, but D is] + * + * Fourth message + * GET /Digest/realm1/expire/ + * Authorization: Digest nonce=D + * + * 200 OK + * Authentication-Info: nextnonce=D + * + */ + + uri = g_strconcat (base_uri, "Digest/realm1/", NULL); + do_digest_nonce_test (session, "First", uri, TRUE, TRUE); + g_free (uri); + sleep (2); + uri = g_strconcat (base_uri, "Digest/realm1/expire/", NULL); + do_digest_nonce_test (session, "Second", uri, TRUE, FALSE); + sleep (1); + do_digest_nonce_test (session, "Third", uri, FALSE, FALSE); + sleep (1); + do_digest_nonce_test (session, "Fourth", uri, FALSE, FALSE); + g_free (uri); + g_main_loop_unref (loop); - g_main_context_unref (g_main_context_default ()); soup_session_abort (session); g_object_unref (session); - apache_cleanup (); - - dprintf ("\n"); - if (errors) { - printf ("auth-test: %d error(s). Run with '-d' for details\n", - errors); - } else - printf ("auth-test: OK\n"); - return errors; + test_cleanup (); + return errors != 0; } diff --git a/tests/context-test.c b/tests/context-test.c index b44f070..fe1b1ba 100644 --- a/tests/context-test.c +++ b/tests/context-test.c @@ -18,138 +18,75 @@ #include #include #include -#include #include #include -gboolean debug = FALSE; -int errors = 0; -GThread *server_thread; -char *base_uri; - -static void -dprintf (const char *format, ...) -{ - va_list args; +#include "test-utils.h" - if (!debug) - return; +char *base_uri; - va_start (args, format); - vprintf (format, args); - va_end (args); -} +typedef struct { + SoupServer *server; + SoupMessage *msg; + GSource *timeout; +} SlowData; static void -request_failed (SoupMessage *msg, gpointer timeout) +request_failed (SoupMessage *msg, gpointer data) { + SlowData *sd = data; + if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) - g_source_destroy (timeout); + g_source_destroy (sd->timeout); + g_free (sd); } static gboolean add_body_chunk (gpointer data) { - SoupMessage *msg = data; + SlowData *sd = data; - soup_message_add_chunk (msg, SOUP_BUFFER_STATIC, - "OK\r\n", 4); - soup_message_add_final_chunk (msg); - soup_message_io_unpause (msg); - g_object_unref (msg); + soup_message_body_append (sd->msg->response_body, + SOUP_MEMORY_STATIC, "OK\r\n", 4); + soup_message_body_complete (sd->msg->response_body); + soup_server_unpause_message (sd->server, sd->msg); + g_object_unref (sd->msg); return FALSE; } static void -server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) { - GSource *timeout; + SlowData *sd; - if (soup_method_get_id (msg->method) != SOUP_METHOD_ID_GET) { + if (msg->method != SOUP_METHOD_GET) { soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); return; } - if (!strcmp (context->path, "/shutdown")) { - soup_server_quit (context->server); - return; - } - soup_message_set_status (msg, SOUP_STATUS_OK); - if (!strcmp (context->path, "/fast")) { + if (!strcmp (path, "/fast")) { soup_message_set_response (msg, "text/plain", - SOUP_BUFFER_STATIC, "OK\r\n", 4); + SOUP_MEMORY_STATIC, "OK\r\n", 4); return; } - soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (msg), - SOUP_TRANSFER_CHUNKED); + soup_message_headers_set_encoding (msg->response_headers, + SOUP_ENCODING_CHUNKED); g_object_ref (msg); - soup_message_io_pause (msg); - - timeout = soup_add_timeout ( - soup_server_get_async_context (context->server), - 200, add_body_chunk, msg); + soup_server_pause_message (server, msg); + + sd = g_new (SlowData, 1); + sd->server = server; + sd->msg = msg; + sd->timeout = soup_add_timeout ( + soup_server_get_async_context (server), + 200, add_body_chunk, sd); g_signal_connect (msg, "finished", - G_CALLBACK (request_failed), timeout); -} - -static gpointer -run_server_thread (gpointer user_data) -{ - SoupServer *server = user_data; - - soup_server_add_handler (server, NULL, NULL, - server_callback, NULL, NULL); - soup_server_run (server); - g_object_unref (server); - - return NULL; -} - -static guint -create_server (void) -{ - SoupServer *server; - GMainContext *async_context; - guint port; - - async_context = g_main_context_new (); - server = soup_server_new (SOUP_SERVER_PORT, 0, - SOUP_SERVER_ASYNC_CONTEXT, async_context, - NULL); - g_main_context_unref (async_context); - - if (!server) { - fprintf (stderr, "Unable to bind server\n"); - exit (1); - } - - port = soup_server_get_port (server); - server_thread = g_thread_create (run_server_thread, server, TRUE, NULL); - - return port; -} - -static void -shutdown_server (void) -{ - SoupSession *session; - char *uri; - SoupMessage *msg; - - session = soup_session_sync_new (); - uri = g_build_filename (base_uri, "shutdown", NULL); - msg = soup_message_new ("GET", uri); - soup_session_send_message (session, msg); - g_object_unref (msg); - g_free (uri); - - soup_session_abort (session); - g_object_unref (session); - - g_thread_join (server_thread); + G_CALLBACK (request_failed), sd); } /* Test 1: An async session in another thread with its own @@ -168,7 +105,7 @@ do_test1 (void) { GMainLoop *loop; - dprintf ("Test 1: blocking the main thread does not block other thread\n"); + debug_printf (1, "Test 1: blocking the main thread does not block other thread\n"); test1_cond = g_cond_new (); test1_mutex = g_mutex_new (); @@ -196,7 +133,7 @@ idle_start_test1_thread (gpointer loop) if (g_cond_timed_wait (test1_cond, test1_mutex, &time)) g_thread_join (thread); else { - dprintf (" timeout!\n"); + debug_printf (1, " timeout!\n"); errors++; } @@ -206,7 +143,7 @@ idle_start_test1_thread (gpointer loop) } static void -test1_finished (SoupMessage *msg, gpointer loop) +test1_finished (SoupSession *session, SoupMessage *msg, gpointer loop) { g_main_loop_quit (loop); } @@ -225,24 +162,25 @@ test1_thread (gpointer user_data) g_mutex_unlock (test1_mutex); async_context = g_main_context_new (); - session = soup_session_async_new_with_options ( + session = soup_test_session_new ( + SOUP_TYPE_SESSION_ASYNC, SOUP_SESSION_ASYNC_CONTEXT, async_context, NULL); g_main_context_unref (async_context); uri = g_build_filename (base_uri, "slow", NULL); - dprintf (" send_message\n"); + debug_printf (1, " send_message\n"); msg = soup_message_new ("GET", uri); soup_session_send_message (session, msg); if (msg->status_code != SOUP_STATUS_OK) { - dprintf (" unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); + debug_printf (1, " unexpected status: %d %s\n", + msg->status_code, msg->reason_phrase); errors++; } g_object_unref (msg); - dprintf (" queue_message\n"); + debug_printf (1, " queue_message\n"); msg = soup_message_new ("GET", uri); loop = g_main_loop_new (async_context, FALSE); g_object_ref (msg); @@ -250,8 +188,8 @@ test1_thread (gpointer user_data) g_main_loop_run (loop); g_main_loop_unref (loop); if (msg->status_code != SOUP_STATUS_OK) { - dprintf (" unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); + debug_printf (1, " unexpected status: %d %s\n", + msg->status_code, msg->reason_phrase); errors++; } g_object_unref (msg); @@ -279,24 +217,25 @@ do_test2 (void) char *uri; SoupMessage *msg; - dprintf ("Test 2: a session with its own context is independent of the main loop.\n"); + debug_printf (1, "Test 2: a session with its own context is independent of the main loop.\n"); idle = g_idle_add_full (G_PRIORITY_HIGH, idle_test2_fail, NULL, NULL); async_context = g_main_context_new (); - session = soup_session_async_new_with_options ( + session = soup_test_session_new ( + SOUP_TYPE_SESSION_ASYNC, SOUP_SESSION_ASYNC_CONTEXT, async_context, NULL); g_main_context_unref (async_context); uri = g_build_filename (base_uri, "slow", NULL); - dprintf (" send_message\n"); + debug_printf (1, " send_message\n"); msg = soup_message_new ("GET", uri); soup_session_send_message (session, msg); if (msg->status_code != SOUP_STATUS_OK) { - dprintf (" unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); + debug_printf (1, " unexpected status: %d %s\n", + msg->status_code, msg->reason_phrase); errors++; } g_object_unref (msg); @@ -311,56 +250,29 @@ do_test2 (void) static gboolean idle_test2_fail (gpointer user_data) { - dprintf (" idle ran!\n"); + debug_printf (1, " idle ran!\n"); errors++; return FALSE; } -static void -quit (int sig) -{ - /* Exit cleanly on ^C in case we're valgrinding. */ - exit (0); -} - int main (int argc, char **argv) { - int opt; - guint port; - - g_type_init (); - g_thread_init (NULL); - signal (SIGINT, quit); - - while ((opt = getopt (argc, argv, "d")) != -1) { - switch (opt) { - case 'd': - debug = TRUE; - break; - default: - fprintf (stderr, "Usage: %s [-d]\n", - argv[0]); - exit (1); - } - } + SoupServer *server; + + test_init (argc, argv, NULL); - port = create_server (); - base_uri = g_strdup_printf ("http://localhost:%u/", port); + server = soup_test_server_new (TRUE); + soup_server_add_handler (server, NULL, server_callback, NULL, NULL); + base_uri = g_strdup_printf ("http://localhost:%u/", + soup_server_get_port (server)); do_test1 (); do_test2 (); - shutdown_server (); g_free (base_uri); - g_main_context_unref (g_main_context_default ()); - - dprintf ("\n"); - if (errors) { - printf ("context-test: %d error(s). Run with '-d' for details\n", - errors); - } else - printf ("context-test: OK\n"); + + test_cleanup (); return errors != 0; } diff --git a/tests/continue-test.c b/tests/continue-test.c new file mode 100644 index 0000000..cba7abb --- /dev/null +++ b/tests/continue-test.c @@ -0,0 +1,456 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Novell, Inc. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "test-utils.h" + +#define SHORT_BODY "This is a test.\r\n" +#define LONG_BODY (SHORT_BODY SHORT_BODY) + +#define MAX_POST_LENGTH (sizeof (SHORT_BODY)) + +int port; +GSList *events; + +static void +event (SoupMessage *msg, char *side, char *message) +{ + char *data = g_strdup_printf ("%s-%s", side, message); + gboolean record_status = + (!strcmp (data, "server-wrote_headers") || + !strcmp (data, "server-wrote_informational")); + + debug_printf (2, " %s", data); + if (record_status) + debug_printf (2, " (%s)", msg->reason_phrase); + debug_printf (2, "\n"); + + events = g_slist_append (events, data); + if (record_status) + events = g_slist_append (events, GUINT_TO_POINTER (msg->status_code)); +} + +#define EVENT_HANDLER(name) \ +static void \ +name (SoupMessage *msg, gpointer side) \ +{ \ + event (msg, side, #name); \ +} + +EVENT_HANDLER (got_informational); +EVENT_HANDLER (got_headers); +EVENT_HANDLER (got_body); +EVENT_HANDLER (wrote_informational); +EVENT_HANDLER (wrote_headers); +EVENT_HANDLER (wrote_body); +EVENT_HANDLER (finished); + +static void +do_message (const char *path, gboolean long_body, + gboolean expect_continue, gboolean auth, + ...) +{ + SoupSession *session; + SoupMessage *msg; + char *uri, *body; + va_list ap; + const char *expected_event; + char *actual_event; + int expected_status, actual_status; + static int count = 1; + + debug_printf (1, "%d. /%s, %s body, %sExpect, %s password\n", + count++, path, + long_body ? "long" : "short", + expect_continue ? "" : "no ", + auth ? "with" : "without"); + + uri = g_strdup_printf ("http://%slocalhost:%d/%s", + auth ? "user:pass@" : "", + port, path); + msg = soup_message_new ("POST", uri); + g_free (uri); + + body = long_body ? LONG_BODY : SHORT_BODY; + soup_message_set_request (msg, "text/plain", SOUP_MEMORY_STATIC, + body, strlen (body)); + soup_message_headers_append (msg->request_headers, "Connection", "close"); + if (expect_continue) { + soup_message_headers_set_expectations (msg->request_headers, + SOUP_EXPECTATION_CONTINUE); + } + + g_signal_connect (msg, "got_informational", + G_CALLBACK (got_informational), "client"); + g_signal_connect (msg, "got_headers", + G_CALLBACK (got_headers), "client"); + g_signal_connect (msg, "got_body", + G_CALLBACK (got_body), "client"); + g_signal_connect (msg, "wrote_informational", + G_CALLBACK (wrote_informational), "client"); + g_signal_connect (msg, "wrote_headers", + G_CALLBACK (wrote_headers), "client"); + g_signal_connect (msg, "wrote_body", + G_CALLBACK (wrote_body), "client"); + g_signal_connect (msg, "finished", + G_CALLBACK (finished), "client"); + + events = NULL; + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + soup_session_send_message (session, msg); + soup_session_abort (session); + g_object_unref (session); + + va_start (ap, auth); + while ((expected_event = va_arg (ap, const char *))) { + + if (!events) { + actual_event = ""; + debug_printf (1, " Expected '%s', got end of list\n", + expected_event); + errors++; + } else { + actual_event = events->data; + if (strcmp (expected_event, actual_event) != 0) { + debug_printf (1, " Expected '%s', got '%s'\n", + expected_event, actual_event); + errors++; + } + events = g_slist_delete_link (events, events); + } + + if (!strcmp (expected_event, "server-wrote_headers") || + !strcmp (expected_event, "server-wrote_informational")) + expected_status = va_arg (ap, int); + else + expected_status = -1; + if (!strcmp (actual_event, "server-wrote_headers") || + !strcmp (actual_event, "server-wrote_informational")) { + actual_status = GPOINTER_TO_INT (events->data); + events = g_slist_delete_link (events, events); + } else + expected_status = -1; + + if (expected_status != -1 && actual_status != -1 && + expected_status != actual_status) { + debug_printf (1, " Expected status '%s', got '%s'\n", + soup_status_get_phrase (expected_status), + soup_status_get_phrase (actual_status)); + errors++; + } + + g_free (actual_event); + } + va_end (ap); + while (events) { + actual_event = events->data; + debug_printf (1, " Expected to be done, got '%s'\n", actual_event); + errors++; + events = g_slist_delete_link (events, events); + + if (!strcmp (actual_event, "server-wrote_headers") || + !strcmp (actual_event, "server-wrote_informational")) + events = g_slist_delete_link (events, events); + } + g_object_unref (msg); +} + +static void +run_tests (void) +{ + do_message ("unauth", FALSE, FALSE, FALSE, + "client-wrote_headers", + "client-wrote_body", + "server-got_headers", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_CREATED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + do_message ("unauth", TRUE, FALSE, FALSE, + "client-wrote_headers", + "client-wrote_body", + "server-got_headers", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + do_message ("unauth", FALSE, TRUE, FALSE, + "client-wrote_headers", + "server-got_headers", + "server-wrote_informational", SOUP_STATUS_CONTINUE, + "client-got_informational", + "client-wrote_body", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_CREATED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + do_message ("unauth", TRUE, TRUE, FALSE, + "client-wrote_headers", + "server-got_headers", + "server-wrote_headers", SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + + do_message ("auth", FALSE, FALSE, FALSE, + "client-wrote_headers", + "client-wrote_body", + "server-got_headers", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_UNAUTHORIZED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + do_message ("auth", TRUE, FALSE, FALSE, + "client-wrote_headers", + "client-wrote_body", + "server-got_headers", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_UNAUTHORIZED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + do_message ("auth", FALSE, TRUE, FALSE, + "client-wrote_headers", + "server-got_headers", + "server-wrote_headers", SOUP_STATUS_UNAUTHORIZED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + do_message ("auth", TRUE, TRUE, FALSE, + "client-wrote_headers", + "server-got_headers", + "server-wrote_headers", SOUP_STATUS_UNAUTHORIZED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + + do_message ("auth", FALSE, FALSE, TRUE, + "client-wrote_headers", + "client-wrote_body", + "server-got_headers", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_UNAUTHORIZED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-wrote_headers", + "client-wrote_body", + "server-got_headers", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_CREATED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + do_message ("auth", TRUE, FALSE, TRUE, + "client-wrote_headers", + "client-wrote_body", + "server-got_headers", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_UNAUTHORIZED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-wrote_headers", + "client-wrote_body", + "server-got_headers", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + do_message ("auth", FALSE, TRUE, TRUE, + "client-wrote_headers", + "server-got_headers", + "server-wrote_headers", SOUP_STATUS_UNAUTHORIZED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-wrote_headers", + "server-got_headers", + "server-wrote_informational", SOUP_STATUS_CONTINUE, + "client-got_informational", + "client-wrote_body", + "server-got_body", + "server-wrote_headers", SOUP_STATUS_CREATED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); + do_message ("auth", TRUE, TRUE, TRUE, + "client-wrote_headers", + "server-got_headers", + "server-wrote_headers", SOUP_STATUS_UNAUTHORIZED, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-wrote_headers", + "server-got_headers", + "server-wrote_headers", SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE, + "server-wrote_body", + "server-finished", + "client-got_headers", + "client-got_body", + "client-finished", + NULL); +} + + +/* SERVER */ + +static void +server_got_headers (SoupMessage *msg, gpointer server) +{ + /* FIXME */ + if (msg->status_code != SOUP_STATUS_CONTINUE && + msg->status_code != 0) + return; + + if (soup_message_headers_get_expectations (msg->request_headers) & + SOUP_EXPECTATION_CONTINUE) { + const char *length; + + length = soup_message_headers_get (msg->request_headers, + "Content-Length"); + if (length && atoi (length) > MAX_POST_LENGTH) { + soup_message_set_status (msg, SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE); + soup_message_headers_append (msg->response_headers, "Connection", "close"); + } + } +} + +static void +request_started (SoupServer *server, SoupMessage *msg, + SoupClientContext *client, gpointer user_data) +{ + g_signal_connect (msg, "got_headers", + G_CALLBACK (server_got_headers), server); + + g_signal_connect (msg, "got_informational", + G_CALLBACK (got_informational), "server"); + g_signal_connect (msg, "got_headers", + G_CALLBACK (got_headers), "server"); + g_signal_connect (msg, "got_body", + G_CALLBACK (got_body), "server"); + g_signal_connect (msg, "wrote_informational", + G_CALLBACK (wrote_informational), "server"); + g_signal_connect (msg, "wrote_headers", + G_CALLBACK (wrote_headers), "server"); + g_signal_connect (msg, "wrote_body", + G_CALLBACK (wrote_body), "server"); + g_signal_connect (msg, "finished", + G_CALLBACK (finished), "server"); +} + +static gboolean +auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg, + const char *username, const char *password, gpointer user_data) +{ + return !strcmp (username, "user") && !strcmp (password, "pass"); +} + +static void +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + if (msg->method != SOUP_METHOD_POST) { + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + soup_message_headers_append (msg->response_headers, "Connection", "close"); + } else if (msg->request_body->length > MAX_POST_LENGTH) { + soup_message_set_status (msg, SOUP_STATUS_REQUEST_ENTITY_TOO_LARGE); + soup_message_headers_append (msg->response_headers, "Connection", "close"); + } else + soup_message_set_status (msg, SOUP_STATUS_CREATED); +} + +static SoupServer * +setup_server (void) +{ + SoupServer *server; + SoupAuthDomain *auth_domain; + + server = soup_test_server_new (FALSE); + + g_signal_connect (server, "request-started", + G_CALLBACK (request_started), NULL); + + soup_server_add_handler (server, NULL, server_callback, NULL, NULL); + + auth_domain = soup_auth_domain_basic_new ( + SOUP_AUTH_DOMAIN_REALM, "continue-test", + SOUP_AUTH_DOMAIN_ADD_PATH, "/auth", + SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback, + NULL); + soup_server_add_auth_domain (server, auth_domain); + + return server; +} + +/* MAIN */ + +int +main (int argc, char **argv) +{ + SoupServer *server; + + test_init (argc, argv, NULL); + + server = setup_server (); + port = soup_server_get_port (server); + + run_tests (); + + test_cleanup (); + return errors != 0; +} diff --git a/tests/date.c b/tests/date.c index ab87a77..e47d3be 100644 --- a/tests/date.c +++ b/tests/date.c @@ -9,55 +9,94 @@ #include #include -static int errors = 0; +#include "test-utils.h" -#define RFC1123_DATE "Sun, 06 Nov 1994 08:49:37 GMT" -#define RFC850_DATE "Sunday, 06-Nov-94 08:49:37 GMT" -#define ASCTIME_DATE "Sun Nov 6 08:49:37 1994" -#define ISO8601_1_DATE "1994-11-06T08:49:37Z" -#define ISO8601_2_DATE "19941106T08:49:37Z" -#define ISO8601_3_DATE "19941106T08:49:37+00:00" -#define ISO8601_4_DATE "19941106T084937+00:00" +const char *date_tests[] = { + /* rfc1123-date, and broken variants */ + "Sun, 06 Nov 2004 08:09:07 GMT", + "Sun, 6 Nov 2004 08:09:07 GMT", + "Sun, 6 Nov 2004 08:09:07 GMT", -#define EXPECTED 784111777 + /* rfc850-date, and broken variants */ + "Sunday, 06-Nov-04 08:09:07 GMT", + "Sunday, 6-Nov-04 08:09:07 GMT", + "Sunday, 6-Nov-04 08:09:07 GMT", + "Sunday, 06-Nov-104 08:09:07 GMT", + + /* asctime-date, and broken variants */ + "Sun Nov 6 08:09:07 2004", + "Sun Nov 06 08:09:07 2004", + "Sun Nov 6 08:09:07 2004", + "Sun Nov 6 08:09:07 2004 GMT", + + /* ISO 8601 */ + "2004-11-06T08:09:07Z", + "20041106T08:09:07Z", + "20041106T08:09:07+00:00", + "20041106T080907+00:00", + + /* Netscape cookie spec date, and broken variants */ + "Sun, 06-Nov-2004 08:09:07 GMT", + "Sun, 6-Nov-2004 08:09:07 GMT", + "Sun, 6-Nov-2004 08:09:07 GMT", + + /* Original version of Netscape cookie spec, and broken variants */ + "Sun, 06-Nov-04 08:09:07 GMT", + "Sun, 6-Nov-04 08:09:07 GMT", + "Sun, 6-Nov-04 08:09:07 GMT", + "Sun, 06-Nov-104 08:09:07 GMT", + + /* Netscape cookie spec example syntax, and broken variants */ + "Sunday, 06-Nov-04 08:09:07 GMT", + "Sunday, 6-Nov-04 08:09:07 GMT", + "Sunday, 6-Nov-04 08:09:07 GMT", + "Sunday, 06-Nov-104 08:09:07 GMT", + "Sunday, 06-Nov-2004 08:09:07 GMT", + "Sunday, 6-Nov-2004 08:09:07 GMT", + "Sunday, 6-Nov-2004 08:09:07 GMT", + + /* Miscellaneous broken formats seen on the web */ + "Sun 06-Nov-2004 08:9:07", + "Sunday, 06-Nov-04 8:9:07 GMT", + "Sun, 06 Nov 2004 08:09:7 GMT", + "Sun, 06-Nov-2004 08:09:07" +}; + +#define TIME_T 1099728547L +#define TIME_T_STRING "1099728547" static void -check (const char *test, const char *date, time_t got) +check (const char *strdate, SoupDate *date) { - if (got == EXPECTED) + if (date && + date->year == 2004 && date->month == 11 && date->day == 6 && + date->hour == 8 && date->minute == 9 && date->second == 7) { + soup_date_free (date); return; + } - fprintf (stderr, "%s date parsing failed for '%s'.\n", test, date); - fprintf (stderr, " expected: %lu, got: %lu\n\n", - (unsigned long)EXPECTED, (unsigned long)got); + fprintf (stderr, "date parsing failed for '%s'.\n", strdate); + if (date) { + fprintf (stderr, " got: %d %d %d - %d %d %d\n\n", + date->year, date->month, date->day, + date->hour, date->minute, date->second); + soup_date_free (date); + } errors++; } int main (int argc, char **argv) { - char *date; - - check ("RFC1123", RFC1123_DATE, soup_date_parse (RFC1123_DATE)); - check ("RFC850", RFC850_DATE, soup_date_parse (RFC850_DATE)); - check ("asctime", ASCTIME_DATE, soup_date_parse (ASCTIME_DATE)); - check ("iso8610[1]", ISO8601_1_DATE, soup_date_iso8601_parse (ISO8601_1_DATE)); - check ("iso8610[2]", ISO8601_2_DATE, soup_date_iso8601_parse (ISO8601_2_DATE)); - check ("iso8610[3]", ISO8601_3_DATE, soup_date_iso8601_parse (ISO8601_3_DATE)); - check ("iso8610[4]", ISO8601_4_DATE, soup_date_iso8601_parse (ISO8601_4_DATE)); - - date = soup_date_generate (EXPECTED); - if (strcmp (date, RFC1123_DATE) != 0) { - fprintf (stderr, "date generation failed.\n"); - fprintf (stderr, " expected: %s\n got: %s\n\n", - RFC1123_DATE, date); - errors++; + int i; + + test_init (argc, argv, NULL); + + for (i = 0; i < G_N_ELEMENTS (date_tests); i++) { + check (date_tests[i], soup_date_new_from_string (date_tests[i])); } - g_free (date); + check (TIME_T_STRING, soup_date_new_from_time_t (TIME_T)); - if (errors == 0) - printf ("date: OK\n"); - else - fprintf (stderr, "date: %d errors\n", errors); - return errors; + test_cleanup (); + return errors != 0; } diff --git a/tests/dict.c b/tests/dict.c deleted file mode 100644 index afaed0e..0000000 --- a/tests/dict.c +++ /dev/null @@ -1,164 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2001-2003, Ximian, Inc. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -SoupSession *session; -GMainLoop *loop; - -static void -got_response (SoupMessage *msg, gpointer user_data) -{ - SoupSoapResponse *response; - SoupSoapParameter *param, *subparam; - char *word, *dict, *def; - int count = 0; - - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - fprintf (stderr, "%d %s\n", msg->status_code, msg->reason_phrase); - exit (1); - } - - response = soup_soap_message_parse_response (SOUP_SOAP_MESSAGE (msg)); - if (!response) { - fprintf (stderr, "Could not parse SOAP response\n"); - exit (1); - } - - param = soup_soap_response_get_first_parameter_by_name (response, "DefineResult"); - if (!param) { - fprintf (stderr, "Could not find result in SOAP response\n"); - exit (1); - } - - param = soup_soap_parameter_get_first_child_by_name (param, "Definitions"); - if (!param) - goto done; - - for (param = soup_soap_parameter_get_first_child_by_name (param, "Definition"); - param; - param = soup_soap_parameter_get_next_child_by_name (param, "Definition")) { - subparam = soup_soap_parameter_get_first_child_by_name (param, "Word"); - if (!subparam) - continue; - word = soup_soap_parameter_get_string_value (subparam); - - subparam = soup_soap_parameter_get_first_child_by_name (param, "Dictionary"); - if (subparam) - subparam = soup_soap_parameter_get_first_child_by_name (subparam, "Name"); - if (subparam) - dict = soup_soap_parameter_get_string_value (subparam); - else - dict = NULL; - - printf ("% 2d. %s (%s):\n", ++count, word, dict); - g_free (word); - g_free (dict); - - subparam = soup_soap_parameter_get_first_child_by_name (param, "WordDefinition"); - if (subparam) { - def = soup_soap_parameter_get_string_value (subparam); - printf ("%s\n", def); - g_free (def); - } - } - - done: - if (count == 0) - printf ("No definition\n"); - - g_object_unref (response); - g_main_quit (loop); -} - -static void -usage (void) -{ - fprintf (stderr, "Usage: dict [-p proxy_uri] WORD\n"); - exit (1); -} - -int -main (int argc, char **argv) -{ - SoupUri *proxy = NULL; - SoupSoapMessage *msg; - int opt; - - g_type_init (); - g_thread_init (NULL); - - while ((opt = getopt (argc, argv, "p:")) != -1) { - switch (opt) { - case 'p': - proxy = soup_uri_new (optarg); - if (!proxy) { - fprintf (stderr, "Could not parse %s as URI\n", - optarg); - exit (1); - } - break; - - case '?': - usage (); - break; - } - } - argc -= optind; - argv += optind; - - if (argc != 1) - usage (); - - session = soup_session_async_new_with_options ( - SOUP_SESSION_PROXY_URI, proxy, - NULL); - - msg = soup_soap_message_new ("POST", - "http://services.aonaware.com/DictService/DictService.asmx", - FALSE, NULL, NULL, NULL); - if (!msg) { - fprintf (stderr, "Could not create web service request\n"); - exit (1); - } - - soup_message_add_header (SOUP_MESSAGE (msg)->request_headers, - "SOAPAction", "http://services.aonaware.com/webservices/Define"); - - soup_soap_message_start_envelope (msg); - soup_soap_message_start_body (msg); - - soup_soap_message_start_element (msg, "Define", NULL, - "http://services.aonaware.com/webservices/"); - soup_soap_message_add_namespace (msg, NULL, "http://services.aonaware.com/webservices/"); - soup_soap_message_start_element (msg, "word", NULL, NULL); - soup_soap_message_write_string (msg, argv[0]); - soup_soap_message_end_element (msg); - soup_soap_message_end_element (msg); - - soup_soap_message_end_body (msg); - soup_soap_message_end_envelope (msg); - soup_soap_message_persist (msg); - - soup_session_queue_message (session, SOUP_MESSAGE (msg), - got_response, NULL); - - loop = g_main_loop_new (NULL, TRUE); - g_main_run (loop); - g_main_loop_unref (loop); - - return 0; -} diff --git a/tests/dns.c b/tests/dns.c index 755ff69..10d6b98 100644 --- a/tests/dns.c +++ b/tests/dns.c @@ -51,7 +51,8 @@ main (int argc, char **argv) exit (1); } - soup_address_resolve_async (addr, resolve_callback, NULL); + soup_address_resolve_async (addr, NULL, NULL, + resolve_callback, NULL); nlookups++; } diff --git a/tests/get.c b/tests/get.c index 4c196df..fdb3ca1 100644 --- a/tests/get.c +++ b/tests/get.c @@ -22,20 +22,20 @@ SoupSession *session; GMainLoop *loop; gboolean recurse = FALSE, debug = FALSE; -const char *method = SOUP_METHOD_GET; +const char *method; char *base; -SoupUri *base_uri; +SoupURI *base_uri; int pending; GHashTable *fetched_urls; static GPtrArray * -find_hrefs (const SoupUri *base, const char *body, int length) +find_hrefs (SoupURI *base, const char *body, int length) { GPtrArray *hrefs = g_ptr_array_new (); char *buf = g_strndup (body, length); char *start = buf, *end; char *href, *frag; - SoupUri *uri; + SoupURI *uri; while ((start = strstr (start, "href"))) { start += 4; @@ -63,7 +63,7 @@ find_hrefs (const SoupUri *base, const char *body, int length) if (!uri) continue; - if (base->protocol != uri->protocol || + if (base->scheme != uri->scheme || base->port != uri->port || g_ascii_strcasecmp (base->host, uri->host) != 0) { soup_uri_free (uri); @@ -100,9 +100,9 @@ mkdirs (const char *path) } static void -print_header (gpointer name, gpointer value, gpointer data) +print_header (const char *name, const char *value, gpointer data) { - printf ("%s: %s\n", (const char *)name, (const char *)value); + printf ("%s: %s\n", name, value); } static void @@ -111,7 +111,7 @@ get_url (const char *url) char *url_to_get, *slash, *name; SoupMessage *msg; int fd, i; - SoupUri *uri; + SoupURI *uri; GPtrArray *hrefs; const char *header; @@ -164,7 +164,7 @@ get_url (const char *url) printf ("HTTP/1.%d %d %s\n", soup_message_get_http_version (msg), msg->status_code, msg->reason_phrase); - soup_message_foreach_header (msg->response_headers, print_header, NULL); + soup_message_headers_foreach (msg->response_headers, print_header, NULL); printf ("\n"); } else printf ("%s: %d %s\n", name, msg->status_code, msg->reason_phrase); @@ -176,7 +176,7 @@ get_url (const char *url) if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) { if (recurse) unlink (name); - header = soup_message_get_header (msg->response_headers, "Location"); + header = soup_message_headers_get (msg->response_headers, "Location"); if (header) { if (!debug) printf (" -> %s\n", header); @@ -192,17 +192,17 @@ get_url (const char *url) fd = open (name, O_WRONLY | O_CREAT | O_TRUNC, 0644); else fd = STDOUT_FILENO; - write (fd, msg->response.body, msg->response.length); + write (fd, msg->response_body->data, msg->response_body->length); if (!recurse) return; close (fd); - header = soup_message_get_header (msg->response_headers, "Content-Type"); + header = soup_message_headers_get (msg->response_headers, "Content-Type"); if (header && g_ascii_strncasecmp (header, "text/html", 9) != 0) return; uri = soup_uri_new (url); - hrefs = find_hrefs (uri, msg->response.body, msg->response.length); + hrefs = find_hrefs (uri, msg->response_body->data, msg->response_body->length); soup_uri_free (uri); for (i = 0; i < hrefs->len; i++) { get_url (hrefs->pdata[i]); @@ -222,13 +222,15 @@ int main (int argc, char **argv) { const char *cafile = NULL; - SoupUri *proxy = NULL; + SoupURI *proxy = NULL; gboolean synchronous = FALSE; int opt; g_type_init (); g_thread_init (NULL); + method = SOUP_METHOD_GET; + while ((opt = getopt (argc, argv, "c:dhp:rs")) != -1) { switch (opt) { case 'c': diff --git a/tests/getbug.c b/tests/getbug.c index e50f6e0..5cef372 100644 --- a/tests/getbug.c +++ b/tests/getbug.c @@ -13,53 +13,69 @@ #include #include -#include -#include -SoupSession *session; GMainLoop *loop; static void +print_value (GValue *value) +{ + if (G_VALUE_HOLDS_STRING (value)) + printf ("%s", g_value_get_string (value)); + else if (G_VALUE_HOLDS_INT (value)) + printf ("%d", g_value_get_int (value)); + else if (G_VALUE_HOLDS_DOUBLE (value)) + printf ("%f", g_value_get_double (value)); + else if (G_VALUE_TYPE (value) == G_TYPE_VALUE_ARRAY) { + GValueArray *array = g_value_get_boxed (value); + int i; + printf ("[ "); + for (i = 0; i < array->n_values; i++) { + if (i != 0) + printf (", "); + print_value (&array->values[i]); + } + printf (" ]"); + } else + printf ("(%s)", g_type_name (G_VALUE_TYPE (value))); +} + +static void print_struct_field (gpointer key, gpointer value, gpointer data) { - char *str; - if (soup_xmlrpc_value_get_string (value, &str)) - printf ("%s: %s\n", (char *)key, str); + printf ("%s: ", (char *)key); + print_value (value); + printf ("\n"); } static void -got_response (SoupMessage *msg, gpointer user_data) +got_response (SoupSession *session, SoupMessage *msg, gpointer user_data) { - SoupXmlrpcResponse *response; - SoupXmlrpcValue *value; GHashTable *hash; + GError *error = NULL; if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { fprintf (stderr, "%d %s\n", msg->status_code, msg->reason_phrase); exit (1); } - response = soup_xmlrpc_message_parse_response (SOUP_XMLRPC_MESSAGE (msg)); - if (!response) { - fprintf (stderr, "Could not parse XMLRPC response\n"); - exit (1); - } - - value = soup_xmlrpc_response_get_value (response); - if (!value) { - fprintf (stderr, "No response value in XMLRPC response\n"); - exit (1); - } - - if (!soup_xmlrpc_value_get_struct (value, &hash)) { - fprintf (stderr, "Could not extract result from XMLRPC response\n"); + if (!soup_xmlrpc_extract_method_response (msg->response_body->data, + msg->response_body->length, + &error, + G_TYPE_HASH_TABLE, &hash)) { + if (!error) { + fprintf (stderr, "Could not parse XMLRPC response:\n%d %s\n\n", + msg->status_code, msg->reason_phrase); + fprintf (stderr, "%s\n", msg->response_body->data); + } else { + fprintf (stderr, "XML-RPC error: %d %s", + error->code, error->message); + } exit (1); } g_hash_table_foreach (hash, print_struct_field, NULL); g_hash_table_destroy (hash); - g_object_unref (response); g_main_quit (loop); } @@ -73,8 +89,9 @@ usage (void) int main (int argc, char **argv) { - SoupUri *proxy = NULL; - SoupXmlrpcMessage *msg; + SoupSession *session; + SoupURI *proxy = NULL; + SoupMessage *msg; const char *uri = "http://bugzilla.redhat.com/bugzilla/xmlrpc.cgi"; int opt, bug; @@ -113,19 +130,13 @@ main (int argc, char **argv) SOUP_SESSION_PROXY_URI, proxy, NULL); - msg = soup_xmlrpc_message_new (uri); + msg = soup_xmlrpc_request_new (uri, "bugzilla.getBug", + G_TYPE_INT, bug, + G_TYPE_INVALID); if (!msg) { fprintf (stderr, "Could not create web service request to '%s'\n", uri); exit (1); } - - soup_xmlrpc_message_start_call (msg, "bugzilla.getBug"); - soup_xmlrpc_message_start_param (msg); - soup_xmlrpc_message_write_int (msg, bug); - soup_xmlrpc_message_end_param (msg); - soup_xmlrpc_message_end_call (msg); - - soup_xmlrpc_message_persist (msg); soup_session_queue_message (session, SOUP_MESSAGE (msg), got_response, NULL); diff --git a/tests/header-parsing.c b/tests/header-parsing.c index c6b61cd..63fcae3 100644 --- a/tests/header-parsing.c +++ b/tests/header-parsing.c @@ -7,30 +7,20 @@ #include "libsoup/soup-message.h" #include "libsoup/soup-headers.h" -gboolean debug = FALSE; +#include "test-utils.h" -static void -dprintf (const char *format, ...) -{ - va_list args; - - if (!debug) - return; - - va_start (args, format); - vprintf (format, args); - va_end (args); -} +typedef struct { + char *name, *value; +} Header; struct RequestTest { char *description; char *request; int length; + guint status; char *method, *path; - SoupHttpVersion version; - struct { - char *name, *value; - } headers[4]; + SoupHTTPVersion version; + Header headers[4]; } reqtests[] = { /**********************/ /*** VALID REQUESTS ***/ @@ -38,12 +28,14 @@ struct RequestTest { { "HTTP 1.0 request with no headers", "GET / HTTP/1.0\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_0, { { NULL } } }, { "Req w/ 1 header", "GET / HTTP/1.1\r\nHost: example.com\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { NULL } @@ -52,6 +44,7 @@ struct RequestTest { { "Req w/ 1 header, no leading whitespace", "GET / HTTP/1.1\r\nHost:example.com\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { NULL } @@ -60,6 +53,7 @@ struct RequestTest { { "Req w/ 1 header including trailing whitespace", "GET / HTTP/1.1\r\nHost: example.com \r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { NULL } @@ -68,6 +62,7 @@ struct RequestTest { { "Req w/ 1 header, wrapped", "GET / HTTP/1.1\r\nFoo: bar\r\n baz\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Foo", "bar baz" }, { NULL } @@ -76,6 +71,7 @@ struct RequestTest { { "Req w/ 1 header, wrapped with additional whitespace", "GET / HTTP/1.1\r\nFoo: bar \r\n baz\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Foo", "bar baz" }, { NULL } @@ -84,6 +80,7 @@ struct RequestTest { { "Req w/ 1 header, wrapped with tab", "GET / HTTP/1.1\r\nFoo: bar\r\n\tbaz\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Foo", "bar baz" }, { NULL } @@ -92,6 +89,7 @@ struct RequestTest { { "Req w/ 1 header, wrapped before value", "GET / HTTP/1.1\r\nFoo:\r\n bar baz\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Foo", "bar baz" }, { NULL } @@ -100,6 +98,7 @@ struct RequestTest { { "Req w/ 1 header with empty value", "GET / HTTP/1.1\r\nHost:\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "" }, { NULL } @@ -108,6 +107,7 @@ struct RequestTest { { "Req w/ 2 headers", "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { "Connection", "close" }, @@ -117,6 +117,7 @@ struct RequestTest { { "Req w/ 3 headers", "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\nBlah: blah\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { "Connection", "close" }, @@ -127,6 +128,7 @@ struct RequestTest { { "Req w/ 3 headers, 1st wrapped", "GET / HTTP/1.1\r\nFoo: bar\r\n baz\r\nConnection: close\r\nBlah: blah\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Foo", "bar baz" }, { "Connection", "close" }, @@ -137,16 +139,18 @@ struct RequestTest { { "Req w/ 3 headers, 2nd wrapped", "GET / HTTP/1.1\r\nConnection: close\r\nBlah: blah\r\nFoo: bar\r\n baz\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Connection", "close" }, - { "Foo", "bar baz" }, { "Blah", "blah" }, + { "Foo", "bar baz" }, { NULL } } }, { "Req w/ 3 headers, 3rd wrapped", "GET / HTTP/1.1\r\nConnection: close\r\nBlah: blah\r\nFoo: bar\r\n baz\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Connection", "close" }, { "Blah", "blah" }, @@ -155,6 +159,15 @@ struct RequestTest { } }, + { "Req w/ same header multiple times", + "GET / HTTP/1.1\r\nFoo: bar\r\nFoo: baz\r\nFoo: quux\r\n", -1, + SOUP_STATUS_OK, + "GET", "/", SOUP_HTTP_1_1, + { { "Foo", "bar, baz, quux" }, + { NULL } + } + }, + /****************************/ /*** RECOVERABLE REQUESTS ***/ /****************************/ @@ -163,6 +176,18 @@ struct RequestTest { { "Spurious leading CRLF", "\r\nGET / HTTP/1.1\r\nHost: example.com\r\n", -1, + SOUP_STATUS_OK, + "GET", "/", SOUP_HTTP_1_1, + { { "Host", "example.com" }, + { NULL } + } + }, + + /* RFC 2616 section 3.1 says we MUST accept this */ + + { "HTTP/01.01 request", + "GET / HTTP/01.01\r\nHost: example.com\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { NULL } @@ -173,6 +198,7 @@ struct RequestTest { { "LF instead of CRLF after header", "GET / HTTP/1.1\nHost: example.com\nConnection: close\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { "Connection", "close" }, @@ -182,6 +208,7 @@ struct RequestTest { { "LF instead of CRLF after Request-Line", "GET / HTTP/1.1\nHost: example.com\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { NULL } @@ -190,6 +217,7 @@ struct RequestTest { { "Req w/ incorrect whitespace in Request-Line", "GET /\tHTTP/1.1\r\nHost: example.com\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { NULL } @@ -198,6 +226,7 @@ struct RequestTest { { "Req w/ incorrect whitespace after Request-Line", "GET / HTTP/1.1 \r\nHost: example.com\r\n", -1, + SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, { { "Host", "example.com" }, { NULL } @@ -210,72 +239,91 @@ struct RequestTest { { "HTTP 0.9 request; not supported", "GET /\r\n", -1, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "HTTP 1.2 request; not supported (no such thing)", + { "HTTP 1.2 request (no such thing)", "GET / HTTP/1.2\r\n", -1, + SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED, + NULL, NULL, -1, + { { NULL } } + }, + + { "HTTP 2000 request (no such thing)", + "GET / HTTP/2000.0\r\n", -1, + SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED, NULL, NULL, -1, { { NULL } } }, { "Non-HTTP request", "GET / SOUP/1.1\r\nHost: example.com\r\n", -1, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, { "Junk after Request-Line", "GET / HTTP/1.1 blah\r\nHost: example.com\r\n", -1, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, { "NUL in Method", "G\x00T / HTTP/1.1\r\nHost: example.com\r\n", 37, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, { "NUL in Path", "GET /\x00 HTTP/1.1\r\nHost: example.com\r\n", 38, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, { "NUL in Header", "GET / HTTP/1.1\r\nHost: example\x00com\r\n", 37, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, { "Header line with no ':'", "GET / HTTP/1.1\r\nHost example.com\r\n", -1, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, { "No terminating CRLF", "GET / HTTP/1.1\r\nHost: example.com", -1, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, { "Blank line before headers", "GET / HTTP/1.1\r\n\r\nHost: example.com\r\n", -1, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, { "Blank line in headers", "GET / HTTP/1.1\r\nHost: example.com\r\n\r\nConnection: close\r\n", -1, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, { "Blank line after headers", "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n", -1, + SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, @@ -287,12 +335,10 @@ struct ResponseTest { char *description; char *response; int length; - SoupHttpVersion version; + SoupHTTPVersion version; guint status_code; char *reason_phrase; - struct { - char *name, *value; - } headers[4]; + Header headers[4]; } resptests[] = { /***********************/ /*** VALID RESPONSES ***/ @@ -333,6 +379,14 @@ struct ResponseTest { } }, + { "Response w/ same header multiple times", + "HTTP/1.1 200 ok\r\nFoo: bar\r\nFoo: baz\r\nFoo: quux\r\n", -1, + SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", + { { "Foo", "bar, baz, quux" }, + { NULL } + } + }, + { "Response w/ no reason phrase", "HTTP/1.1 200 \r\nFoo: bar\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "", @@ -345,6 +399,16 @@ struct ResponseTest { /*** RECOVERABLE RESPONSES ***/ /*****************************/ + /* RFC 2616 section 3.1 says we MUST accept this */ + + { "HTTP/01.01 response", + "HTTP/01.01 200 ok\r\nFoo: bar\r\n", -1, + SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", + { { "Foo", "bar" }, + { NULL } + } + }, + /* RFC 2616 section 19.3 says we SHOULD accept these */ { "Response w/ LF instead of CRLF after Status-Line", @@ -451,53 +515,82 @@ struct ResponseTest { }; static const int num_resptests = G_N_ELEMENTS (resptests); +struct QValueTest { + char *header_value; + char *acceptable[7]; + char *unacceptable[2]; +} qvaluetests[] = { + { "text/plain; q=0.5, text/html,\t text/x-dvi; q=0.8, text/x-c", + { "text/html", "text/x-c", "text/x-dvi", "text/plain", NULL }, + { NULL }, + }, + + { "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5", + { "text/html;level=1", "text/html", "*/*", "text/html;level=2", + "text/*", NULL }, + { NULL } + }, + + { "gzip;q=1.0, identity; q=0.5, *;q=0", + { "gzip", "identity", NULL }, + { "*", NULL }, + } +}; +static const int num_qvaluetests = G_N_ELEMENTS (qvaluetests); + static void -print_header (gpointer key, gpointer value, gpointer data) +print_header (const char *name, const char *value, gpointer data) { - GSList *values = value; - dprintf (" '%s': '%s'\n", - (char *)key, (char*)values->data); + debug_printf (1, " '%s': '%s'\n", name, value); } -static void -free_headers (gpointer value) +typedef struct { + Header *headers; + int i; + gboolean ok; +} HeaderForeachData; + +static gboolean +check_headers (Header *headers, SoupMessageHeaders *hdrs) { - GSList *headers = value; + int i; + const char *value; - /* We know that there are no duplicate headers in any of the - * test cases, so... - */ - g_free (headers->data); - g_slist_free (headers); + for (i = 0; headers[i].name; i++) { + value = soup_message_headers_get (hdrs, headers[i].name); + if (strcmp (value, headers[i].value) != 0) + return FALSE; + } + return TRUE; } -static int +static void do_request_tests (void) { - int i, len, h, errors = 0; + int i, len, h; char *method, *path; - GSList *values; - SoupHttpVersion version; - GHashTable *headers; + SoupHTTPVersion version; + SoupMessageHeaders *headers; + guint status; - dprintf ("Request tests\n"); - for (i = 0; i < 1; i++) { + debug_printf (1, "Request tests\n"); + for (i = 0; i < num_reqtests; i++) { gboolean ok = TRUE; - dprintf ("%2d. %s (%s): ", i + 1, reqtests[i].description, - reqtests[i].method ? "should parse" : "should NOT parse"); + debug_printf (1, "%2d. %s (%s): ", i + 1, reqtests[i].description, + soup_status_get_phrase (reqtests[i].status)); - headers = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, free_headers); + headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST); method = path = NULL; if (reqtests[i].length == -1) len = strlen (reqtests[i].request); else len = reqtests[i].length; - if (soup_headers_parse_request (reqtests[i].request, len, - headers, &method, &path, - &version)) { + status = soup_headers_parse_request (reqtests[i].request, len, + headers, &method, &path, + &version); + if (SOUP_STATUS_IS_SUCCESSFUL (status)) { if ((reqtests[i].method && strcmp (reqtests[i].method, method) != 0) || !reqtests[i].method) ok = FALSE; if ((reqtests[i].path && strcmp (reqtests[i].path, path) != 0) || !reqtests[i].path) @@ -505,71 +598,66 @@ do_request_tests (void) if (reqtests[i].version != version) ok = FALSE; - for (h = 0; reqtests[i].headers[h].name; h++) { - values = g_hash_table_lookup (headers, reqtests[i].headers[h].name); - if (!values || values->next || - strcmp (reqtests[i].headers[h].value, values->data) != 0) - ok = FALSE; - } - if (g_hash_table_size (headers) != h) + if (!check_headers (reqtests[i].headers, headers)) ok = FALSE; } else { - if (reqtests[i].method) + if (status != reqtests[i].status) ok = FALSE; } if (ok) - dprintf ("OK!\n"); + debug_printf (1, "OK!\n"); else { - dprintf ("BAD!\n"); + debug_printf (1, "BAD!\n"); errors++; if (reqtests[i].method) { - dprintf (" expected: '%s' '%s' 'HTTP/1.%d'\n", - reqtests[i].method, reqtests[i].path, - reqtests[i].version); + debug_printf (1, " expected: '%s' '%s' 'HTTP/1.%d'\n", + reqtests[i].method, + reqtests[i].path, + reqtests[i].version); for (h = 0; reqtests[i].headers[h].name; h++) { - dprintf (" '%s': '%s'\n", - reqtests[i].headers[h].name, - reqtests[i].headers[h].value); + debug_printf (1, " '%s': '%s'\n", + reqtests[i].headers[h].name, + reqtests[i].headers[h].value); } - } else - dprintf (" expected: parse error\n"); + } else { + debug_printf (1, " expected: %s\n", + soup_status_get_phrase (reqtests[i].status)); + } if (method) { - dprintf (" got: '%s' '%s' 'HTTP/1.%d'\n", - method, path, version); - g_hash_table_foreach (headers, print_header, NULL); - } else - dprintf (" got: parse error\n"); + debug_printf (1, " got: '%s' '%s' 'HTTP/1.%d'\n", + method, path, version); + soup_message_headers_foreach (headers, print_header, NULL); + } else { + debug_printf (1, " got: %s\n", + soup_status_get_phrase (status)); + } } g_free (method); g_free (path); - g_hash_table_destroy (headers); + soup_message_headers_free (headers); } - dprintf ("\n"); - - return errors; + debug_printf (1, "\n"); } -static int +static void do_response_tests (void) { - int i, len, h, errors = 0; + int i, len, h; guint status_code; char *reason_phrase; - GSList *values; - SoupHttpVersion version; - GHashTable *headers; + SoupHTTPVersion version; + SoupMessageHeaders *headers; - dprintf ("Response tests\n"); + debug_printf (1, "Response tests\n"); for (i = 0; i < num_resptests; i++) { gboolean ok = TRUE; - dprintf ("%2d. %s (%s): ", i + 1, resptests[i].description, - resptests[i].reason_phrase ? "should parse" : "should NOT parse"); + debug_printf (1, "%2d. %s (%s): ", i + 1, resptests[i].description, + resptests[i].reason_phrase ? "should parse" : "should NOT parse"); - headers = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, free_headers); + headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE); reason_phrase = NULL; if (resptests[i].length == -1) @@ -586,13 +674,7 @@ do_response_tests (void) if ((resptests[i].reason_phrase && strcmp (resptests[i].reason_phrase, reason_phrase) != 0) || !resptests[i].reason_phrase) ok = FALSE; - for (h = 0; resptests[i].headers[h].name; h++) { - values = g_hash_table_lookup (headers, resptests[i].headers[h].name); - if (!values || values->next || - strcmp (resptests[i].headers[h].value, values->data) != 0) - ok = FALSE; - } - if (g_hash_table_size (headers) != h) + if (!check_headers (resptests[i].headers, headers)) ok = FALSE; } else { if (resptests[i].reason_phrase) @@ -600,62 +682,104 @@ do_response_tests (void) } if (ok) - dprintf ("OK!\n"); + debug_printf (1, "OK!\n"); else { - dprintf ("BAD!\n"); + debug_printf (1, "BAD!\n"); errors++; if (resptests[i].reason_phrase) { - dprintf (" expected: 'HTTP/1.%d' '%03d' '%s'\n", - resptests[i].version, - resptests[i].status_code, - resptests[i].reason_phrase); + debug_printf (1, " expected: 'HTTP/1.%d' '%03d' '%s'\n", + resptests[i].version, + resptests[i].status_code, + resptests[i].reason_phrase); for (h = 0; resptests[i].headers[h].name; h++) { - dprintf (" '%s': '%s'\n", - resptests[i].headers[h].name, - resptests[i].headers[h].value); + debug_printf (1, " '%s': '%s'\n", + resptests[i].headers[h].name, + resptests[i].headers[h].value); } } else - dprintf (" expected: parse error\n"); + debug_printf (1, " expected: parse error\n"); if (reason_phrase) { - dprintf (" got: 'HTTP/1.%d' '%03d' '%s'\n", - version, status_code, reason_phrase); - g_hash_table_foreach (headers, print_header, NULL); + debug_printf (1, " got: 'HTTP/1.%d' '%03d' '%s'\n", + version, status_code, reason_phrase); + soup_message_headers_foreach (headers, print_header, NULL); } else - dprintf (" got: parse error\n"); + debug_printf (1, " got: parse error\n"); } g_free (reason_phrase); - g_hash_table_destroy (headers); + soup_message_headers_free (headers); } - dprintf ("\n"); + debug_printf (1, "\n"); +} - return errors; +static void +do_qvalue_tests (void) +{ + int i, j; + GSList *acceptable, *unacceptable, *iter; + gboolean wrong; + + debug_printf (1, "qvalue tests\n"); + for (i = 0; i < num_qvaluetests; i++) { + debug_printf (1, "%2d. %s:\n", i + 1, qvaluetests[i].header_value); + + unacceptable = NULL; + acceptable = soup_header_parse_quality_list (qvaluetests[i].header_value, + &unacceptable); + + debug_printf (1, " acceptable: "); + wrong = FALSE; + if (acceptable) { + for (iter = acceptable, j = 0; iter; iter = iter->next, j++) { + debug_printf (1, "%s ", iter->data); + if (!qvaluetests[i].acceptable[j] || + strcmp (iter->data, qvaluetests[i].acceptable[j]) != 0) + wrong = TRUE; + } + debug_printf (1, "\n"); + } else + debug_printf (1, "(none)\n"); + if (wrong) { + debug_printf (1, " WRONG! expected: "); + for (j = 0; qvaluetests[i].acceptable[j]; j++) + debug_printf (1, "%s ", qvaluetests[i].acceptable[j]); + debug_printf (1, "\n"); + errors++; + } + + debug_printf (1, " unacceptable: "); + wrong = FALSE; + if (unacceptable) { + for (iter = unacceptable, j = 0; iter; iter = iter->next, j++) { + debug_printf (1, "%s ", iter->data); + if (!qvaluetests[i].unacceptable[j] || + strcmp (iter->data, qvaluetests[i].unacceptable[j]) != 0) + wrong = TRUE; + } + debug_printf (1, "\n"); + } else + debug_printf (1, "(none)\n"); + if (wrong) { + debug_printf (1, " WRONG! expected: "); + for (j = 0; qvaluetests[i].unacceptable[j]; j++) + debug_printf (1, "%s ", qvaluetests[i].unacceptable[j]); + debug_printf (1, "\n"); + errors++; + } + + debug_printf (1, "\n"); + } } int main (int argc, char **argv) { - int opt, errors; - - while ((opt = getopt (argc, argv, "d")) != -1) { - switch (opt) { - case 'd': - debug = TRUE; - break; - default: - fprintf (stderr, "Usage: %s [-d]\n", argv[0]); - return 1; - } - } + test_init (argc, argv, NULL); - errors = do_request_tests (); - errors += do_response_tests (); + do_request_tests (); + do_response_tests (); + do_qvalue_tests (); - dprintf ("\n"); - if (errors) { - printf ("header-parsing: %d error(s). Run with '-d' for details\n", - errors); - } else - printf ("header-parsing: OK\n"); - return errors; + test_cleanup (); + return errors != 0; } diff --git a/tests/httpd.conf.in b/tests/httpd.conf.in index ca9ebdf..c2e5881 100644 --- a/tests/httpd.conf.in +++ b/tests/httpd.conf.in @@ -217,6 +217,7 @@ Alias /Basic @srcdir@ Alias /Digest/realm1/realm2/realm1 @srcdir@ Alias /Digest/realm1/realm2 @srcdir@ Alias /Digest/realm1/subdir @srcdir@ +Alias /Digest/realm1/expire @srcdir@ Alias /Digest/realm1/not @srcdir@ Alias /Digest/realm1 @srcdir@ Alias /Digest/realm2 @srcdir@ @@ -231,6 +232,15 @@ Alias /Digest @srcdir@ Require valid-user + + AuthType Digest + AuthName realm1 + AuthUserFile @srcdir@/htdigest + AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1 + AuthDigestNonceLifetime 2 + Require valid-user + + AuthType Digest AuthName realm1 diff --git a/tests/ntlm-test.c b/tests/ntlm-test.c index 30e01da..5a4515b 100644 --- a/tests/ntlm-test.c +++ b/tests/ntlm-test.c @@ -21,25 +21,14 @@ #include #include +#include #include #include -#include #include -gboolean debug = FALSE; +#include "test-utils.h" -static void -dprintf (const char *format, ...) -{ - va_list args; - - if (!debug) - return; - - va_start (args, format); - vprintf (format, args); - va_end (args); -} +GHashTable *connections; typedef enum { NTLM_UNAUTHENTICATED, @@ -57,20 +46,20 @@ typedef enum { #define NTLM_RESPONSE_USER(response) ((response)[87] == 'h' ? NTLM_AUTHENTICATED_ALICE : NTLM_AUTHENTICATED_BOB) static void -server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *client, gpointer data) { GHashTable *connections = data; const char *auth; - char *path; NTLMServerState state, required_user; gboolean not_found = FALSE; - if (soup_method_get_id (msg->method) != SOUP_METHOD_ID_GET) { + if (msg->method != SOUP_METHOD_GET) { soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); return; } - path = soup_uri_to_string (soup_message_get_uri (msg), TRUE); if (!strcmp (path, "/noauth")) required_user = 0; else if (!strncmp (path, "/alice", 6)) @@ -79,10 +68,9 @@ server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) required_user = NTLM_AUTHENTICATED_BOB; if (strstr (path, "/404")) not_found = TRUE; - g_free (path); - state = GPOINTER_TO_INT (g_hash_table_lookup (connections, context->sock)); - auth = soup_message_get_header (msg->request_headers, "Authorization"); + state = GPOINTER_TO_INT (g_hash_table_lookup (connections, soup_client_context_get_socket (client))); + auth = soup_message_headers_get (msg->request_headers, "Authorization"); if (auth && !strncmp (auth, "NTLM ", 5)) { if (!strncmp (auth + 5, NTLM_REQUEST_START, @@ -98,39 +86,36 @@ server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) if (state == NTLM_RECEIVED_REQUEST) { soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED); - soup_message_add_header (msg->response_headers, - "WWW-Authenticate", "NTLM " NTLM_CHALLENGE); + soup_message_headers_append (msg->response_headers, + "WWW-Authenticate", + "NTLM " NTLM_CHALLENGE); state = NTLM_SENT_CHALLENGE; } else if (!required_user || required_user == state) { if (not_found) soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); else { soup_message_set_response (msg, "text/plain", - SOUP_BUFFER_STATIC, + SOUP_MEMORY_STATIC, "OK\r\n", 4); soup_message_set_status (msg, SOUP_STATUS_OK); } } else { soup_message_set_status (msg, SOUP_STATUS_UNAUTHORIZED); - soup_message_add_header (msg->response_headers, - "WWW-Authenticate", "NTLM"); - soup_message_add_header (msg->response_headers, - "Connection", "close"); + soup_message_headers_append (msg->response_headers, + "WWW-Authenticate", "NTLM"); + soup_message_headers_append (msg->response_headers, + "Connection", "close"); } - g_hash_table_insert (connections, context->sock, + g_hash_table_insert (connections, soup_client_context_get_socket (client), GINT_TO_POINTER (state)); } static void authenticate (SoupSession *session, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password, gpointer data) + SoupAuth *auth, gboolean retrying, gpointer user) { - const char *user = data; - - *username = g_strdup (user); - *password = g_strdup ("password"); + soup_auth_authenticate (auth, user, "password"); } typedef struct { @@ -148,8 +133,8 @@ ntlm_prompt_check (SoupMessage *msg, gpointer user_data) if (state->sent_request) return; - header = soup_message_get_header (msg->response_headers, - "WWW-Authenticate"); + header = soup_message_headers_get (msg->response_headers, + "WWW-Authenticate"); if (header && !strcmp (header, "NTLM")) state->got_prompt = TRUE; } @@ -160,8 +145,8 @@ ntlm_challenge_check (SoupMessage *msg, gpointer user_data) NTLMState *state = user_data; const char *header; - header = soup_message_get_header (msg->response_headers, - "WWW-Authenticate"); + header = soup_message_headers_get (msg->response_headers, + "WWW-Authenticate"); if (header && !strncmp (header, "NTLM ", 5)) state->got_challenge = TRUE; } @@ -172,8 +157,8 @@ ntlm_request_check (SoupMessage *msg, gpointer user_data) NTLMState *state = user_data; const char *header; - header = soup_message_get_header (msg->request_headers, - "Authorization"); + header = soup_message_headers_get (msg->request_headers, + "Authorization"); if (header && !strncmp (header, "NTLM " NTLM_REQUEST_START, strlen ("NTLM " NTLM_REQUEST_START))) state->sent_request = TRUE; @@ -185,163 +170,144 @@ ntlm_response_check (SoupMessage *msg, gpointer user_data) NTLMState *state = user_data; const char *header; - header = soup_message_get_header (msg->request_headers, - "Authorization"); + header = soup_message_headers_get (msg->request_headers, + "Authorization"); if (header && !strncmp (header, "NTLM " NTLM_RESPONSE_START, strlen ("NTLM " NTLM_RESPONSE_START))) state->sent_response = TRUE; } -static int -do_message (SoupSession *session, SoupUri *base_uri, const char *path, +static void +do_message (SoupSession *session, SoupURI *base_uri, const char *path, gboolean get_prompt, gboolean do_ntlm, guint status_code) { - SoupUri *uri; + SoupURI *uri; SoupMessage *msg; NTLMState state = { FALSE, FALSE, FALSE, FALSE }; - int errors = 0; - uri = soup_uri_copy (base_uri); - g_free (uri->path); - uri->path = g_strdup (path); + uri = soup_uri_new_with_base (base_uri, path); msg = soup_message_new_from_uri ("GET", uri); soup_uri_free (uri); - soup_message_add_header_handler (msg, "WWW-Authenticate", - SOUP_HANDLER_PRE_BODY, - ntlm_prompt_check, &state); - soup_message_add_header_handler (msg, "WWW-Authenticate", - SOUP_HANDLER_PRE_BODY, - ntlm_challenge_check, &state); + g_signal_connect (msg, "got_headers", + G_CALLBACK (ntlm_prompt_check), &state); + g_signal_connect (msg, "got_headers", + G_CALLBACK (ntlm_challenge_check), &state); g_signal_connect (msg, "wrote-headers", G_CALLBACK (ntlm_request_check), &state); g_signal_connect (msg, "wrote-headers", G_CALLBACK (ntlm_response_check), &state); soup_session_send_message (session, msg); - dprintf (" %-10s -> ", path); + debug_printf (1, " %-10s -> ", path); if (state.got_prompt) { - dprintf (" PROMPT"); + debug_printf (1, " PROMPT"); if (!get_prompt) { - dprintf ("???"); + debug_printf (1, "???"); errors++; } } else if (get_prompt) { - dprintf (" no-prompt???"); + debug_printf (1, " no-prompt???"); errors++; } if (state.sent_request) { - dprintf (" REQUEST"); + debug_printf (1, " REQUEST"); if (!do_ntlm) { - dprintf ("???"); + debug_printf (1, "???"); errors++; } } else if (do_ntlm) { - dprintf (" no-request???"); + debug_printf (1, " no-request???"); errors++; } if (state.got_challenge) { - dprintf (" CHALLENGE"); + debug_printf (1, " CHALLENGE"); if (!do_ntlm) { - dprintf ("???"); + debug_printf (1, "???"); errors++; } } else if (do_ntlm) { - dprintf (" no-challenge???"); + debug_printf (1, " no-challenge???"); errors++; } if (state.sent_response) { - dprintf (" RESPONSE"); + debug_printf (1, " RESPONSE"); if (!do_ntlm) { - dprintf ("???"); + debug_printf (1, "???"); errors++; } } else if (do_ntlm) { - dprintf (" no-response???"); + debug_printf (1, " no-response???"); errors++; } - dprintf (" -> %s", msg->reason_phrase); + debug_printf (1, " -> %s", msg->reason_phrase); if (msg->status_code != status_code) { - dprintf ("???"); + debug_printf (1, "???"); errors++; } - dprintf ("\n"); + debug_printf (1, "\n"); g_object_unref (msg); - return errors; } -static int -do_ntlm_round (SoupUri *base_uri, const char *user) +static void +do_ntlm_round (SoupURI *base_uri, const char *user) { SoupSession *session; - int errors = 0; gboolean use_ntlm = user != NULL; gboolean alice = use_ntlm && !strcmp (user, "alice"); gboolean bob = use_ntlm && !strcmp (user, "bob"); - g_return_val_if_fail (use_ntlm || !alice, 0); + g_return_if_fail (use_ntlm || !alice); - session = soup_session_async_new_with_options ( + session = soup_test_session_new ( + SOUP_TYPE_SESSION_ASYNC, SOUP_SESSION_USE_NTLM, use_ntlm, NULL); g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), (char *)user); - errors += do_message (session, base_uri, "/noauth", - FALSE, use_ntlm, SOUP_STATUS_OK); - errors += do_message (session, base_uri, "/alice", - !use_ntlm || bob, FALSE, - alice ? SOUP_STATUS_OK : - SOUP_STATUS_UNAUTHORIZED); - errors += do_message (session, base_uri, "/alice/404", - !use_ntlm, bob, - alice ? SOUP_STATUS_NOT_FOUND : - SOUP_STATUS_UNAUTHORIZED); - errors += do_message (session, base_uri, "/alice", - !use_ntlm, bob, - alice ? SOUP_STATUS_OK : - SOUP_STATUS_UNAUTHORIZED); - errors += do_message (session, base_uri, "/bob", - !use_ntlm || alice, bob, - bob ? SOUP_STATUS_OK : - SOUP_STATUS_UNAUTHORIZED); - errors += do_message (session, base_uri, "/alice", - !use_ntlm || bob, alice, - alice ? SOUP_STATUS_OK : - SOUP_STATUS_UNAUTHORIZED); + do_message (session, base_uri, "/noauth", + FALSE, use_ntlm, SOUP_STATUS_OK); + do_message (session, base_uri, "/alice", + !use_ntlm || bob, FALSE, + alice ? SOUP_STATUS_OK : + SOUP_STATUS_UNAUTHORIZED); + do_message (session, base_uri, "/alice/404", + !use_ntlm, bob, + alice ? SOUP_STATUS_NOT_FOUND : + SOUP_STATUS_UNAUTHORIZED); + do_message (session, base_uri, "/alice", + !use_ntlm, bob, + alice ? SOUP_STATUS_OK : + SOUP_STATUS_UNAUTHORIZED); + do_message (session, base_uri, "/bob", + !use_ntlm || alice, bob, + bob ? SOUP_STATUS_OK : + SOUP_STATUS_UNAUTHORIZED); + do_message (session, base_uri, "/alice", + !use_ntlm || bob, alice, + alice ? SOUP_STATUS_OK : + SOUP_STATUS_UNAUTHORIZED); soup_session_abort (session); g_object_unref (session); - - return errors; -} - -static int -do_ntlm_tests (SoupUri *base_uri) -{ - int errors = 0; - - dprintf ("Round 1: Non-NTLM Connection\n"); - errors += do_ntlm_round (base_uri, NULL); - dprintf ("Round 2: NTLM Connection, user=alice\n"); - errors += do_ntlm_round (base_uri, "alice"); - dprintf ("Round 3: NTLM Connection, user=bob\n"); - errors += do_ntlm_round (base_uri, "bob"); - - return errors; } static void -quit (int sig) +do_ntlm_tests (SoupURI *base_uri) { - /* Exit cleanly on ^C in case we're valgrinding. */ - exit (0); + debug_printf (1, "Round 1: Non-NTLM Connection\n"); + do_ntlm_round (base_uri, NULL); + debug_printf (1, "Round 2: NTLM Connection, user=alice\n"); + do_ntlm_round (base_uri, "alice"); + debug_printf (1, "Round 3: NTLM Connection, user=bob\n"); + do_ntlm_round (base_uri, "bob"); } int @@ -349,59 +315,26 @@ main (int argc, char **argv) { GMainLoop *loop; SoupServer *server; - int opt; GHashTable *connections; - SoupUri *uri; - int errors; - - g_type_init (); - g_thread_init (NULL); - signal (SIGINT, quit); - - while ((opt = getopt (argc, argv, "d")) != -1) { - switch (opt) { - case 'd': - debug = TRUE; - break; - default: - fprintf (stderr, "Usage: %s [-d]\n", - argv[0]); - exit (1); - } - } + SoupURI *uri; - connections = g_hash_table_new (NULL, NULL); + test_init (argc, argv, NULL); - server = soup_server_new (SOUP_SERVER_PORT, 0, - NULL); - if (!server) { - fprintf (stderr, "Unable to bind server\n"); - exit (1); - } - soup_server_add_handler (server, NULL, NULL, - server_callback, NULL, connections); - soup_server_run_async (server); + server = soup_test_server_new (FALSE); + connections = g_hash_table_new (NULL, NULL); + soup_server_add_handler (server, NULL, + server_callback, connections, NULL); loop = g_main_loop_new (NULL, TRUE); - uri = g_new0 (SoupUri, 1); - uri->protocol = SOUP_PROTOCOL_HTTP; - uri->host = g_strdup ("localhost"); - uri->port = soup_server_get_port (server); - errors = do_ntlm_tests (uri); + uri = soup_uri_new ("http://localhost/"); + soup_uri_set_port (uri, soup_server_get_port (server)); + do_ntlm_tests (uri); soup_uri_free (uri); - soup_server_quit (server); - g_object_unref (server); g_main_loop_unref (loop); g_hash_table_destroy (connections); - g_main_context_unref (g_main_context_default ()); - - dprintf ("\n"); - if (errors) { - printf ("ntlm-test: %d error(s). Run with '-d' for details\n", - errors); - } else - printf ("ntlm-test: OK\n"); + + test_cleanup (); return errors != 0; } diff --git a/tests/proxy-test.c b/tests/proxy-test.c index 2133db7..5d238c8 100644 --- a/tests/proxy-test.c +++ b/tests/proxy-test.c @@ -8,23 +8,7 @@ #include #include "libsoup/soup.h" -#include "apache-wrapper.h" - -int errors = 0; -gboolean debug = FALSE; - -static void -dprintf (const char *format, ...) -{ - va_list args; - - if (!debug) - return; - - va_start (args, format); - vprintf (format, args); - va_end (args); -} +#include "test-utils.h" typedef struct { const char *explanation; @@ -62,21 +46,20 @@ static const char *proxy_names[] = { static void authenticate (SoupSession *session, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password, gpointer data) + SoupAuth *auth, gboolean retrying, gpointer data) { - *username = g_strdup ("user1"); - *password = g_strdup ("realm1"); + if (!retrying) + soup_auth_authenticate (auth, "user1", "realm1"); } static void test_url (const char *url, int proxy, guint expected, gboolean sync) { SoupSession *session; - SoupUri *proxy_uri; + SoupURI *proxy_uri; SoupMessage *msg; - dprintf (" GET %s via %s\n", url, proxy_names[proxy]); + debug_printf (1, " GET %s via %s\n", url, proxy_names[proxy]); if (proxy == UNAUTH_PROXY && expected != SOUP_STATUS_FORBIDDEN) expected = SOUP_STATUS_PROXY_UNAUTHORIZED; @@ -99,9 +82,9 @@ test_url (const char *url, int proxy, guint expected, gboolean sync) soup_session_send_message (session, msg); - dprintf (" %d %s\n", msg->status_code, msg->reason_phrase); + debug_printf (1, " %d %s\n", msg->status_code, msg->reason_phrase); if (msg->status_code != expected) { - dprintf (" EXPECTED %d!\n", expected); + debug_printf (1, " EXPECTED %d!\n", expected); errors++; } @@ -115,8 +98,8 @@ run_test (int i, gboolean sync) { char *http_url, *https_url; - dprintf ("Test %d: %s (%s)\n", i + 1, tests[i].explanation, - sync ? "sync" : "async"); + debug_printf (1, "Test %d: %s (%s)\n", i + 1, tests[i].explanation, + sync ? "sync" : "async"); if (!strncmp (tests[i].url, "http", 4)) { http_url = g_strdup (tests[i].url); @@ -141,46 +124,22 @@ run_test (int i, gboolean sync) g_free (http_url); g_free (https_url); - dprintf ("\n"); + debug_printf (1, "\n"); } int main (int argc, char **argv) { - int i, opt; - - g_type_init (); - g_thread_init (NULL); - - while ((opt = getopt (argc, argv, "d")) != -1) { - switch (opt) { - case 'd': - debug = TRUE; - break; - default: - fprintf (stderr, "Usage: %s [-d]\n", argv[0]); - return 1; - } - } + int i; - if (!apache_init ()) { - fprintf (stderr, "Could not start apache\n"); - return 1; - } + test_init (argc, argv, NULL); + apache_init (); for (i = 0; i < ntests; i++) { run_test (i, FALSE); run_test (i, TRUE); } - apache_cleanup (); - g_main_context_unref (g_main_context_default ()); - - dprintf ("\n"); - if (errors) { - printf ("proxy-test: %d error(s). Run with '-d' for details\n", - errors); - } else - printf ("proxy-test: OK\n"); - return errors; + test_cleanup (); + return errors != 0; } diff --git a/tests/pull-api.c b/tests/pull-api.c index 723cc88..e84eb08 100644 --- a/tests/pull-api.c +++ b/tests/pull-api.c @@ -11,33 +11,16 @@ #include "libsoup/soup.h" #include "libsoup/soup-session.h" -#include "apache-wrapper.h" +#include "test-utils.h" -int errors = 0; -int debug = 0; -char *correct_response; -guint correct_response_len; - -static void -dprintf (int level, const char *format, ...) -{ - va_list args; - - if (debug < level) - return; - - va_start (args, format); - vprintf (format, args); - va_end (args); -} +SoupBuffer *correct_response; static void authenticate (SoupSession *session, SoupMessage *msg, - const char *auth_type, const char *auth_realm, - char **username, char **password, gpointer data) + SoupAuth *auth, gboolean retrying, gpointer data) { - *username = g_strdup ("user2"); - *password = g_strdup ("realm2"); + if (!retrying) + soup_auth_authenticate (auth, "user2", "realm2"); } static void @@ -46,7 +29,7 @@ get_correct_response (const char *uri) SoupSession *session; SoupMessage *msg; - session = soup_session_async_new (); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); msg = soup_message_new (SOUP_METHOD_GET, uri); soup_session_send_message (session, msg); if (msg->status_code != SOUP_STATUS_OK) { @@ -55,8 +38,7 @@ get_correct_response (const char *uri) exit (1); } - correct_response_len = msg->response.length; - correct_response = g_strndup (msg->response.body, correct_response_len); + correct_response = soup_message_body_flatten (msg->response_body); g_object_unref (msg); soup_session_abort (session); @@ -71,6 +53,7 @@ get_correct_response (const char *uri) typedef struct { GMainLoop *loop; + SoupSession *session; SoupMessage *msg; guint timeout; gboolean chunks_ready; @@ -81,8 +64,10 @@ typedef struct { } FullyAsyncData; static void fully_async_got_headers (SoupMessage *msg, gpointer user_data); -static void fully_async_got_chunk (SoupMessage *msg, gpointer user_data); -static void fully_async_finished (SoupMessage *msg, gpointer user_data); +static void fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk, + gpointer user_data); +static void fully_async_finished (SoupSession *session, SoupMessage *msg, + gpointer user_data); static gboolean fully_async_request_chunk (gpointer user_data); static void @@ -98,12 +83,13 @@ do_fully_async_test (SoupSession *session, loop = g_main_loop_new (NULL, FALSE); uri = g_build_filename (base_uri, sub_uri, NULL); - dprintf (1, "GET %s\n", uri); + debug_printf (1, "GET %s\n", uri); msg = soup_message_new (SOUP_METHOD_GET, uri); g_free (uri); ad.loop = loop; + ad.session = session; ad.msg = msg; ad.chunks_ready = FALSE; ad.chunk_wanted = FALSE; @@ -112,8 +98,8 @@ do_fully_async_test (SoupSession *session, ad.expected_status = expected_status; /* Since we aren't going to look at the final value of - * msg->response.body, we set OVERWRITE_CHUNKS, to tell - * libsoup to not even bother generating it. + * msg->response, we set OVERWRITE_CHUNKS, to tell libsoup to + * not even bother generating it. */ soup_message_set_flags (msg, SOUP_MESSAGE_OVERWRITE_CHUNKS); @@ -152,23 +138,23 @@ fully_async_request_chunk (gpointer user_data) FullyAsyncData *ad = user_data; if (!ad->did_first_timeout) { - dprintf (1, " first timeout\n"); + debug_printf (1, " first timeout\n"); ad->did_first_timeout = TRUE; } else - dprintf (2, " timeout\n"); + debug_printf (2, " timeout\n"); ad->timeout = 0; /* ad->chunks_ready and ad->chunk_wanted are used because * there's a race condition between the application requesting * the first chunk, and the message reaching a point where * it's actually ready to read chunks. If chunks_ready has - * been set, we can just call soup_message_io_unpause() to + * been set, we can just call soup_session_unpause_message() to * cause the first chunk to be read. But if it's not, we just * set chunk_wanted, to let the got_headers handler below know * that a chunk has already been requested. */ if (ad->chunks_ready) - soup_message_io_unpause (ad->msg); + soup_session_unpause_message (ad->session, ad->msg); else ad->chunk_wanted = TRUE; @@ -180,15 +166,15 @@ fully_async_got_headers (SoupMessage *msg, gpointer user_data) { FullyAsyncData *ad = user_data; - dprintf (1, " %d %s\n", msg->status_code, msg->reason_phrase); + debug_printf (1, " %d %s\n", msg->status_code, msg->reason_phrase); if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) { /* Let soup handle this one; this got_headers handler * will get called again next time around. */ return; } else if (msg->status_code != SOUP_STATUS_OK) { - dprintf (1, " unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); + debug_printf (1, " unexpected status: %d %s\n", + msg->status_code, msg->reason_phrase); errors++; return; } @@ -202,42 +188,35 @@ fully_async_got_headers (SoupMessage *msg, gpointer user_data) g_signal_connect (msg, "got_chunk", G_CALLBACK (fully_async_got_chunk), ad); if (!ad->chunk_wanted) - soup_message_io_pause (msg); + soup_session_pause_message (ad->session, msg); } static void -fully_async_got_chunk (SoupMessage *msg, gpointer user_data) +fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) { FullyAsyncData *ad = user_data; - dprintf (2, " got chunk from %lu - %lu\n", - (unsigned long) ad->read_so_far, - (unsigned long) ad->read_so_far + msg->response.length); + debug_printf (2, " got chunk from %lu - %lu\n", + (unsigned long) ad->read_so_far, + (unsigned long) ad->read_so_far + chunk->length); /* We've got a chunk, let's process it. In the case of the * test program, that means comparing it against * correct_response to make sure that we got the right data. - * We're using SOUP_MESSAGE_OVERWRITE_CHUNKS, so msg->response - * contains just the latest chunk. ad->read_so_far tells us - * how far we've read so far. - * - * Note that since we're using OVERWRITE_CHUNKS, msg->response - * is only good until we return from this signal handler; if - * you wanted to process it later, you'd need to copy it - * somewhere. */ - if (ad->read_so_far + msg->response.length > correct_response_len) { - dprintf (1, " read too far! (%lu > %lu)\n", - (unsigned long) (ad->read_so_far + msg->response.length), - (unsigned long) correct_response_len); + if (ad->read_so_far + chunk->length > correct_response->length) { + debug_printf (1, " read too far! (%lu > %lu)\n", + (unsigned long) (ad->read_so_far + chunk->length), + (unsigned long) correct_response->length); errors++; - } else if (memcmp (msg->response.body, correct_response + ad->read_so_far, - msg->response.length) != 0) { - dprintf (1, " data mismatch in block starting at %lu\n", - (unsigned long) ad->read_so_far); + } else if (memcmp (chunk->data, + correct_response->data + ad->read_so_far, + chunk->length) != 0) { + debug_printf (1, " data mismatch in block starting at %lu\n", + (unsigned long) ad->read_so_far); errors++; } - ad->read_so_far += msg->response.length; + ad->read_so_far += chunk->length; /* Now pause I/O, and prepare to read another chunk later. * (Again, the timeout just abstractly represents the idea of @@ -245,20 +224,21 @@ fully_async_got_chunk (SoupMessage *msg, gpointer user_data) * point in the future. You wouldn't be using a timeout in a * real program.) */ - soup_message_io_pause (msg); + soup_session_pause_message (ad->session, msg); ad->chunk_wanted = FALSE; ad->timeout = g_timeout_add (10, fully_async_request_chunk, ad); } static void -fully_async_finished (SoupMessage *msg, gpointer user_data) +fully_async_finished (SoupSession *session, SoupMessage *msg, + gpointer user_data) { FullyAsyncData *ad = user_data; if (msg->status_code != ad->expected_status) { - dprintf (1, " unexpected final status: %d %s !\n", - msg->status_code, msg->reason_phrase); + debug_printf (1, " unexpected final status: %d %s !\n", + msg->status_code, msg->reason_phrase); errors++; } @@ -277,17 +257,21 @@ fully_async_finished (SoupMessage *msg, gpointer user_data) typedef struct { GMainLoop *loop; - GByteArray *chunk; + SoupSession *session; + SoupBuffer *chunk; } SyncAsyncData; static void sync_async_send (SoupSession *session, SoupMessage *msg); -static GByteArray *sync_async_read_chunk (SoupMessage *msg); +static gboolean sync_async_is_finished(SoupMessage *msg); +static SoupBuffer *sync_async_read_chunk (SoupMessage *msg); static void sync_async_cleanup (SoupMessage *msg); static void sync_async_got_headers (SoupMessage *msg, gpointer user_data); -static void sync_async_copy_chunk (SoupMessage *msg, gpointer user_data); -static void sync_async_finished (SoupMessage *msg, gpointer user_data); +static void sync_async_copy_chunk (SoupMessage *msg, SoupBuffer *chunk, + gpointer user_data); +static void sync_async_finished (SoupSession *session, SoupMessage *msg, + gpointer user_data); static void do_synchronously_async_test (SoupSession *session, @@ -297,10 +281,10 @@ do_synchronously_async_test (SoupSession *session, SoupMessage *msg; char *uri; gsize read_so_far; - GByteArray *chunk; + SoupBuffer *chunk; uri = g_build_filename (base_uri, sub_uri, NULL); - dprintf (1, "GET %s\n", uri); + debug_printf (1, "GET %s\n", uri); msg = soup_message_new (SOUP_METHOD_GET, uri); g_free (uri); @@ -312,13 +296,13 @@ do_synchronously_async_test (SoupSession *session, /* Send the message, get back headers */ sync_async_send (session, msg); - if (msg->status == SOUP_MESSAGE_STATUS_FINISHED && + if (sync_async_is_finished (msg) && expected_status == SOUP_STATUS_OK) { - dprintf (1, " finished without reading response!\n"); + debug_printf (1, " finished without reading response!\n"); errors++; - } else if (msg->status != SOUP_MESSAGE_STATUS_FINISHED && + } else if (!sync_async_is_finished (msg) && expected_status != SOUP_STATUS_OK) { - dprintf (1, " request failed to fail!\n"); + debug_printf (1, " request failed to fail!\n"); errors++; } @@ -327,34 +311,34 @@ do_synchronously_async_test (SoupSession *session, */ read_so_far = 0; while ((chunk = sync_async_read_chunk (msg))) { - dprintf (2, " read chunk from %lu - %lu\n", - (unsigned long) read_so_far, - (unsigned long) read_so_far + chunk->len); - - if (read_so_far + chunk->len > correct_response_len) { - dprintf (1, " read too far! (%lu > %lu)\n", - (unsigned long) read_so_far + chunk->len, - (unsigned long) correct_response_len); + debug_printf (2, " read chunk from %lu - %lu\n", + (unsigned long) read_so_far, + (unsigned long) read_so_far + chunk->length); + + if (read_so_far + chunk->length > correct_response->length) { + debug_printf (1, " read too far! (%lu > %lu)\n", + (unsigned long) read_so_far + chunk->length, + (unsigned long) correct_response->length); errors++; } else if (memcmp (chunk->data, - correct_response + read_so_far, - chunk->len) != 0) { - dprintf (1, " data mismatch in block starting at %lu\n", - (unsigned long) read_so_far); + correct_response->data + read_so_far, + chunk->length) != 0) { + debug_printf (1, " data mismatch in block starting at %lu\n", + (unsigned long) read_so_far); errors++; } - read_so_far += chunk->len; - g_byte_array_free (chunk, TRUE); + read_so_far += chunk->length; + soup_buffer_free (chunk); } - if (msg->status != SOUP_MESSAGE_STATUS_FINISHED || + if (!sync_async_is_finished (msg) || (msg->status_code == SOUP_STATUS_OK && - read_so_far != correct_response_len)) { - dprintf (1, " loop ended before message was fully read!\n"); + read_so_far != correct_response->length)) { + debug_printf (1, " loop ended before message was fully read!\n"); errors++; } else if (msg->status_code != expected_status) { - dprintf (1, " unexpected final status: %d %s !\n", - msg->status_code, msg->reason_phrase); + debug_printf (1, " unexpected final status: %d %s !\n", + msg->status_code, msg->reason_phrase); errors++; } @@ -382,6 +366,7 @@ sync_async_send (SoupSession *session, SoupMessage *msg) * want to pass that, rather than NULL, here. */ ad->loop = g_main_loop_new (NULL, FALSE); + ad->session = session; g_signal_connect (msg, "got_headers", G_CALLBACK (sync_async_got_headers), ad); @@ -413,42 +398,48 @@ sync_async_got_headers (SoupMessage *msg, gpointer user_data) { SyncAsyncData *ad = user_data; - dprintf (1, " %d %s\n", msg->status_code, msg->reason_phrase); + debug_printf (1, " %d %s\n", msg->status_code, msg->reason_phrase); if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) { /* Let soup handle this one; this got_headers handler * will get called again next time around. */ return; } else if (msg->status_code != SOUP_STATUS_OK) { - dprintf (1, " unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); + debug_printf (1, " unexpected status: %d %s\n", + msg->status_code, msg->reason_phrase); errors++; return; } /* Stop I/O and return to the caller */ - soup_message_io_pause (msg); + soup_session_pause_message (ad->session, msg); g_main_loop_quit (ad->loop); } -/* Tries to read a chunk. Returns %NULL on error/end-of-response. (The - * cases can be distinguished by looking at msg->status and - * msg->status_code.) - */ -static GByteArray * +static gboolean +sync_async_is_finished (SoupMessage *msg) +{ + SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData"); + + /* sync_async_finished clears ad->loop */ + return ad->loop == NULL; +} + +/* Tries to read a chunk. Returns %NULL on error/end-of-response. */ +static SoupBuffer * sync_async_read_chunk (SoupMessage *msg) { SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData"); guint handler; - if (msg->status == SOUP_MESSAGE_STATUS_FINISHED) + if (sync_async_is_finished (msg)) return NULL; ad->chunk = NULL; handler = g_signal_connect (msg, "got_chunk", G_CALLBACK (sync_async_copy_chunk), ad); - soup_message_io_unpause (msg); + soup_session_unpause_message (ad->session, msg); g_main_loop_run (ad->loop); g_signal_handler_disconnect (msg, handler); @@ -456,27 +447,21 @@ sync_async_read_chunk (SoupMessage *msg) } static void -sync_async_copy_chunk (SoupMessage *msg, gpointer user_data) +sync_async_copy_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) { SyncAsyncData *ad = user_data; - /* It's unfortunate that we have to do an extra copy here, - * but the data in msg->response.body won't last beyond - * the invocation of this handler. - */ - ad->chunk = g_byte_array_new (); - g_byte_array_append (ad->chunk, (gpointer)msg->response.body, - msg->response.length); + ad->chunk = soup_buffer_copy (chunk); /* Now pause and return from the g_main_loop_run() call in * sync_async_read_chunk(). */ - soup_message_io_pause (msg); + soup_session_pause_message (ad->session, msg); g_main_loop_quit (ad->loop); } static void -sync_async_finished (SoupMessage *msg, gpointer user_data) +sync_async_finished (SoupSession *session, SoupMessage *msg, gpointer user_data) { SyncAsyncData *ad = user_data; @@ -486,6 +471,8 @@ sync_async_finished (SoupMessage *msg, gpointer user_data) * the final tests there. */ g_main_loop_quit (ad->loop); + g_main_loop_unref (ad->loop); + ad->loop = NULL; } static void @@ -493,7 +480,8 @@ sync_async_cleanup (SoupMessage *msg) { SyncAsyncData *ad = g_object_get_data (G_OBJECT (msg), "SyncAsyncData"); - g_main_loop_unref (ad->loop); + if (ad->loop) + g_main_loop_unref (ad->loop); g_free (ad); } @@ -503,31 +491,15 @@ main (int argc, char **argv) { SoupSession *session; char *base_uri; - int opt; - - g_type_init (); - g_thread_init (NULL); - - while ((opt = getopt (argc, argv, "d")) != -1) { - switch (opt) { - case 'd': - debug++; - break; - default: - fprintf (stderr, "Usage: %s [-d [-d]]\n", argv[0]); - return 1; - } - } - if (!apache_init ()) { - fprintf (stderr, "Could not start apache\n"); - return 1; - } + test_init (argc, argv, NULL); + apache_init (); + base_uri = "http://localhost:47524/"; get_correct_response (base_uri); - dprintf (1, "\nFully async, fast requests\n"); - session = soup_session_async_new (); + debug_printf (1, "\nFully async, fast requests\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), NULL); do_fully_async_test (session, base_uri, "/", @@ -539,8 +511,8 @@ main (int argc, char **argv) soup_session_abort (session); g_object_unref (session); - dprintf (1, "\nFully async, slow requests\n"); - session = soup_session_async_new (); + debug_printf (1, "\nFully async, slow requests\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), NULL); do_fully_async_test (session, base_uri, "/", @@ -552,8 +524,8 @@ main (int argc, char **argv) soup_session_abort (session); g_object_unref (session); - dprintf (1, "\nSynchronously async\n"); - session = soup_session_async_new (); + debug_printf (1, "\nSynchronously async\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), NULL); do_synchronously_async_test (session, base_uri, "/", @@ -566,16 +538,8 @@ main (int argc, char **argv) soup_session_abort (session); g_object_unref (session); - g_free (correct_response); - - apache_cleanup (); - g_main_context_unref (g_main_context_default ()); + soup_buffer_free (correct_response); - dprintf (1, "\n"); - if (errors) { - printf ("pull-api: %d error(s). Run with '-d' for details\n", - errors); - } else - printf ("pull-api: OK\n"); - return errors; + test_cleanup (); + return errors != 0; } diff --git a/tests/query-test.c b/tests/query-test.c new file mode 100644 index 0000000..682e938 --- /dev/null +++ b/tests/query-test.c @@ -0,0 +1,231 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007, 2008 Red Hat, Inc. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "test-utils.h" + +GMainLoop *loop; + +struct { + char *title, *name; + char *result; +} tests[] = { + /* Both fields must be filled in */ + { NULL, "Name", "" }, + { "Mr.", NULL, "" }, + + /* Filled-in but empty is OK */ + { "", "", "Hello, " }, + { "", "Name", "Hello, Name" }, + { "Mr.", "", "Hello, MR. " }, + + /* Simple */ + { "Mr.", "Name", "Hello, MR. Name" }, + + /* Encoding of spaces */ + { "Mr.", "Full Name", "Hello, MR. Full Name" }, + { "Mr. and Mrs.", "Full Name", "Hello, MR. AND MRS. Full Name" }, + + /* Encoding of "+" */ + { "Mr.+Mrs.", "Full Name", "Hello, MR.+MRS. Full Name" }, + + /* Encoding of non-ASCII. */ + { "Se\xC3\xB1or", "Nombre", "Hello, SE\xC3\xB1OR Nombre" }, + + /* Encoding of '%' */ + { "Mr.", "Foo %2f Bar", "Hello, MR. Foo %2f Bar" }, +}; + +static void +do_test (int n, gboolean extra, const char *uri) +{ + GPtrArray *args; + GHashTable *form_data_set; + char *title_arg = NULL, *name_arg = NULL; + char *stdout = NULL; + + debug_printf (1, "%2d. '%s' '%s'%s: ", n * 2 + (extra ? 2 : 1), + tests[n].title ? tests[n].title : "(null)", + tests[n].name ? tests[n].name : "(null)", + extra ? " + extra" : ""); + + form_data_set = g_hash_table_new (g_str_hash, g_str_equal); + + args = g_ptr_array_new (); + g_ptr_array_add (args, "curl"); + g_ptr_array_add (args, "-G"); + if (tests[n].title) { + g_hash_table_insert (form_data_set, "title", tests[n].title); + title_arg = soup_form_encode_urlencoded (form_data_set); + g_hash_table_remove_all (form_data_set); + + g_ptr_array_add (args, "-d"); + g_ptr_array_add (args, title_arg); + } + if (tests[n].name) { + g_hash_table_insert (form_data_set, "name", tests[n].name); + name_arg = soup_form_encode_urlencoded (form_data_set); + g_hash_table_remove_all (form_data_set); + + g_ptr_array_add (args, "-d"); + g_ptr_array_add (args, name_arg); + } + if (extra) { + g_ptr_array_add (args, "-d"); + g_ptr_array_add (args, "extra=something"); + } + g_ptr_array_add (args, (char *)uri); + g_ptr_array_add (args, NULL); + + if (g_spawn_sync (NULL, (char **)args->pdata, NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, + &stdout, NULL, NULL, NULL)) { + if (stdout && !strcmp (stdout, tests[n].result)) + debug_printf (1, "OK!\n"); + else { + debug_printf (1, "WRONG!\n"); + debug_printf (1, " expected '%s', got '%s'\n", + tests[n].result, + stdout ? stdout : "(error)"); + errors++; + } + g_free (stdout); + } else { + debug_printf (1, "ERROR!\n"); + errors++; + } + g_ptr_array_free (args, TRUE); + g_hash_table_destroy (form_data_set); + g_free (title_arg); + g_free (name_arg); +} + +static void +do_query_tests (const char *uri) +{ + int n; + + for (n = 0; n < G_N_ELEMENTS (tests); n++) { + do_test (n, FALSE, uri); + do_test (n, TRUE, uri); + } +} + +GThread *server_thread; + +static void +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + char *title, *name, *fmt; + const char *content_type; + GString *buf; + + if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_HEAD) { + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + return; + } + + if (query) { + title = g_hash_table_lookup (query, "title"); + name = g_hash_table_lookup (query, "name"); + fmt = g_hash_table_lookup (query, "fmt"); + } else + title = name = fmt = NULL; + + buf = g_string_new (NULL); + if (!query || (fmt && !strcmp (fmt, "html"))) { + content_type = "text/html"; + g_string_append (buf, "query-test\r\n"); + if (title && name) { + /* mumble mumble html-escape... */ + g_string_append_printf (buf, "

Hello, %s %s

\r\n", + title, name); + } + g_string_append (buf, "
" + "

Title:

" + "

Name:

" + "

" + "

" + "
\r\n"); + g_string_append (buf, "\r\n"); + } else { + content_type = "text/plain"; + if (title && name) { + char *uptitle = g_ascii_strup (title, -1); + g_string_append_printf (buf, "Hello, %s %s", + uptitle, name); + g_free (uptitle); + } + } + + soup_message_set_response (msg, content_type, + SOUP_MEMORY_TAKE, + buf->str, buf->len); + g_string_free (buf, FALSE); + soup_message_set_status (msg, SOUP_STATUS_OK); +} + +gboolean run_tests = TRUE; + +static GOptionEntry no_test_entry[] = { + { "no-tests", 'n', G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &run_tests, + "Don't run tests, just run the test server", NULL }, + { NULL } +}; + +int +main (int argc, char **argv) +{ + GMainLoop *loop; + SoupServer *server; + guint port; + char *uri_str; + + test_init (argc, argv, no_test_entry); + + server = soup_test_server_new (TRUE); + soup_server_add_handler (server, NULL, + server_callback, NULL, NULL); + port = soup_server_get_port (server); + + loop = g_main_loop_new (NULL, TRUE); + + if (run_tests) { + uri_str = g_strdup_printf ("http://localhost:%u", port); + do_query_tests (uri_str); + g_free (uri_str); + } else { + printf ("Listening on port %d\n", port); + g_main_loop_run (loop); + } + + g_main_loop_unref (loop); + + if (run_tests) + test_cleanup (); + return errors != 0; +} diff --git a/tests/revserver.c b/tests/revserver.c deleted file mode 100644 index a09b7bc..0000000 --- a/tests/revserver.c +++ /dev/null @@ -1,187 +0,0 @@ -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include -#include -#include -#include - -#include -#include - -#include - -static void rev_read (SoupSocket *sock, GString *buf); -static void rev_write (SoupSocket *sock, GString *buf); - -static void -reverse (GString *buf) -{ - char tmp, *a, *b; - - a = buf->str; - b = buf->str + buf->len - 1; - - while (isspace ((unsigned char)*b) && b > a) - b--; - - while (a < b) { - tmp = *a; - *a++ = *b; - *b-- = tmp; - } -} - -static void -rev_done (SoupSocket *sock, GString *buf) -{ - g_object_unref (sock); - g_string_free (buf, TRUE); -} - -static void -rev_write (SoupSocket *sock, GString *buf) -{ - SoupSocketIOStatus status; - gsize nwrote; - - do { - status = soup_socket_write (sock, buf->str, buf->len, &nwrote); - memmove (buf->str, buf->str + nwrote, buf->len - nwrote); - buf->len -= nwrote; - } while (status == SOUP_SOCKET_OK && buf->len); - - switch (status) { - case SOUP_SOCKET_OK: - rev_read (sock, buf); - break; - - case SOUP_SOCKET_WOULD_BLOCK: - g_error ("Can't happen"); - break; - - default: - g_warning ("Socket error"); - /* fall through */ - - case SOUP_SOCKET_EOF: - rev_done (sock, buf); - break; - } -} - -static void -rev_read (SoupSocket *sock, GString *buf) -{ - SoupSocketIOStatus status; - char tmp[10]; - gsize nread; - gboolean eol; - - do { - status = soup_socket_read_until (sock, tmp, sizeof (tmp), - "\n", 1, &nread, &eol); - if (status == SOUP_SOCKET_OK) - g_string_append_len (buf, tmp, nread); - } while (status == SOUP_SOCKET_OK && !eol); - - switch (status) { - case SOUP_SOCKET_OK: - reverse (buf); - rev_write (sock, buf); - break; - - case SOUP_SOCKET_WOULD_BLOCK: - g_error ("Can't happen"); - break; - - default: - g_warning ("Socket error"); - /* fall through */ - - case SOUP_SOCKET_EOF: - rev_done (sock, buf); - break; - } -} - -static void * -start_thread (void *client) -{ - rev_read (client, g_string_new (NULL)); - - return NULL; -} - -static void -new_connection (SoupSocket *listener, SoupSocket *client, gpointer user_data) -{ - GThread *thread; - GError *error = NULL; - - g_object_ref (client); - g_object_set (G_OBJECT (client), - SOUP_SOCKET_FLAG_NONBLOCKING, FALSE, - NULL); - - thread = g_thread_create (start_thread, client, FALSE, &error); - if (thread == NULL) { - g_warning ("Could not start thread: %s", error->message); - g_error_free (error); - g_object_unref (client); - } -} - -int -main (int argc, char **argv) -{ - SoupSocket *listener; - SoupAddressFamily family = SOUP_ADDRESS_FAMILY_IPV4; - guint port = SOUP_ADDRESS_ANY_PORT; - SoupAddress *addr; - GMainLoop *loop; - int opt; - - g_type_init (); - g_thread_init (NULL); - - while ((opt = getopt (argc, argv, "6p:")) != -1) { - switch (opt) { - case '6': - family = SOUP_ADDRESS_FAMILY_IPV6; - break; - case 'p': - port = atoi (optarg); - break; - default: - fprintf (stderr, "Usage: %s [-6] [-p port]\n", - argv[0]); - exit (1); - } - } - - addr = soup_address_new_any (family, port); - if (!addr) { - fprintf (stderr, "Could not create listener address\n"); - exit (1); - } - - listener = soup_socket_server_new (addr, NULL, - new_connection, NULL); - g_object_unref (addr); - if (!listener) { - fprintf (stderr, "Could not create listening socket\n"); - exit (1); - } - printf ("Listening on port %d\n", - soup_address_get_port ( - soup_socket_get_local_address (listener))); - - loop = g_main_loop_new (NULL, TRUE); - g_main_loop_run (loop); - - g_object_unref (listener); - return 0; -} diff --git a/tests/server-auth-test.c b/tests/server-auth-test.c new file mode 100644 index 0000000..0d188f1 --- /dev/null +++ b/tests/server-auth-test.c @@ -0,0 +1,351 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2003, Ximian, Inc. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "test-utils.h" + +GMainLoop *loop; + +struct { + gboolean client_sent_basic, client_sent_digest; + gboolean server_requested_basic, server_requested_digest; + gboolean succeeded; +} test_data; + +static void +curl_exited (GPid pid, int status, gpointer data) +{ + gboolean *done = data; + + *done = TRUE; + test_data.succeeded = (status == 0); +} + +static void +do_test (int n, SoupURI *base_uri, const char *path, gboolean good_password, + gboolean offer_basic, gboolean offer_digest, + gboolean client_sends_basic, gboolean client_sends_digest, + gboolean server_requests_basic, gboolean server_requests_digest, + gboolean success) +{ + SoupURI *uri; + char *uri_str; + GPtrArray *args; + GPid pid; + gboolean done; + + debug_printf (1, "%2d. %s, %soffer Basic, %soffer Digest, %s password\n", + n, path, offer_basic ? "" : "don't ", + offer_digest ? "" : "don't ", + good_password ? "good" : "bad"); + + uri = soup_uri_new_with_base (base_uri, path); + uri_str = soup_uri_to_string (uri, FALSE); + soup_uri_free (uri); + + args = g_ptr_array_new (); + g_ptr_array_add (args, "curl"); + g_ptr_array_add (args, "-f"); + g_ptr_array_add (args, "-s"); + if (offer_basic || offer_digest) { + g_ptr_array_add (args, "-u"); + if (good_password) + g_ptr_array_add (args, "user:password"); + else + g_ptr_array_add (args, "user:badpassword"); + + if (offer_basic && offer_digest) + g_ptr_array_add (args, "--anyauth"); + else if (offer_basic) + g_ptr_array_add (args, "--basic"); + else + g_ptr_array_add (args, "--digest"); + } + g_ptr_array_add (args, uri_str); + g_ptr_array_add (args, NULL); + + memset (&test_data, 0, sizeof (test_data)); + if (g_spawn_async (NULL, (char **)args->pdata, NULL, + G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL | G_SPAWN_DO_NOT_REAP_CHILD, + NULL, NULL, &pid, NULL)) { + done = FALSE; + g_child_watch_add (pid, curl_exited, &done); + + while (!done) + g_main_iteration (TRUE); + } else + test_data.succeeded = FALSE; + g_ptr_array_free (args, TRUE); + g_free (uri_str); + + if (server_requests_basic != test_data.server_requested_basic) { + errors++; + if (test_data.server_requested_basic) + debug_printf (1, " Server sent WWW-Authenticate: Basic, but shouldn't have!\n"); + else + debug_printf (1, " Server didn't send WWW-Authenticate: Basic, but should have!\n"); + } + if (server_requests_digest != test_data.server_requested_digest) { + errors++; + if (test_data.server_requested_digest) + debug_printf (1, " Server sent WWW-Authenticate: Digest, but shouldn't have!\n"); + else + debug_printf (1, " Server didn't send WWW-Authenticate: Digest, but should have!\n"); + } + if (client_sends_basic != test_data.client_sent_basic) { + errors++; + if (test_data.client_sent_basic) + debug_printf (1, " Client sent Authorization: Basic, but shouldn't have!\n"); + else + debug_printf (1, " Client didn't send Authorization: Basic, but should have!\n"); + } + if (client_sends_digest != test_data.client_sent_digest) { + errors++; + if (test_data.client_sent_digest) + debug_printf (1, " Client sent Authorization: Digest, but shouldn't have!\n"); + else + debug_printf (1, " Client didn't send Authorization: Digest, but should have!\n"); + } + if (success && !test_data.succeeded) { + errors++; + debug_printf (1, " Should have succeeded, but didn't!\n"); + } else if (!success && test_data.succeeded) { + errors++; + debug_printf (1, " Should not have succeeded, but did!\n"); + } +} + +static void +do_auth_tests (SoupURI *base_uri) +{ + int i, n = 1; + gboolean use_basic, use_digest, good_password; + gboolean preemptive_basic; + + for (i = 0; i < 8; i++) { + use_basic = (i & 1) == 1; + use_digest = (i & 2) == 2; + good_password = (i & 4) == 4; + + /* Curl will preemptively send Basic if it's told to + * use Basic but not Digest. + */ + preemptive_basic = use_basic && !use_digest; + + /* 1. No auth required. The server will ignore the + * Authorization headers completely, and the request + * will always succeed. + */ + do_test (n++, base_uri, "/foo", good_password, + /* request */ + use_basic, use_digest, + /* expected from client */ + preemptive_basic, FALSE, + /* expected from server */ + FALSE, FALSE, + /* success? */ + TRUE); + + /* 2. Basic auth required. The server will send + * "WWW-Authenticate: Basic" if the client fails to + * send an Authorization: Basic on the first request, + * or if it sends a bad password. + */ + do_test (n++, base_uri, "/Basic/foo", good_password, + /* request */ + use_basic, use_digest, + /* expected from client */ + use_basic, FALSE, + /* expected from server */ + !preemptive_basic || !good_password, FALSE, + /* success? */ + use_basic && good_password); + + /* 3. Digest auth required. Simpler than the basic + * case because the client can't send Digest auth + * premptively. + */ + do_test (n++, base_uri, "/Digest/foo", good_password, + /* request */ + use_basic, use_digest, + /* expected from client */ + preemptive_basic, use_digest, + /* expected from server */ + FALSE, TRUE, + /* success? */ + use_digest && good_password); + + /* 4. Any auth required. */ + do_test (n++, base_uri, "/Any/foo", good_password, + /* request */ + use_basic, use_digest, + /* expected from client */ + preemptive_basic, use_digest, + /* expected from server */ + !preemptive_basic || !good_password, !preemptive_basic || !good_password, + /* success? */ + (use_basic || use_digest) && good_password); + } +} + +static gboolean +basic_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg, + const char *username, const char *password, gpointer data) +{ + return !strcmp (username, "user") && !strcmp (password, "password"); +} + +static char * +digest_auth_callback (SoupAuthDomain *auth_domain, SoupMessage *msg, + const char *username, gpointer data) +{ + if (strcmp (username, "user") != 0) + return NULL; + + /* Note: this is exactly how you *shouldn't* do it in the real + * world; you should have the pre-encoded password stored in a + * database of some sort rather than using the cleartext + * password in the callback. + */ + return soup_auth_domain_digest_encode_password ("user", + "server-auth-test", + "password"); +} + +static void +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_HEAD) { + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + return; + } + + soup_message_set_response (msg, "text/plain", + SOUP_MEMORY_STATIC, + "OK\r\n", 4); + soup_message_set_status (msg, SOUP_STATUS_OK); +} + +static void +got_headers_callback (SoupMessage *msg, gpointer data) +{ + const char *header; + + header = soup_message_headers_get (msg->request_headers, + "Authorization"); + if (header) { + if (strstr (header, "Basic ")) + test_data.client_sent_basic = TRUE; + if (strstr (header, "Digest ")) + test_data.client_sent_digest = TRUE; + } +} + +static void +wrote_headers_callback (SoupMessage *msg, gpointer data) +{ + const char *header; + + header = soup_message_headers_get (msg->response_headers, + "WWW-Authenticate"); + if (header) { + if (strstr (header, "Basic ")) + test_data.server_requested_basic = TRUE; + if (strstr (header, "Digest ")) + test_data.server_requested_digest = TRUE; + } +} + +static void +request_started_callback (SoupServer *server, SoupMessage *msg, + SoupClientContext *client, gpointer data) +{ + g_signal_connect (msg, "got_headers", + G_CALLBACK (got_headers_callback), NULL); + g_signal_connect (msg, "wrote_headers", + G_CALLBACK (wrote_headers_callback), NULL); +} + +gboolean run_tests = TRUE; + +static GOptionEntry no_test_entry[] = { + { "no-tests", 'n', G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_REVERSE, + G_OPTION_ARG_NONE, &run_tests, + "Don't run tests, just run the test server", NULL }, + { NULL } +}; + +int +main (int argc, char **argv) +{ + GMainLoop *loop; + SoupServer *server; + SoupURI *uri; + SoupAuthDomain *auth_domain; + gboolean run_tests = TRUE; + + test_init (argc, argv, no_test_entry); + + server = soup_test_server_new (FALSE); + g_signal_connect (server, "request_started", + G_CALLBACK (request_started_callback), NULL); + soup_server_add_handler (server, NULL, + server_callback, NULL, NULL); + + auth_domain = soup_auth_domain_basic_new ( + SOUP_AUTH_DOMAIN_REALM, "server-auth-test", + SOUP_AUTH_DOMAIN_ADD_PATH, "/Basic", + SOUP_AUTH_DOMAIN_ADD_PATH, "/Any", + SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, basic_auth_callback, + NULL); + soup_server_add_auth_domain (server, auth_domain); + + auth_domain = soup_auth_domain_digest_new ( + SOUP_AUTH_DOMAIN_REALM, "server-auth-test", + SOUP_AUTH_DOMAIN_ADD_PATH, "/Digest", + SOUP_AUTH_DOMAIN_ADD_PATH, "/Any", + SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK, digest_auth_callback, + NULL); + soup_server_add_auth_domain (server, auth_domain); + + loop = g_main_loop_new (NULL, TRUE); + + if (run_tests) { + uri = soup_uri_new ("http://localhost"); + soup_uri_set_port (uri, soup_server_get_port (server)); + do_auth_tests (uri); + soup_uri_free (uri); + } else { + printf ("Listening on port %d\n", soup_server_get_port (server)); + g_main_loop_run (loop); + } + + g_main_loop_unref (loop); + + if (run_tests) + test_cleanup (); + return errors != 0; +} diff --git a/tests/simple-httpd.c b/tests/simple-httpd.c index ea9a54a..f0c568b 100644 --- a/tests/simple-httpd.c +++ b/tests/simple-httpd.c @@ -17,106 +17,140 @@ #include #include #include -#include static void -print_header (gpointer name, gpointer value, gpointer data) +do_get (SoupServer *server, SoupMessage *msg, const char *path) { - printf ("%s: %s\n", (char *)name, (char *)value); -} - -static void -server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) -{ - char *path, *path_to_open, *slash; - SoupMethodId method; + char *slash; struct stat st; int fd; - path = soup_uri_to_string (soup_message_get_uri (msg), TRUE); - printf ("%s %s HTTP/1.%d\n", msg->method, path, - soup_message_get_http_version (msg)); - soup_message_foreach_header (msg->request_headers, print_header, NULL); - if (msg->request.length) - printf ("%.*s\n", msg->request.length, msg->request.body); - - method = soup_method_get_id (msg->method); - if (method != SOUP_METHOD_ID_GET && method != SOUP_METHOD_ID_HEAD) { - soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); - goto DONE; - } - - if (path) { - if (*path != '/') { - soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST); - goto DONE; - } - } else - path = g_strdup (""); - - path_to_open = g_strdup_printf (".%s", path); - - AGAIN: - if (stat (path_to_open, &st) == -1) { - g_free (path_to_open); + if (stat (path, &st) == -1) { if (errno == EPERM) soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); else if (errno == ENOENT) soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); else soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); - goto DONE; + return; } if (S_ISDIR (st.st_mode)) { - slash = strrchr (path_to_open, '/'); + char *index_path; + + slash = strrchr (path, '/'); if (!slash || slash[1]) { char *uri, *redir_uri; uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE); redir_uri = g_strdup_printf ("%s/", uri); - soup_message_add_header (msg->response_headers, - "Location", redir_uri); + soup_message_headers_append (msg->response_headers, + "Location", redir_uri); soup_message_set_status (msg, SOUP_STATUS_MOVED_PERMANENTLY); g_free (redir_uri); g_free (uri); - g_free (path_to_open); - goto DONE; + return; } - g_free (path_to_open); - path_to_open = g_strdup_printf (".%s/index.html", path); - goto AGAIN; + index_path = g_strdup_printf ("%s/index.html", path); + do_get (server, msg, index_path); + g_free (index_path); + return; } - fd = open (path_to_open, O_RDONLY); - g_free (path_to_open); + fd = open (path, O_RDONLY); if (fd == -1) { soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); - goto DONE; + return; } - msg->response.owner = SOUP_BUFFER_SYSTEM_OWNED; - msg->response.length = st.st_size; - - if (method == SOUP_METHOD_ID_GET) { - msg->response.body = g_malloc (msg->response.length); - read (fd, msg->response.body, msg->response.length); - } else /* method == SOUP_METHOD_ID_HEAD */ { - /* SoupServer will ignore response.body and only use - * response.length when responding to HEAD, so we - * could just use the same code for both GET and HEAD. - * But we'll optimize and avoid the extra malloc. + if (msg->method == SOUP_METHOD_GET) { + char *buf; + + buf = g_malloc (st.st_size); + read (fd, buf, st.st_size); + close (fd); + soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE, + buf, st.st_size); + } else /* msg->method == SOUP_METHOD_HEAD */ { + char *length; + + /* We could just use the same code for both GET and + * HEAD. But we'll optimize and avoid the extra + * malloc. */ - msg->response.body = NULL; + length = g_strdup_printf ("%lu", (gulong)st.st_size); + soup_message_headers_append (msg->response_headers, + "Content-Length", length); + g_free (length); } - close (fd); - soup_message_set_status (msg, SOUP_STATUS_OK); +} + +static void +do_put (SoupServer *server, SoupMessage *msg, const char *path) +{ + struct stat st; + FILE *f; + gboolean created = TRUE; + + if (stat (path, &st) != -1) { + const char *match = soup_message_headers_get (msg->request_headers, "If-None-Match"); + if (match && !strcmp (match, "*")) { + soup_message_set_status (msg, SOUP_STATUS_CONFLICT); + return; + } + + if (!S_ISREG (st.st_mode)) { + soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); + return; + } + + created = FALSE; + } + + f = fopen (path, "w"); + if (!f) { + soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + return; + } + + fwrite (msg->request_body->data, 1, msg->request_body->length, f); + fclose (f); + + soup_message_set_status (msg, created ? SOUP_STATUS_CREATED : SOUP_STATUS_OK); +} + +static void +print_header (const char *name, const char *value, gpointer data) +{ + printf ("%s: %s\n", name, value); +} + +static void +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + char *file_path; + + printf ("%s %s HTTP/1.%d\n", msg->method, path, + soup_message_get_http_version (msg)); + soup_message_headers_foreach (msg->request_headers, print_header, NULL); + if (msg->request_body->length) + printf ("%s\n", msg->request_body->data); + + file_path = g_strdup_printf (".%s", path); + + if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD) + do_get (server, msg, file_path); + else if (msg->method == SOUP_METHOD_PUT) + do_put (server, msg, file_path); + else + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); - DONE: - g_free (path); + g_free (file_path); printf (" -> %d %s\n\n", msg->status_code, msg->reason_phrase); } @@ -168,7 +202,7 @@ main (int argc, char **argv) fprintf (stderr, "Unable to bind to server port %d\n", port); exit (1); } - soup_server_add_handler (server, NULL, NULL, + soup_server_add_handler (server, NULL, server_callback, NULL, NULL); printf ("\nStarting Server on port %d\n", soup_server_get_port (server)); @@ -185,7 +219,7 @@ main (int argc, char **argv) fprintf (stderr, "Unable to bind to SSL server port %d\n", ssl_port); exit (1); } - soup_server_add_handler (ssl_server, NULL, NULL, + soup_server_add_handler (ssl_server, NULL, server_callback, NULL, NULL); printf ("Starting SSL Server on port %d\n", soup_server_get_port (ssl_server)); diff --git a/tests/simple-proxy.c b/tests/simple-proxy.c index bcfb9ef..cc3da2b 100644 --- a/tests/simple-proxy.c +++ b/tests/simple-proxy.c @@ -17,7 +17,6 @@ #include #include #include -#include #include /* WARNING: this is really really really not especially compliant with @@ -25,11 +24,12 @@ */ SoupSession *session; +SoupServer *server; static void -copy_header (gpointer name, gpointer value, gpointer dest_headers) +copy_header (const char *name, const char *value, gpointer dest_headers) { - soup_message_add_header (dest_headers, name, value); + soup_message_headers_append (dest_headers, name, value); } static void @@ -41,42 +41,45 @@ send_headers (SoupMessage *from, SoupMessage *to) soup_message_set_status_full (to, from->status_code, from->reason_phrase); - soup_message_foreach_header (from->response_headers, copy_header, - to->response_headers); - soup_message_remove_header (to->response_headers, "Content-Length"); - soup_message_io_unpause (to); + soup_message_headers_foreach (from->response_headers, copy_header, + to->response_headers); + soup_message_headers_remove (to->response_headers, "Content-Length"); + soup_server_unpause_message (server, to); } static void -send_chunk (SoupMessage *from, SoupMessage *to) +send_chunk (SoupMessage *from, SoupBuffer *chunk, SoupMessage *to) { - printf ("[%p] writing chunk of %d bytes\n", to, from->response.length); + printf ("[%p] writing chunk of %lu bytes\n", to, + (unsigned long)chunk->length); - soup_message_add_chunk (to, SOUP_BUFFER_USER_OWNED, - from->response.body, from->response.length); - soup_message_io_unpause (to); + soup_message_body_append_buffer (to->response_body, chunk); + soup_server_unpause_message (server, to); } static void client_msg_failed (SoupMessage *msg, gpointer msg2) { - soup_message_set_status (msg2, SOUP_STATUS_IO_ERROR); - soup_session_cancel_message (session, msg2); + soup_session_cancel_message (session, msg2, SOUP_STATUS_IO_ERROR); } static void -finish_msg (SoupMessage *msg2, gpointer msg) +finish_msg (SoupSession *session, SoupMessage *msg2, gpointer data) { + SoupMessage *msg = data; + printf ("[%p] done\n\n", msg); g_signal_handlers_disconnect_by_func (msg, client_msg_failed, msg2); - soup_message_add_final_chunk (msg); - soup_message_io_unpause (msg); + soup_message_body_complete (msg->response_body); + soup_server_unpause_message (server, msg); g_object_unref (msg); } static void -server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) { SoupMessage *msg2; char *uristr; @@ -85,37 +88,37 @@ server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data) printf ("[%p] %s %s HTTP/1.%d\n", msg, msg->method, uristr, soup_message_get_http_version (msg)); - if (soup_method_get_id (msg->method) == SOUP_METHOD_ID_CONNECT) { + if (msg->method == SOUP_METHOD_CONNECT) { soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); return; } msg2 = soup_message_new (msg->method, uristr); - soup_message_foreach_header (msg->request_headers, copy_header, - msg2->request_headers); - soup_message_remove_header (msg2->request_headers, "Host"); - soup_message_remove_header (msg2->request_headers, "Connection"); - - if (msg->request.length) { - msg2->request.owner = SOUP_BUFFER_USER_OWNED; - msg2->request.body = msg->request.body; - msg2->request.length = msg->request.length; + msg2 = soup_message_new (msg->method, uristr); + soup_message_headers_foreach (msg->request_headers, copy_header, + msg2->request_headers); + soup_message_headers_remove (msg2->request_headers, "Host"); + soup_message_headers_remove (msg2->request_headers, "Connection"); + + if (msg->request_body->length) { + SoupBuffer *request = soup_message_body_flatten (msg->request_body); + soup_message_body_append_buffer (msg2->request_body, request); + soup_buffer_free (request); } - soup_server_message_set_encoding (SOUP_SERVER_MESSAGE (msg), - SOUP_TRANSFER_CHUNKED); + soup_message_headers_set_encoding (msg->response_headers, + SOUP_ENCODING_CHUNKED); g_signal_connect (msg2, "got_headers", G_CALLBACK (send_headers), msg); g_signal_connect (msg2, "got_chunk", G_CALLBACK (send_chunk), msg); - soup_message_set_flags (msg2, SOUP_MESSAGE_OVERWRITE_CHUNKS); g_signal_connect (msg, "finished", G_CALLBACK (client_msg_failed), msg2); soup_session_queue_message (session, msg2, finish_msg, msg); g_object_ref (msg); - soup_message_io_pause (msg); + soup_server_pause_message (server, msg); } static void @@ -131,7 +134,6 @@ main (int argc, char **argv) GMainLoop *loop; int opt; int port = SOUP_ADDRESS_ANY_PORT; - SoupServer *server; g_type_init (); g_thread_init (NULL); @@ -155,7 +157,7 @@ main (int argc, char **argv) fprintf (stderr, "Unable to bind to server port %d\n", port); exit (1); } - soup_server_add_handler (server, NULL, NULL, + soup_server_add_handler (server, NULL, server_callback, NULL, NULL); printf ("\nStarting proxy on port %d\n", diff --git a/tests/ssl-test.c b/tests/ssl-test.c index cdd88bc..13eed66 100644 --- a/tests/ssl-test.c +++ b/tests/ssl-test.c @@ -7,6 +7,7 @@ #include #include +#include "libsoup/soup-address.h" #include "libsoup/soup-socket.h" #include "libsoup/soup-ssl.h" @@ -132,17 +133,20 @@ async_read (SoupSocket *sock, gpointer user_data) AsyncData *data = user_data; SoupSocketIOStatus status; gsize n; + GError *error = NULL; do { status = soup_socket_read (sock, data->readbuf + data->total, - BUFSIZE - data->total, &n); + BUFSIZE - data->total, &n, + NULL, &error); if (status == SOUP_SOCKET_OK) data->total += n; } while (status == SOUP_SOCKET_OK && data->total < BUFSIZE); - if (status == SOUP_SOCKET_ERROR || status == SOUP_SOCKET_EOF) - g_error ("Async read got status %d", status); - else if (status == SOUP_SOCKET_WOULD_BLOCK) + if (status == SOUP_SOCKET_ERROR || status == SOUP_SOCKET_EOF) { + g_error ("Async read got status %d: %s", status, + error ? error->message : "(unknown)"); + } else if (status == SOUP_SOCKET_WOULD_BLOCK) return; if (memcmp (data->writebuf, data->readbuf, BUFSIZE) != 0) @@ -158,17 +162,20 @@ async_write (SoupSocket *sock, gpointer user_data) AsyncData *data = user_data; SoupSocketIOStatus status; gsize n; + GError *error = NULL; do { status = soup_socket_write (sock, data->writebuf + data->total, - BUFSIZE - data->total, &n); + BUFSIZE - data->total, &n, + NULL, &error); if (status == SOUP_SOCKET_OK) data->total += n; } while (status == SOUP_SOCKET_OK && data->total < BUFSIZE); - if (status == SOUP_SOCKET_ERROR || status == SOUP_SOCKET_EOF) - g_error ("Async write got status %d", status); - else if (status == SOUP_SOCKET_WOULD_BLOCK) + if (status == SOUP_SOCKET_ERROR || status == SOUP_SOCKET_EOF) { + g_error ("Async write got status %d: %s", status, + error ? error->message : "(unknown)"); + } else if (status == SOUP_SOCKET_WOULD_BLOCK) return; data->total = 0; @@ -211,10 +218,12 @@ main (int argc, char **argv) struct sockaddr_in sin; GThread *server; char writebuf[BUFSIZE], readbuf[BUFSIZE]; + SoupAddress *addr; SoupSSLCredentials *creds; SoupSocket *sock; gsize n, total; SoupSocketIOStatus status; + GError *error = NULL; g_type_init (); g_thread_init (NULL); @@ -269,15 +278,20 @@ main (int argc, char **argv) port = ntohs (sin.sin_port); /* Create the client */ + addr = soup_address_new ("127.0.0.1", port); creds = soup_ssl_get_client_credentials (NULL); - sock = soup_socket_client_new_sync ("127.0.0.1", port, - creds, &status); + sock = soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, addr, + SOUP_SOCKET_FLAG_NONBLOCKING, FALSE, + SOUP_SOCKET_SSL_CREDENTIALS, creds, + NULL); + g_object_unref (addr); + status = soup_socket_connect_sync (sock, NULL); if (status != SOUP_STATUS_OK) { g_error ("Could not create client socket: %s", soup_status_get_phrase (status)); } - soup_socket_start_ssl (sock); + soup_socket_start_ssl (sock, NULL); /* Now spawn server thread */ server = g_thread_create (server_thread, GINT_TO_POINTER (listener), @@ -290,18 +304,22 @@ main (int argc, char **argv) total = 0; while (total < BUFSIZE) { status = soup_socket_write (sock, writebuf + total, - BUFSIZE - total, &n); + BUFSIZE - total, &n, + NULL, &error); if (status != SOUP_SOCKET_OK) - g_error ("Sync write got status %d", status); + g_error ("Sync write got status %d: %s", status, + error ? error->message : "(unknown)"); total += n; } total = 0; while (total < BUFSIZE) { status = soup_socket_read (sock, readbuf + total, - BUFSIZE - total, &n); + BUFSIZE - total, &n, + NULL, &error); if (status != SOUP_SOCKET_OK) - g_error ("Sync read got status %d", status); + g_error ("Sync read got status %d: %s", status, + error ? error->message : "(unknown)"); total += n; } diff --git a/tests/test-utils.c b/tests/test-utils.c new file mode 100644 index 0000000..b7564a4 --- /dev/null +++ b/tests/test-utils.c @@ -0,0 +1,284 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "test-utils.h" +#include "libsoup/soup-logger.h" +#include "libsoup/soup-misc.h" +#include "libsoup/soup-server.h" + +#include +#include +#include +#include +#include + +#ifdef HAVE_APACHE +static gboolean apache_running; +#endif +static SoupServer *test_server; +GThread *server_thread; +static void test_server_shutdown (void); + +static SoupLogger *logger; + +int debug_level, http_debug_level, errors; + +static gboolean +increment_debug_level (const char *option_name, const char *value, + gpointer data, GError **error) +{ + debug_level++; + return TRUE; +} + +static gboolean +increment_http_debug_level (const char *option_name, const char *value, + gpointer data, GError **error) +{ + http_debug_level++; + return TRUE; +} + +static GOptionEntry debug_entry[] = { + { "debug", 'd', G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, increment_debug_level, + "Enable (or increase) test-specific debugging", NULL }, + { "http-debug", 'h', G_OPTION_FLAG_NO_ARG, + G_OPTION_ARG_CALLBACK, increment_http_debug_level, + "Enable (or increase) HTTP-level debugging", NULL }, + { NULL } +}; + +static void +quit (int sig) +{ +#ifdef HAVE_APACHE + if (apache_running) + apache_cleanup (); +#endif + if (test_server) + test_server_shutdown (); + + exit (1); +} + +void +test_init (int argc, char **argv, GOptionEntry *entries) +{ + GOptionContext *opts; + char *name; + GError *error = NULL; + + g_type_init (); + g_thread_init (NULL); + + name = strrchr (argv[0], '/'); + if (!name++) + name = argv[0]; + if (!strncmp (name, "lt-", 3)) + name += 3; + g_set_prgname (name); + + opts = g_option_context_new (NULL); + g_option_context_add_main_entries (opts, debug_entry, NULL); + if (entries) + g_option_context_add_main_entries (opts, entries, NULL); + + if (!g_option_context_parse (opts, &argc, &argv, &error)) { + fprintf (stderr, "Could not parse arguments: %s\n", + error->message); + fprintf (stderr, "%s", + g_option_context_get_help (opts, TRUE, NULL)); + exit (1); + } + g_option_context_free (opts); + + /* Exit cleanly on ^C in case we're valgrinding. */ + signal (SIGINT, quit); +} + +void +test_cleanup (void) +{ + debug_printf (1, "\n"); + if (errors) { + printf ("%s: %d error(s).%s\n", + g_get_prgname (), errors, + debug_level == 0 ? " Run with '-d' for details" : ""); + } else + printf ("%s: OK\n", g_get_prgname ()); + +#ifdef HAVE_APACHE + if (apache_running) + apache_cleanup (); +#endif + if (test_server) + test_server_shutdown (); + + if (logger) + g_object_unref (logger); + + g_main_context_unref (g_main_context_default ()); +} + +void +debug_printf (int level, const char *format, ...) +{ + va_list args; + + if (debug_level < level) + return; + + va_start (args, format); + vprintf (format, args); + va_end (args); +} + +#ifdef HAVE_APACHE + +static gboolean +apache_cmd (char *cmd) +{ + char *argv[8]; + char *cwd, *conf; + int status; + gboolean ok; + + cwd = g_get_current_dir (); + conf = g_build_filename (cwd, "httpd.conf", NULL); + + argv[0] = APACHE_HTTPD; + argv[1] = "-d"; + argv[2] = cwd; + argv[3] = "-f"; + argv[4] = conf; + argv[5] = "-k"; + argv[6] = cmd; + argv[7] = NULL; + + ok = g_spawn_sync (cwd, argv, NULL, 0, NULL, NULL, + NULL, NULL, &status, NULL); + if (ok) + ok = (status == 0); + + g_free (cwd); + g_free (conf); + + return ok; +} + +void +apache_init (void) +{ + if (!apache_cmd ("start")) { + fprintf (stderr, "Could not start apache\n"); + exit (1); + } + apache_running = TRUE; +} + +void +apache_cleanup (void) +{ + pid_t pid; + char *contents; + + if (g_file_get_contents ("httpd.pid", &contents, NULL, NULL)) { + pid = strtoul (contents, NULL, 10); + g_free (contents); + } else + pid = 0; + + if (!apache_cmd ("graceful-stop")) + return; + apache_running = FALSE; + + if (pid) { + while (kill (pid, 0) == 0) + g_usleep (100); + } +} + +#endif /* HAVE_APACHE */ + +SoupSession * +soup_test_session_new (GType type, ...) +{ + va_list args; + const char *propname; + SoupSession *session; + + va_start (args, type); + propname = va_arg (args, const char *); + session = (SoupSession *)g_object_new_valist (type, propname, args); + va_end (args); + + if (http_debug_level && !logger) { + SoupLoggerLogLevel level = MIN ((SoupLoggerLogLevel)http_debug_level, SOUP_LOGGER_LOG_BODY); + + logger = soup_logger_new (level, -1); + } + + if (logger) + soup_logger_attach (logger, session); + + return session; +} + +static gpointer run_server_thread (gpointer user_data); + +SoupServer * +soup_test_server_new (gboolean in_own_thread) +{ + GMainContext *async_context; + + async_context = in_own_thread ? g_main_context_new () : NULL; + test_server = soup_server_new (SOUP_SERVER_ASYNC_CONTEXT, async_context, + NULL); + if (async_context) + g_main_context_unref (async_context); + + if (!test_server) { + fprintf (stderr, "Unable to create server\n"); + exit (1); + } + + if (in_own_thread) { + server_thread = g_thread_create (run_server_thread, test_server, + TRUE, NULL); + } else + soup_server_run_async (test_server); + + return test_server; +} + +static gpointer +run_server_thread (gpointer user_data) +{ + SoupServer *server = user_data; + + soup_server_run (server); + return NULL; +} + +static gboolean +idle_quit_server (gpointer server) +{ + soup_server_quit (server); + return FALSE; +} + +static void +test_server_shutdown (void) +{ + if (server_thread) { + soup_add_idle (soup_server_get_async_context (test_server), + idle_quit_server, test_server); + g_thread_join (server_thread); + } else + soup_server_quit (test_server); + g_object_unref (test_server); +} + + diff --git a/tests/test-utils.h b/tests/test-utils.h new file mode 100644 index 0000000..fa0dc13 --- /dev/null +++ b/tests/test-utils.h @@ -0,0 +1,19 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "libsoup/soup-types.h" + +void test_init (int argc, char **argv, GOptionEntry *entries); +void test_cleanup (void); + +extern int debug_level, errors; +void debug_printf (int level, const char *format, ...); + +#ifdef HAVE_APACHE +void apache_init (void); +void apache_cleanup (void); +#endif + +SoupSession *soup_test_session_new (GType type, ...); +SoupServer *soup_test_server_new (gboolean in_own_thread); diff --git a/tests/uri-parsing.c b/tests/uri-parsing.c index fa73292..cbafc53 100644 --- a/tests/uri-parsing.c +++ b/tests/uri-parsing.c @@ -5,22 +5,10 @@ #include #include #include -#include "libsoup/soup-uri.h" - -gboolean debug = FALSE; - -static void -dprintf (const char *format, ...) -{ - va_list args; - if (!debug) - return; +#include "libsoup/soup-uri.h" - va_start (args, format); - vprintf (format, args); - va_end (args); -} +#include "test-utils.h" struct { const char *uri_string, *result; @@ -32,23 +20,48 @@ struct { { "ftp://user@host:9999/path", "ftp://user@host:9999/path" }, { "ftp://user:password@host/path", "ftp://user@host/path" }, { "ftp://user:password@host:9999/path", "ftp://user@host:9999/path" }, - { "http://us%65r@host", "http://user@host" }, - { "http://us%40r@host", "http://us%40r@host" }, - { "http://us%3ar@host", "http://us%3ar@host" }, - { "http://us%2fr@host", "http://us%2fr@host" }, - - { "http://control-chars/%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%7f", - "http://control-chars/%01%02%03%04%05%06%07%08%09%0a%0b%0c%0d%0e%0f%10%11%12%13%14%15%16%17%18%19%1a%1b%1c%1d%1e%1f%7f"}, + { "ftp://user:password@host", "ftp://user@host" }, + { "http://us%65r@host", "http://user@host/" }, + { "http://us%40r@host", "http://us%40r@host/" }, + { "http://us%3ar@host", "http://us%3Ar@host/" }, + { "http://us%2fr@host", "http://us%2Fr@host/" }, + { "http://us%3fr@host", "http://us%3Fr@host/" }, + { "http://host?query", "http://host/?query" }, + { "http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fchildparam%3Dchildvalue¶m=value", + "http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fchildparam%3Dchildvalue¶m=value" }, + { "http://control-chars/%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F", + "http://control-chars/%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F"}, { "http://space/%20", "http://space/%20" }, - { "http://delims/%3c%3e%23%25%22", - "http://delims/%3c%3e%23%25%22" }, - { "http://unwise-chars/%7b%7d%7c%5c%5e%5b%5d%60", - "http://unwise-chars/%7b%7d%7c%5c%5e%5b%5d%60" } + { "http://delims/%3C%3E%23%25%22", + "http://delims/%3C%3E%23%25%22" }, + { "http://unwise-chars/%7B%7D%7C%5C%5E%5B%5D%60", + "http://unwise-chars/%7B%7D%7C%5C%5E%5B%5D%60" }, + { "http://host/path%", NULL }, + { "http://host/path%%", NULL }, + { "http://host/path%%%", NULL }, + { "http://host/path%/x/", NULL }, + { "http://host/path%0x/", NULL }, + + /* From RFC 2732 */ + { "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html", + "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/index.html" }, + { "http://[1080:0:0:0:8:800:200C:417A]/index.html", + "http://[1080:0:0:0:8:800:200C:417A]/index.html" }, + { "http://[3ffe:2a00:100:7031::1]", + "http://[3ffe:2a00:100:7031::1]/" }, + { "http://[1080::8:800:200C:417A]/foo", + "http://[1080::8:800:200C:417A]/foo" }, + { "http://[::192.9.5.5]/ipng", + "http://[::192.9.5.5]/ipng" }, + { "http://[::FFFF:129.144.52.38]:80/index.html", + "http://[::FFFF:129.144.52.38]/index.html" }, + { "http://[2010:836B:4179::836B:4179]", + "http://[2010:836B:4179::836B:4179]/" } }; int num_abs_tests = G_N_ELEMENTS(abs_tests); -/* From RFC 2396. */ +/* From RFC 3986. */ const char *base = "http://a/b/c/d;p?q"; struct { const char *uri_string, *result; @@ -58,8 +71,8 @@ struct { { "./g", "http://a/b/c/g" }, { "g/", "http://a/b/c/g/" }, { "/g", "http://a/g" }, - { "//g", "http://g" }, - { "?y", "http://a/b/c/?y" }, + { "//g", "http://g/" }, + { "?y", "http://a/b/c/d;p?y" }, { "g?y", "http://a/b/c/g?y" }, { "#s", "http://a/b/c/d;p?q#s" }, { "g#s", "http://a/b/c/g#s" }, @@ -76,10 +89,10 @@ struct { { "../../", "http://a/" }, { "../../g", "http://a/g" }, { "", "http://a/b/c/d;p?q" }, - { "../../../g", "http://a/../g" }, - { "../../../../g", "http://a/../../g" }, - { "/./g", "http://a/./g" }, - { "/../g", "http://a/../g" }, + { "../../../g", "http://a/g" }, + { "../../../../g", "http://a/g" }, + { "/./g", "http://a/g" }, + { "/../g", "http://a/g" }, { "g.", "http://a/b/c/g." }, { ".g", "http://a/b/c/.g" }, { "g..", "http://a/b/c/g.." }, @@ -95,7 +108,7 @@ struct { { "g#s/./x", "http://a/b/c/g#s/./x" }, { "g#s/../x", "http://a/b/c/g#s/../x" }, - /* RFC 2396 notes that some old parsers will parse this as + /* RFC 3986 notes that some old parsers will parse this as * a relative URL ("http://a/b/c/g"), but it should be * interpreted as absolute. libsoup should parse it * correctly as being absolute, but then reject it since it's @@ -105,29 +118,41 @@ struct { }; int num_rel_tests = G_N_ELEMENTS(rel_tests); +struct { + char *one, *two; +} eq_tests[] = { + { "example://a/b/c/%7Bfoo%7D", "eXAMPLE://a/./b/../b/%63/%7bfoo%7d" }, + { "http://example.com", "http://example.com/" }, + /* From RFC 2616 */ + { "http://abc.com:80/~smith/home.html", "http://abc.com:80/~smith/home.html" }, + { "http://abc.com:80/~smith/home.html", "http://ABC.com/%7Esmith/home.html" }, + { "http://abc.com:80/~smith/home.html", "http://ABC.com:/%7esmith/home.html" }, +}; +int num_eq_tests = G_N_ELEMENTS(eq_tests); + static gboolean -do_uri (SoupUri *base_uri, const char *base_str, +do_uri (SoupURI *base_uri, const char *base_str, const char *in_uri, const char *out_uri) { - SoupUri *uri; + SoupURI *uri; char *uri_string; if (base_uri) { - dprintf ("<%s> + <%s> = <%s>? ", base_str, in_uri, - out_uri ? out_uri : "ERR"); + debug_printf (1, "<%s> + <%s> = <%s>? ", base_str, in_uri, + out_uri ? out_uri : "ERR"); uri = soup_uri_new_with_base (base_uri, in_uri); } else { - dprintf ("<%s> => <%s>? ", in_uri, - out_uri ? out_uri : "ERR"); + debug_printf (1, "<%s> => <%s>? ", in_uri, + out_uri ? out_uri : "ERR"); uri = soup_uri_new (in_uri); } if (!uri) { if (out_uri) { - dprintf ("ERR\n Could not parse %s\n", in_uri); + debug_printf (1, "ERR\n Could not parse %s\n", in_uri); return FALSE; } else { - dprintf ("OK\n"); + debug_printf (1, "OK\n"); return TRUE; } } @@ -136,47 +161,38 @@ do_uri (SoupUri *base_uri, const char *base_str, soup_uri_free (uri); if (!out_uri) { - dprintf ("ERR\n Got %s\n", uri_string); + debug_printf (1, "ERR\n Got %s\n", uri_string); return FALSE; } if (strcmp (uri_string, out_uri) != 0) { - dprintf ("NO\n Unparses to <%s>\n", uri_string); + debug_printf (1, "NO\n Unparses to <%s>\n", uri_string); g_free (uri_string); return FALSE; } g_free (uri_string); - dprintf ("OK\n"); + debug_printf (1, "OK\n"); return TRUE; } int main (int argc, char **argv) { - SoupUri *base_uri; + SoupURI *base_uri, *uri1, *uri2; char *uri_string; - int i, errs = 0, opt; - - while ((opt = getopt (argc, argv, "d")) != -1) { - switch (opt) { - case 'd': - debug = TRUE; - break; - default: - fprintf (stderr, "Usage: %s [-d]\n", argv[0]); - return 1; - } - } + int i; - dprintf ("Absolute URI parsing\n"); + test_init (argc, argv, NULL); + + debug_printf (1, "Absolute URI parsing\n"); for (i = 0; i < num_abs_tests; i++) { if (!do_uri (NULL, NULL, abs_tests[i].uri_string, abs_tests[i].result)) - errs++; + errors++; } - dprintf ("\nRelative URI parsing\n"); + debug_printf (1, "\nRelative URI parsing\n"); base_uri = soup_uri_new (base); if (!base_uri) { fprintf (stderr, "Could not parse %s!\n", base); @@ -187,22 +203,33 @@ main (int argc, char **argv) if (strcmp (uri_string, base) != 0) { fprintf (stderr, "URI <%s> unparses to <%s>\n", base, uri_string); - errs++; + errors++; } g_free (uri_string); for (i = 0; i < num_rel_tests; i++) { if (!do_uri (base_uri, base, rel_tests[i].uri_string, rel_tests[i].result)) - errs++; + errors++; } soup_uri_free (base_uri); - dprintf ("\n"); - if (errs) { - printf ("uri-parsing: %d error(s). Run with '-d' for details\n", - errs); - } else - printf ("uri-parsing: OK\n"); - return errs; + debug_printf (1, "\nURI equality testing\n"); + for (i = 0; i < num_eq_tests; i++) { + uri1 = soup_uri_new (eq_tests[i].one); + uri2 = soup_uri_new (eq_tests[i].two); + debug_printf (1, "<%s> == <%s>? ", eq_tests[i].one, eq_tests[i].two); + if (soup_uri_equal (uri1, uri2)) + debug_printf (1, "OK\n"); + else { + debug_printf (1, "NO\n"); + debug_printf (1, "%s : %s : %s\n%s : %s : %s\n", + uri1->scheme, uri1->host, uri1->path, + uri2->scheme, uri2->host, uri2->path); + errors++; + } + } + + test_cleanup (); + return errors != 0; } diff --git a/tests/xmlrpc-test.c b/tests/xmlrpc-test.c index a77c5b3..8686b71 100644 --- a/tests/xmlrpc-test.c +++ b/tests/xmlrpc-test.c @@ -3,33 +3,17 @@ * Copyright (C) 2001-2003, Ximian, Inc. */ +#include +#include #include #include #include -#include -#include -#include -#include -#include "apache-wrapper.h" +#include "test-utils.h" SoupSession *session; static const char *uri = "http://localhost:47524/xmlrpc-server.php"; -int debug; - -static void -dprintf (int level, const char *format, ...) -{ - va_list args; - - if (debug < level) - return; - - va_start (args, format); - vprintf (format, args); - va_end (args); -} static const char *const value_type[] = { "BAD", @@ -43,209 +27,197 @@ static const char *const value_type[] = { "array" }; -static SoupXmlrpcResponse * -do_xmlrpc (SoupXmlrpcMessage *xmsg, SoupXmlrpcValueType type) +static gboolean +do_xmlrpc (const char *method, GValue *retval, ...) { - SoupMessage *msg = SOUP_MESSAGE (xmsg); - SoupXmlrpcResponse *response; - SoupXmlrpcValue *value; - int status; + SoupMessage *msg; + va_list args; + GValueArray *params; + GError *err = NULL; + char *body; - soup_xmlrpc_message_persist (xmsg); - status = soup_session_send_message (session, msg); + va_start (args, retval); + params = soup_value_array_from_args (args); + va_end (args); + + body = soup_xmlrpc_build_method_call (method, params->values, + params->n_values); + g_value_array_free (params); + if (!body) + return FALSE; - dprintf (3, "\n%.*s\n%d %s\n%.*s\n", - msg->request.length, msg->request.body, - msg->status_code, msg->reason_phrase, - msg->response.length, msg->response.body); + msg = soup_message_new ("POST", uri); + soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE, + body, strlen (body)); + soup_session_send_message (session, msg); + + if (debug_level >= 3) { + debug_printf (3, "\n%s\n%d %s\n%s\n", + msg->request_body->data, + msg->status_code, msg->reason_phrase, + msg->response_body->data); + } - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - dprintf (1, "ERROR: %d %s\n", status, msg->reason_phrase); + if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { + debug_printf (1, "ERROR: %d %s\n", msg->status_code, + msg->reason_phrase); g_object_unref (msg); return FALSE; } - response = soup_xmlrpc_message_parse_response (xmsg); - g_object_unref (msg); - if (!response || soup_xmlrpc_response_is_fault (response)) { - if (!response) - dprintf (1, "ERROR: no response\n"); - else { - dprintf (1, "ERROR: fault\n"); - g_object_unref (response); - } + if (!soup_xmlrpc_parse_method_response (msg->response_body->data, + msg->response_body->length, + retval, &err)) { + if (err) { + debug_printf (1, "FAULT: %d %s\n", err->code, err->message); + g_error_free (err); + } else + debug_printf (1, "ERROR: could not parse response\n"); + g_object_unref (msg); return FALSE; } + g_object_unref (msg); - value = soup_xmlrpc_response_get_value (response); - if (!value) { - dprintf (1, "ERROR: no value?\n"); - g_object_unref (response); - return NULL; - } else if (soup_xmlrpc_value_get_type (value) != type) { - dprintf (1, "ERROR: wrong value type; expected %s, got %s\n", - value_type[type], value_type[soup_xmlrpc_value_get_type (value)]); - g_object_unref (response); - return NULL; + return TRUE; +} + +static gboolean +check_xmlrpc (GValue *value, GType type, ...) +{ + va_list args; + + if (!G_VALUE_HOLDS (value, type)) { + debug_printf (1, "ERROR: could not parse response\n"); + g_value_unset (value); + return FALSE; } - return response; + va_start (args, type); + SOUP_VALUE_GETV (value, type, args); + va_end (args); + return TRUE; } static gboolean test_sum (void) { - SoupXmlrpcMessage *msg; - SoupXmlrpcResponse *response; - SoupXmlrpcValue *value; - int i, val, sum; - long result; - - dprintf (1, "sum (array of int -> int): "); - - msg = soup_xmlrpc_message_new (uri); - soup_xmlrpc_message_start_call (msg, "sum"); - soup_xmlrpc_message_start_param (msg); - soup_xmlrpc_message_start_array (msg); + GValueArray *ints; + int i, val, sum, result; + GValue retval; + gboolean ok; + + debug_printf (1, "sum (array of int -> int): "); + + ints = g_value_array_new (10); for (i = sum = 0; i < 10; i++) { val = rand () % 100; - dprintf (2, "%s%d", i == 0 ? "[" : ", ", val); - soup_xmlrpc_message_write_int (msg, val); + debug_printf (2, "%s%d", i == 0 ? "[" : ", ", val); + soup_value_array_append (ints, G_TYPE_INT, val); sum += val; } - dprintf (2, "] -> "); - soup_xmlrpc_message_end_array (msg); - soup_xmlrpc_message_end_param (msg); - soup_xmlrpc_message_end_call (msg); + debug_printf (2, "] -> "); - response = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_INT); - if (!response) - return FALSE; - value = soup_xmlrpc_response_get_value (response); + ok = (do_xmlrpc ("sum", &retval, + G_TYPE_VALUE_ARRAY, ints, + G_TYPE_INVALID) && + check_xmlrpc (&retval, G_TYPE_INT, &result)); + g_value_array_free (ints); - if (!soup_xmlrpc_value_get_int (value, &result)) { - dprintf (1, "wrong type?\n"); - g_object_unref (response); + if (!ok) return FALSE; - } - g_object_unref (response); - dprintf (2, "%ld: ", result); - dprintf (1, "%s\n", result == sum ? "OK!" : "WRONG!"); + debug_printf (2, "%d: ", result); + debug_printf (1, "%s\n", result == sum ? "OK!" : "WRONG!"); return result == sum; } static gboolean test_countBools (void) { - SoupXmlrpcMessage *msg; - SoupXmlrpcResponse *response; - SoupXmlrpcValue *value; + GValueArray *bools; int i, trues, falses; - long ret_trues, ret_falses; + GValue retval; + int ret_trues, ret_falses; gboolean val, ok; GHashTable *result; - dprintf (1, "countBools (array of boolean -> struct of ints): "); + debug_printf (1, "countBools (array of boolean -> struct of ints): "); - msg = soup_xmlrpc_message_new (uri); - soup_xmlrpc_message_start_call (msg, "countBools"); - soup_xmlrpc_message_start_param (msg); - soup_xmlrpc_message_start_array (msg); + bools = g_value_array_new (10); for (i = trues = falses = 0; i < 10; i++) { val = rand () > (RAND_MAX / 2); - dprintf (2, "%s%c", i == 0 ? "[" : ", ", val ? 'T' : 'F'); - soup_xmlrpc_message_write_boolean (msg, val); + debug_printf (2, "%s%c", i == 0 ? "[" : ", ", val ? 'T' : 'F'); + soup_value_array_append (bools, G_TYPE_BOOLEAN, val); if (val) trues++; else falses++; } - dprintf (2, "] -> "); - soup_xmlrpc_message_end_array (msg); - soup_xmlrpc_message_end_param (msg); - soup_xmlrpc_message_end_call (msg); - - response = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_STRUCT); - if (!response) - return FALSE; - value = soup_xmlrpc_response_get_value (response); - - if (!soup_xmlrpc_value_get_struct (value, &result)) { - dprintf (1, "wrong type?\n"); - g_object_unref (response); + debug_printf (2, "] -> "); + + ok = (do_xmlrpc ("countBools", &retval, + G_TYPE_VALUE_ARRAY, bools, + G_TYPE_INVALID) && + check_xmlrpc (&retval, G_TYPE_HASH_TABLE, &result)); + g_value_array_free (bools); + if (!ok) return FALSE; - } - if (!soup_xmlrpc_value_get_int (g_hash_table_lookup (result, "true"), &ret_trues)) { - dprintf (1, "NO 'true' value in response\n"); - g_hash_table_destroy (result); - g_object_unref (response); + if (!soup_value_hash_lookup (result, "true", G_TYPE_INT, &ret_trues)) { + debug_printf (1, "NO 'true' value in response\n"); return FALSE; } - if (!soup_xmlrpc_value_get_int (g_hash_table_lookup (result, "false"), &ret_falses)) { - dprintf (1, "NO 'false' value in response\n"); - g_hash_table_destroy (result); - g_object_unref (response); + if (!soup_value_hash_lookup (result, "false", G_TYPE_INT, &ret_falses)) { + debug_printf (1, "NO 'false' value in response\n"); return FALSE; } g_hash_table_destroy (result); - g_object_unref (response); - dprintf (2, "{ true: %ld, false: %ld } ", ret_trues, ret_falses); + debug_printf (2, "{ true: %d, false: %d } ", ret_trues, ret_falses); ok = (trues == ret_trues) && (falses == ret_falses); - dprintf (1, "%s\n", ok ? "OK!" : "WRONG!"); + debug_printf (1, "%s\n", ok ? "OK!" : "WRONG!"); return ok; } static gboolean test_md5sum (void) { - SoupXmlrpcMessage *msg; - SoupXmlrpcResponse *response; - SoupXmlrpcValue *value; - GByteArray *result; - char data[512]; + GByteArray *data, *result; int i; - SoupMD5Context md5; + GChecksum *checksum; guchar digest[16]; + gsize digest_len = sizeof (digest); + GValue retval; gboolean ok; - dprintf (1, "md5sum (base64 -> base64): "); + debug_printf (1, "md5sum (base64 -> base64): "); - msg = soup_xmlrpc_message_new (uri); - soup_xmlrpc_message_start_call (msg, "md5sum"); - soup_xmlrpc_message_start_param (msg); - for (i = 0; i < sizeof (data); i++) - data[i] = (char)(rand () & 0xFF); - soup_xmlrpc_message_write_base64 (msg, data, sizeof (data)); - soup_xmlrpc_message_end_param (msg); - soup_xmlrpc_message_end_call (msg); + data = g_byte_array_new (); + g_byte_array_set_size (data, 256); + for (i = 0; i < data->len; i++) + data->data[i] = (char)(rand ()); - response = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_BASE64); - if (!response) - return FALSE; - value = soup_xmlrpc_response_get_value (response); + checksum = g_checksum_new (G_CHECKSUM_MD5); + g_checksum_update (checksum, data->data, data->len); + g_checksum_get_digest (checksum, digest, &digest_len); + g_checksum_free (checksum); - if (!soup_xmlrpc_value_get_base64 (value, &result)) { - dprintf (1, "wrong type?\n"); - g_object_unref (response); + ok = (do_xmlrpc ("md5sum", &retval, + SOUP_TYPE_BYTE_ARRAY, data, + G_TYPE_INVALID) && + check_xmlrpc (&retval, SOUP_TYPE_BYTE_ARRAY, &result)); + g_byte_array_free (data, TRUE); + if (!ok) return FALSE; - } - g_object_unref (response); - if (result->len != 16) { - dprintf (1, "result has WRONG length (%d)\n", result->len); + if (result->len != digest_len) { + debug_printf (1, "result has WRONG length (%d)\n", result->len); g_byte_array_free (result, TRUE); return FALSE; } - soup_md5_init (&md5); - soup_md5_update (&md5, data, sizeof (data)); - soup_md5_final (&md5, digest); - - ok = (memcmp (digest, result->data, 16) == 0); - dprintf (1, "%s\n", ok ? "OK!" : "WRONG!"); + ok = (memcmp (digest, result->data, digest_len) == 0); + debug_printf (1, "%s\n", ok ? "OK!" : "WRONG!"); g_byte_array_free (result, TRUE); return ok; } @@ -253,105 +225,96 @@ test_md5sum (void) static gboolean test_dateChange (void) { - SoupXmlrpcMessage *msg; - SoupXmlrpcResponse *response; - SoupXmlrpcValue *value; - struct tm tm; - time_t when, result; - char timestamp[128]; - - dprintf (1, "dateChange (struct of time and ints -> time): "); - - msg = soup_xmlrpc_message_new (uri); - soup_xmlrpc_message_start_call (msg, "dateChange"); - soup_xmlrpc_message_start_param (msg); - soup_xmlrpc_message_start_struct (msg); - - soup_xmlrpc_message_start_member (msg, "date"); - memset (&tm, 0, sizeof (tm)); - tm.tm_year = 70 + (rand () % 50); - tm.tm_mon = rand () % 12; - tm.tm_mday = 1 + (rand () % 28); - tm.tm_hour = rand () % 24; - tm.tm_min = rand () % 60; - tm.tm_sec = rand () % 60; - when = soup_mktime_utc (&tm); - soup_xmlrpc_message_write_datetime (msg, when); - soup_xmlrpc_message_end_member (msg); - - strftime (timestamp, sizeof (timestamp), - "%Y-%m-%dT%H:%M:%S", &tm); - dprintf (2, "{ date: %s", timestamp); + GHashTable *structval; + SoupDate *date, *result; + char *timestamp; + GValue retval; + gboolean ok; + + debug_printf (1, "dateChange (struct of time and ints -> time): "); + + structval = soup_value_hash_new (); + + date = soup_date_new (1970 + (rand () % 50), + 1 + rand () % 12, + 1 + rand () % 28, + rand () % 24, + rand () % 60, + rand () % 60); + soup_value_hash_insert (structval, "date", SOUP_TYPE_DATE, date); + + if (debug_level >= 2) { + timestamp = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC); + debug_printf (2, "{ date: %s", timestamp); + g_free (timestamp); + } if (rand () % 3) { - tm.tm_year = 70 + (rand () % 50); - dprintf (2, ", tm_year: %d", tm.tm_year); - soup_xmlrpc_message_start_member (msg, "tm_year"); - soup_xmlrpc_message_write_int (msg, tm.tm_year); - soup_xmlrpc_message_end_member (msg); + date->year = 1970 + (rand () % 50); + debug_printf (2, ", tm_year: %d", date->year - 1900); + soup_value_hash_insert (structval, "tm_year", + G_TYPE_INT, date->year - 1900); } if (rand () % 3) { - tm.tm_mon = rand () % 12; - dprintf (2, ", tm_mon: %d", tm.tm_mon); - soup_xmlrpc_message_start_member (msg, "tm_mon"); - soup_xmlrpc_message_write_int (msg, tm.tm_mon); - soup_xmlrpc_message_end_member (msg); + date->month = 1 + rand () % 12; + debug_printf (2, ", tm_mon: %d", date->month - 1); + soup_value_hash_insert (structval, "tm_mon", + G_TYPE_INT, date->month - 1); } if (rand () % 3) { - tm.tm_mday = 1 + (rand () % 28); - dprintf (2, ", tm_mday: %d", tm.tm_mday); - soup_xmlrpc_message_start_member (msg, "tm_mday"); - soup_xmlrpc_message_write_int (msg, tm.tm_mday); - soup_xmlrpc_message_end_member (msg); + date->day = 1 + rand () % 28; + debug_printf (2, ", tm_mday: %d", date->day); + soup_value_hash_insert (structval, "tm_mday", + G_TYPE_INT, date->day); } if (rand () % 3) { - tm.tm_hour = rand () % 24; - dprintf (2, ", tm_hour: %d", tm.tm_hour); - soup_xmlrpc_message_start_member (msg, "tm_hour"); - soup_xmlrpc_message_write_int (msg, tm.tm_hour); - soup_xmlrpc_message_end_member (msg); + date->hour = rand () % 24; + debug_printf (2, ", tm_hour: %d", date->hour); + soup_value_hash_insert (structval, "tm_hour", + G_TYPE_INT, date->hour); } if (rand () % 3) { - tm.tm_min = rand () % 60; - dprintf (2, ", tm_min: %d", tm.tm_min); - soup_xmlrpc_message_start_member (msg, "tm_min"); - soup_xmlrpc_message_write_int (msg, tm.tm_min); - soup_xmlrpc_message_end_member (msg); + date->minute = rand () % 60; + debug_printf (2, ", tm_min: %d", date->minute); + soup_value_hash_insert (structval, "tm_min", + G_TYPE_INT, date->minute); } if (rand () % 3) { - tm.tm_sec = rand () % 60; - dprintf (2, ", tm_sec: %d", tm.tm_sec); - soup_xmlrpc_message_start_member (msg, "tm_sec"); - soup_xmlrpc_message_write_int (msg, tm.tm_sec); - soup_xmlrpc_message_end_member (msg); + date->second = rand () % 60; + debug_printf (2, ", tm_sec: %d", date->second); + soup_value_hash_insert (structval, "tm_sec", + G_TYPE_INT, date->second); } - when = soup_mktime_utc (&tm); - dprintf (2, " } -> "); + debug_printf (2, " } -> "); - soup_xmlrpc_message_end_struct (msg); - soup_xmlrpc_message_end_param (msg); - soup_xmlrpc_message_end_call (msg); - - response = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_DATETIME); - if (!response) + ok = (do_xmlrpc ("dateChange", &retval, + G_TYPE_HASH_TABLE, structval, + G_TYPE_INVALID) && + check_xmlrpc (&retval, SOUP_TYPE_DATE, &result)); + g_hash_table_destroy (structval); + if (!ok) { + soup_date_free (date); return FALSE; - value = soup_xmlrpc_response_get_value (response); + } - if (!soup_xmlrpc_value_get_datetime (value, &result)) { - dprintf (1, "wrong type?\n"); - g_object_unref (response); - return FALSE; + if (debug_level >= 2) { + timestamp = soup_date_to_string (result, SOUP_DATE_ISO8601_XMLRPC); + debug_printf (2, "%s: ", timestamp); + g_free (timestamp); } - g_object_unref (response); - memset (&tm, 0, sizeof (tm)); - soup_gmtime (&result, &tm); - strftime (timestamp, sizeof (timestamp), "%Y-%m-%dT%H:%M:%S", &tm); - dprintf (2, "%s: ", timestamp); + ok = ((date->year == result->year) && + (date->month == result->month) && + (date->day == result->day) && + (date->hour == result->hour) && + (date->minute == result->minute) && + (date->second == result->second)); + soup_date_free (date); + soup_date_free (result); - dprintf (1, "%s\n", (when == result) ? "OK!" : "WRONG!"); - return (when == result); + debug_printf (1, "%s\n", ok ? "OK!" : "WRONG!"); + return ok; } static const char *const echo_strings[] = { @@ -365,104 +328,64 @@ static const char *const echo_strings[] = { static gboolean test_echo (void) { - SoupXmlrpcMessage *msg; - SoupXmlrpcResponse *response; - SoupXmlrpcValue *value, *elt; - SoupXmlrpcValueArrayIterator *iter; - char *echo; + GValueArray *originals, *echoes; + GValue retval; int i; - dprintf (1, "echo (array of string -> array of string): "); + debug_printf (1, "echo (array of string -> array of string): "); - msg = soup_xmlrpc_message_new (uri); - soup_xmlrpc_message_start_call (msg, "echo"); - soup_xmlrpc_message_start_param (msg); - soup_xmlrpc_message_start_array (msg); + originals = g_value_array_new (N_ECHO_STRINGS); for (i = 0; i < N_ECHO_STRINGS; i++) { - dprintf (2, "%s\"%s\"", i == 0 ? "[" : ", ", echo_strings[i]); - soup_xmlrpc_message_write_string (msg, echo_strings[i]); + soup_value_array_append (originals, G_TYPE_STRING, echo_strings[i]); + debug_printf (2, "%s\"%s\"", i == 0 ? "[" : ", ", echo_strings[i]); } - dprintf (2, "] -> "); - soup_xmlrpc_message_end_array (msg); - soup_xmlrpc_message_end_param (msg); - soup_xmlrpc_message_end_call (msg); + debug_printf (2, "] -> "); - response = do_xmlrpc (msg, SOUP_XMLRPC_VALUE_TYPE_ARRAY); - if (!response) + if (!(do_xmlrpc ("echo", &retval, + G_TYPE_VALUE_ARRAY, originals, + G_TYPE_INVALID) && + check_xmlrpc (&retval, G_TYPE_VALUE_ARRAY, &echoes))) { + g_value_array_free (originals); return FALSE; - value = soup_xmlrpc_response_get_value (response); + } + g_value_array_free (originals); + + if (debug_level >= 2) { + for (i = 0; i < echoes->n_values; i++) { + debug_printf (2, "%s\"%s\"", i == 0 ? "[" : ", ", + g_value_get_string (&echoes->values[i])); + } + debug_printf (2, "] -> "); + } - if (!soup_xmlrpc_value_array_get_iterator (value, &iter)) { - dprintf (1, "wrong type?\n"); - g_object_unref (response); + if (echoes->n_values != N_ECHO_STRINGS) { + debug_printf (1, " WRONG! Wrong number of return strings"); + g_value_array_free (echoes); return FALSE; } - i = 0; - while (iter) { - if (!soup_xmlrpc_value_array_iterator_get_value (iter, &elt)) { - dprintf (1, " WRONG! Can't get result element %d\n", i + 1); - g_object_unref (response); - return FALSE; - } - if (!soup_xmlrpc_value_get_string (elt, &echo)) { - dprintf (1, " WRONG! Result element %d is not a string", i + 1); - g_object_unref (response); - return FALSE; - } - dprintf (2, "%s\"%s\"", i == 0 ? "[" : ", ", echo); - if (strcmp (echo_strings[i], echo) != 0) { - dprintf (1, " WRONG! Mismatch at %d\n", i + 1); - g_free (echo); - g_object_unref (response); + + for (i = 0; i < echoes->n_values; i++) { + if (strcmp (echo_strings[i], g_value_get_string (&echoes->values[i])) != 0) { + debug_printf (1, " WRONG! Mismatch at %d\n", i + 1); + g_value_array_free (echoes); return FALSE; } - g_free (echo); - - iter = soup_xmlrpc_value_array_iterator_next (iter); - i++; } - dprintf (2, "] "); - g_object_unref (response); - - dprintf (1, "%s\n", i == N_ECHO_STRINGS ? "OK!" : "WRONG! Too few results"); - return i == N_ECHO_STRINGS; -} -static void -usage (void) -{ - fprintf (stderr, "Usage: xmlrpc-test [-d] [-d]\n"); - exit (1); + debug_printf (1, "OK!\n"); + g_value_array_free (echoes); + return TRUE; } int main (int argc, char **argv) { - int opt, errors = 0; - - g_type_init (); - g_thread_init (NULL); - - while ((opt = getopt (argc, argv, "d")) != -1) { - switch (opt) { - case 'd': - debug++; - break; - - case '?': - usage (); - break; - } - } + test_init (argc, argv, NULL); + apache_init (); srand (time (NULL)); - if (!apache_init ()) { - fprintf (stderr, "Could not start apache\n"); - return 1; - } - - session = soup_session_sync_new (); + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); if (!test_sum ()) errors++; @@ -478,13 +401,6 @@ main (int argc, char **argv) soup_session_abort (session); g_object_unref (session); - apache_cleanup (); - - dprintf (1, "\n"); - if (errors) { - printf ("xmlrpc-test: %d error(s). Run with '-d' for details\n", - errors); - } else - printf ("xmlrpc-test: OK\n"); - return errors; + test_cleanup (); + return errors = 0; } -- 2.7.4