summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan Winship <danw@src.gnome.org>2008-01-15 17:40:47 +0000
committerDan Winship <danw@src.gnome.org>2008-01-15 17:40:47 +0000
commit96d28e7f42ead1ddde6bccca9fba6831710a531f (patch)
tree8b3fd4ae679d19656aef6264cfcf260e515a8eec
parent3f957a48574e9ac8eba06cf91fb2f101ffcf982d (diff)
downloadlibsoup-96d28e7f42ead1ddde6bccca9fba6831710a531f.tar.gz
Merge libsoup-2.4 branch to trunk
* Merge libsoup-2.4 branch to trunk svn path=/trunk/; revision=1041
-rw-r--r--ChangeLog805
-rw-r--r--README2
-rw-r--r--configure.in18
-rw-r--r--docs/reference/Makefile.am14
-rw-r--r--docs/reference/client-howto.xml180
-rw-r--r--docs/reference/libsoup-docs.sgml24
-rw-r--r--docs/reference/libsoup-overrides.txt5
-rw-r--r--docs/reference/libsoup-sections.txt762
-rw-r--r--docs/reference/libsoup.types33
-rw-r--r--docs/reference/porting-2.2-2.4.xml878
-rw-r--r--docs/reference/server-howto.xml205
-rw-r--r--docs/reference/tmpl/libsoup-unused.sgml497
-rw-r--r--docs/reference/tmpl/soup-address.sgml162
-rw-r--r--docs/reference/tmpl/soup-auth.sgml117
-rw-r--r--docs/reference/tmpl/soup-connection-ntlm.sgml26
-rw-r--r--docs/reference/tmpl/soup-connection.sgml254
-rw-r--r--docs/reference/tmpl/soup-dns.sgml123
-rw-r--r--docs/reference/tmpl/soup-md5-utils.sgml49
-rw-r--r--docs/reference/tmpl/soup-message-filter.sgml28
-rw-r--r--docs/reference/tmpl/soup-message-private.sgml82
-rw-r--r--docs/reference/tmpl/soup-message-queue.sgml102
-rw-r--r--docs/reference/tmpl/soup-message.sgml522
-rw-r--r--docs/reference/tmpl/soup-misc.sgml277
-rw-r--r--docs/reference/tmpl/soup-server-auth.sgml127
-rw-r--r--docs/reference/tmpl/soup-server-message.sgml95
-rw-r--r--docs/reference/tmpl/soup-server.sgml249
-rw-r--r--docs/reference/tmpl/soup-session-async.sgml45
-rw-r--r--docs/reference/tmpl/soup-session-sync.sgml49
-rw-r--r--docs/reference/tmpl/soup-session.sgml219
-rw-r--r--docs/reference/tmpl/soup-soap-message.sgml333
-rw-r--r--docs/reference/tmpl/soup-soap-response.sgml200
-rw-r--r--docs/reference/tmpl/soup-socket.sgml348
-rw-r--r--docs/reference/tmpl/soup-ssl.sgml110
-rw-r--r--docs/reference/tmpl/soup-status.sgml152
-rw-r--r--docs/reference/tmpl/soup-uri.sgml146
-rw-r--r--docs/reference/tmpl/soup-xmlrpc-message.sgml216
-rw-r--r--docs/reference/tmpl/soup-xmlrpc-response.sgml231
-rw-r--r--libsoup/Makefile.am84
-rw-r--r--libsoup/soup-address.c166
-rw-r--r--libsoup/soup-address.h48
-rw-r--r--libsoup/soup-auth-basic.c27
-rw-r--r--libsoup/soup-auth-digest.c478
-rw-r--r--libsoup/soup-auth-digest.h37
-rw-r--r--libsoup/soup-auth-domain-basic.c294
-rw-r--r--libsoup/soup-auth-domain-basic.h52
-rw-r--r--libsoup/soup-auth-domain-digest.c454
-rw-r--r--libsoup/soup-auth-domain-digest.h62
-rw-r--r--libsoup/soup-auth-domain.c415
-rw-r--r--libsoup/soup-auth-domain.h69
-rw-r--r--libsoup/soup-auth-manager.c451
-rw-r--r--libsoup/soup-auth-manager.h27
-rw-r--r--libsoup/soup-auth-ntlm.c134
-rw-r--r--libsoup/soup-auth-ntlm.h35
-rw-r--r--libsoup/soup-auth.c337
-rw-r--r--libsoup/soup-auth.h36
-rw-r--r--libsoup/soup-connection-ntlm.c78
-rw-r--r--libsoup/soup-connection.c196
-rw-r--r--libsoup/soup-connection.h43
-rw-r--r--libsoup/soup-date.c596
-rw-r--r--libsoup/soup-date.h50
-rw-r--r--libsoup/soup-dns.c199
-rw-r--r--libsoup/soup-dns.h23
-rw-r--r--libsoup/soup-enum-types.c.tmpl33
-rw-r--r--libsoup/soup-enum-types.h.tmpl24
-rw-r--r--libsoup/soup-form.c163
-rw-r--r--libsoup/soup-form.h20
-rw-r--r--libsoup/soup-gnutls.c17
-rw-r--r--libsoup/soup-headers.c557
-rw-r--r--libsoup/soup-headers.h60
-rw-r--r--libsoup/soup-logger.c638
-rw-r--r--libsoup/soup-logger.h76
-rw-r--r--libsoup/soup-marshal.list7
-rw-r--r--libsoup/soup-md5-utils.c306
-rw-r--r--libsoup/soup-md5-utils.h42
-rw-r--r--libsoup/soup-message-body.c429
-rw-r--r--libsoup/soup-message-body.h63
-rw-r--r--libsoup/soup-message-client-io.c85
-rw-r--r--libsoup/soup-message-filter.c30
-rw-r--r--libsoup/soup-message-filter.h34
-rw-r--r--libsoup/soup-message-handlers.c268
-rw-r--r--libsoup/soup-message-headers.c540
-rw-r--r--libsoup/soup-message-headers.h71
-rw-r--r--libsoup/soup-message-io.c276
-rw-r--r--libsoup/soup-message-private.h45
-rw-r--r--libsoup/soup-message-queue.c4
-rw-r--r--libsoup/soup-message-queue.h7
-rw-r--r--libsoup/soup-message-server-io.c127
-rw-r--r--libsoup/soup-message.c984
-rw-r--r--libsoup/soup-message.h290
-rw-r--r--libsoup/soup-method.c83
-rw-r--r--libsoup/soup-method.h79
-rw-r--r--libsoup/soup-misc.c92
-rw-r--r--libsoup/soup-misc.h48
-rw-r--r--libsoup/soup-path-map.c186
-rw-r--r--libsoup/soup-path-map.h26
-rw-r--r--libsoup/soup-server-auth.c459
-rw-r--r--libsoup/soup-server-auth.h90
-rw-r--r--libsoup/soup-server-message.c141
-rw-r--r--libsoup/soup-server-message.h49
-rw-r--r--libsoup/soup-server.c999
-rw-r--r--libsoup/soup-server.h103
-rw-r--r--libsoup/soup-session-async.c52
-rw-r--r--libsoup/soup-session-async.h5
-rw-r--r--libsoup/soup-session-private.h32
-rw-r--r--libsoup/soup-session-sync.c37
-rw-r--r--libsoup/soup-session-sync.h5
-rw-r--r--libsoup/soup-session.c731
-rw-r--r--libsoup/soup-session.h49
-rw-r--r--libsoup/soup-soap-message.c827
-rw-r--r--libsoup/soup-soap-message.h96
-rw-r--r--libsoup/soup-soap-response.c554
-rw-r--r--libsoup/soup-soap-response.h66
-rw-r--r--libsoup/soup-socket.c748
-rw-r--r--libsoup/soup-socket.h89
-rw-r--r--libsoup/soup-ssl.h17
-rw-r--r--libsoup/soup-status.c153
-rw-r--r--libsoup/soup-status.h156
-rw-r--r--libsoup/soup-types.h33
-rw-r--r--libsoup/soup-uri.c811
-rw-r--r--libsoup/soup-uri.h110
-rw-r--r--libsoup/soup-value-utils.c292
-rw-r--r--libsoup/soup-value-utils.h69
-rw-r--r--libsoup/soup-xmlrpc-message.c402
-rw-r--r--libsoup/soup-xmlrpc-message.h80
-rw-r--r--libsoup/soup-xmlrpc-response.c649
-rw-r--r--libsoup/soup-xmlrpc-response.h88
-rw-r--r--libsoup/soup-xmlrpc.c796
-rw-r--r--libsoup/soup-xmlrpc.h67
-rw-r--r--libsoup/soup.h15
-rw-r--r--tests/Makefile.am46
-rw-r--r--tests/apache-wrapper.c69
-rw-r--r--tests/apache-wrapper.h4
-rw-r--r--tests/auth-test.c310
-rw-r--r--tests/context-test.c214
-rw-r--r--tests/continue-test.c456
-rw-r--r--tests/date.c111
-rw-r--r--tests/dict.c164
-rw-r--r--tests/dns.c3
-rw-r--r--tests/get.c30
-rw-r--r--tests/getbug.c81
-rw-r--r--tests/header-parsing.c386
-rw-r--r--tests/httpd.conf.in10
-rw-r--r--tests/ntlm-test.c261
-rw-r--r--tests/proxy-test.c73
-rw-r--r--tests/pull-api.c264
-rw-r--r--tests/query-test.c231
-rw-r--r--tests/revserver.c187
-rw-r--r--tests/server-auth-test.c351
-rw-r--r--tests/simple-httpd.c166
-rw-r--r--tests/simple-proxy.c70
-rw-r--r--tests/ssl-test.c48
-rw-r--r--tests/test-utils.c284
-rw-r--r--tests/test-utils.h19
-rw-r--r--tests/uri-parsing.c165
-rw-r--r--tests/xmlrpc-test.c544
155 files changed, 15607 insertions, 15596 deletions
diff --git a/ChangeLog b/ChangeLog
index b97a83bd..3f625e15 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,808 @@
-2008-01-09 Benjamin Otte <otte@gnome.org>
+2008-01-15 Dan Winship <danw@gnome.org>
+
+ * Merge libsoup-2.4 branch to trunk
+
+2008-01-15 Dan Winship <danw@gnome.org>
+
+ * libsoup/soup-dns.c (resolve_status): Fix the logic here
+
+2008-01-15 Dan Winship <danw@gnome.org>
+
+ * docs/reference/porting-2.2-2.4.xml: add a few more updates
+
+2008-01-15 Dan Winship <danw@gnome.org>
+
+ * libsoup/soup-auth-digest.c: Use GChecksum for MD5
+
+ * libsoup/soup-md5-utils.[ch]: gone
+
+2008-01-15 Dan Winship <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <otte@gnome.org>
* libsoup/soup-socket.c: (soup_socket_class_init): clarify docs for
- new-connetion signal.
+ new-connection signal.
+
+2008-01-14 Dan Winship <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * docs/reference/porting-2.2-2.4.xml: Notes on porting from 2.2 to
+ 2.4
+
+2008-01-07 Dan Winship <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * libsoup/Makefile.am (soup_headers): oops, move soup-auth.h here
+
+2008-01-02 Dan Winship <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
+
+ * 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 <danw@gnome.org>
diff --git a/README b/README
index d2f656ab..bf3b70a6 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 f04ecc6f..ac8c9df9 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 0dae9709..f34e154a 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 ac7caa06..c52f6353 100644
--- a/docs/reference/client-howto.xml
+++ b/docs/reference/client-howto.xml
@@ -44,7 +44,7 @@ slightly different behavior:
</itemizedlist>
<para>
-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.
</para>
@@ -142,8 +142,10 @@ only need to create the message and it's ready to send:
<para>
In more complicated cases, you can use various <link
-linkend="SoupMessage">SoupMessage</link> methods to set the request
-headers and body of the message:
+linkend="SoupMessage">SoupMessage</link>, <link
+linkend="SoupMessageHeaders">SoupMessageHeaders</link>, and <link
+linkend="SoupMessageBody">SoupMessageBody</link> methods to set the
+request headers and body of the message:
</para>
<informalexample><programlisting>
@@ -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);
</programlisting></informalexample>
<para>
@@ -180,20 +182,18 @@ linkend="soup-session-send-message"><function>soup_session_send_message</functio
</programlisting></informalexample>
<para>
-<literal>session</literal> can be either a <link
-linkend="SoupSessionSync"><type>SoupSessionSync</type></link> or a
-<link linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link>;
-if you use <function>soup_session_send_message</function> on an async
-session, it will run the main loop itself until the message is
-complete.
+(If you use <function>soup_session_send_message</function> with a
+<link linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link>,
+it will run the main loop itself until the message is complete.)
</para>
<para>
-The return value from <function>soup_session_send</function> is a <link
-linkend="soup-status">soup status code</link>, indicating either a
-transport error that prevented the message from being sent, or the
+The return value from <function>soup_session_send</function> is a
+<link linkend="soup-status">soup status code</link>, 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
+<literal>msg->status_code</literal>.)
</para>
</refsect3>
@@ -202,10 +202,8 @@ message.
<title>Sending a Message Asynchronously</title>
<para>
-To send a message asynchronously (which can only be done if you're
-using <link
-linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link>), use
-<link linkend="soup-session-queue-message"><function>soup_session_queue_message</function></link>:
+To send a message asynchronously, use <link
+linkend="soup-session-queue-message"><function>soup_session_queue_message</function></link>:
</para>
<informalexample><programlisting>
@@ -215,7 +213,7 @@ linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link>), 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 <function>soup_session_queue_message</function>.
</para>
+<para>
+(If you use <link
+linkend="soup-session-queue-message"><function>soup_session_queue_message</function></link>
+with a <link
+linkend="SoupSessionSync"><type>SoupSessionSync</type></link>, the
+message will be sent in another thread, with the callback eventually
+being invoked in the session's <link linkend="SOUP-SESSION-ASYNC-CONTEXT:CAPS"><literal>SOUP_SESSION_ASYNC_CONTEXT</literal></link>.)
+</para>
+
</refsect3>
</refsect2>
@@ -245,11 +252,11 @@ asynchronously, you can look at the response fields in the
status and textual status response from the server.
<structfield>response_headers</structfield> contains the response
headers, which you can investigate using <link
-linkend="soup-message-get-header"><function>soup_message_get_header</function></link> and
+linkend="soup-message-headers-get"><function>soup_message_headers_get</function></link> and
<link
- linkend="soup-message-foreach-header"><function>soup_message_foreach_header</function></link>.
+ linkend="soup-message-headers-foreach"><function>soup_message_headers_foreach</function></link>.
The response body (if any) is in the
-<structfield>response</structfield> field.
+<structfield>response_body</structfield> field.
</para>
<para>
@@ -272,17 +279,17 @@ it.
<title>Intermediate/Automatic Processing</title>
<para>
-You can also connect to various <literal>SoupMessage</literal>
-signals, or set up handlers using <link
-linkend="soup-message-add-handler"><function>soup_message_add_handler</function></link>
-and the other handler methods. Notably, <link
+You can also connect to various <literal>SoupMessage</literal> signals
+to do processing at intermediate stages of HTTP I/O.
+<literal>SoupMessage</literal> also provides two convenience methods,
+<link
linkend="soup-message-add-header-handler"><function>soup_message_add_header_handler</function></link>,
-<link linkend="soup-message-add-status-code-handler"><function>soup_message_add_status_code_handler</function></link>,
-and
-<link linkend="soup-message-add-status-class-handler"><function>soup_message_add_status_class_handler</function></link>
-allow you to invoke a handler automatically for messages with certain
-response headers or status codes. <type>SoupSession</type> uses
-this internally to handle authentication and redirection.
+and <link
+linkend="soup-message-add-status-code-handler"><function>soup_message_add_status_code_handler</function></link>,
+which allow you to set up a signal handler that will only be invoked
+for messages with certain response headers or status codes.
+<type>SoupSession</type> uses this internally to handle authentication
+and redirection.
</para>
<para>
@@ -293,10 +300,9 @@ linkend="soup-session-send-message"><function>soup_session_send_message</functio
<para>
To automatically set up handlers on all messages sent via a session,
-you can create a <link
-linkend="SoupMessageFilter">SoupMessageFilter</link> and attach it to
-the session with <link
-linkend="soup-session-add-filter"><function>soup_session_add_filter</function></link>.
+you can connect to the session's <link
+linkend="SoupSession-request-started"><literal>request_started</literal></link>
+signal, and add handlers to each message from there.
</para>
</refsect2>
@@ -309,76 +315,36 @@ linkend="soup-session-add-filter"><function>soup_session_add_filter</function></
authentication for you. If it receives a 401 ("Unauthorized") or 407
("Proxy Authentication Required") response, the session will emit the
<link linkend="SoupSession-authenticate">authenticate</link> 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 <parameter>username</parameter>
-and <parameter>password</parameter> parameters with authentication
-information. (The session will <function>g_free</function> 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 <link
+linkend="SoupAuth"><type>SoupAuth</type></link> 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 <link
+linkend="soup-auth-authenticate"><function>soup_auth_authenticate</function></link>
+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
+<function>soup_auth_authenticate</function>, the session will just
+return the message to the application with its 401 or 407 status.)
</para>
<para>
-If the <literal>authenticate</literal> handler returns a username and
-password, but the request still gets an authorization error using that
-information, then the session will emit the <link
-linkend="SoupSession-reauthenticate">reauthenticate</link> 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 <link
+linkend="SoupSession-authenticate">authenticate</link> again, with the
+<literal>retrying</literal> parameter set to <link
+linkend="TRUE:CAPS"><literal>TRUE</literal></link>. 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 <literal>reauthenticate</literal> again and again until the
-returned username/password successfully authentications, or until the
-signal handler fails to provide a username, at which point
-<application>libsoup</application> will allow the message to fail
-(with status 401 or 407).
+emit <literal>authenticate</literal> again and again until the
+provided username/password successfully authenticates, or until the
+signal handler fails to call <link
+linkend="soup-auth-authenticate"><function>soup_auth_authenticate</function></link>,
+at which point <application>libsoup</application> will allow the
+message to fail (with status 401 or 407).
</para>
-<para>
-There are basically three ways an application might want to use
-the signals:
-</para>
-
-<itemizedlist>
- <listitem><para>
- An interactive application that doesn't cache passwords could
- just connect both <literal>authenticate</literal> and
- <literal>reauthenticate</literal> 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").
- </para></listitem>
-
- <listitem><para>
- A slightly cleverer interactive application would look in its
- password cache from the <literal>authenticate</literal>
- handler, and return a password from there if one was
- available. If no password was cached, it would just call its
- <literal>reauthenticate</literal> handler to prompt the user.
- The <literal>reauthenticate</literal> 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
- <literal>reauthenticate</literal> will be called again to
- force it to be uncached.)
- </para></listitem>
-
- <listitem><para>
- A non-interactive program that only has access to cached
- passwords would only connect to
- <literal>authenticate</literal>. If the username and password
- that <literal>authenticate</literal> returns fail, the session
- will emit <literal>reauthenticate</literal>, 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.
- </para></listitem>
-</itemizedlist>
-
</refsect2>
<refsect2>
@@ -396,12 +362,12 @@ A few sample programs are available in the
</para></listitem>
<listitem><para>
- <emphasis role="bold"><literal>dict</literal></emphasis> and
- <emphasis role="bold"><literal>getbug</literal></emphasis> are trivial
- demonstrations of the <link
- linkend="SoupSoapMessage">SOAP</link> and <link
- linkend="SoupXmlrpcMessage">XMLRPC</link> interfaces,
- respectively.
+ <emphasis role="bold"><literal>getbug</literal></emphasis> is a trivial
+ demonstration of the <link
+ linkend="libsoup-XMLRPC-Support">XMLRPC</link> interface.
+ (<emphasis
+ role="bold"><literal>xmlrpc-test</literal></emphasis> provides
+ a slightly more complicated example.)
</para></listitem>
<listitem><para>
diff --git a/docs/reference/libsoup-docs.sgml b/docs/reference/libsoup-docs.sgml
index 6c8011f8..ce97a913 100644
--- a/docs/reference/libsoup-docs.sgml
+++ b/docs/reference/libsoup-docs.sgml
@@ -10,36 +10,32 @@
<title>libsoup Tutorial</title>
<xi:include href="client-howto.xml"/>
<xi:include href="server-howto.xml"/>
+ <xi:include href="porting-2.2-2.4.xml"/>
</chapter>
<chapter>
<title>libsoup API Reference</title>
<xi:include href="xml/soup-address.xml"/>
<xi:include href="xml/soup-auth.xml"/>
- <xi:include href="xml/soup-connection.xml"/>
- <xi:include href="xml/soup-connection-ntlm.xml"/>
+ <xi:include href="xml/soup-auth-domain.xml"/>
+ <xi:include href="xml/soup-auth-domain-basic.xml"/>
+ <xi:include href="xml/soup-auth-domain-digest.xml"/>
+ <xi:include href="xml/soup-logger.xml"/>
<xi:include href="xml/soup-message.xml"/>
- <xi:include href="xml/soup-message-filter.xml"/>
- <xi:include href="xml/soup-server-message.xml"/>
+ <xi:include href="xml/soup-message-headers.xml"/>
+ <xi:include href="xml/soup-message-body.xml"/>
+ <xi:include href="xml/soup-method.xml"/>
<xi:include href="xml/soup-server.xml"/>
<xi:include href="xml/soup-session.xml"/>
<xi:include href="xml/soup-session-async.xml"/>
<xi:include href="xml/soup-session-sync.xml"/>
- <xi:include href="xml/soup-soap-message.xml"/>
- <xi:include href="xml/soup-soap-response.xml"/>
<xi:include href="xml/soup-socket.xml"/>
<xi:include href="xml/soup-status.xml"/>
<xi:include href="xml/soup-uri.xml"/>
- <xi:include href="xml/soup-xmlrpc-message.xml"/>
- <xi:include href="xml/soup-xmlrpc-response.xml"/>
+ <xi:include href="xml/soup-value-utils.xml"/>
+ <xi:include href="xml/soup-xmlrpc.xml"/>
<xi:include href="xml/soup-misc.xml"/>
</chapter>
- <chapter>
- <title>libsoup internals</title>
- <xi:include href="xml/soup-dns.xml"/>
- <xi:include href="xml/soup-ssl.xml"/>
- </chapter>
-
<index></index>
</book>
diff --git a/docs/reference/libsoup-overrides.txt b/docs/reference/libsoup-overrides.txt
index 9828402a..06826ca5 100644
--- a/docs/reference/libsoup-overrides.txt
+++ b/docs/reference/libsoup-overrides.txt
@@ -1,9 +1,4 @@
<FUNCTION>
-<NAME>soup_dns_lookup_get_address</NAME>
-<RETURNS>struct sockaddr *</RETURNS>
-SoupDNSLookup *lookup
-</FUNCTION>
-<FUNCTION>
<NAME>soup_address_get_sockaddr</NAME>
<RETURNS>struct sockaddr *</RETURNS>
SoupAddress *addr,
diff --git a/docs/reference/libsoup-sections.txt b/docs/reference/libsoup-sections.txt
index c9f24c9a..afda49ff 100644
--- a/docs/reference/libsoup-sections.txt
+++ b/docs/reference/libsoup-sections.txt
@@ -2,26 +2,13 @@
<FILE>soup-message</FILE>
<TITLE>SoupMessage</TITLE>
SoupMessage
-SoupMessageStatus
-SOUP_MESSAGE_IS_STARTING
-SoupTransferEncoding
-SoupOwnership
-SoupDataBuffer
-SoupMessageCallbackFn
<SUBSECTION>
soup_message_new
soup_message_new_from_uri
soup_message_set_request
soup_message_set_response
<SUBSECTION>
-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
-<SUBSECTION>
-SoupHttpVersion
+SoupHTTPVersion
soup_message_set_http_version
soup_message_get_http_version
soup_message_get_uri
@@ -32,25 +19,17 @@ soup_message_get_flags
<SUBSECTION>
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
<SUBSECTION>
-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
-<SUBSECTION>
-soup_message_send_request
-soup_message_read_request
-soup_message_io_pause
-soup_message_io_unpause
-soup_message_io_stop
+<SUBSECTION>
+SOUP_MESSAGE_METHOD
+SOUP_MESSAGE_URI
+SOUP_MESSAGE_HTTP_VERSION
+SOUP_MESSAGE_FLAGS
+SOUP_MESSAGE_STATUS_CODE
+SOUP_MESSAGE_REASON_PHRASE
<SUBSECTION Standard>
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
<SUBSECTION 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
+</SECTION>
+
+<SECTION>
+<FILE>soup-method</FILE>
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
+</SECTION>
+
+<SECTION>
+<FILE>soup-message-headers</FILE>
+<TITLE>SoupMessageHeaders</TITLE>
+SoupMessageHeaders
+SoupMessageHeadersType
+soup_message_headers_new
+soup_message_headers_free
+<SUBSECTION>
+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
+<SUBSECTION>
+SoupEncoding
+soup_message_headers_get_encoding
+soup_message_headers_set_encoding
+soup_message_headers_get_content_length
+soup_message_headers_set_content_length
+<SUBSECTION>
+SoupExpectation
+soup_message_headers_get_expectations
+soup_message_headers_set_expectations
+</SECTION>
+
+<SECTION>
+<FILE>soup-message-body</FILE>
+<TITLE>SoupMessageBody</TITLE>
+SoupBuffer
+SoupMemoryUse
+soup_buffer_new
+soup_buffer_new_subbuffer
+soup_buffer_copy
+soup_buffer_free
+<SUBSECTION>
+SoupMessageBody
+soup_message_body_new
+soup_message_body_free
+<SUBSECTION>
+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
+<SUBSECTION Standard>
+SOUP_TYPE_BUFFER
+soup_buffer_get_type
</SECTION>
<SECTION>
<FILE>soup-status</FILE>
-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
-<SUBSECTION Standard>
-SOUP_STATUS_IS_TRANSPORT_ERROR
+<SUBSECTION>
+SOUP_HTTP_ERROR
+<SUBSECTION Private>
+soup_http_error_quark
</SECTION>
<SECTION>
<FILE>soup-server</FILE>
<TITLE>SoupServer</TITLE>
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
<SUBSECTION>
-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
+<SUBSECTION>
+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
+<SUBSECTION>
+soup_server_add_auth_domain
+soup_server_remove_auth_domain
+<SUBSECTION>
+soup_server_pause_message
+soup_server_unpause_message
<SUBSECTION>
SOUP_SERVER_PORT
SOUP_SERVER_INTERFACE
SOUP_SERVER_SSL_CERT_FILE
SOUP_SERVER_SSL_KEY_FILE
SOUP_SERVER_ASYNC_CONTEXT
+SOUP_SERVER_RAW_PATHS
<SUBSECTION Standard>
SOUP_SERVER
SOUP_IS_SERVER
@@ -156,18 +185,87 @@ SOUP_SERVER_CLASS
SOUP_IS_SERVER_CLASS
SOUP_SERVER_GET_CLASS
SoupServerClass
-<SUBSECTION Private>
-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
+</SECTION>
+
+<SECTION>
+<FILE>soup-auth-domain</FILE>
+<TITLE>SoupAuthDomain</TITLE>
+SoupAuthDomain
+<SUBSECTION>
+soup_auth_domain_add_path
+soup_auth_domain_remove_path
+SoupAuthDomainFilter
+soup_auth_domain_set_filter
+soup_auth_domain_get_realm
+<SUBSECTION>
+soup_auth_domain_covers
+soup_auth_domain_accepts
+soup_auth_domain_challenge
+<SUBSECTION>
+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
+<SUBSECTION Standard>
+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
+</SECTION>
+
+<SECTION>
+<FILE>soup-auth-domain-basic</FILE>
+<TITLE>SoupAuthDomainBasic</TITLE>
+SoupAuthDomainBasic
+soup_auth_domain_basic_new
+<SUBSECTION>
+SoupAuthDomainBasicAuthCallback
+soup_auth_domain_basic_set_auth_callback
+<SUBSECTION>
+SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK
+SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA
+<SUBSECTION Standard>
+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
+</SECTION>
+
+<SECTION>
+<FILE>soup-auth-domain-digest</FILE>
+<TITLE>SoupAuthDomainDigest</TITLE>
+SoupAuthDomainDigest
+soup_auth_domain_digest_new
+<SUBSECTION>
+SoupAuthDomainDigestAuthCallback
+soup_auth_domain_digest_set_auth_callback
+soup_auth_domain_digest_encode_password
+<SUBSECTION>
+soup_auth_domain_digest_evil_check_password
+<SUBSECTION>
+SOUP_AUTH_DOMAIN_DIGEST_AUTH_CALLBACK
+SOUP_AUTH_DOMAIN_DIGEST_AUTH_DATA
+<SUBSECTION Standard>
+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
</SECTION>
<SECTION>
@@ -179,10 +277,11 @@ SOUP_ADDRESS_ANY_PORT
soup_address_new
soup_address_new_from_sockaddr
soup_address_new_any
+<SUBSECTION>
SoupAddressCallback
soup_address_resolve_async
-soup_address_resolve_async_full
soup_address_resolve_sync
+<SUBSECTION>
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
-</SECTION>
-
-<SECTION>
-<FILE>soup-soap-message</FILE>
-SoupSoapMessage
-<TITLE>SoupSoapMessage</TITLE>
-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
-<SUBSECTION Standard>
-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
-</SECTION>
-
-<SECTION>
-<FILE>soup-soap-response</FILE>
-<TITLE>SoupSoapResponse</TITLE>
-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
-<SUBSECTION Standard>
-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
+<SUBSECTION Private>
+AF_INET6
</SECTION>
<SECTION>
<FILE>soup-session</FILE>
<TITLE>SoupSession</TITLE>
SoupSession
+<SUBSECTION>
+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
+<SUBSECTION>
+soup_session_pause_message
+soup_session_unpause_message
+<SUBSECTION>
+soup_session_get_async_context
+<SUBSECTION>
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
-<SUBSECTION Private>
-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
</SECTION>
<SECTION>
@@ -360,16 +372,27 @@ soup_session_sync_get_type
<FILE>soup-auth</FILE>
<TITLE>SoupAuth</TITLE>
SoupAuth
-SoupAuthBasic
-SoupAuthDigest
-soup_auth_new_from_header_list
+soup_auth_new
+soup_auth_update
+<SUBSECTION>
+soup_auth_is_for_proxy
soup_auth_get_scheme_name
+soup_auth_get_host
soup_auth_get_realm
+soup_auth_get_info
+<SUBSECTION>
soup_auth_authenticate
soup_auth_is_authenticated
+<SUBSECTION>
soup_auth_get_authorization
soup_auth_get_protection_space
soup_auth_free_protection_space
+<SUBSECTION>
+SOUP_AUTH_SCHEME_NAME
+SOUP_AUTH_REALM
+SOUP_AUTH_HOST
+SOUP_AUTH_IS_FOR_PROXY
+SOUP_AUTH_IS_AUTHENTICATED
<SUBSECTION Standard>
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
</SECTION>
<SECTION>
@@ -402,31 +409,38 @@ SoupAuthDigestClass
<TITLE>SoupSocket</TITLE>
SoupSocket
soup_socket_new
-soup_socket_connect
+<SUBSECTION>
+SoupSocketCallback
+soup_socket_connect_async
+soup_socket_connect_sync
+<SUBSECTION>
soup_socket_listen
+<SUBSECTION>
soup_socket_start_ssl
soup_socket_start_proxy_ssl
+soup_socket_is_ssl
+<SUBSECTION>
soup_socket_disconnect
soup_socket_is_connected
-SoupSocketCallback
-SoupSocketListenerCallback
-soup_socket_client_new_async
-soup_socket_client_new_sync
-soup_socket_server_new
+<SUBSECTION>
soup_socket_get_local_address
soup_socket_get_remote_address
+<SUBSECTION>
SoupSocketIOStatus
soup_socket_read
soup_socket_read_until
soup_socket_write
+<SUBSECTION>
+SOUP_SSL_ERROR
+SoupSSLError
+<SUBSECTION>
+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
<SUBSECTION Standard>
SOUP_SOCKET
SOUP_IS_SOCKET
@@ -436,260 +450,154 @@ SOUP_SOCKET_CLASS
SOUP_IS_SOCKET_CLASS
SOUP_SOCKET_GET_CLASS
SoupSocketClass
-</SECTION>
-
-<SECTION>
-<FILE>soup-connection-ntlm</FILE>
-<TITLE>SoupConnectionNTLM</TITLE>
-SoupConnectionNTLM
-<SUBSECTION Standard>
-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
-</SECTION>
-
-<SECTION>
-<FILE>soup-connection</FILE>
-<TITLE>SoupConnection</TITLE>
-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
-<SUBSECTION Standard>
-SOUP_CONNECTION
-SOUP_IS_CONNECTION
-SOUP_TYPE_CONNECTION
-soup_connection_get_type
-SOUP_CONNECTION_CLASS
-SOUP_IS_CONNECTION_CLASS
-SOUP_CONNECTION_GET_CLASS
-SoupConnectionClass
-</SECTION>
-
-<SECTION>
-<FILE>soup-server-message</FILE>
-<TITLE>SoupServerMessage</TITLE>
-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
-<SUBSECTION Standard>
-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
-</SECTION>
-
-<SECTION>
-<FILE>soup-dns</FILE>
-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
-</SECTION>
-
-<SECTION>
-<FILE>soup-ssl</FILE>
-soup_ssl_supported
-<SUBSECTION>
-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
+<SUBSECTION Private>
soup_ssl_error_quark
-SoupSocketError
</SECTION>
<SECTION>
<FILE>soup-uri</FILE>
-<TITLE>SoupUri</TITLE>
-SoupUri
-SoupProtocol
-SOUP_PROTOCOL_HTTP
-SOUP_PROTOCOL_HTTPS
+<TITLE>SoupURI</TITLE>
+SoupURI
soup_uri_new_with_base
soup_uri_new
soup_uri_to_string
+<SUBSECTION>
soup_uri_copy
-soup_uri_copy_root
soup_uri_equal
soup_uri_free
+<SUBSECTION>
soup_uri_encode
soup_uri_decode
+soup_uri_normalize
+<SUBSECTION>
+SOUP_URI_SCHEME_HTTP
+SOUP_URI_SCHEME_HTTPS
soup_uri_uses_default_port
-</SECTION>
-
-<SECTION>
-<FILE>soup-message-filter</FILE>
-<TITLE>SoupMessageFilter</TITLE>
-soup_message_filter_setup_message
+<SUBSECTION>
+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
<SUBSECTION Standard>
-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
</SECTION>
<SECTION>
<FILE>soup-misc</FILE>
<TITLE>Soup Miscellaneous Utilities</TITLE>
-soup_base64_decode
-soup_base64_encode
-soup_base64_decode_step
-soup_base64_encode_step
-soup_base64_encode_close
-<SUBSECTION>
-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
<SUBSECTION>
-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
<SUBSECTION>
-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
<SUBSECTION>
+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
+<SUBSECTION>
soup_str_case_equal
soup_str_case_hash
<SUBSECTION>
-soup_xml_real_node
+soup_add_idle
+soup_add_io_watch
+soup_add_timeout
+<SUBSECTION>
+soup_ssl_supported
+<SUBSECTION Private>
+soup_date_copy
+soup_signal_connect_once
+SOUP_TYPE_DATE
+soup_date_get_type
</SECTION>
<SECTION>
-<FILE>soup-xmlrpc-message</FILE>
-<TITLE>SoupXmlrpcMessage</TITLE>
-SoupXmlrpcMessage
-soup_xmlrpc_message_new
-soup_xmlrpc_message_new_from_uri
-soup_xmlrpc_message_from_string
-<SUBSECTION>
-soup_xmlrpc_message_start_call
-soup_xmlrpc_message_end_call
-soup_xmlrpc_message_start_param
-soup_xmlrpc_message_end_param
-<SUBSECTION>
-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
-<SUBSECTION>
-soup_xmlrpc_message_start_struct
-soup_xmlrpc_message_end_struct
-soup_xmlrpc_message_start_member
-soup_xmlrpc_message_end_member
-<SUBSECTION>
-soup_xmlrpc_message_start_array
-soup_xmlrpc_message_end_array
-<SUBSECTION>
-soup_xmlrpc_message_to_string
-soup_xmlrpc_message_persist
-<SUBSECTION>
-soup_xmlrpc_message_parse_response
-<SUBSECTION Standard>
-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
+<FILE>soup-xmlrpc</FILE>
+<TITLE>XMLRPC Support</TITLE>
+<SUBSECTION>
+soup_xmlrpc_build_method_call
+soup_xmlrpc_request_new
+soup_xmlrpc_parse_method_response
+soup_xmlrpc_extract_method_response
+<SUBSECTION>
+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
+<SUBSECTION>
+SOUP_XMLRPC_ERROR
+SoupXMLRPCError
+SOUP_XMLRPC_FAULT
+<SUBSECTION Private>
+soup_xmlrpc_error_quark
+soup_xmlrpc_fault_quark
</SECTION>
<SECTION>
-<FILE>soup-xmlrpc-response</FILE>
-<TITLE>SoupXmlrpcResponse</TITLE>
-SoupXmlrpcResponse
-soup_xmlrpc_response_new
-soup_xmlrpc_response_new_from_string
-soup_xmlrpc_response_from_string
-soup_xmlrpc_response_to_string
-<SUBSECTION>
-SoupXmlrpcValue
-SoupXmlrpcValueType
-soup_xmlrpc_response_is_fault
-soup_xmlrpc_response_get_value
-<SUBSECTION>
-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
-<SUBSECTION>
-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
-<SUBSECTION>
-soup_xmlrpc_value_dump
+<FILE>soup-value-utils</FILE>
+<TITLE>GValue Support</TITLE>
+soup_value_hash_new
+soup_value_hash_insert_value
+soup_value_hash_insert
+soup_value_hash_lookup
+<SUBSECTION>
+soup_value_array_from_args
+soup_value_array_to_args
+soup_value_array_insert
+soup_value_array_append
+soup_value_array_get_nth
+<SUBSECTION>
+SOUP_VALUE_SETV
+SOUP_VALUE_GETV
+<SUBSECTION>
+SOUP_TYPE_BYTE_ARRAY
+<SUBSECTION Private>
+soup_byte_array_get_type
+</SECTION>
+
+<SECTION>
+<FILE>soup-logger</FILE>
+<TITLE>SoupLogger</TITLE>
+SoupLogger
+SoupLoggerLogLevel
+soup_logger_new
+soup_logger_attach
+soup_logger_detach
+<SUBSECTION>
+SoupLoggerFilter
+soup_logger_set_request_filter
+soup_logger_set_response_filter
+<SUBSECTION>
+SoupLoggerPrinter
+soup_logger_set_printer
<SUBSECTION Standard>
-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
</SECTION>
diff --git a/docs/reference/libsoup.types b/docs/reference/libsoup.types
index 7c6a4d23..9effee14 100644
--- a/docs/reference/libsoup.types
+++ b/docs/reference/libsoup.types
@@ -1,37 +1,14 @@
-#include <libsoup/soup-address.h>
-#include <libsoup/soup-auth.h>
-#include <libsoup/soup-auth-basic.h>
-#include <libsoup/soup-auth-digest.h>
-#include <libsoup/soup-connection.h>
-#include <libsoup/soup-connection-ntlm.h>
-#include <libsoup/soup-message.h>
-#include <libsoup/soup-message-filter.h>
-#include <libsoup/soup-server-message.h>
-#include <libsoup/soup-server.h>
-#include <libsoup/soup-session.h>
-#include <libsoup/soup-session-sync.h>
-#include <libsoup/soup-session-async.h>
-#include <libsoup/soup-soap-message.h>
-#include <libsoup/soup-soap-response.h>
-#include <libsoup/soup-socket.h>
-#include <libsoup/soup-xmlrpc-message.h>
-#include <libsoup/soup-xmlrpc-response.h>
+#include <libsoup/soup.h>
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 00000000..4e01880c
--- /dev/null
+++ b/docs/reference/porting-2.2-2.4.xml
@@ -0,0 +1,878 @@
+<?xml version="1.0"?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
+<refentry id="libsoup-porting-2.2-2.4">
+<refmeta>
+<refentrytitle>libsoup 2.2 to 2.4 porting notes</refentrytitle>
+<manvolnum>3</manvolnum>
+<refmiscinfo>LIBSOUP Library</refmiscinfo>
+</refmeta>
+
+<refnamediv>
+<refname>Porting notes</refname><refpurpose>Notes on porting from libsoup 2.2 to 2.4</refpurpose>
+</refnamediv>
+
+<refsect2>
+<title>Overview</title>
+
+<para>
+After many API-compatible releases in the 2.2 series,
+<application>libsoup</application> has now changed its API and bumped
+its version number to 2.4. Changes were made for a variety of reasons:
+</para>
+
+<itemizedlist>
+ <listitem><para>
+ To fix bugs and add features that couldn't be done ABI-compatibly.
+ </para></listitem>
+
+ <listitem><para>
+ To make it easier to generate bindings for libsoup for
+ languages other than C.
+ </para></listitem>
+
+ <listitem><para>
+ To clean up ugly/confusing old APIs
+ </para></listitem>
+
+ <listitem><para>
+ To be more glib/gobject/gtk-like in general.
+ </para></listitem>
+</itemizedlist>
+
+</refsect2>
+
+<refsect2>
+<title>SoupMessage</title>
+
+<para>
+<link linkend="SoupMessage"><type>SoupMessage</type></link> has had a
+number of API changes made, mostly to increase its
+language-bindability.
+</para>
+
+<refsect3>
+<title>SoupMessageHeaders</title>
+
+<para>
+ <type>SoupMessage</type>'s
+ <structfield>request_headers</structfield> and
+ <structfield>response_headers</structfield> fields are now an
+ opaque type (<link
+ linkend="SoupMessageHeaders"><type>SoupMessageHeaders</type></link>)
+ rather than being <type>GHashTables</type>. The method names have
+ changed slightly to reflect this:
+</para>
+
+<variablelist>
+ <varlistentry>
+ <term><function>soup_message_add_header</function></term>
+ <listitem><para>
+ &#8594; <link linkend="soup-message-headers-append"><function>soup_message_headers_append</function></link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><function>soup_message_get_header</function></term>
+ <listitem><para>
+ &#8594; <link linkend="soup-message-headers-get"><function>soup_message_headers_get</function></link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><function>soup_message_foreach_header</function></term>
+ <listitem><para>
+ &#8594; <link linkend="soup-message-headers-foreach"><function>soup_message_headers_foreach</function></link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><function>soup_message_remove_header</function></term>
+ <listitem><para>
+ &#8594; <link linkend="soup-message-headers-remove"><function>soup_message_headers_remove</function></link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><function>soup_message_clear_headers</function></term>
+ <listitem><para>
+ &#8594; <link linkend="soup-message-headers-clear"><function>soup_message_headers_append</function></link>
+ </para></listitem>
+ </varlistentry>
+</variablelist>
+
+<para>
+ <function>soup_message_get_header_list</function> has no equivalent;
+ if multiple copies of a header are present,
+ <function>soup_message_headers_get</function> 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.
+</para>
+
+<para>
+ In addition, certain important header fields now have
+ dedicated get/set methods:
+</para>
+
+<itemizedlist>
+ <listitem><para>
+ <link linkend="soup-message-headers-get-encoding"><function>soup_message_headers_get_encoding</function></link> / <link linkend="soup-message-headers-set-encoding"><function>soup_message_headers_set_encoding</function></link>
+ </para></listitem>
+
+ <listitem><para>
+ <link linkend="soup-message-headers-get-content-length"><function>soup_message_headers_get_content_length</function></link> / <link linkend="soup-message-headers-set-content-length"><function>soup_message_headers_set_content_length</function></link>
+ </para></listitem>
+
+ <listitem><para>
+ <link linkend="soup-message-headers-get-expectations"><function>soup_message_headers_get_expectations</function></link> / <link linkend="soup-message-headers-set-expectations"><function>soup_message_headers_set_expectations</function></link>
+ </para></listitem>
+</itemizedlist>
+
+<para>
+ (<literal>soup_message_headers_set_expectation(msg,&#160;SOUP_EXPECTATION_CONTINUE)</literal>
+ replaces the <literal>SOUP_MESSAGE_EXPECT_CONTINUE</literal>
+ message flag).
+</para>
+
+</refsect3>
+
+<refsect3>
+<title>SoupMessageBody</title>
+
+<para>
+ Similarly, the <structfield>request_body</structfield> and
+ <structfield>response</structfield> fields (renamed from
+ <structfield>request</structfield> and <structfield>response</structfield>) are
+ now a new type, <link
+ linkend="SoupMessageBody"><type>SoupMessageBody</type></link>,
+ implemented in terms of <link
+ linkend="SoupBuffer"><type>SoupBuffer</type></link>, a refcounted
+ memory buffer type with clearer semantics than the old
+ <type>SoupDataBuffer</type>/<type>SoupOwnership</type>.
+</para>
+
+<variablelist>
+ <varlistentry>
+ <term><literal>SOUP_BUFFER_STATIC</literal></term>
+ <listitem><para>
+ &#8594; <link
+ linkend="SOUP-MEMORY-STATIC:CAPS"><literal>SOUP_MEMORY_STATIC</literal></link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>SOUP_BUFFER_SYSTEM_OWNED</literal></term>
+ <listitem><para>
+ &#8594; <link
+ linkend="SOUP-MEMORY-TAKE:CAPS"><literal>SOUP_MEMORY_TAKE</literal></link>
+ (meaning <application>libsoup</application>
+ should take ownership of the memory from your).
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>SOUP_BUFFER_USER_OWNED</literal></term>
+ <listitem><para>
+ &#8594; <link
+ linkend="SOUP-MEMORY-COPY:CAPS"><literal>SOUP_MEMORY_COPY</literal></link>
+ (meaning <application>libsoup</application>
+ should make a copy of the memory, because you
+ can't make any guarantees about how long it will
+ last.)
+ </para></listitem>
+ </varlistentry>
+</variablelist>
+
+<para>
+ A fourth <type>SoupMemoryUse</type> value is also available: <link
+ linkend="SOUP-MEMORY-TEMPORARY:CAPS"><literal>SOUP_MEMORY_TEMPORARY</literal></link>,
+ which helps to avoid extra copies in some cases.
+ <literal>SOUP_MEMORY_TEMPORARY</literal> means that the memory
+ will last at least as long as the object you are handing it to (a
+ <type>SoupBuffer</type>, <type>SoupMessageBody</type>, or
+ <type>SoupMessage</type>), and so doesn't need to be copied right
+ away, but that if anyone makes a copy of the buffer,
+ <application>libsoup</application> 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.
+</para>
+
+<para>
+ (In the future, there may be additional <type>SoupBuffer</type>
+ and <type>SoupMessageBody</type> methods to work directly with
+ mmapped memory, splicing to file descriptors, etc.)
+</para>
+
+<para>
+ <link
+ linkend="soup-message-set-request"><function>soup_message_set_request</function></link>
+ and <link
+ linkend="soup-message-set-response"><function>soup_message_set_response</function></link>
+ still work roughly like they used to.
+</para>
+
+<para>
+ Unlike the old <structfield>request</structfield> and
+ <structfield>response</structfield> fields, the new
+ <structfield>request_body</structfield> and
+ <structfield>response_body</structfield> fields are not guaranteed
+ to be filled in at all times. (In particular, the
+ <structfield>response_body</structfield> is not filled in until it
+ has been fully read, although you can use <link
+ linkend="soup-message-body-get-chunk"><function>soup_message_body_get_chunk</function></link>
+ to iterate through the chunks before that point if you need to.)
+</para>
+
+<para>
+ When <structfield>request_body</structfield> and
+ <structfield>response_body</structfield> <emphasis>are</emphasis>
+ filled in, they are <literal>'\0'</literal>-terminated for your
+ processing convenience. (The terminating 0 byte is not included in
+ their length.)
+</para>
+
+</refsect3>
+
+<refsect3>
+<title>Chunked encoding</title>
+
+<para>
+ The prototype of the <link
+ linkend="SoupMessage-got-chunk"><literal>SoupMessage::got_chunk</literal></link>
+ signal has been changed; it now includes the chunk as a
+ <type>SoupBuffer</type> parameter (rather than storing the chunk
+ data in <literal>msg->response</literal> as in 2.2). <link
+ linkend="SoupMessageFlags"><literal>SOUP_MESSAGE_OVERWRITE_CHUNKS</literal></link>
+ 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 <literal>msg->response_body</literal> will not be filled
+ in with the complete response at the end of message processing.
+</para>
+
+<para>
+ The API for sending chunked responses from a
+ <type>SoupServer</type> is also slightly different now:
+</para>
+
+<variablelist>
+ <varlistentry>
+ <term><function>soup_server_message_set_encoding</function></term>
+ <listitem><para>
+ &#8594; <link
+ linkend="soup-message-headers-set-encoding"><function>soup_message_headers_set_encoding</function></link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><function>soup_message_add_chunk</function></term>
+ <listitem><para>
+ &#8594; <link
+ linkend="soup-message-body-append"><function>soup_message_body_append</function></link>
+ or <link
+ linkend="soup-message-body-append-buffer"><function>soup_message_body_append_buffer</function></link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><function>soup_message_add_final_chunk</function></term>
+ <listitem><para>
+ &#8594; <link
+ linkend="soup-message-body-complete"><function>soup_message_body_complete</function></link>
+ </para></listitem>
+ </varlistentry>
+</variablelist>
+
+<para>
+ Since the new chunk-sending APIs require you to explicitly pass
+ the
+ <structfield>request_headers</structfield>/<structfield>request_body</structfield>
+ 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.
+</para>
+
+</refsect3>
+
+<refsect3>
+<title>Methods</title>
+
+<para>
+ <type>SoupMessage</type>'s
+ <structfield>method</structfield> field is now an interned
+ string, and you can compare the method directly against
+ the defines such as <link
+ linkend="SOUP-METHOD-GET:CAPS"><literal>SOUP_METHOD_GET</literal></link>
+ (eg, in a <type>SoupServer</type> request handler).
+ <function>soup_method_get_id</function> and the
+ <literal>SOUP_METHOD_ID_*</literal> macros are now gone.
+</para>
+</refsect3>
+
+<refsect3>
+<title>Handlers</title>
+
+<para>
+ <link
+ linkend="soup-message-add-header-handler"><function>soup_message_add_header_handler</function></link>
+ and <link
+ linkend="soup-message-add-status-code-handler"><function>soup_message_add_status_code_handler</function></link>
+ are now just clever wrappers around
+ <function>g_signal_connect</function>. In particular, you now pass
+ a signal name to them rather than a <type>SoupHandlerPhase</type>,
+ 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
+ <function>g_signal_connect</function> if you don't want that
+ behavior.)
+</para>
+</refsect3>
+
+<refsect3>
+<title>I/O-related <type>SoupMessage</type> methods</title>
+
+<para>
+ <function>soup_message_io_pause</function> and
+ <function>soup_message_io_unpause</function> have been moved to
+ <type>SoupSession</type> and <type>SoupServer</type>, to better
+ reflect the fact that the session/server control the I/O, and
+ <type>SoupMessage</type> is merely acted-upon by them.
+</para>
+
+<variablelist>
+ <varlistentry>
+ <term><function>soup_message_io_pause</function></term>
+ <listitem><para>
+ &#8594; <link linkend="soup-session-pause-message"><function>soup_session_pause_message</function></link> / <link linkend="soup-server-pause-message"><function>soup_server_pause_message</function></link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><function>soup_message_io_unpause</function></term>
+ <listitem><para>
+ &#8594; <link linkend="soup-session-unpause-message"><function>soup_session_unpause_message</function></link> / <link linkend="soup-server-unpause-message"><function>soup_server_unpause_message</function></link>
+ </para></listitem>
+ </varlistentry>
+</variablelist>
+
+<para>
+ <literal>msg->status</literal> (the I/O status) is now
+ gone as well, because (a) it's really an internal state of
+ <type>SoupSession</type>, and (b) it's too easy to confuse
+ with <literal>msg->status_code</literal> (the HTTP status)
+ anyway. Code that used to check if status was
+ <literal>SOUP_MESSAGE_STATUS_FINISHED</literal> needs to
+ be rewritten to track whether or not the <link
+ linkend="SoupMessage-finished"><literal>finished</literal></link>
+ signal has been emitted.
+</para>
+
+</refsect3>
+
+<refsect3>
+<title>HTTP-Version</title>
+
+<para>
+ <type>SoupHttpVersion</type> is now <link
+ linkend="SoupHTTPVersion"><type>SoupHTTPVersion</type></link>
+</para>
+</refsect3>
+
+</refsect2>
+
+<refsect2>
+<title>SoupSession</title>
+
+<refsect3>
+<title><function>soup_session_queue_message</function> callback</title>
+
+<para>
+ <link
+ linkend="soup-session-queue-message"><function>soup_session_queue_message</function></link>'s
+ callback parameter now includes the <type>SoupSession</type> as a
+ parameter, reflecting the fact that it is a
+ <type>SoupSession</type> callback, not a <type>SoupMessage</type>
+ callback. (It has also been renamed, from
+ <type>SoupMessageCallbackFn</type> to <link
+ linkend="SoupSessionCallback"><type>SoupSessionCallback</type></link>.)
+</para>
+</refsect3>
+
+<refsect3>
+<title>Authentication</title>
+
+<para>
+ <type>SoupSession</type>'s <literal>authenticate</literal> and
+ <literal>reauthenticate</literal> signals have been merged into a
+ single <link
+ linkend="SoupSession-authenticate"><literal>authenticate</literal></link>
+ signal with a <parameter>retrying</parameter> parameter to indicate if
+ it's the second (or later) try. Also, the signal now includes a
+ <link linkend="SoupAuth"><type>SoupAuth</type></link> directly,
+ and you authenticate by calling <link
+ linkend="soup-auth-authenticate"><function>soup_auth_authenticate</function></link>
+ on the auth (rather than passing back a username and password from
+ the signal handler).
+</para>
+</refsect3>
+
+<refsect3>
+<title><type>SoupLogger</type></title>
+
+<para>
+<link linkend="libsoup-SoupLogger"><type>SoupLogger</type></link> is a
+new object that copies the behavior of
+<application>evolution-exchange</application>'s
+<literal>E2K_DEBUG</literal> and its clones. That is, it causes a
+<type>SoupSession</type> to start logging some or all of its HTTP
+traffic to stdout, for debugging purposes.
+</para>
+</refsect3>
+
+<refsect3>
+<title><type>SoupMessageFilter</type></title>
+
+<para>
+ <type>SoupMessageFilter</type> is gone; code that used to use it
+ can now connect to the <link
+ linkend="SoupSession-request-started"><literal>SoupSession::request-started</literal></link>
+ signal to get a chance to act on each message as it is sent.
+ (This is how <type>SoupLogger</type> works.)
+</para>
+</refsect3>
+
+<refsect3>
+<title>Internal types</title>
+
+<para>
+ The <type>SoupConnection</type> and <type>SoupMessageQueue</type>
+ types (which should always have been internal to
+ <type>SoupSession</type>) have been removed from the public API.
+</para>
+</refsect3>
+
+</refsect2>
+
+<refsect2>
+<title>SoupURI</title>
+
+<type>SoupUri</type> has been renamed <link
+linkend="SoupURI"><type>SoupURI</type></link>, and its behavior has
+changed in a few ways:
+
+<itemizedlist>
+ <listitem>
+ <para>
+ 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
+ <structfield>broken_encoding</structfield> behavior, but
+ that flag no longer exists, since it is the default and
+ there's no way to turn it off.
+ </para>
+
+ <para>
+ In theory, this is an ABI-breaking change, especially for
+ <link
+ linkend="SoupServer"><type>SoupServer</type></link>s.
+ However, it is unlikely to actually break anything. (And
+ in the <type>SoupServer</type> case, servers now
+ fully-decode the <structfield>path</structfield> component
+ themselves unless you set the <link
+ linkend="SOUP-SERVER-RAW-PATHS:CAPS"><literal>SOUP_SERVER_RAW_PATHS</literal></link>
+ flag on the server, so the behavior should still be the
+ same.
+ </para>
+ </listitem>
+
+ <listitem><para>
+ It uses the RFC3986 parsing rules, including support for IPv6 literal
+ addresses.
+ </para></listitem>
+
+ <listitem>
+ <para>
+ The field formerly called
+ <structfield>protocol</structfield> is now
+ <structfield>scheme</structfield>, to match the spec, and
+ it's an interned string rather than a quark. The names of
+ the predefined values have changed to match:
+ </para>
+
+ <variablelist>
+ <varlistentry>
+ <term><function>SOUP_PROTOCOL_HTTP</function></term>
+ <listitem><para>
+ &#8594; <link linkend="SOUP-URI-SCHEME-HTTP:CAPS"><literal>SOUP_URI_SCHEME_HTTP</literal></link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><function>SOUP_PROTOCOL_HTTPS</function></term>
+ <listitem><para>
+ &#8594; <link linkend="SOUP-URI-SCHEME-HTTPS:CAPS"><literal>SOUP_URI_SCHEME_HTTPS</literal></link>
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+</itemizedlist>
+
+<para>
+<link linkend="soup-uri-decode"><function>soup_uri_decode</function></link>
+now returns a new string rather than modifying its input string in
+place. The new method <link
+linkend="soup-uri-normalize"><function>soup_uri_normalize</function></link>,
+which removes some, but not all, %-encoding, behaves similarly.
+</para>
+
+<para>
+Finally, <type>SoupURI</type> (as well as most other struct types in
+<application>libsoup</application>) now uses the glib "slice"
+allocator, so any code that uses <link
+linkend="g-new"><function>g_new</function></link> to create
+<type>SoupURI</type>s is wrong. If you want to create a URI "by hand",
+you can call <link
+linkend="soup-uri-new"><function>soup_uri_new</function></link>,
+passing <literal>NULL</literal>, and you will get back an empty
+<type>SoupURI</type>. There are also now methods that can be used to
+set its fields (eg, <link
+linkend="soup-uri-set-scheme"><function>soup_uri_set_scheme</function></link>,
+<link
+linkend="soup-uri-set-path"><function>soup_uri_set_path</function></link>,
+etc) rather than mucking with the fields directly.
+</para>
+
+<refsect3>
+<title>Forms</title>
+
+<para>
+Related to <type>SoupURI</type>, there are some new helper methods for
+dealing with HTML forms. <link
+linkend="soup-form-decode-urlencoded">soup_form_decode_urlencoded</link>
+decodes a URI <structfield>query</structfield> component (or an
+<literal>application/x-www-form-urlencoded</literal> request body)
+into a <type>GHashTable</type>. <link
+linkend="soup-form-encode-urlencoded">soup_form_encode_urlencoded</link>
+reverses the process, allowing you to fill in a
+<literal>uri->query</literal> with a properly-encoded form dataset.
+(<type>SoupURI</type> also provides <link
+linkend="soup-uri-set-query-from-form"><function>soup_uri_set_query_from_form</function></link>
+to help with this.)
+</para>
+</refsect3>
+
+</refsect2>
+
+
+<refsect2>
+<title>XML-RPC and SOAP</title>
+
+<refsect3>
+<title>SOAP</title>
+<para>
+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.
+</para>
+</refsect3>
+
+<refsect3>
+<title>XML-RPC</title>
+<para>
+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 <link
+linkend="libsoup-XMLRPC-Support">new XML-RPC API</link> makes use of
+<type>GValue</type>s, with the following type mappings:
+</para>
+
+<variablelist>
+ <varlistentry>
+ <term><literal>int</literal></term>
+ <listitem><para>
+ &#8594; <type>int</type> (<link linkend="G-TYPE-INT:CAPS"><literal>G_TYPE_INT</literal></link>)
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>boolean</literal></term>
+ <listitem><para>
+ &#8594; <type>gboolean</type> (<link linkend="G-TYPE-BOOLEAN:CAPS"><literal>G_TYPE_BOOLEAN</literal></link>)
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>string</literal></term>
+ <listitem><para>
+ &#8594; <type>char *</type> (<link linkend="G-TYPE-STRING:CAPS"><literal>G_TYPE_STRING</literal></link>)
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>double</literal></term>
+ <listitem><para>
+ &#8594; <type>double</type> (<link linkend="G-TYPE-DOUBLE:CAPS"><literal>G_TYPE_DOUBLE</literal></link>)
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>dateTime.iso8601</literal></term>
+ <listitem><para>
+ &#8594; <link linkend="SoupDate"><type>SoupDate</type></link> (<link linkend="SOUP-TYPE-DATE:CAPS"><literal>SOUP_TYPE_DATE</literal></link>)
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>base64</literal></term>
+ <listitem><para>
+ &#8594; <type>GByteArray</type> (<link linkend="SOUP-TYPE-BYTE-ARRAY:CAPS"><literal>SOUP_TYPE_BYTE_ARRAY</literal></link>)
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>struct</literal></term>
+ <listitem><para>
+ &#8594; <type>GHashTable</type> (<link linkend="G-TYPE-HASH-TABLE:CAPS"><literal>G_TYPE_HASH_TABLE</literal></link>)
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>array</literal></term>
+ <listitem><para>
+ &#8594; <type>GValueArray</type> (<link linkend="G-TYPE-VALUE-ARRAY:CAPS"><literal>G_TYPE_VALUE_ARRAY</literal></link>)
+ </para></listitem>
+ </varlistentry>
+</variablelist>
+
+<para>
+<type>SoupDate</type> is discussed below.
+<literal>SOUP_TYPE_BYTE_ARRAY</literal> is just a new
+<type>GType</type> value defined by <application>libsoup</application>
+to represent <type>GByteArray</type>s, which glib does not define a
+<type>GType</type> for.
+</para>
+
+<para>
+<application>libsoup</application> provides some additional <link
+linkend="libsoup-GValue-Support"><type>GValue</type> support
+methods</link> for working with
+<type>GValueArray</type>s, and <type>GHashTable</type>s of
+<type>GValue</type>s, for the XML-RPC <literal>struct</literal> and
+<literal>array</literal> types. Eg, you can use <link
+linkend="soup-value-hash-new"><function>soup_value_hash_new</function></link>
+to create a <type>GHashTable</type> to use with the XML-RPC methods,
+and <link
+linkend="soup-value-hash-insert"><function>soup_value_hash_insert</function></link>
+to add values to it without needing to muck with <type>GValue</type>s
+directly.
+</para>
+
+<para>
+The <literal>getbug</literal> and <literal>xmlrpc-test</literal>
+programs in the <application>libsoup</application> sources provide
+examples of how to use the new API. (Beware that
+<literal>xmlrpc-test</literal>'s use of the API is a little
+complicated because of the way it sends all calls through a single
+<literal>do_xmlrpc</literal> method.)
+</para>
+
+</refsect3>
+</refsect2>
+
+<refsect2>
+<title>SoupServer</title>
+
+<refsect3>
+<title>SoupServer handlers</title>
+
+<para>
+ The prototypes for <link
+ linkend="soup-server-add-handler"><function>soup_server_add_handler</function></link>,
+ and for the <link
+ linkend="SoupServerCallback"><type>SoupServer</type>
+ handlers</link> themselves have changed:
+</para>
+
+<informalexample><programlisting>
+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);
+</programlisting></informalexample>
+
+<para>
+ <function>soup_server_add_handler</function> no longer takes a
+ <type>SoupServerAuthContext</type> (see the discussion of server
+ authentication below), and the order of the final two arguments
+ has been swapped. (Additionally, <type>SoupServerCallbackFn</type>
+ has been renamed to <type>SoupServerCallback</type>, and the old
+ <parameter>unregister</parameter> parameter of type
+ <type>SoupServerUnregisterFn</type> is now a standard
+ <type>GDestroyNotify</type>. The change to
+ <type>GDestroyNotify</type> and the swapping of the final two
+ arguments is to make the method conform to standard glib/gtk
+ practices.)
+</para>
+
+<para>
+ In <type>SoupServerCallback</type>, several bits of data that used
+ to be part of the <parameter>context</parameter> argument are now
+ provided directly, and <parameter>context</parameter> specifically
+ only contains more specifically-client-related information (such
+ as the <type>SoupSocket</type> that the request arrived on, and
+ information about authentication).
+</para>
+
+<para>
+ <parameter>path</parameter> is the fully %-decoded path component
+ of <parameter>msg</parameter>'s URI, and
+ <parameter>query</parameter> is a hash table containing
+ <parameter>msg</parameter>'s URI's
+ <structfield>query</structfield> component decoded with <link
+ linkend="soup-form-decode-urlencoded">soup_form_decode_urlencoded</link>.
+ These are provided for your convenience; if you need the raw
+ query, you can get it out of <parameter>msg</parameter>'s URI
+ directly. If you need the raw path, you'll need to set the <link
+ linkend="SOUP-SERVER-RAW-PATHS:CAPS"><literal>SOUP_SERVER_RAW_PATHS</literal></link>
+ property on the server, which actually changes the behavior of the
+ server with respect to how paths are matched; see the
+ documentation for details.
+</para>
+</refsect3>
+
+<refsect3>
+<title>Server-side authentication</title>
+
+<para>
+ <type>SoupServer</type> authentication has been completely
+ rewritten, with <type>SoupServerAuthContext</type> being replaced
+ with <link
+ linkend="SoupAuthDomain"><type>SoupAuthDomain</type></link>. Among
+ other improvements, you no longer need to have the cleartext
+ password available to check against. See the
+ <type>SoupAuthDomain</type> documentation, the <link
+ linkend="libsoup-server-howto">server tutorial</link>, and
+ <literal>tests/server-auth-test.c</literal>.
+</para>
+</refsect3>
+
+<refsect3>
+<title><literal>Expect: 100-continue</literal> and other early <type>SoupMessage</type> processing</title>
+
+<para>
+ <type>SoupServer</type> now handles
+ "<literal>Expect:&#160;100-continue</literal>" correctly. In
+ particular, if the client passes that header, and your server
+ requires authentication, then authentication will be checked
+ before reading the request body.
+</para>
+
+<para>
+ If you want to do additional pre-request-body handling, you can
+ connect to <type>SoupServer</type>'s <link
+ linkend="SoupServer-request-started"><literal>request_started</literal></link>
+ signal, and connect to the request's <link
+ linkend="SoupMessage-got-headers"><literal>got_headers</literal></link>
+ signal from there. (See the description of
+ <literal>request_started</literal> for information about other
+ related <type>SoupServer</type> signals.)
+</para>
+</refsect3>
+
+<refsect3>
+<title>Date header</title>
+
+<para>
+ <type>SoupServer</type> now automatically sets the
+ <literal>Date</literal> header on all responses, as required by
+ RFC 2616.
+</para>
+</refsect3>
+
+<refsect3>
+<title>SoupServerMessage</title>
+
+<para>
+ <type>SoupServerMessage</type> is now merged into
+ <type>SoupMessage</type>.
+ <function>soup_server_message_set_encoding</function> is replaced
+ with <link
+ linkend="soup-message-headers-set-encoding"><function>soup_message_headers_set_encoding</function></link>
+ as described in the section on <type>SoupMessage</type> above.
+</para>
+</refsect3>
+
+<refsect3>
+<title><function>soup_server_run</function> / <function>soup_server_quit</function></title>
+
+<para>
+ <link
+ linkend="soup-server-run"><function>soup_server_run</function></link>
+ and <link
+ linkend="soup-server-run-async"><function>soup_server_run_async</function></link>
+ no longer <function>g_object_ref</function> the server, and
+ <link
+ linkend="soup-server-quit"><function>soup_server_quit</function></link>
+ no longer unrefs it.
+</para>
+</refsect3>
+
+</refsect2>
+
+<refsect2>
+<title>Miscellaneous</title>
+
+<refsect3>
+<title>SoupDate</title>
+
+<para>
+ The new <link linkend="SoupDate"><type>SoupDate</type></link> type
+ replaces the old <function>soup_date_*</function> methods, and has
+ an improved (more liberal) date parser.
+</para>
+</refsect3>
+
+<refsect3>
+<title>Header parsing</title>
+
+<para>
+ <literal>soup-headers.h</literal> now has a few additional methods
+ for parsing list-type headers.
+</para>
+</refsect3>
+
+<refsect3>
+<title>SoupAddress, SoupSocket</title>
+
+<para>
+ <type>SoupSocket</type> has had various simplifications made to
+ reflect the fact that this is specifically libsoup's socket
+ implementation, not some random generic socket API.
+</para>
+
+<para>
+ Various <type>SoupAddress</type> and <type>SoupSocket</type>
+ methods now take arguments of the new <link
+ linkend="GCancellable"><type>GCancellable</type></link> type, from
+ libgio. When porting old code, you can just pass
+ <literal>NULL</literal> for these. (<link
+ linkend="soup-address-resolve-async"><function>soup_address_resolve_async</function></link>
+ also takes another new argument, a <link
+ linkend="GMainContext"><type>GMainContext</type></link> that
+ you'll want to pass <literal>NULL</literal> for.) If you pass a
+ <type>GCancellable</type>, you can use it to cleanly cancel the
+ address resolution / socket operation.
+</para>
+
+</refsect3>
+
+<refsect3>
+<title>Base64 methods</title>
+
+<para>
+ The deprecated base64 methods are now gone; use glib's base64
+ methods instead.
+</para>
+</refsect3>
+
+</refsect2>
+
+</refentry>
diff --git a/docs/reference/server-howto.xml b/docs/reference/server-howto.xml
index 2d7971b6..76c19182 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, <link
linkend="SoupServer"><type>SoupServer</type></link>.
</para>
-<warning>
- <para>
- Note that <type>SoupServer</type> isn't as polished as
- <type>SoupSession</type>, 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.
- </para>
-</warning>
-
-
<para>
You create the server with <link
linkend="soup-server-new"><function>soup_server_new</function></link>,
@@ -83,6 +72,16 @@ various additional options:
other than the main thread.
</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><link linkend="SOUP-SERVER-RAW-PATHS:CAPS"><literal>SOUP_SERVER_RAW_PATHS</literal></link></term>
+ <listitem><para>
+ Set this to <literal>TRUE</literal> if you don't want
+ <application>libsoup</application> to decode %-encoding
+ in the Request-URI. (Eg, because you need to treat
+ <literal>"/foo/bar"</literal> and
+ <literal>"/foo%2Fbar"</literal> as different paths.
+ </para></listitem>
+ </varlistentry>
</variablelist>
</refsect2>
@@ -100,8 +99,8 @@ to set a callback to handle certain URI paths.
</para>
<informalexample><programlisting>
-soup_server_add_handler (server, "/foo", NULL, server_callback,
- unregister_callback, data);
+ soup_server_add_handler (server, "/foo", server_callback,
+ data, destroy_notify);
</programlisting></informalexample>
<para>
@@ -131,18 +130,28 @@ A handler callback looks something like this:
<informalexample><programlisting>
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)
{
...
}
</programlisting></informalexample>
<para>
-<literal>msg</literal> is the request that has been received.
-<literal>data</literal> is the same data that was passed to <link
+<literal>msg</literal> is the request that has been received and
+<literal>user_data</literal> is the data that was passed to <link
linkend="soup-server-add-handler"><function>soup_server_add_handler</function></link>.
-The <link>context</link> argument contains some additional information
-related to the request.
+<literal>path</literal> is the path (from <literal>msg</literal>'s
+URI), and <literal>query</literal> contains the result of parsing the
+URI query field. (It is <literal>NULL</literal> if there was no
+query.) <literal>client</literal> is a <link
+linkend="SoupClientContext"><type>SoupClientContext</type></link>,
+which contains additional information about the client (including its
+IP address, and whether or not it used HTTP authentication).
</para>
<para>
@@ -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 <link
-linkend="soup-message-io-pause"><function>soup_message_io_pause</function></link>
+linkend="soup-server-pause-message"><function>soup_server_pause_message</function></link>
on the message before returning from the callback. This will delay
sending a response until you call <link
-linkend="soup-message-io-unpause"><function>soup_message_io_unpause</function></link>.
+linkend="soup-server-unpause-message"><function>soup_server_unpause_message</function></link>.
(You must also connect to the <link
linkend="SoupMessage-finished">finished</link> 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 <link
linkend="soup-message-set-status"><function>soup_message_set_status</function></link>
or <link
linkend="soup-message-set-status-full"><function>soup_message_set_status_full</function></link>.
-If the response requires a body, the callback must call <link
-linkend="soup-server-message-set-encoding"><function>soup_server_message_set_encoding</function></link>
-to indicate whether it will provide the response all at once with
-<literal>Content-Length</literal> encoding, or in pieces with
+If the response requires a body, you must decide whether to use
+<literal>Content-Length</literal> encoding (the default), or
<literal>chunked</literal> encoding.
</para>
@@ -184,29 +191,34 @@ data available at once.
<informalexample><programlisting>
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);
}
</programlisting></informalexample>
@@ -219,16 +231,22 @@ server_callback (SoupServerContext *context, SoupMessage *msg, gpointer data)
<para>
If you want to supply the response body in chunks as it becomes
available, use <literal>chunked</literal> encoding instead. In this
-case, call <link
-linkend="soup-message-add-chunk"><function>soup_message_add_chunk</function></link> with
-each chunk of the response body as it becomes available, and call
-<link
-linkend="soup-message-add-final-chunk"><function>soup_message_add_final_chunk</function></link>
+case, first call <link
+linkend="soup-message-headers-set-encoding"><function>soup_message_headers_set_encoding</function></link>&#160;<literal>(msg->response_headers,&#160;<link
+linkend="SoupEncoding">SOUP_ENCODING_CHUNKED</link>)</literal>
+to tell <application>libsoup</application> that you'll be using
+chunked encoding. Then call <link
+linkend="soup-message-body-append"><function>soup_message_body_append</function></link>
+(or <link
+linkend="soup-message-body-append-buffer"><function>soup_message_body_append_buffer</function></link>)
+on <literal>msg->response_body</literal> with each chunk of the
+response body as it becomes available, and call <link
+linkend="soup-message-body-complete"><function>soup_message_body_complete</function></link>
when the response is complete. After each of these calls, you must
also call <link
-linkend="soup-message-io-unpause"><function>soup_message_io_unpause</function></link> to
-cause the chunk to be sent. (You do not normally need to call
-<link linkend="soup-message-io-pause"><function>soup_message_io_pause</function></link>,
+linkend="soup-server-unpause-message"><function>soup_server_unpause_message</function></link>
+to cause the chunk to be sent. (You do not normally need to call <link
+linkend="soup-server-pause-message"><function>soup_server_pause_message</function></link>,
because I/O is automatically paused when doing a
<literal>chunked</literal> transfer if no chunks are available.)
</para>
@@ -239,7 +257,9 @@ linkend="SoupMessage-finished">finished</link> signal on the message,
so that you will be notified if the client disconnects between two
chunks; <type>SoupServer</type> 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 <link linkend="SoupMessage-wrote-chunk">wrote_chunk</link> signal
+is emitted indicating that the previous one was written successfully.)
</para>
<para>
@@ -257,59 +277,92 @@ using <literal>chunked</literal> encoding.
<para>
To have <link linkend="SoupServer"><type>SoupServer</type></link>
-handle HTTP authentication for you, pass a <link
-linkend="SoupAuthContext"><type>SoupAuthContext</type></link> to <link
-linkend="soup-server-add-handler"><function>soup_server_add_handler</function></link>:
+handle HTTP authentication for you, create a <link
+linkend="SoupAuthDomainBasic"><type>SoupAuthDomainBasic</type></link>
+or <link
+linkend="SoupAuthDomainDigest"><type>SoupAuthDomainDigest</type></link>,
+and pass it to <link
+linkend="soup-server-add-auth-domain"><function>soup_server_add_auth_domain</function></link>:
</para>
<informalexample><programlisting>
-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", &amp;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);
</programlisting></informalexample>
<para>
-Then, every request that matches that handler will be passed to the
-<literal>auth_callback</literal> first before being passed to the
-<literal>server_callback</literal>:
+Then, every request under one of the auth domain's paths will be
+passed to the <literal>auth_callback</literal> first before being
+passed to the <literal>server_callback</literal>:
</para>
<informalexample><programlisting>
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;
}
</programlisting></informalexample>
<para>
-The <literal>auth</literal> parameter indicates the authentication
-information passed by the client. If no
-<literal>WWW-Authenticate</literal> header was present, this will be
-<literal>NULL</literal>, so we return <literal>FALSE</literal> from
-the callback to indicate that the server should return a <literal>401
-Unauthorized</literal> response. If it is non-<literal>NULL</literal>,
-we extract the username from it, and compare it against our stored
-password. Assuming it matches, we return <literal>TRUE</literal>, and
-the server callback is then invoked normally.
+The <link
+linkend="SoupAuthDomainBasicAuthCallback"><type>SoupAuthDomainBasicAuthCallback</type></link>
+is given the username and password from the
+<literal>Authorization</literal> 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.)
+</para>
+
+<para>
+If you are using Digest authentication, note that <link
+linkend="SoupAuthDomainDigestAuthCallback"><type>SoupAuthDomainDigestAuthCallback</type></link>
+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 <link
+linkend="SoupAuthDomainDigest"><type>SoupAuthDomainDigest</type></link>
+for more details.
+</para>
+
+<para>
+You can have multiple <type>SoupAuthDomain</type>s attached to a
+<literal>SoupServer</literal>, 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 <emphasis>any</emphasis> of the
+domains.
+</para>
+
+<para>
+If you want to require authentication for some requests under a
+certain path, but not all of them (eg, you want to authenticate
+<literal>PUT</literal>s, but not <literal>GET</literal>s), use a
+<link
+linkend="SoupAuthDomainFilter"><type>SoupAuthDomainFilter</type></link>.
</para>
</refsect2>
diff --git a/docs/reference/tmpl/libsoup-unused.sgml b/docs/reference/tmpl/libsoup-unused.sgml
deleted file mode 100644
index f4901ef4..00000000
--- a/docs/reference/tmpl/libsoup-unused.sgml
+++ /dev/null
@@ -1,497 +0,0 @@
-<!-- ##### SECTION ./tmpl/soup-auth-basic.sgml:Long_Description ##### -->
-<para>
-This implements the Basic HTTP Authentication mechanism, as described
-in RFC 2617. It is created automatically by #SoupSession when needed.
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-auth-basic.sgml:See_Also ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-auth-basic.sgml:Short_Description ##### -->
-HTTP Basic Authentication
-
-
-<!-- ##### SECTION ./tmpl/soup-auth-basic.sgml:Title ##### -->
-SoupAuthBasic
-
-
-<!-- ##### SECTION ./tmpl/soup-auth-digest.sgml:Long_Description ##### -->
-<para>
-This implements the Digest HTTP Authentication mechanism, as described
-in RFC 2617. It is created automatically by #SoupSession when needed.
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-auth-digest.sgml:See_Also ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-auth-digest.sgml:Short_Description ##### -->
-HTTP Digest Authentication
-
-
-<!-- ##### SECTION ./tmpl/soup-auth-digest.sgml:Title ##### -->
-SoupAuthDigest
-
-
-<!-- ##### SECTION ./tmpl/soup-md5-utils.sgml:Long_Description ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-md5-utils.sgml:See_Also ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-md5-utils.sgml:Short_Description ##### -->
-MD5 hash utilities
-
-
-<!-- ##### SECTION ./tmpl/soup-md5-utils.sgml:Title ##### -->
-soup-md5-utils
-
-
-<!-- ##### SECTION ./tmpl/soup-message-filter.sgml.sgml:Long_Description ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-message-filter.sgml.sgml:See_Also ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-message-filter.sgml.sgml:Short_Description ##### -->
-
-
-
-<!-- ##### SECTION ./tmpl/soup-message-filter.sgml.sgml:Stability_Level ##### -->
-
-
-
-<!-- ##### SECTION ./tmpl/soup-message-filter.sgml.sgml:Title ##### -->
-Soup Miscellaneous Utilities
-
-
-<!-- ##### SECTION ./tmpl/soup-message-private.sgml:Long_Description ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-message-private.sgml:See_Also ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-message-private.sgml:Short_Description ##### -->
-
-
-
-<!-- ##### SECTION ./tmpl/soup-message-private.sgml:Title ##### -->
-soup-message-private
-
-
-<!-- ##### SECTION ./tmpl/soup-message-queue.sgml:Long_Description ##### -->
-<para>
-#SoupMessageQueue maintains the queue of pending messages in a
-#SoupSession.
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-message-queue.sgml:See_Also ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-message-queue.sgml:Short_Description ##### -->
-Message queue object
-
-
-<!-- ##### SECTION ./tmpl/soup-message-queue.sgml:Title ##### -->
-soup-message-queue
-
-
-<!-- ##### SECTION ./tmpl/soup-server-auth.sgml:Long_Description ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-server-auth.sgml:See_Also ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SECTION ./tmpl/soup-server-auth.sgml:Short_Description ##### -->
-Server-side authentication structures
-
-
-<!-- ##### SECTION ./tmpl/soup-server-auth.sgml:Title ##### -->
-soup-server-auth
-
-
-<!-- ##### ENUM SoupAuthType ##### -->
-<para>
-
-</para>
-
-@SOUP_AUTH_TYPE_BASIC:
-@SOUP_AUTH_TYPE_DIGEST:
-
-<!-- ##### ENUM SoupDigestAlgorithm ##### -->
-<para>
-
-</para>
-
-@SOUP_ALGORITHM_MD5:
-@SOUP_ALGORITHM_MD5_SESS:
-
-<!-- ##### STRUCT SoupMD5Context ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### USER_FUNCTION SoupMessageGetHeadersFn ##### -->
-<para>
-
-</para>
-
-@msg:
-@headers:
-@encoding:
-@user_data:
-
-<!-- ##### USER_FUNCTION SoupMessageParseHeadersFn ##### -->
-<para>
-
-</para>
-
-@msg:
-@headers:
-@header_len:
-@encoding:
-@content_len:
-@user_data:
-@Returns:
-
-<!-- ##### TYPEDEF SoupMessageQueue ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### STRUCT SoupMessageQueueIter ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### STRUCT SoupServerAuthBasic ##### -->
-<para>
-
-</para>
-
-@type:
-@user:
-@passwd:
-
-<!-- ##### USER_FUNCTION SoupServerAuthCallbackFn ##### -->
-<para>
-
-</para>
-
-@auth_ctx:
-@auth:
-@msg:
-@data:
-@Returns:
-
-<!-- ##### STRUCT SoupServerAuthContext ##### -->
-<para>
-
-</para>
-
-@types:
-@callback:
-@user_data:
-
-<!-- ##### STRUCT SoupServerAuthDigest ##### -->
-<para>
-
-</para>
-
-@type:
-@algorithm:
-@integrity:
-@realm:
-@user:
-@nonce:
-@nonce_count:
-@cnonce:
-@digest_uri:
-@digest_response:
-@request_method:
-
-<!-- ##### FUNCTION soup_md5_final ##### -->
-<para>
-
-</para>
-
-@ctx:
-@digest:
-
-<!-- ##### FUNCTION soup_md5_final_hex ##### -->
-<para>
-
-</para>
-
-@ctx:
-@digest:
-
-<!-- ##### FUNCTION soup_md5_init ##### -->
-<para>
-
-</para>
-
-@ctx:
-
-<!-- ##### FUNCTION soup_md5_update ##### -->
-<para>
-
-</para>
-
-@ctx:
-@buf:
-@len:
-
-<!-- ##### FUNCTION soup_message_cleanup_response ##### -->
-<para>
-
-</para>
-
-@req:
-
-<!-- ##### FUNCTION soup_message_finished ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_message_got_body ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_message_got_chunk ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_message_got_headers ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_message_got_informational ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_message_io_client ##### -->
-<para>
-
-</para>
-
-@msg:
-@sock:
-@get_headers_cb:
-@parse_headers_cb:
-@user_data:
-
-<!-- ##### FUNCTION soup_message_io_server ##### -->
-<para>
-
-</para>
-
-@msg:
-@sock:
-@get_headers_cb:
-@parse_headers_cb:
-@user_data:
-
-<!-- ##### FUNCTION soup_message_queue_append ##### -->
-<para>
-
-</para>
-
-@queue:
-@msg:
-
-<!-- ##### FUNCTION soup_message_queue_destroy ##### -->
-<para>
-
-</para>
-
-@queue:
-
-<!-- ##### FUNCTION soup_message_queue_first ##### -->
-<para>
-
-</para>
-
-@queue:
-@iter:
-@Returns:
-
-<!-- ##### FUNCTION soup_message_queue_free_iter ##### -->
-<para>
-
-</para>
-
-@queue:
-@iter:
-
-<!-- ##### FUNCTION soup_message_queue_new ##### -->
-<para>
-
-</para>
-
-@Returns:
-
-<!-- ##### FUNCTION soup_message_queue_next ##### -->
-<para>
-
-</para>
-
-@queue:
-@iter:
-@Returns:
-
-<!-- ##### FUNCTION soup_message_queue_remove ##### -->
-<para>
-
-</para>
-
-@queue:
-@iter:
-@Returns:
-
-<!-- ##### FUNCTION soup_message_queue_remove_message ##### -->
-<para>
-
-</para>
-
-@queue:
-@msg:
-
-<!-- ##### FUNCTION soup_message_restarted ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_message_run_handlers ##### -->
-<para>
-
-</para>
-
-@msg:
-@phase:
-
-<!-- ##### FUNCTION soup_message_wrote_body ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_message_wrote_chunk ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_message_wrote_headers ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_message_wrote_informational ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### FUNCTION soup_server_auth_check_passwd ##### -->
-<para>
-
-</para>
-
-@auth:
-@passwd:
-@Returns:
-
-<!-- ##### FUNCTION soup_server_auth_context_challenge ##### -->
-<para>
-
-</para>
-
-@auth_ctx:
-@msg:
-@header_name:
-
-<!-- ##### FUNCTION soup_server_auth_free ##### -->
-<para>
-
-</para>
-
-@auth:
-
-<!-- ##### FUNCTION soup_server_auth_get_user ##### -->
-<para>
-
-</para>
-
-@auth:
-@Returns:
-
-<!-- ##### FUNCTION soup_server_auth_new ##### -->
-<para>
-
-</para>
-
-@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 5cebb3bc..00000000
--- a/docs/reference/tmpl/soup-address.sgml
+++ /dev/null
@@ -1,162 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupAddress
-
-<!-- ##### SECTION Short_Description ##### -->
-Higher-level IP address object
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-#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.)
-</para>
-
-<para>
-If libsoup was built with IPv6 support, #SoupAddress will allow both
-IPv4 and IPv6 addresses.
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupAddress ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SIGNAL SoupAddress::dns-result ##### -->
-<para>
-
-</para>
-
-@addr:
-@status:
-
-<!-- ##### ENUM SoupAddressFamily ##### -->
-<para>
-
-</para>
-
-@SOUP_ADDRESS_FAMILY_IPV4:
-@SOUP_ADDRESS_FAMILY_IPV6:
-
-<!-- ##### MACRO SOUP_ADDRESS_ANY_PORT ##### -->
-<para>
-
-</para>
-
-
-
-<!-- ##### FUNCTION soup_address_new ##### -->
-<para>
-
-</para>
-
-@name:
-@port:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_address_new_from_sockaddr ##### -->
-<para>
-
-</para>
-
-@sa:
-@len:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_address_new_any ##### -->
-<para>
-
-</para>
-
-@family:
-@port:
-@Returns:
-
-
-<!-- ##### USER_FUNCTION SoupAddressCallback ##### -->
-<para>
-
-</para>
-
-@addr:
-@status:
-@data:
-
-
-<!-- ##### FUNCTION soup_address_resolve_async ##### -->
-<para>
-
-</para>
-
-@addr:
-@callback:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_address_resolve_async_full ##### -->
-<para>
-
-</para>
-
-@addr:
-@async_context:
-@callback:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_address_resolve_sync ##### -->
-<para>
-
-</para>
-
-@addr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_address_get_name ##### -->
-<para>
-
-</para>
-
-@addr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_address_get_sockaddr ##### -->
-<para>
-
-</para>
-
-@addr:
-@len:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_address_get_physical ##### -->
-<para>
-
-</para>
-
-@addr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_address_get_port ##### -->
-<para>
-
-</para>
-
-@addr:
-@Returns:
-
-
diff --git a/docs/reference/tmpl/soup-auth.sgml b/docs/reference/tmpl/soup-auth.sgml
deleted file mode 100644
index e612ffbb..00000000
--- a/docs/reference/tmpl/soup-auth.sgml
+++ /dev/null
@@ -1,117 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupAuth
-
-<!-- ##### SECTION Short_Description ##### -->
-HTTP Authentication support
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-#SoupAuth objects store the authentication data associated with a
-given bit of webspace. They are created and maintained automatically
-by #SoupSession.
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-#SoupSession, #SoupConnectionNTLM
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupAuth ##### -->
-<para>
-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.)
-</para>
-
-
-<!-- ##### STRUCT SoupAuthBasic ##### -->
-<para>
-An object representing Basic HTTP authentication.
-</para>
-
-
-<!-- ##### STRUCT SoupAuthDigest ##### -->
-<para>
-An object representing Digest HTTP authentication.
-</para>
-
-
-<!-- ##### FUNCTION soup_auth_new_from_header_list ##### -->
-<para>
-
-</para>
-
-@vals:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_auth_get_scheme_name ##### -->
-<para>
-
-</para>
-
-@auth:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_auth_get_realm ##### -->
-<para>
-
-</para>
-
-@auth:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_auth_authenticate ##### -->
-<para>
-
-</para>
-
-@auth:
-@username:
-@password:
-
-
-<!-- ##### FUNCTION soup_auth_is_authenticated ##### -->
-<para>
-
-</para>
-
-@auth:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_auth_get_authorization ##### -->
-<para>
-
-</para>
-
-@auth:
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_auth_get_protection_space ##### -->
-<para>
-
-</para>
-
-@auth:
-@source_uri:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_auth_free_protection_space ##### -->
-<para>
-
-</para>
-
-@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 f72ad2aa..00000000
--- a/docs/reference/tmpl/soup-connection-ntlm.sgml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupConnectionNTLM
-
-<!-- ##### SECTION Short_Description ##### -->
-NTLM-authenticated connection
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-#SoupSession automatically creates #SoupConnectionNTLM rather than
-#SoupConnection if you set the %SOUP_SESSION_USE_NTLM flag.
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupConnectionNTLM ##### -->
-<para>
-
-</para>
-
-
diff --git a/docs/reference/tmpl/soup-connection.sgml b/docs/reference/tmpl/soup-connection.sgml
deleted file mode 100644
index 3af9305f..00000000
--- a/docs/reference/tmpl/soup-connection.sgml
+++ /dev/null
@@ -1,254 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupConnection
-
-<!-- ##### SECTION Short_Description ##### -->
-a single possibly-persistent HTTP connection
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-#SoupConnection represents a single connection to an HTTP server
-(possibly via a proxy). Connection objects are created and destroyed
-automatically by #SoupSession.
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupConnection ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SIGNAL SoupConnection::authenticate ##### -->
-<para>
-
-</para>
-
-@conn:
-@msg:
-@auth_type:
-@auth_realm:
-@username:
-@password:
-
-<!-- ##### SIGNAL SoupConnection::connect-result ##### -->
-<para>
-
-</para>
-
-@conn:
-@status:
-
-<!-- ##### SIGNAL SoupConnection::disconnected ##### -->
-<para>
-
-</para>
-
-@conn:
-
-<!-- ##### SIGNAL SoupConnection::reauthenticate ##### -->
-<para>
-
-</para>
-
-@conn:
-@msg:
-@auth_type:
-@auth_realm:
-@username:
-@password:
-
-<!-- ##### ARG SoupConnection:async-context ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupConnection:message-filter ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupConnection:origin-uri ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupConnection:proxy-uri ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupConnection:ssl-creds ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupConnection:timeout ##### -->
-<para>
-
-</para>
-
-<!-- ##### FUNCTION soup_connection_new ##### -->
-<para>
-
-</para>
-
-@propname1:
-@Varargs:
-@Returns:
-
-
-<!-- ##### USER_FUNCTION SoupConnectionCallback ##### -->
-<para>
-
-</para>
-
-@conn:
-@status:
-@data:
-
-
-<!-- ##### FUNCTION soup_connection_connect_async ##### -->
-<para>
-
-</para>
-
-@conn:
-@callback:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_connection_connect_sync ##### -->
-<para>
-
-</para>
-
-@conn:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_connection_disconnect ##### -->
-<para>
-
-</para>
-
-@conn:
-
-
-<!-- ##### FUNCTION soup_connection_is_in_use ##### -->
-<para>
-
-</para>
-
-@conn:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_connection_last_used ##### -->
-<para>
-
-</para>
-
-@conn:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_connection_send_request ##### -->
-<para>
-
-</para>
-
-@conn:
-@req:
-
-
-<!-- ##### FUNCTION soup_connection_authenticate ##### -->
-<para>
-
-</para>
-
-@conn:
-@msg:
-@auth_type:
-@auth_realm:
-@username:
-@password:
-
-
-<!-- ##### FUNCTION soup_connection_reauthenticate ##### -->
-<para>
-
-</para>
-
-@conn:
-@msg:
-@auth_type:
-@auth_realm:
-@username:
-@password:
-
-
-<!-- ##### FUNCTION soup_connection_release ##### -->
-<para>
-
-</para>
-
-@conn:
-
-
-<!-- ##### FUNCTION soup_connection_reserve ##### -->
-<para>
-
-</para>
-
-@conn:
-
-
-<!-- ##### MACRO SOUP_CONNECTION_ORIGIN_URI ##### -->
-<para>
-An alias for the "origin-uri" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_CONNECTION_PROXY_URI ##### -->
-<para>
-An alias for the "proxy-uri" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_CONNECTION_SSL_CREDENTIALS ##### -->
-<para>
-An alias for the "ssl-creds" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_CONNECTION_MESSAGE_FILTER ##### -->
-<para>
-An alias for the "message-filter" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_CONNECTION_ASYNC_CONTEXT ##### -->
-<para>
-An alias for the "async-context" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_CONNECTION_TIMEOUT ##### -->
-<para>
-An alias for the "timeout" property.
-</para>
-
-
-
diff --git a/docs/reference/tmpl/soup-dns.sgml b/docs/reference/tmpl/soup-dns.sgml
deleted file mode 100644
index bc13b525..00000000
--- a/docs/reference/tmpl/soup-dns.sgml
+++ /dev/null
@@ -1,123 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-soup-dns
-
-<!-- ##### SECTION Short_Description ##### -->
-Low-level DNS routines
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### FUNCTION soup_dns_init ##### -->
-<para>
-
-</para>
-
-
-
-<!-- ##### FUNCTION soup_dns_ntop ##### -->
-<para>
-
-</para>
-
-@sa:
-@Returns:
-
-
-<!-- ##### TYPEDEF SoupDNSLookup ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_dns_lookup_name ##### -->
-<para>
-
-</para>
-
-@name:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_dns_lookup_address ##### -->
-<para>
-
-</para>
-
-@sockaddr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_dns_lookup_resolve ##### -->
-<para>
-
-</para>
-
-@lookup:
-@Returns:
-
-
-<!-- ##### USER_FUNCTION SoupDNSCallback ##### -->
-<para>
-
-</para>
-
-@lookup:
-@success:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_dns_lookup_resolve_async ##### -->
-<para>
-
-</para>
-
-@lookup:
-@async_context:
-@callback:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_dns_lookup_cancel ##### -->
-<para>
-
-</para>
-
-@lookup:
-
-
-<!-- ##### FUNCTION soup_dns_lookup_get_hostname ##### -->
-<para>
-
-</para>
-
-@lookup:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_dns_lookup_get_address ##### -->
-<para>
-
-</para>
-
-@lookup:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_dns_lookup_free ##### -->
-<para>
-
-</para>
-
-@lookup:
-
-
diff --git a/docs/reference/tmpl/soup-md5-utils.sgml b/docs/reference/tmpl/soup-md5-utils.sgml
deleted file mode 100644
index afb42b2b..00000000
--- a/docs/reference/tmpl/soup-md5-utils.sgml
+++ /dev/null
@@ -1,49 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-soup-md5-utils
-
-<!-- ##### SECTION Short_Description ##### -->
-MD5 hash utilities
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### STRUCT SoupMD5Context ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_md5_init ##### -->
-<para>
-
-</para>
-
-@ctx:
-
-
-<!-- ##### FUNCTION soup_md5_update ##### -->
-<para>
-
-</para>
-
-@ctx:
-@buf:
-@len:
-
-
-<!-- ##### FUNCTION soup_md5_final ##### -->
-<para>
-
-</para>
-
-@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 f1c02af5..00000000
--- a/docs/reference/tmpl/soup-message-filter.sgml
+++ /dev/null
@@ -1,28 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupMessageFilter
-
-<!-- ##### SECTION Short_Description ##### -->
-Automatic message processing
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### FUNCTION soup_message_filter_setup_message ##### -->
-<para>
-
-</para>
-
-@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 581531f2..00000000
--- a/docs/reference/tmpl/soup-message-private.sgml
+++ /dev/null
@@ -1,82 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-soup-message-private
-
-<!-- ##### SECTION Short_Description ##### -->
-
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### FUNCTION soup_message_run_handlers ##### -->
-<para>
-
-</para>
-
-@msg:
-@phase:
-
-
-<!-- ##### FUNCTION soup_message_cleanup_response ##### -->
-<para>
-
-</para>
-
-@req:
-
-
-<!-- ##### USER_FUNCTION SoupMessageGetHeadersFn ##### -->
-<para>
-
-</para>
-
-@msg:
-@headers:
-@encoding:
-@user_data:
-
-
-<!-- ##### USER_FUNCTION SoupMessageParseHeadersFn ##### -->
-<para>
-
-</para>
-
-@msg:
-@headers:
-@header_len:
-@encoding:
-@content_len:
-@user_data:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_io_client ##### -->
-<para>
-
-</para>
-
-@msg:
-@sock:
-@get_headers_cb:
-@parse_headers_cb:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_message_io_server ##### -->
-<para>
-
-</para>
-
-@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 468f0d68..00000000
--- a/docs/reference/tmpl/soup-message-queue.sgml
+++ /dev/null
@@ -1,102 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-soup-message-queue
-
-<!-- ##### SECTION Short_Description ##### -->
-Message queue object
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-#SoupMessageQueue maintains the queue of pending messages in a
-#SoupSession.
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### TYPEDEF SoupMessageQueue ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### STRUCT SoupMessageQueueIter ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_message_queue_new ##### -->
-<para>
-
-</para>
-
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_queue_append ##### -->
-<para>
-
-</para>
-
-@queue:
-@msg:
-
-
-<!-- ##### FUNCTION soup_message_queue_first ##### -->
-<para>
-
-</para>
-
-@queue:
-@iter:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_queue_next ##### -->
-<para>
-
-</para>
-
-@queue:
-@iter:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_queue_remove ##### -->
-<para>
-
-</para>
-
-@queue:
-@iter:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_queue_free_iter ##### -->
-<para>
-
-</para>
-
-@queue:
-@iter:
-
-
-<!-- ##### FUNCTION soup_message_queue_destroy ##### -->
-<para>
-
-</para>
-
-@queue:
-
-
-<!-- ##### FUNCTION soup_message_queue_remove_message ##### -->
-<para>
-
-</para>
-
-@queue:
-@msg:
-
-
diff --git a/docs/reference/tmpl/soup-message.sgml b/docs/reference/tmpl/soup-message.sgml
deleted file mode 100644
index 195a394f..00000000
--- a/docs/reference/tmpl/soup-message.sgml
+++ /dev/null
@@ -1,522 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupMessage
-
-<!-- ##### SECTION Short_Description ##### -->
-An HTTP request and response.
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupMessage ##### -->
-<para>
-
-</para>
-
-@method:
-@status_code:
-@reason_phrase:
-@request:
-@request_headers:
-@response:
-@response_headers:
-@status:
-
-<!-- ##### SIGNAL SoupMessage::finished ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### SIGNAL SoupMessage::got-body ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### SIGNAL SoupMessage::got-chunk ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### SIGNAL SoupMessage::got-headers ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### SIGNAL SoupMessage::got-informational ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### SIGNAL SoupMessage::restarted ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### SIGNAL SoupMessage::wrote-body ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### SIGNAL SoupMessage::wrote-chunk ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### SIGNAL SoupMessage::wrote-headers ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### SIGNAL SoupMessage::wrote-informational ##### -->
-<para>
-
-</para>
-
-@msg:
-
-<!-- ##### ENUM SoupMessageStatus ##### -->
-<para>
-
-</para>
-
-@SOUP_MESSAGE_STATUS_IDLE:
-@SOUP_MESSAGE_STATUS_QUEUED:
-@SOUP_MESSAGE_STATUS_CONNECTING:
-@SOUP_MESSAGE_STATUS_RUNNING:
-@SOUP_MESSAGE_STATUS_FINISHED:
-
-<!-- ##### MACRO SOUP_MESSAGE_IS_STARTING ##### -->
-
-<para>
-
-</para>
-
-@msg:
-@Returns:
-
-
-<!-- ##### ENUM SoupTransferEncoding ##### -->
-<para>
-
-</para>
-
-@SOUP_TRANSFER_UNKNOWN:
-@SOUP_TRANSFER_CHUNKED:
-@SOUP_TRANSFER_CONTENT_LENGTH:
-@SOUP_TRANSFER_BYTERANGES:
-@SOUP_TRANSFER_NONE:
-@SOUP_TRANSFER_EOF:
-
-<!-- ##### ENUM SoupOwnership ##### -->
-<para>
-
-</para>
-
-@SOUP_BUFFER_SYSTEM_OWNED:
-@SOUP_BUFFER_USER_OWNED:
-@SOUP_BUFFER_STATIC:
-
-<!-- ##### STRUCT SoupDataBuffer ##### -->
-<para>
-
-</para>
-
-@owner:
-@body:
-@length:
-
-<!-- ##### USER_FUNCTION SoupMessageCallbackFn ##### -->
-<para>
-
-</para>
-
-@req:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_message_new ##### -->
-<para>
-
-</para>
-
-@method:
-@uri_string:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_new_from_uri ##### -->
-<para>
-
-</para>
-
-@method:
-@uri:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_set_request ##### -->
-<para>
-
-</para>
-
-@msg:
-@content_type:
-@req_owner:
-@req_body:
-@req_length:
-
-
-<!-- ##### FUNCTION soup_message_set_response ##### -->
-<para>
-
-</para>
-
-@msg:
-@content_type:
-@resp_owner:
-@resp_body:
-@resp_length:
-
-
-<!-- ##### FUNCTION soup_message_add_header ##### -->
-<para>
-
-</para>
-
-@hash:
-@name:
-@value:
-
-
-<!-- ##### FUNCTION soup_message_get_header ##### -->
-<para>
-
-</para>
-
-@hash:
-@name:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_get_header_list ##### -->
-<para>
-
-</para>
-
-@hash:
-@name:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_foreach_header ##### -->
-<para>
-
-</para>
-
-@hash:
-@func:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_message_remove_header ##### -->
-<para>
-
-</para>
-
-@hash:
-@name:
-
-
-<!-- ##### FUNCTION soup_message_clear_headers ##### -->
-<para>
-
-</para>
-
-@hash:
-
-
-<!-- ##### ENUM SoupHttpVersion ##### -->
-<para>
-
-</para>
-
-@SOUP_HTTP_1_0:
-@SOUP_HTTP_1_1:
-
-<!-- ##### FUNCTION soup_message_set_http_version ##### -->
-<para>
-
-</para>
-
-@msg:
-@version:
-
-
-<!-- ##### FUNCTION soup_message_get_http_version ##### -->
-<para>
-
-</para>
-
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_get_uri ##### -->
-<para>
-
-</para>
-
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_set_uri ##### -->
-<para>
-
-</para>
-
-@msg:
-@uri:
-
-
-<!-- ##### ENUM SoupMessageFlags ##### -->
-<para>
-
-</para>
-
-@SOUP_MESSAGE_NO_REDIRECT:
-@SOUP_MESSAGE_OVERWRITE_CHUNKS:
-@SOUP_MESSAGE_EXPECT_CONTINUE:
-
-<!-- ##### FUNCTION soup_message_set_flags ##### -->
-<para>
-
-</para>
-
-@msg:
-@flags:
-
-
-<!-- ##### FUNCTION soup_message_get_flags ##### -->
-<para>
-
-</para>
-
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_set_status ##### -->
-<para>
-
-</para>
-
-@msg:
-@status_code:
-
-
-<!-- ##### FUNCTION soup_message_set_status_full ##### -->
-<para>
-
-</para>
-
-@msg:
-@status_code:
-@reason_phrase:
-
-
-<!-- ##### FUNCTION soup_message_add_chunk ##### -->
-<para>
-
-</para>
-
-@msg:
-@owner:
-@body:
-@length:
-
-
-<!-- ##### FUNCTION soup_message_add_final_chunk ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_message_pop_chunk ##### -->
-<para>
-
-</para>
-
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_is_keepalive ##### -->
-<para>
-
-</para>
-
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_get_request_encoding ##### -->
-<para>
-
-</para>
-
-@msg:
-@content_length:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_message_get_response_encoding ##### -->
-<para>
-
-</para>
-
-@msg:
-@content_length:
-@Returns:
-
-
-<!-- ##### ENUM SoupHandlerPhase ##### -->
-<para>
-
-</para>
-
-@SOUP_HANDLER_POST_REQUEST:
-@SOUP_HANDLER_PRE_BODY:
-@SOUP_HANDLER_BODY_CHUNK:
-@SOUP_HANDLER_POST_BODY:
-
-<!-- ##### FUNCTION soup_message_add_handler ##### -->
-<para>
-
-</para>
-
-@msg:
-@phase:
-@handler_cb:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_message_add_header_handler ##### -->
-<para>
-
-</para>
-
-@msg:
-@header:
-@phase:
-@handler_cb:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_message_add_status_code_handler ##### -->
-<para>
-
-</para>
-
-@msg:
-@status_code:
-@phase:
-@handler_cb:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_message_add_status_class_handler ##### -->
-<para>
-
-</para>
-
-@msg:
-@status_class:
-@phase:
-@handler_cb:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_message_remove_handler ##### -->
-<para>
-
-</para>
-
-@msg:
-@phase:
-@handler_cb:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_message_send_request ##### -->
-<para>
-
-</para>
-
-@req:
-@sock:
-@is_via_proxy:
-
-
-<!-- ##### FUNCTION soup_message_read_request ##### -->
-<para>
-
-</para>
-
-@req:
-@sock:
-
-
-<!-- ##### FUNCTION soup_message_io_pause ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_message_io_unpause ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_message_io_stop ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
diff --git a/docs/reference/tmpl/soup-misc.sgml b/docs/reference/tmpl/soup-misc.sgml
deleted file mode 100644
index 7f949d01..00000000
--- a/docs/reference/tmpl/soup-misc.sgml
+++ /dev/null
@@ -1,277 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-soup-misc
-
-<!-- ##### SECTION Short_Description ##### -->
-Miscellaneous functions
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### FUNCTION soup_base64_decode ##### -->
-<para>
-
-</para>
-
-@text:
-@out_len:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_base64_encode ##### -->
-<para>
-
-</para>
-
-@text:
-@len:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_base64_decode_step ##### -->
-<para>
-
-</para>
-
-@in:
-@len:
-@out:
-@state:
-@save:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_base64_encode_step ##### -->
-<para>
-
-</para>
-
-@in:
-@len:
-@break_lines:
-@out:
-@state:
-@save:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_base64_encode_close ##### -->
-<para>
-
-</para>
-
-@in:
-@inlen:
-@break_lines:
-@out:
-@state:
-@save:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_add_idle ##### -->
-<para>
-
-</para>
-
-@async_context:
-@function:
-@data:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_add_io_watch ##### -->
-<para>
-
-</para>
-
-@async_context:
-@chan:
-@condition:
-@function:
-@data:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_add_timeout ##### -->
-<para>
-
-</para>
-
-@async_context:
-@interval:
-@function:
-@data:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_signal_connect_once ##### -->
-<para>
-
-</para>
-
-@instance:
-@detailed_signal:
-@c_handler:
-@data:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_date_parse ##### -->
-<para>
-
-</para>
-
-@timestamp:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_date_iso8601_parse ##### -->
-<para>
-
-</para>
-
-@timestamp:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_date_generate ##### -->
-<para>
-
-</para>
-
-@when:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_gmtime ##### -->
-<para>
-
-</para>
-
-@when:
-@tm:
-
-
-<!-- ##### FUNCTION soup_mktime_utc ##### -->
-<para>
-
-</para>
-
-@tm:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_header_param_copy_token ##### -->
-<para>
-
-</para>
-
-@tokens:
-@t:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_header_param_decode_token ##### -->
-<para>
-
-</para>
-
-@in:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_header_param_destroy_hash ##### -->
-<para>
-
-</para>
-
-@table:
-
-
-<!-- ##### FUNCTION soup_header_param_parse_list ##### -->
-<para>
-
-</para>
-
-@header:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_headers_parse_request ##### -->
-<para>
-
-</para>
-
-@str:
-@len:
-@dest:
-@req_method:
-@req_path:
-@ver:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_headers_parse_response ##### -->
-<para>
-
-</para>
-
-@str:
-@len:
-@dest:
-@ver:
-@status_code:
-@reason_phrase:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_headers_parse_status_line ##### -->
-<para>
-
-</para>
-
-@status_line:
-@ver:
-@status_code:
-@reason_phrase:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_str_case_equal ##### -->
-<para>
-
-</para>
-
-@v1:
-@v2:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_str_case_hash ##### -->
-<para>
-
-</para>
-
-@key:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xml_real_node ##### -->
-<para>
-
-</para>
-
-@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 cbe22e33..00000000
--- a/docs/reference/tmpl/soup-server-auth.sgml
+++ /dev/null
@@ -1,127 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-soup-server-auth
-
-<!-- ##### SECTION Short_Description ##### -->
-Server-side authentication structures
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### STRUCT SoupServerAuthContext ##### -->
-<para>
-
-</para>
-
-@types:
-@callback:
-@user_data:
-
-<!-- ##### USER_FUNCTION SoupServerAuthCallbackFn ##### -->
-<para>
-
-</para>
-
-@auth_ctx:
-@auth:
-@msg:
-@data:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_auth_context_challenge ##### -->
-<para>
-
-</para>
-
-@auth_ctx:
-@msg:
-@header_name:
-
-
-<!-- ##### ENUM SoupAuthType ##### -->
-<para>
-
-</para>
-
-@SOUP_AUTH_TYPE_BASIC:
-@SOUP_AUTH_TYPE_DIGEST:
-
-<!-- ##### STRUCT SoupServerAuthBasic ##### -->
-<para>
-
-</para>
-
-@type:
-@user:
-@passwd:
-
-<!-- ##### ENUM SoupDigestAlgorithm ##### -->
-<para>
-
-</para>
-
-@SOUP_ALGORITHM_MD5:
-@SOUP_ALGORITHM_MD5_SESS:
-
-<!-- ##### STRUCT SoupServerAuthDigest ##### -->
-<para>
-
-</para>
-
-@type:
-@algorithm:
-@integrity:
-@realm:
-@user:
-@nonce:
-@nonce_count:
-@cnonce:
-@digest_uri:
-@digest_response:
-@request_method:
-
-<!-- ##### FUNCTION soup_server_auth_new ##### -->
-<para>
-
-</para>
-
-@auth_ctx:
-@auth_hdrs:
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_auth_free ##### -->
-<para>
-
-</para>
-
-@auth:
-
-
-<!-- ##### FUNCTION soup_server_auth_get_user ##### -->
-<para>
-
-</para>
-
-@auth:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_auth_check_passwd ##### -->
-<para>
-
-</para>
-
-@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 d5ed721c..00000000
--- a/docs/reference/tmpl/soup-server-message.sgml
+++ /dev/null
@@ -1,95 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupServerMessage
-
-<!-- ##### SECTION Short_Description ##### -->
-Server-side #SoupMessage
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupServerMessage ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_server_message_new ##### -->
-<para>
-
-</para>
-
-@server:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_message_get_server ##### -->
-<para>
-
-</para>
-
-@smsg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_message_set_encoding ##### -->
-<para>
-
-</para>
-
-@smsg:
-@encoding:
-
-
-<!-- ##### FUNCTION soup_server_message_get_encoding ##### -->
-<para>
-
-</para>
-
-@smsg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_message_start ##### -->
-<para>
-
-</para>
-
-@smsg:
-
-
-<!-- ##### FUNCTION soup_server_message_is_started ##### -->
-<para>
-
-</para>
-
-@smsg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_message_finish ##### -->
-<para>
-
-</para>
-
-@smsg:
-
-
-<!-- ##### FUNCTION soup_server_message_is_finished ##### -->
-<para>
-
-</para>
-
-@smsg:
-@Returns:
-
-
diff --git a/docs/reference/tmpl/soup-server.sgml b/docs/reference/tmpl/soup-server.sgml
deleted file mode 100644
index 29babd83..00000000
--- a/docs/reference/tmpl/soup-server.sgml
+++ /dev/null
@@ -1,249 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupServer
-
-<!-- ##### SECTION Short_Description ##### -->
-HTTP server
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-#SoupServer implements a simple HTTP server.
-</para>
-
-<para>
-This API is less stable than the soup client API, and will most likely
-change in the next release.
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupServer ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### ARG SoupServer:async-context ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupServer:interface ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupServer:port ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupServer:ssl-cert-file ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupServer:ssl-key-file ##### -->
-<para>
-
-</para>
-
-<!-- ##### STRUCT SoupServerContext ##### -->
-<para>
-
-</para>
-
-@msg:
-@path:
-@method_id:
-@auth:
-@server:
-@handler:
-@sock:
-
-<!-- ##### USER_FUNCTION SoupServerCallbackFn ##### -->
-<para>
-
-</para>
-
-@context:
-@msg:
-@user_data:
-
-
-<!-- ##### USER_FUNCTION SoupServerUnregisterFn ##### -->
-<para>
-
-</para>
-
-@server:
-@handler:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_server_new ##### -->
-<para>
-
-</para>
-
-@optname1:
-@Varargs:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_get_protocol ##### -->
-<para>
-
-</para>
-
-@serv:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_get_port ##### -->
-<para>
-
-</para>
-
-@serv:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_get_listener ##### -->
-<para>
-
-</para>
-
-@serv:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_run ##### -->
-<para>
-
-</para>
-
-@serv:
-
-
-<!-- ##### FUNCTION soup_server_run_async ##### -->
-<para>
-
-</para>
-
-@serv:
-
-
-<!-- ##### FUNCTION soup_server_quit ##### -->
-<para>
-
-</para>
-
-@serv:
-
-
-<!-- ##### TYPEDEF SoupServerHandler ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_server_add_handler ##### -->
-<para>
-
-</para>
-
-@serv:
-@path:
-@auth_ctx:
-@callback:
-@unreg:
-@data:
-
-
-<!-- ##### FUNCTION soup_server_remove_handler ##### -->
-<para>
-
-</para>
-
-@serv:
-@path:
-
-
-<!-- ##### FUNCTION soup_server_get_handler ##### -->
-<para>
-
-</para>
-
-@serv:
-@path:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_list_handlers ##### -->
-<para>
-
-</para>
-
-@serv:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_context_get_client_address ##### -->
-<para>
-
-</para>
-
-@ctx:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_server_context_get_client_host ##### -->
-<para>
-
-</para>
-
-@ctx:
-@Returns:
-
-
-<!-- ##### MACRO SOUP_SERVER_PORT ##### -->
-<para>
-An alias for the "port" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SERVER_INTERFACE ##### -->
-<para>
-An alias for the "interface" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SERVER_SSL_CERT_FILE ##### -->
-<para>
-An alias for the "ssl-cert-file" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SERVER_SSL_KEY_FILE ##### -->
-<para>
-An alias for the "ssl-key-file" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SERVER_ASYNC_CONTEXT ##### -->
-<para>
-An alias for the "async-context" property.
-</para>
-
-
-
diff --git a/docs/reference/tmpl/soup-session-async.sgml b/docs/reference/tmpl/soup-session-async.sgml
deleted file mode 100644
index 54533242..00000000
--- a/docs/reference/tmpl/soup-session-async.sgml
+++ /dev/null
@@ -1,45 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupSessionAsync
-
-<!-- ##### SECTION Short_Description ##### -->
-Soup session for asynchronous (main-loop-based) I/O.
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-#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.
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupSessionAsync ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_session_async_new ##### -->
-<para>
-
-</para>
-
-@Returns:
-
-
-<!-- ##### FUNCTION soup_session_async_new_with_options ##### -->
-<para>
-
-</para>
-
-@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 63e3093f..00000000
--- a/docs/reference/tmpl/soup-session-sync.sgml
+++ /dev/null
@@ -1,49 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupSessionSync
-
-<!-- ##### SECTION Short_Description ##### -->
-Soup session for blocking I/O in multithreaded programs.
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-#SoupSessionSync is an implementation of #SoupSession that uses
-synchronous I/O, intended for use in multi-threaded programs.
-</para>
-
-<para>
-Note that you cannot use soup_session_queue_message() with a
-synchronous session. You can only use soup_session_send_message().
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupSessionSync ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_session_sync_new ##### -->
-<para>
-
-</para>
-
-@Returns:
-
-
-<!-- ##### FUNCTION soup_session_sync_new_with_options ##### -->
-<para>
-
-</para>
-
-@optname1:
-@Varargs:
-@Returns:
-
-
diff --git a/docs/reference/tmpl/soup-session.sgml b/docs/reference/tmpl/soup-session.sgml
deleted file mode 100644
index 4c5d80d9..00000000
--- a/docs/reference/tmpl/soup-session.sgml
+++ /dev/null
@@ -1,219 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupSession
-
-<!-- ##### SECTION Short_Description ##### -->
-Soup session state object
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupSession ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SIGNAL SoupSession::authenticate ##### -->
-<para>
-
-</para>
-
-@session:
-@msg:
-@auth_type:
-@auth_realm:
-@username:
-@password:
-
-<!-- ##### SIGNAL SoupSession::reauthenticate ##### -->
-<para>
-
-</para>
-
-@session:
-@msg:
-@auth_type:
-@auth_realm:
-@username:
-@password:
-
-<!-- ##### ARG SoupSession:async-context ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSession:max-conns ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSession:max-conns-per-host ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSession:proxy-uri ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSession:ssl-ca-file ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSession:timeout ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSession:use-ntlm ##### -->
-<para>
-
-</para>
-
-<!-- ##### FUNCTION soup_session_queue_message ##### -->
-<para>
-
-</para>
-
-@session:
-@msg:
-@callback:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_session_requeue_message ##### -->
-<para>
-
-</para>
-
-@session:
-@msg:
-
-
-<!-- ##### FUNCTION soup_session_send_message ##### -->
-<para>
-
-</para>
-
-@session:
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_session_abort ##### -->
-<para>
-
-</para>
-
-@session:
-
-
-<!-- ##### FUNCTION soup_session_add_filter ##### -->
-<para>
-
-</para>
-
-@session:
-@filter:
-
-
-<!-- ##### FUNCTION soup_session_cancel_message ##### -->
-<para>
-
-</para>
-
-@session:
-@msg:
-
-
-<!-- ##### FUNCTION soup_session_get_connection ##### -->
-<para>
-
-</para>
-
-@session:
-@msg:
-@try_pruning:
-@is_new:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_session_remove_filter ##### -->
-<para>
-
-</para>
-
-@session:
-@filter:
-
-
-<!-- ##### FUNCTION soup_session_try_prune_connection ##### -->
-<para>
-
-</para>
-
-@session:
-@Returns:
-
-
-<!-- ##### MACRO SOUP_SESSION_PROXY_URI ##### -->
-<para>
-An alias for the "proxy-uri" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SESSION_MAX_CONNS ##### -->
-<para>
-An alias for the "max-conns" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SESSION_MAX_CONNS_PER_HOST ##### -->
-<para>
-An alias for the "max-conns-per-host" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SESSION_USE_NTLM ##### -->
-<para>
-An alias for the "use-ntlm" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SESSION_SSL_CA_FILE ##### -->
-<para>
-An alias for the "ssl-ca-file" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SESSION_ASYNC_CONTEXT ##### -->
-<para>
-An alias for the "async-context" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SESSION_TIMEOUT ##### -->
-<para>
-An alias for the "timeout" property.
-</para>
-
-
-
diff --git a/docs/reference/tmpl/soup-soap-message.sgml b/docs/reference/tmpl/soup-soap-message.sgml
deleted file mode 100644
index 72c83ed4..00000000
--- a/docs/reference/tmpl/soup-soap-message.sgml
+++ /dev/null
@@ -1,333 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupSoapMessage
-
-<!-- ##### SECTION Short_Description ##### -->
-A SOAP request
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupSoapMessage ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_soap_message_new ##### -->
-<para>
-
-</para>
-
-@method:
-@uri_string:
-@standalone:
-@xml_encoding:
-@env_prefix:
-@env_uri:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_message_new_from_uri ##### -->
-<para>
-
-</para>
-
-@method:
-@uri:
-@standalone:
-@xml_encoding:
-@env_prefix:
-@env_uri:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_message_start_envelope ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_end_envelope ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_start_body ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_end_body ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_start_element ##### -->
-<para>
-
-</para>
-
-@msg:
-@name:
-@prefix:
-@ns_uri:
-
-
-<!-- ##### FUNCTION soup_soap_message_end_element ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_start_fault ##### -->
-<para>
-
-</para>
-
-@msg:
-@faultcode:
-@faultstring:
-@faultfactor:
-
-
-<!-- ##### FUNCTION soup_soap_message_end_fault ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_start_fault_detail ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_end_fault_detail ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_start_header ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_end_header ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_start_header_element ##### -->
-<para>
-
-</para>
-
-@msg:
-@name:
-@must_understand:
-@actor_uri:
-@prefix:
-@ns_uri:
-
-
-<!-- ##### FUNCTION soup_soap_message_end_header_element ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_write_int ##### -->
-<para>
-
-</para>
-
-@msg:
-@i:
-
-
-<!-- ##### FUNCTION soup_soap_message_write_double ##### -->
-<para>
-
-</para>
-
-@msg:
-@d:
-
-
-<!-- ##### FUNCTION soup_soap_message_write_base64 ##### -->
-<para>
-
-</para>
-
-@msg:
-@string:
-@len:
-
-
-<!-- ##### FUNCTION soup_soap_message_write_time ##### -->
-<para>
-
-</para>
-
-@msg:
-@timeval:
-
-
-<!-- ##### FUNCTION soup_soap_message_write_string ##### -->
-<para>
-
-</para>
-
-@msg:
-@string:
-
-
-<!-- ##### FUNCTION soup_soap_message_write_buffer ##### -->
-<para>
-
-</para>
-
-@msg:
-@buffer:
-@len:
-
-
-<!-- ##### FUNCTION soup_soap_message_set_element_type ##### -->
-<para>
-
-</para>
-
-@msg:
-@xsi_type:
-
-
-<!-- ##### FUNCTION soup_soap_message_set_null ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_add_attribute ##### -->
-<para>
-
-</para>
-
-@msg:
-@name:
-@value:
-@prefix:
-@ns_uri:
-
-
-<!-- ##### FUNCTION soup_soap_message_add_namespace ##### -->
-<para>
-
-</para>
-
-@msg:
-@prefix:
-@ns_uri:
-
-
-<!-- ##### FUNCTION soup_soap_message_set_default_namespace ##### -->
-<para>
-
-</para>
-
-@msg:
-@ns_uri:
-
-
-<!-- ##### FUNCTION soup_soap_message_set_encoding_style ##### -->
-<para>
-
-</para>
-
-@msg:
-@enc_style:
-
-
-<!-- ##### FUNCTION soup_soap_message_reset ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_persist ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_soap_message_get_namespace_prefix ##### -->
-<para>
-
-</para>
-
-@msg:
-@ns_uri:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_message_get_xml_doc ##### -->
-<para>
-
-</para>
-
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_message_parse_response ##### -->
-<para>
-
-</para>
-
-@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 0bde3c00..00000000
--- a/docs/reference/tmpl/soup-soap-response.sgml
+++ /dev/null
@@ -1,200 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupSoapResponse
-
-<!-- ##### SECTION Short_Description ##### -->
-A SOAP response
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupSoapResponse ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### TYPEDEF SoupSoapParameter ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_soap_response_new ##### -->
-<para>
-
-</para>
-
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_response_new_from_string ##### -->
-<para>
-
-</para>
-
-@xmlstr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_response_set_method_name ##### -->
-<para>
-
-</para>
-
-@response:
-@method_name:
-
-
-<!-- ##### FUNCTION soup_soap_parameter_get_first_child ##### -->
-<para>
-
-</para>
-
-@param:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_parameter_get_first_child_by_name ##### -->
-<para>
-
-</para>
-
-@param:
-@name:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_parameter_get_int_value ##### -->
-<para>
-
-</para>
-
-@param:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_parameter_get_name ##### -->
-<para>
-
-</para>
-
-@param:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_parameter_get_next_child ##### -->
-<para>
-
-</para>
-
-@param:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_parameter_get_next_child_by_name ##### -->
-<para>
-
-</para>
-
-@param:
-@name:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_parameter_get_property ##### -->
-<para>
-
-</para>
-
-@param:
-@prop_name:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_parameter_get_string_value ##### -->
-<para>
-
-</para>
-
-@param:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_response_from_string ##### -->
-<para>
-
-</para>
-
-@response:
-@xmlstr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_response_get_first_parameter ##### -->
-<para>
-
-</para>
-
-@response:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_response_get_first_parameter_by_name ##### -->
-<para>
-
-</para>
-
-@response:
-@name:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_response_get_method_name ##### -->
-<para>
-
-</para>
-
-@response:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_response_get_next_parameter ##### -->
-<para>
-
-</para>
-
-@response:
-@from:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_response_get_next_parameter_by_name ##### -->
-<para>
-
-</para>
-
-@response:
-@from:
-@name:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_soap_response_get_parameters ##### -->
-<para>
-
-</para>
-
-@response:
-@Returns:
-
-
diff --git a/docs/reference/tmpl/soup-socket.sgml b/docs/reference/tmpl/soup-socket.sgml
deleted file mode 100644
index 0a86e703..00000000
--- a/docs/reference/tmpl/soup-socket.sgml
+++ /dev/null
@@ -1,348 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupSocket
-
-<!-- ##### SECTION Short_Description ##### -->
-a network socket
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupSocket ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### SIGNAL SoupSocket::connect-result ##### -->
-<para>
-
-</para>
-
-@sock:
-@status:
-
-<!-- ##### SIGNAL SoupSocket::disconnected ##### -->
-<para>
-
-</para>
-
-@sock:
-
-<!-- ##### SIGNAL SoupSocket::new-connection ##### -->
-<para>
-
-</para>
-
-@sock:
-@new:
-
-<!-- ##### SIGNAL SoupSocket::readable ##### -->
-<para>
-
-</para>
-
-@sock:
-
-<!-- ##### SIGNAL SoupSocket::writable ##### -->
-<para>
-
-</para>
-
-@sock:
-
-<!-- ##### ARG SoupSocket:async-context ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSocket:cloexec ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSocket:is-server ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSocket:nodelay ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSocket:non-blocking ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSocket:reuseaddr ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSocket:ssl-creds ##### -->
-<para>
-
-</para>
-
-<!-- ##### ARG SoupSocket:timeout ##### -->
-<para>
-
-</para>
-
-<!-- ##### FUNCTION soup_socket_new ##### -->
-<para>
-
-</para>
-
-@optname1:
-@Varargs:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_connect ##### -->
-<para>
-
-</para>
-
-@sock:
-@remote_addr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_listen ##### -->
-<para>
-
-</para>
-
-@sock:
-@local_addr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_start_ssl ##### -->
-<para>
-
-</para>
-
-@sock:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_start_proxy_ssl ##### -->
-<para>
-
-</para>
-
-@sock:
-@ssl_host:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_disconnect ##### -->
-<para>
-
-</para>
-
-@sock:
-
-
-<!-- ##### FUNCTION soup_socket_is_connected ##### -->
-<para>
-
-</para>
-
-@sock:
-@Returns:
-
-
-<!-- ##### USER_FUNCTION SoupSocketCallback ##### -->
-<para>
-
-</para>
-
-@sock:
-@status:
-@user_data:
-
-
-<!-- ##### USER_FUNCTION SoupSocketListenerCallback ##### -->
-<para>
-
-</para>
-
-@listener:
-@sock:
-@user_data:
-
-
-<!-- ##### FUNCTION soup_socket_client_new_async ##### -->
-<para>
-
-</para>
-
-@hostname:
-@port:
-@ssl_creds:
-@callback:
-@user_data:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_client_new_sync ##### -->
-<para>
-
-</para>
-
-@hostname:
-@port:
-@ssl_creds:
-@status_ret:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_server_new ##### -->
-<para>
-
-</para>
-
-@local_addr:
-@ssl_creds:
-@callback:
-@user_data:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_get_local_address ##### -->
-<para>
-
-</para>
-
-@sock:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_get_remote_address ##### -->
-<para>
-
-</para>
-
-@sock:
-@Returns:
-
-
-<!-- ##### ENUM SoupSocketIOStatus ##### -->
-<para>
-
-</para>
-
-@SOUP_SOCKET_OK:
-@SOUP_SOCKET_WOULD_BLOCK:
-@SOUP_SOCKET_EOF:
-@SOUP_SOCKET_ERROR:
-
-<!-- ##### FUNCTION soup_socket_read ##### -->
-<para>
-
-</para>
-
-@sock:
-@buffer:
-@len:
-@nread:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_read_until ##### -->
-<para>
-
-</para>
-
-@sock:
-@buffer:
-@len:
-@boundary:
-@boundary_len:
-@nread:
-@got_boundary:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_socket_write ##### -->
-<para>
-
-</para>
-
-@sock:
-@buffer:
-@len:
-@nwrote:
-@Returns:
-
-
-<!-- ##### MACRO SOUP_SOCKET_FLAG_NONBLOCKING ##### -->
-<para>
-An alias for the "non-blocking" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SOCKET_FLAG_NODELAY ##### -->
-<para>
-An alias for the "nodelay" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SOCKET_FLAG_REUSEADDR ##### -->
-<para>
-An alias for the "reuseaddr" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SOCKET_FLAG_CLOEXEC ##### -->
-<para>
-An alias for the "cloexec" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SOCKET_TIMEOUT ##### -->
-<para>
-An alias for the "timeout" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SOCKET_IS_SERVER ##### -->
-<para>
-An alias for the "is-server" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SOCKET_SSL_CREDENTIALS ##### -->
-<para>
-An alias for the "ssl-creds" property.
-</para>
-
-
-
-<!-- ##### MACRO SOUP_SOCKET_ASYNC_CONTEXT ##### -->
-<para>
-An alias for the "async-context" property.
-</para>
-
-
-
diff --git a/docs/reference/tmpl/soup-ssl.sgml b/docs/reference/tmpl/soup-ssl.sgml
deleted file mode 100644
index 714c88eb..00000000
--- a/docs/reference/tmpl/soup-ssl.sgml
+++ /dev/null
@@ -1,110 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-soup-ssl
-
-<!-- ##### SECTION Short_Description ##### -->
-SSL/TLS handling
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### VARIABLE soup_ssl_supported ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### ENUM SoupSSLType ##### -->
-<para>
-
-</para>
-
-@SOUP_SSL_TYPE_CLIENT:
-@SOUP_SSL_TYPE_SERVER:
-
-<!-- ##### TYPEDEF SoupSSLCredentials ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_ssl_get_client_credentials ##### -->
-<para>
-
-</para>
-
-@ca_file:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_ssl_free_client_credentials ##### -->
-<para>
-
-</para>
-
-@creds:
-
-
-<!-- ##### FUNCTION soup_ssl_get_server_credentials ##### -->
-<para>
-
-</para>
-
-@cert_file:
-@key_file:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_ssl_free_server_credentials ##### -->
-<para>
-
-</para>
-
-@creds:
-
-
-<!-- ##### FUNCTION soup_ssl_wrap_iochannel ##### -->
-<para>
-
-</para>
-
-@sock:
-@type:
-@remote_host:
-@creds:
-@Returns:
-
-
-<!-- ##### MACRO SOUP_SSL_ERROR ##### -->
-<para>
-
-</para>
-
-
-
-<!-- ##### FUNCTION soup_ssl_error_quark ##### -->
-<para>
-
-</para>
-
-@Returns:
-
-
-<!-- ##### ENUM SoupSocketError ##### -->
-<para>
-
-</para>
-
-@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 b5d8f179..00000000
--- a/docs/reference/tmpl/soup-status.sgml
+++ /dev/null
@@ -1,152 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-soup-status
-
-<!-- ##### SECTION Short_Description ##### -->
-HTTP and Soup status codes
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### ENUM SoupStatusClass ##### -->
-<para>
-
-</para>
-
-@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:
-
-<!-- ##### MACRO SOUP_STATUS_IS_INFORMATIONAL ##### -->
-<para>
-
-</para>
-
-@status:
-@Returns:
-
-
-<!-- ##### MACRO SOUP_STATUS_IS_SUCCESSFUL ##### -->
-<para>
-
-</para>
-
-@status:
-@Returns:
-
-
-<!-- ##### MACRO SOUP_STATUS_IS_REDIRECTION ##### -->
-<para>
-
-</para>
-
-@status:
-@Returns:
-
-
-<!-- ##### MACRO SOUP_STATUS_IS_CLIENT_ERROR ##### -->
-<para>
-
-</para>
-
-@status:
-@Returns:
-
-
-<!-- ##### MACRO SOUP_STATUS_IS_SERVER_ERROR ##### -->
-<para>
-
-</para>
-
-@status:
-@Returns:
-
-
-<!-- ##### ENUM SoupKnownStatusCode ##### -->
-<para>
-
-</para>
-
-@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:
-
-<!-- ##### FUNCTION soup_status_get_phrase ##### -->
-<para>
-
-</para>
-
-@status_code:
-@Returns:
-
-
diff --git a/docs/reference/tmpl/soup-uri.sgml b/docs/reference/tmpl/soup-uri.sgml
deleted file mode 100644
index a3814d7b..00000000
--- a/docs/reference/tmpl/soup-uri.sgml
+++ /dev/null
@@ -1,146 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-soup-uri
-
-<!-- ##### SECTION Short_Description ##### -->
-URIs
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupUri ##### -->
-<para>
-
-</para>
-
-@protocol:
-@user:
-@passwd:
-@host:
-@port:
-@path:
-@query:
-@fragment:
-@broken_encoding:
-
-<!-- ##### TYPEDEF SoupProtocol ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### MACRO SOUP_PROTOCOL_HTTP ##### -->
-<para>
-
-</para>
-
-
-
-<!-- ##### MACRO SOUP_PROTOCOL_HTTPS ##### -->
-<para>
-
-</para>
-
-
-
-<!-- ##### FUNCTION soup_uri_new_with_base ##### -->
-<para>
-
-</para>
-
-@base:
-@uri_string:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_uri_new ##### -->
-<para>
-
-</para>
-
-@uri_string:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_uri_to_string ##### -->
-<para>
-
-</para>
-
-@uri:
-@just_path:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_uri_copy ##### -->
-<para>
-
-</para>
-
-@uri:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_uri_copy_root ##### -->
-<para>
-
-</para>
-
-@uri:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_uri_equal ##### -->
-<para>
-
-</para>
-
-@uri1:
-@uri2:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_uri_free ##### -->
-<para>
-
-</para>
-
-@uri:
-
-
-<!-- ##### FUNCTION soup_uri_encode ##### -->
-<para>
-
-</para>
-
-@part:
-@escape_extra:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_uri_decode ##### -->
-<para>
-
-</para>
-
-@part:
-
-
-<!-- ##### FUNCTION soup_uri_uses_default_port ##### -->
-<para>
-
-</para>
-
-@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 93e4ce63..00000000
--- a/docs/reference/tmpl/soup-xmlrpc-message.sgml
+++ /dev/null
@@ -1,216 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupXmlrpcMessage
-
-<!-- ##### SECTION Short_Description ##### -->
-An XML-RPC Message
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupXmlrpcMessage ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_new ##### -->
-<para>
-
-</para>
-
-@uri_string:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_new_from_uri ##### -->
-<para>
-
-</para>
-
-@uri:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_from_string ##### -->
-<para>
-
-</para>
-
-@message:
-@xmlstr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_start_call ##### -->
-<para>
-
-</para>
-
-@msg:
-@method_name:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_end_call ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_start_param ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_end_param ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_write_int ##### -->
-<para>
-
-</para>
-
-@msg:
-@Param2:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_write_boolean ##### -->
-<para>
-
-</para>
-
-@msg:
-@b:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_write_string ##### -->
-<para>
-
-</para>
-
-@msg:
-@str:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_write_double ##### -->
-<para>
-
-</para>
-
-@msg:
-@d:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_write_datetime ##### -->
-<para>
-
-</para>
-
-@msg:
-@timeval:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_write_base64 ##### -->
-<para>
-
-</para>
-
-@msg:
-@buf:
-@len:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_start_struct ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_end_struct ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_start_member ##### -->
-<para>
-
-</para>
-
-@msg:
-@name:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_end_member ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_start_array ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_end_array ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_to_string ##### -->
-<para>
-
-</para>
-
-@msg:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_persist ##### -->
-<para>
-
-</para>
-
-@msg:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_message_parse_response ##### -->
-<para>
-
-</para>
-
-@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 2f678b28..00000000
--- a/docs/reference/tmpl/soup-xmlrpc-response.sgml
+++ /dev/null
@@ -1,231 +0,0 @@
-<!-- ##### SECTION Title ##### -->
-SoupXmlrpcResponse
-
-<!-- ##### SECTION Short_Description ##### -->
-An XML-RPC response
-
-<!-- ##### SECTION Long_Description ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION See_Also ##### -->
-<para>
-
-</para>
-
-<!-- ##### SECTION Stability_Level ##### -->
-
-
-<!-- ##### STRUCT SoupXmlrpcResponse ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_xmlrpc_response_new ##### -->
-<para>
-
-</para>
-
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_response_new_from_string ##### -->
-<para>
-
-</para>
-
-@xmlstr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_response_from_string ##### -->
-<para>
-
-</para>
-
-@response:
-@xmlstr:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_response_to_string ##### -->
-<para>
-
-</para>
-
-@response:
-@Returns:
-
-
-<!-- ##### TYPEDEF SoupXmlrpcValue ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### ENUM SoupXmlrpcValueType ##### -->
-<para>
-
-</para>
-
-@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:
-
-<!-- ##### FUNCTION soup_xmlrpc_response_is_fault ##### -->
-<para>
-
-</para>
-
-@response:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_response_get_value ##### -->
-<para>
-
-</para>
-
-@response:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_get_type ##### -->
-<para>
-
-</para>
-
-@value:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_get_int ##### -->
-<para>
-
-</para>
-
-@value:
-@i:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_get_double ##### -->
-<para>
-
-</para>
-
-@value:
-@b:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_get_boolean ##### -->
-<para>
-
-</para>
-
-@value:
-@b:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_get_string ##### -->
-<para>
-
-</para>
-
-@value:
-@str:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_get_datetime ##### -->
-<para>
-
-</para>
-
-@value:
-@timeval:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_get_base64 ##### -->
-<para>
-
-</para>
-
-@value:
-@data:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_get_struct ##### -->
-<para>
-
-</para>
-
-@value:
-@table:
-@Returns:
-
-
-<!-- ##### TYPEDEF SoupXmlrpcValueArrayIterator ##### -->
-<para>
-
-</para>
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_array_get_iterator ##### -->
-<para>
-
-</para>
-
-@value:
-@iter:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_array_iterator_prev ##### -->
-<para>
-
-</para>
-
-@iter:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_array_iterator_next ##### -->
-<para>
-
-</para>
-
-@iter:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_array_iterator_get_value ##### -->
-<para>
-
-</para>
-
-@iter:
-@value:
-@Returns:
-
-
-<!-- ##### FUNCTION soup_xmlrpc_value_dump ##### -->
-<para>
-
-</para>
-
-@value:
-
-
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am
index d2810797..2979eed8 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 20f54cb0..628f4ba2 100644
--- a/libsoup/soup-address.c
+++ b/libsoup/soup-address.c
@@ -15,7 +15,6 @@
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
-#include <signal.h>
#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 68357b09..92e71fef 100644
--- a/libsoup/soup-address.h
+++ b/libsoup/soup-address.h
@@ -8,6 +8,8 @@
#include <sys/types.h>
+#include <gio/gio.h>
+
#include <libsoup/soup-portability.h>
#include <libsoup/soup-types.h>
@@ -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 003bda6f..35f47084 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 3841e988..8ad5f854 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 b8bf8fa8..453c40c7 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 00000000..c3c64e25
--- /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 <config.h>
+#endif
+
+#include <string.h>
+
+#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 00000000..66ccb292
--- /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 <libsoup/soup-auth-domain.h>
+
+#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 00000000..b303ce84
--- /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 <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#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 00000000..8f8ae10c
--- /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 <libsoup/soup-auth-domain.h>
+
+#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 00000000..8ff77bb9
--- /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 <config.h>
+#endif
+
+#include <string.h>
+
+#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
+ * <emphasis>might</emphasis> 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 00000000..8b903f76
--- /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 <libsoup/soup-types.h>
+
+#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 00000000..6417197b
--- /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 <config.h>
+#endif
+
+#include <string.h>
+
+#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 00000000..89b7bed8
--- /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 00000000..63833114
--- /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 <config.h>
+#endif
+
+#include <string.h>
+
+#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 00000000..f494c02d
--- /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 51ad491a..30792814 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 0feda0c7..50e3849f 100644
--- a/libsoup/soup-auth.h
+++ b/libsoup/soup-auth.h
@@ -7,6 +7,7 @@
#define SOUP_AUTH_H 1
#include <libsoup/soup-types.h>
+#include <libsoup/soup-headers.h>
#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 1ffb66b0..efd1570b 100644
--- a/libsoup/soup-connection-ntlm.c
+++ b/libsoup/soup-connection-ntlm.c
@@ -13,7 +13,9 @@
#include <string.h>
#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 f3c32708..4dc6d427 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 b36d5fa5..98f20d6a 100644
--- a/libsoup/soup-connection.h
+++ b/libsoup/soup-connection.h
@@ -8,7 +8,7 @@
#include <time.h>
-#include <libsoup/soup-types.h>
+#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 6f92e74d..563849e2 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 29bdf486..106b0824 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 <time.h>
-#include <glib/gmacros.h>
+#include <libsoup/soup-types.h>
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 6ab3f4ff..31d1d208 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 2a8bb0fc..6519a9c5 100644
--- a/libsoup/soup-dns.h
+++ b/libsoup/soup-dns.h
@@ -7,6 +7,7 @@
#define SOUP_DNS_H
#include <glib.h>
+#include <gio/gio.h>
#include <sys/types.h>
#include <libsoup/soup-portability.h>
@@ -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 00000000..ea107820
--- /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 00000000..e18d7e02
--- /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 <glib-object.h>
+
+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 00000000..299fc884
--- /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 <config.h>
+#endif
+
+#include <string.h>
+
+#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 00000000..9502244c
--- /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 <libsoup/soup-types.h>
+
+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 c4b66f0d..b402850a 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 dd7b5162..1e69b020 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 428ab940..d040e755 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 00000000..a8221341
--- /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 <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#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:
+ *
+ * <informalexample><screen>
+ * > 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.
+ *
+ * &lt; HTTP/1.1 201 Created
+ * &lt; Soup-Debug-Timestamp: 1200171744
+ * &lt; Soup-Debug: msg 1 (0x617000)
+ * &lt; Date: Sun, 12 Jan 2008 21:02:24 GMT
+ * &lt; Content-Length: 0
+ * </screen></informalexample>
+ *
+ * The <literal>Soup-Debug-Timestamp</literal> line gives the time (as
+ * a #time_t) when the request was sent, or the response fully
+ * received.
+ *
+ * The <literal>Soup-Debug</literal> 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:
+ *
+ * <informalexample><programlisting>
+ * printf ("%c %s\n", direction, data);
+ * </programlisting></informalexample>
+ **/
+
+/**
+ * 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 00000000..dc96bc08
--- /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 <libsoup/soup-types.h>
+
+#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 9f429269..4e1b2bc9 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 e3dedfa8..00000000
--- 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 <string.h>
-
-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<<s | 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 83eb031e..00000000
--- 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 <glib.h>
-
-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 00000000..effabd7b
--- /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 <string.h>
+
+#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 00000000..329c7736
--- /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 <libsoup/soup-types.h>
+
+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 109d976c..5d5b2447 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 4b516c1a..00000000
--- 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 <config.h>
-#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 cebd93ef..00000000
--- 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 <libsoup/soup-types.h>
-
-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 3d598213..00000000
--- 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 00000000..646aaed3
--- /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 <stdio.h>
+
+#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 00000000..b6eb94e4
--- /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 <libsoup/soup-types.h>
+
+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 7c089e87..f10d4c39 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 14f55d22..1a22ac6c 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 d730dc30..a73749a4 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 d3b0185e..24e8ddb9 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 79273197..c4b85cbd 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 5a0f8ba6..12f62e13 100644
--- a/libsoup/soup-message.c
+++ b/libsoup/soup-message.c
@@ -9,13 +9,50 @@
#include <string.h>
#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 (&param_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 (&param_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 e048a4a7..6f38a316 100644
--- a/libsoup/soup-message.h
+++ b/libsoup/soup-message.h
@@ -7,6 +7,8 @@
#define SOUP_MESSAGE_H 1
#include <libsoup/soup-types.h>
+#include <libsoup/soup-message-body.h>
+#include <libsoup/soup-message-headers.h>
#include <libsoup/soup-method.h>
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 26f5c245..00000000
--- 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 <glib.h>
-
-#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 4ef69bf5..861f7ef1 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 <glib/gmacros.h>
-
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 <emphasis>are</emphasis> 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:
+ *
+ * <informalexample><programlisting>
+ * if (msg->method != SOUP_METHOD_GET &amp;&amp; msg->method != SOUP_METHOD_HEAD) {
+ * soup_message_set_status (msg, SOUP_METHOD_NOT_IMPLEMENTED);
+ * return;
+ * }
+ * </programlisting></informalexample>
+ **/
+
+#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 252596df..79a7399b 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 d88edae7..36996064 100644
--- a/libsoup/soup-misc.h
+++ b/libsoup/soup-misc.h
@@ -7,40 +7,9 @@
#define SOUP_MISC_H 1
#include <glib-object.h>
-#include <libxml/tree.h>
G_BEGIN_DECLS
-#ifndef LIBSOUP_DISABLE_DEPRECATED
-/* Base64 encoding/decoding. DEPRECATED: use <glib/base64.h> */
-
-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 00000000..60191b42
--- /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 <string.h>
+
+/* 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 00000000..37b5fb9e
--- /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 <soup-types.h>
+
+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 e2c5138d..00000000
--- 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 <glib.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#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 f537e378..00000000
--- 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 <libsoup/soup-types.h>
-
-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 ca4cc1e5..00000000
--- 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 <config.h>
-#endif
-
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#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 cae27747..00000000
--- 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 <libsoup/soup-message.h>
-
-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 051effe2..933c80f2 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 <config.h>
#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 aab27690..bfa0f2ef 100644
--- a/libsoup/soup-server.h
+++ b/libsoup/soup-server.h
@@ -7,7 +7,6 @@
#define SOUP_SERVER_H 1
#include <libsoup/soup-types.h>
-#include <libsoup/soup-method.h>
#include <libsoup/soup-uri.h>
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 a5634f20..a383900d 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 67e72f43..15fb22da 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 00000000..5d3313a5
--- /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 b45408ec..2db8435f 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 cf478d4c..3edbb167 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 37c19004..e6e54261 100644
--- a/libsoup/soup-session.c
+++ b/libsoup/soup-session.c
@@ -14,18 +14,28 @@
#include <stdlib.h>
#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 38ce3825..cc63dcfa 100644
--- a/libsoup/soup-session.h
+++ b/libsoup/soup-session.h
@@ -8,7 +8,6 @@
#include <libsoup/soup-types.h>
#include <libsoup/soup-message.h>
-#include <libsoup/soup-message-queue.h>
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 0c50c72d..00000000
--- 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 <string.h>
-#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 9048d62b..00000000
--- 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 <time.h>
-#include <libxml/tree.h>
-#include <libsoup/soup-message.h>
-#include <libsoup/soup-soap-response.h>
-
-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 65b77aad..00000000
--- 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 <stdlib.h>
-#include <string.h>
-#include <libxml/tree.h>
-#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 32427e27..00000000
--- 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 <glib-object.h>
-#include <libxml/tree.h>
-
-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 eeafa9c8..953c6f8e 100644
--- a/libsoup/soup-socket.c
+++ b/libsoup/soup-socket.c
@@ -24,10 +24,15 @@
#include <sys/time.h>
#include <sys/types.h>
+/**
+ * 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 5d60080a..0cdac17b 100644
--- a/libsoup/soup-socket.h
+++ b/libsoup/soup-socket.h
@@ -7,6 +7,7 @@
#define SOUP_SOCKET_H 1
#include <libsoup/soup-types.h>
+#include <gio/gio.h>
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 e0b2b1c8..37f6e41a 100644
--- a/libsoup/soup-ssl.h
+++ b/libsoup/soup-ssl.h
@@ -8,13 +8,6 @@
#include <glib.h>
-/**
- * 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 923e68e3..923b72ab 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 507d6905..983250b3 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 fa5a400f..951dd969 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 ecbf6a9d..b9569c7c 100644
--- a/libsoup/soup-uri.c
+++ b/libsoup/soup-uri.c
@@ -10,33 +10,106 @@
#include <stdlib.h>
#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 "<segment>/../" where <segment> != ".." */
- 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 "<segment>/.." at end where <segment> != ".." */
- 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 "<segment>/../" where <segment> != ".." */
+ 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 "<segment>/.." at end where <segment> != ".." */
+ 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
+ * <literal>http://example.com/foo/b%<!-- -->61r</literal> into
+ * <literal>http://example.com/foo/bar</literal>, which is guaranteed
+ * to mean the same thing. However, "/" is "reserved", so
+ * <literal>http://example.com/foo%<!-- -->2Fbar</literal> 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 <structfield>scheme</structfield>
+ **/
+
+/**
+ * SOUP_URI_SCHEME_HTTPS:
+ *
+ * "https" as an interned string. This can be compared directly
+ * against the value of a #SoupURI's <structfield>scheme</structfield>
+ **/
+
+/**
+ * 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 c9c9e787..e939b1bc 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 00000000..af214f33
--- /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 <string.h>
+
+/**
+ * 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 00000000..19f7ccec
--- /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 <libsoup/soup-types.h>
+#include <gobject/gvaluecollector.h>
+
+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 b2954b9a..00000000
--- 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 <mariano@gnome.org>
- * Copyright (C) 2004, Fernando Herrera <fherrera@onirica.com>
- * Copyright (C) 2005, Jeff Bailey <jbailey@ubuntu.com>
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <string.h>
-#include <time.h>
-
-#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 64cc1322..00000000
--- 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 <time.h>
-#include <libxml/tree.h>
-#include <libsoup/soup-message.h>
-#include <libsoup/soup-uri.h>
-
-#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 ab0f82c6..00000000
--- 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 <mariano@gnome.org>
- * Copyright (C) 2004, Fernando Herrera <fherrera@onirica.com>
- * Copyright (C) 2005, Jeff Bailey <jbailey@ubuntu.com>
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <ctype.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#include <glib.h>
-#include <libxml/tree.h>
-
-#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 f2207c79..00000000
--- 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 <glib-object.h>
-#include <libxml/tree.h>
-
-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 00000000..9ebfe83a
--- /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 <config.h>
+#endif
+
+#include <string.h>
+#include <time.h>
+
+#include <libxml/tree.h>
+
+#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, &params[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, &params))
+ 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 00000000..faf84f6a
--- /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 <libsoup/soup-types.h>
+
+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 da67d871..6ed7828d 100644
--- a/libsoup/soup.h
+++ b/libsoup/soup.h
@@ -10,12 +10,27 @@
extern "C" {
#endif
+#include <libsoup/soup-address.h>
+#include <libsoup/soup-auth.h>
+#include <libsoup/soup-auth-domain.h>
+#include <libsoup/soup-auth-domain-basic.h>
+#include <libsoup/soup-auth-domain-digest.h>
+#include <libsoup/soup-date.h>
+#include <libsoup/soup-enum-types.h>
+#include <libsoup/soup-form.h>
+#include <libsoup/soup-headers.h>
+#include <libsoup/soup-logger.h>
#include <libsoup/soup-message.h>
+#include <libsoup/soup-method.h>
#include <libsoup/soup-misc.h>
+#include <libsoup/soup-server.h>
#include <libsoup/soup-session-async.h>
#include <libsoup/soup-session-sync.h>
#include <libsoup/soup-socket.h>
+#include <libsoup/soup-status.h>
#include <libsoup/soup-uri.h>
+#include <libsoup/soup-value-utils.h>
+#include <libsoup/soup-xmlrpc.h>
#ifdef __cplusplus
}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 99295076..3f9ce551 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 bc859aee..00000000
--- a/tests/apache-wrapper.c
+++ /dev/null
@@ -1,69 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#ifdef HAVE_APACHE
-
-#include <signal.h>
-#include <stdlib.h>
-
-#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 58302a9b..00000000
--- a/tests/apache-wrapper.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#include <glib.h>
-
-gboolean apache_init (void);
-void apache_cleanup (void);
diff --git a/tests/auth-test.c b/tests/auth-test.c
index cf4473d4..4ef5e3cd 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 b44f0707..fe1b1ba0 100644
--- a/tests/context-test.c
+++ b/tests/context-test.c
@@ -18,138 +18,75 @@
#include <libsoup/soup-message.h>
#include <libsoup/soup-misc.h>
#include <libsoup/soup-server.h>
-#include <libsoup/soup-server-message.h>
#include <libsoup/soup-session-async.h>
#include <libsoup/soup-session-sync.h>
-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 00000000..cba7abbf
--- /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 <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <libsoup/soup.h>
+#include <libsoup/soup-auth-domain-basic.h>
+#include <libsoup/soup-server.h>
+
+#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 ab87a775..e47d3be6 100644
--- a/tests/date.c
+++ b/tests/date.c
@@ -9,55 +9,94 @@
#include <libsoup/soup-date.h>
#include <glib.h>
-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 afaed0e0..00000000
--- 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 <ctype.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <libsoup/soup.h>
-#include <libsoup/soup-soap-message.h>
-#include <libsoup/soup-soap-response.h>
-
-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 755ff694..10d6b98c 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 4c196df2..fdb3ca17 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 e50f6e0f..5cef3723 100644
--- a/tests/getbug.c
+++ b/tests/getbug.c
@@ -13,53 +13,69 @@
#include <unistd.h>
#include <libsoup/soup.h>
-#include <libsoup/soup-xmlrpc-message.h>
-#include <libsoup/soup-xmlrpc-response.h>
-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 c6b61cdc..63fcae36 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 ca9ebdff..c2e5881c 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
</Location>
+<Location /Digest/realm1/expire>
+ AuthType Digest
+ AuthName realm1
+ AuthUserFile @srcdir@/htdigest
+ AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1
+ AuthDigestNonceLifetime 2
+ Require valid-user
+</Location>
+
<Location /Digest/realm1/not>
AuthType Digest
AuthName realm1
diff --git a/tests/ntlm-test.c b/tests/ntlm-test.c
index 30e01dad..5a4515bf 100644
--- a/tests/ntlm-test.c
+++ b/tests/ntlm-test.c
@@ -21,25 +21,14 @@
#include <glib.h>
#include <libsoup/soup-address.h>
+#include <libsoup/soup-auth.h>
#include <libsoup/soup-message.h>
#include <libsoup/soup-server.h>
-#include <libsoup/soup-server-message.h>
#include <libsoup/soup-session-async.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);
-}
+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 2133db7b..5d238c8f 100644
--- a/tests/proxy-test.c
+++ b/tests/proxy-test.c
@@ -8,23 +8,7 @@
#include <unistd.h>
#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 723cc88c..e84eb08f 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 00000000..682e938c
--- /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 <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <libsoup/soup-form.h>
+#include <libsoup/soup-message.h>
+#include <libsoup/soup-server.h>
+#include <libsoup/soup-session-sync.h>
+
+#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, "<html><head><title>query-test</title></head><body>\r\n");
+ if (title && name) {
+ /* mumble mumble html-escape... */
+ g_string_append_printf (buf, "<p>Hello, <b><em>%s</em> %s</b></p>\r\n",
+ title, name);
+ }
+ g_string_append (buf, "<form action='/' method='get'>"
+ "<p>Title: <input name='title'></p>"
+ "<p>Name: <input name='name'></p>"
+ "<p><input type=hidden name='fmt' value='html'></p>"
+ "<p><input type=submit></p>"
+ "</form>\r\n");
+ g_string_append (buf, "</body></html>\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 a09b7bca..00000000
--- a/tests/revserver.c
+++ /dev/null
@@ -1,187 +0,0 @@
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <ctype.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-#include <libsoup/soup-address.h>
-#include <libsoup/soup-socket.h>
-
-#include <glib/gthread.h>
-
-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 00000000..0d188f1e
--- /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 <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <libsoup/soup-address.h>
+#include <libsoup/soup-auth-domain-basic.h>
+#include <libsoup/soup-auth-domain-digest.h>
+#include <libsoup/soup-message.h>
+#include <libsoup/soup-server.h>
+
+#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 ea9a54ab..f0c568b4 100644
--- a/tests/simple-httpd.c
+++ b/tests/simple-httpd.c
@@ -17,106 +17,140 @@
#include <libsoup/soup-address.h>
#include <libsoup/soup-message.h>
#include <libsoup/soup-server.h>
-#include <libsoup/soup-server-message.h>
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 bcfb9efc..cc3da2b6 100644
--- a/tests/simple-proxy.c
+++ b/tests/simple-proxy.c
@@ -17,7 +17,6 @@
#include <libsoup/soup-address.h>
#include <libsoup/soup-message.h>
#include <libsoup/soup-server.h>
-#include <libsoup/soup-server-message.h>
#include <libsoup/soup-session-async.h>
/* 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 cdd88bc9..13eed661 100644
--- a/tests/ssl-test.c
+++ b/tests/ssl-test.c
@@ -7,6 +7,7 @@
#include <netinet/in.h>
#include <sys/socket.h>
+#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 00000000..b7564a4a
--- /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 <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 00000000..fa0dc132
--- /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 fa732921..cbafc533 100644
--- a/tests/uri-parsing.c
+++ b/tests/uri-parsing.c
@@ -5,22 +5,10 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#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&param=value",
+ "http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fchildparam%3Dchildvalue&param=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 a77c5b37..8686b712 100644
--- a/tests/xmlrpc-test.c
+++ b/tests/xmlrpc-test.c
@@ -3,33 +3,17 @@
* Copyright (C) 2001-2003, Ximian, Inc.
*/
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libsoup/soup.h>
-#include <libsoup/soup-date.h>
-#include <libsoup/soup-md5-utils.h>
-#include <libsoup/soup-xmlrpc-message.h>
-#include <libsoup/soup-xmlrpc-response.h>
-#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;
}