diff options
Diffstat (limited to 'libsoup')
128 files changed, 11007 insertions, 6761 deletions
diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index fdba1efa..60920bdc 100644 --- a/libsoup/Makefile.am +++ b/libsoup/Makefile.am @@ -8,16 +8,15 @@ if OS_WIN32 LIBWS2_32 = -lws2_32 endif -INCLUDES = \ +AM_CPPFLAGS = \ -DG_LOG_DOMAIN=\"libsoup\" \ -DLOCALEDIR=\"$(localedir)\" \ -I$(top_srcdir) \ + -I$(top_builddir) \ $(SOUP_DEBUG_FLAGS) \ - $(SOUP_MAINTAINER_FLAGS) \ $(GLIB_CFLAGS) \ $(XML_CFLAGS) \ - $(SQLITE_CFLAGS) \ - $(GNOME_KEYRING_CFLAGS) + $(SQLITE_CFLAGS) libsoupincludedir = $(includedir)/libsoup-2.4/libsoup @@ -28,11 +27,13 @@ soup_headers = \ soup-auth-domain.h \ soup-auth-domain-basic.h \ soup-auth-domain-digest.h \ + soup-auth-manager.h \ soup-cache.h \ soup-content-decoder.h \ soup-content-sniffer.h \ soup-cookie.h \ soup-cookie-jar.h \ + soup-cookie-jar-db.h \ soup-cookie-jar-text.h \ soup-date.h \ soup-form.h \ @@ -44,6 +45,7 @@ soup_headers = \ soup-method.h \ soup-misc.h \ soup-multipart.h \ + soup-multipart-input-stream.h \ soup-password-manager.h \ soup-portability.h \ soup-proxy-resolver.h \ @@ -71,16 +73,23 @@ libsoupinclude_HEADERS = \ $(soup_headers) \ soup-enum-types.h +nodist_libsoupinclude_HEADERS = \ + soup-version.h + lib_LTLIBRARIES = libsoup-2.4.la libsoup_2_4_la_LDFLAGS = \ - -version-info $(SOUP_CURRENT):$(SOUP_REVISION):$(SOUP_AGE) -no-undefined + -version-info $(SOUP_CURRENT):$(SOUP_REVISION):$(SOUP_AGE) \ + -no-undefined \ + -export-symbols $(srcdir)/libsoup-2.4.sym + +EXTRA_DIST += libsoup-2.4.sym libsoup_2_4_la_LIBADD = \ $(GLIB_LIBS) \ + $(LIBWS2_32) \ $(XML_LIBS) \ - -lz \ - $(LIBWS2_32) + $(SQLITE_LIBS) libsoup_2_4_la_SOURCES = \ soup-address.c \ @@ -94,21 +103,24 @@ libsoup_2_4_la_SOURCES = \ soup-auth-domain.c \ soup-auth-domain-basic.c \ soup-auth-domain-digest.c \ - soup-auth-manager.h \ soup-auth-manager.c \ - soup-auth-manager-ntlm.h \ - soup-auth-manager-ntlm.c \ soup-body-input-stream.h \ soup-body-input-stream.c \ soup-body-output-stream.h \ soup-body-output-stream.c \ soup-cache.c \ + soup-cache-input-stream.h \ + soup-cache-input-stream.c \ soup-cache-private.h \ soup-client-input-stream.h \ soup-client-input-stream.c \ soup-connection.h \ soup-connection.c \ + soup-connection-auth.h \ + soup-connection-auth.c \ soup-content-decoder.c \ + soup-content-processor.h \ + soup-content-processor.c \ soup-content-sniffer.c \ soup-content-sniffer-stream.h \ soup-content-sniffer-stream.c \ @@ -116,6 +128,7 @@ libsoup_2_4_la_SOURCES = \ soup-converter-wrapper.c \ soup-cookie.c \ soup-cookie-jar.c \ + soup-cookie-jar-db.c \ soup-cookie-jar-text.c \ soup-date.c \ soup-directory-input-stream.h \ @@ -129,8 +142,6 @@ libsoup_2_4_la_SOURCES = \ soup-io-stream.h \ soup-io-stream.c \ soup-logger.c \ - soup-marshal.h \ - soup-marshal.c \ soup-message.c \ soup-message-body.c \ soup-message-client-io.c \ @@ -144,13 +155,14 @@ libsoup_2_4_la_SOURCES = \ soup-misc.c \ soup-misc-private.h \ soup-multipart.c \ + soup-multipart-input-stream.c \ soup-password-manager.c \ soup-path-map.h \ soup-path-map.c \ soup-proxy-resolver.c \ soup-proxy-resolver-default.c \ - soup-proxy-resolver-static.h \ - soup-proxy-resolver-static.c \ + soup-proxy-resolver-wrapper.h \ + soup-proxy-resolver-wrapper.c \ soup-proxy-uri-resolver.c \ soup-request.c \ soup-request-data.c \ @@ -169,6 +181,7 @@ libsoup_2_4_la_SOURCES = \ soup-tld-private.h \ soup-uri.c \ soup-value-utils.c \ + soup-version.c \ soup-xmlrpc.c # TLD rules @@ -181,14 +194,6 @@ tld_data.inc: tld-parser.py $(TLD_DATA_FILE) if BUILD_LIBSOUP_GNOME -if OS_WIN32 -soup_password_manager_gnome_files = -else -soup_password_manager_gnome_files = \ - soup-password-manager-gnome.h \ - soup-password-manager-gnome.c -endif - libsoupgnomeincludedir = $(includedir)/libsoup-gnome-2.4/libsoup libsoupgnomeinclude_HEADERS = \ @@ -198,30 +203,32 @@ libsoupgnomeinclude_HEADERS = \ lib_LTLIBRARIES += libsoup-gnome-2.4.la -libsoup_gnome_2_4_la_LDFLAGS = $(libsoup_2_4_la_LDFLAGS) +libsoup_gnome_2_4_la_LDFLAGS = \ + -version-info $(SOUP_CURRENT):$(SOUP_REVISION):$(SOUP_AGE) \ + -no-undefined \ + -export-symbols $(srcdir)/libsoup-gnome-2.4.sym + +EXTRA_DIST += libsoup-gnome-2.4.sym libsoup_gnome_2_4_la_LIBADD = \ libsoup-2.4.la \ - $(GLIB_LIBS) \ - $(SQLITE_LIBS) \ - $(GNOME_KEYRING_LIBS) + $(GLIB_LIBS) libsoup_gnome_2_4_la_SOURCES = \ soup-cookie-jar-sqlite.c \ soup-gnome-features.c \ soup-proxy-resolver-gnome.h \ soup-proxy-resolver-gnome.c \ - $(soup_password_manager_gnome_files) + soup-password-manager-gnome.h \ + soup-password-manager-gnome.c endif -GLIB_GENERATED = soup-marshal.c soup-marshal.h -GLIB_GENERATED += soup-enum-types.c soup-enum-types.h +GLIB_GENERATED = soup-enum-types.c soup-enum-types.h BUILT_SOURCES = \ $(GLIB_GENERATED) \ tld_data.inc -soup_marshal_sources = $(libsoup_2_4_la_SOURCES) $(libsoup_gnome_2_4_la_SOURCES) soup_enum_types_sources = $(libsoupinclude_HEADERS) $(libsoupgnomeinclude_HEADERS) soup_enum_types_MKENUMS_C_FLAGS = --fhead "\#define LIBSOUP_USE_UNSTABLE_REQUEST_API" @@ -237,13 +244,13 @@ if HAVE_INTROSPECTION # Core library gi_soup_files = \ - $(filter-out soup.h soup-enum-types.% soup-marshal.% soup-proxy-resolver.h,\ + $(filter-out soup.h soup-enum-types.% soup-proxy-resolver.h,\ $(soup_headers) $(filter-out %.h, $(libsoup_2_4_la_SOURCES))) gi_built_soup_files = soup-enum-types.h Soup-2.4.gir: libsoup-2.4.la Soup_2_4_gir_INCLUDES = Gio-2.0 -Soup_2_4_gir_CFLAGS = $(INCLUDES) -DLIBSOUP_USE_UNSTABLE_REQUEST_API +Soup_2_4_gir_CFLAGS = $(AM_CPPFLAGS) -DLIBSOUP_USE_UNSTABLE_REQUEST_API Soup_2_4_gir_LIBS = libsoup-2.4.la Soup_2_4_gir_EXPORT_PACKAGES = libsoup-2.4 Soup_2_4_gir_SCANNERFLAGS = --c-include "libsoup/soup.h" @@ -268,7 +275,7 @@ SoupGNOME_2_4_gir_SCANNERFLAGS = \ --symbol-prefix=soup \ --c-include "libsoup/soup-gnome.h" \ --include-uninstalled=$(builddir)/Soup-2.4.gir -SoupGNOME_2_4_gir_CFLAGS = $(INCLUDES) +SoupGNOME_2_4_gir_CFLAGS = $(AM_CPPFLAGS) SoupGNOME_2_4_gir_LIBS = libsoup-gnome-2.4.la libsoup-2.4.la SoupGNOME_2_4_gir_FILES = $(addprefix $(srcdir)/,$(gi_soup_gnome_files)) SoupGNOME_2_4_gir_EXPORT_PACKAGES = libsoup-gnome-2.4 diff --git a/libsoup/libsoup-2.4.sym b/libsoup/libsoup-2.4.sym new file mode 100644 index 00000000..be4cd7f4 --- /dev/null +++ b/libsoup/libsoup-2.4.sym @@ -0,0 +1,508 @@ +soup_add_completion +soup_add_idle +soup_add_io_watch +soup_address_equal_by_ip +soup_address_equal_by_name +soup_address_family_get_type +soup_address_get_gsockaddr +soup_address_get_name +soup_address_get_physical +soup_address_get_port +soup_address_get_sockaddr +soup_address_get_type +soup_address_hash_by_ip +soup_address_hash_by_name +soup_address_is_resolved +soup_address_new +soup_address_new_any +soup_address_new_from_sockaddr +soup_address_resolve_async +soup_address_resolve_sync +soup_add_timeout +soup_auth_authenticate +soup_auth_basic_get_type +soup_auth_digest_get_type +soup_auth_domain_accepts +soup_auth_domain_add_path +soup_auth_domain_basic_get_type +soup_auth_domain_basic_new +soup_auth_domain_basic_set_auth_callback +soup_auth_domain_challenge +soup_auth_domain_check_password +soup_auth_domain_covers +soup_auth_domain_digest_encode_password +soup_auth_domain_digest_get_type +soup_auth_domain_digest_new +soup_auth_domain_digest_set_auth_callback +soup_auth_domain_get_realm +soup_auth_domain_get_type +soup_auth_domain_remove_path +soup_auth_domain_set_filter +soup_auth_domain_set_generic_auth_callback +soup_auth_domain_try_generic_auth_callback +soup_auth_free_protection_space +soup_auth_get_authorization +soup_auth_get_host +soup_auth_get_info +soup_auth_get_protection_space +soup_auth_get_realm +soup_auth_get_saved_password +soup_auth_get_saved_users +soup_auth_get_scheme_name +soup_auth_get_type +soup_auth_has_saved_password +soup_auth_is_authenticated +soup_auth_is_for_proxy +soup_auth_is_ready +soup_auth_manager_get_type +soup_auth_manager_use_auth +soup_auth_new +soup_auth_ntlm_get_type +soup_auth_save_password +soup_auth_update +soup_buffer_copy +soup_buffer_free +soup_buffer_get_as_bytes +soup_buffer_get_data +soup_buffer_get_owner +soup_buffer_get_type +soup_buffer_new +soup_buffer_new_subbuffer +soup_buffer_new_take +soup_buffer_new_with_owner +soup_byte_array_get_type +soup_cacheability_get_type +soup_cache_clear +soup_cache_dump +soup_cache_flush +soup_cache_get_max_size +soup_cache_get_type +soup_cache_load +soup_cache_new +soup_cache_response_get_type +soup_cache_set_max_size +soup_cache_type_get_type +soup_char_attributes +soup_check_version +soup_client_context_get_address +soup_client_context_get_auth_domain +soup_client_context_get_auth_user +soup_client_context_get_host +soup_client_context_get_socket +soup_client_context_get_type +soup_connection_state_get_type +soup_content_decoder_get_type +soup_content_sniffer_get_buffer_size +soup_content_sniffer_get_type +soup_content_sniffer_new +soup_content_sniffer_sniff +soup_cookie_applies_to_uri +soup_cookie_copy +soup_cookie_domain_matches +soup_cookie_equal +soup_cookie_free +soup_cookie_get_domain +soup_cookie_get_expires +soup_cookie_get_http_only +soup_cookie_get_name +soup_cookie_get_path +soup_cookie_get_secure +soup_cookie_get_type +soup_cookie_get_value +soup_cookie_jar_accept_policy_get_type +soup_cookie_jar_add_cookie +soup_cookie_jar_add_cookie_with_first_party +soup_cookie_jar_all_cookies +soup_cookie_jar_db_get_type +soup_cookie_jar_db_new +soup_cookie_jar_delete_cookie +soup_cookie_jar_get_accept_policy +soup_cookie_jar_get_cookie_list +soup_cookie_jar_get_cookies +soup_cookie_jar_get_type +soup_cookie_jar_is_persistent +soup_cookie_jar_new +soup_cookie_jar_save +soup_cookie_jar_set_accept_policy +soup_cookie_jar_set_cookie +soup_cookie_jar_set_cookie_with_first_party +soup_cookie_jar_text_get_type +soup_cookie_jar_text_new +soup_cookie_new +soup_cookie_parse +soup_cookie_set_domain +soup_cookie_set_expires +soup_cookie_set_http_only +soup_cookie_set_max_age +soup_cookie_set_name +soup_cookie_set_path +soup_cookie_set_secure +soup_cookie_set_value +soup_cookies_free +soup_cookies_from_request +soup_cookies_from_response +soup_cookies_to_cookie_header +soup_cookies_to_request +soup_cookies_to_response +soup_cookie_to_cookie_header +soup_cookie_to_set_cookie_header +soup_date_copy +soup_date_format_get_type +soup_date_free +soup_date_get_day +soup_date_get_hour +soup_date_get_minute +soup_date_get_month +soup_date_get_offset +soup_date_get_second +soup_date_get_type +soup_date_get_utc +soup_date_get_year +soup_date_is_past +soup_date_new +soup_date_new_from_now +soup_date_new_from_string +soup_date_new_from_time_t +soup_date_to_string +soup_date_to_time_t +soup_date_to_timeval +soup_encoding_get_type +soup_expectation_get_type +soup_form_decode +soup_form_decode_multipart +soup_form_encode +soup_form_encode_datalist +soup_form_encode_hash +soup_form_encode_valist +soup_form_request_new +soup_form_request_new_from_datalist +soup_form_request_new_from_hash +soup_form_request_new_from_multipart +soup_get_major_version +soup_get_micro_version +soup_get_minor_version +soup_header_contains +soup_header_free_list +soup_header_free_param_list +soup_header_g_string_append_param +soup_header_g_string_append_param_quoted +soup_header_parse_list +soup_header_parse_param_list +soup_header_parse_quality_list +soup_header_parse_semi_param_list +soup_headers_parse +soup_headers_parse_request +soup_headers_parse_response +soup_headers_parse_status_line +soup_http_error_quark +soup_http_version_get_type +soup_known_status_code_get_type +soup_logger_attach +soup_logger_detach +soup_logger_get_type +soup_logger_log_level_get_type +soup_logger_new +soup_logger_set_printer +soup_logger_set_request_filter +soup_logger_set_response_filter +soup_memory_use_get_type +soup_message_add_header_handler +soup_message_add_status_code_handler +soup_message_body_append +soup_message_body_append_buffer +soup_message_body_append_take +soup_message_body_complete +soup_message_body_flatten +soup_message_body_free +soup_message_body_get_accumulate +soup_message_body_get_chunk +soup_message_body_get_type +soup_message_body_got_chunk +soup_message_body_new +soup_message_body_set_accumulate +soup_message_body_truncate +soup_message_body_wrote_chunk +soup_message_content_sniffed +soup_message_disable_feature +soup_message_finished +soup_message_flags_get_type +soup_message_get_address +soup_message_get_first_party +soup_message_get_flags +soup_message_get_https_status +soup_message_get_http_version +soup_message_get_priority +soup_message_get_soup_request +soup_message_get_type +soup_message_get_uri +soup_message_got_body +soup_message_got_chunk +soup_message_got_headers +soup_message_got_informational +soup_message_headers_append +soup_message_headers_clean_connection_headers +soup_message_headers_clear +soup_message_headers_foreach +soup_message_headers_free +soup_message_headers_free_ranges +soup_message_headers_get +soup_message_headers_get_content_disposition +soup_message_headers_get_content_length +soup_message_headers_get_content_range +soup_message_headers_get_content_type +soup_message_headers_get_encoding +soup_message_headers_get_expectations +soup_message_headers_get_list +soup_message_headers_get_one +soup_message_headers_get_ranges +soup_message_headers_get_type +soup_message_headers_iter_init +soup_message_headers_iter_next +soup_message_headers_new +soup_message_headers_remove +soup_message_headers_replace +soup_message_headers_set_content_disposition +soup_message_headers_set_content_length +soup_message_headers_set_content_range +soup_message_headers_set_content_type +soup_message_headers_set_encoding +soup_message_headers_set_expectations +soup_message_headers_set_range +soup_message_headers_set_ranges +soup_message_headers_type_get_type +soup_message_io_cleanup +soup_message_is_keepalive +soup_message_new +soup_message_new_from_uri +soup_message_priority_get_type +soup_message_restarted +soup_message_set_chunk_allocator +soup_message_set_first_party +soup_message_set_flags +soup_message_set_http_version +soup_message_set_priority +soup_message_set_redirect +soup_message_set_request +soup_message_set_response +soup_message_set_status +soup_message_set_status_full +soup_message_set_uri +soup_message_wrote_body +soup_message_wrote_body_data +soup_message_wrote_chunk +soup_message_wrote_headers +soup_message_wrote_informational +soup_multipart_append_form_file +soup_multipart_append_form_string +soup_multipart_append_part +soup_multipart_free +soup_multipart_get_length +soup_multipart_get_part +soup_multipart_get_type +soup_multipart_input_stream_get_headers +soup_multipart_input_stream_get_type +soup_multipart_input_stream_new +soup_multipart_input_stream_next_part +soup_multipart_input_stream_next_part_async +soup_multipart_input_stream_next_part_finish +soup_multipart_new +soup_multipart_new_from_message +soup_multipart_to_message +soup_password_manager_get_passwords_async +soup_password_manager_get_passwords_sync +soup_password_manager_get_type +soup_proxy_resolver_default_get_type +soup_proxy_resolver_get_proxy_async +soup_proxy_resolver_get_proxy_sync +soup_proxy_resolver_get_type +soup_proxy_uri_resolver_get_proxy_uri_async +soup_proxy_uri_resolver_get_proxy_uri_sync +soup_proxy_uri_resolver_get_type +soup_request_data_get_type +soup_requester_error_get_type +soup_requester_error_quark +soup_requester_get_type +soup_requester_new +soup_requester_request +soup_requester_request_uri +soup_request_error_get_type +soup_request_error_quark +soup_request_file_get_file +soup_request_file_get_type +soup_request_get_content_length +soup_request_get_content_type +soup_request_get_session +soup_request_get_type +soup_request_get_uri +soup_request_http_get_message +soup_request_http_get_type +soup_request_send +soup_request_send_async +soup_request_send_finish +soup_server_add_auth_domain +soup_server_add_handler +soup_server_disconnect +soup_server_get_async_context +soup_server_get_listener +soup_server_get_port +soup_server_get_type +soup_server_is_https +soup_server_new +soup_server_pause_message +soup_server_quit +soup_server_remove_auth_domain +soup_server_remove_handler +soup_server_run +soup_server_run_async +soup_server_unpause_message +soup_session_abort +soup_session_add_feature +soup_session_add_feature_by_type +soup_session_async_get_type +soup_session_async_new +soup_session_async_new_with_options +soup_session_cancel_message +soup_session_feature_add_feature +soup_session_feature_attach +soup_session_feature_detach +soup_session_feature_get_type +soup_session_feature_has_feature +soup_session_feature_remove_feature +soup_session_get_async_context +soup_session_get_feature +soup_session_get_feature_for_message +soup_session_get_features +soup_session_get_type +soup_session_has_feature +soup_session_new +soup_session_new_with_options +soup_session_pause_message +soup_session_prefetch_dns +soup_session_prepare_for_uri +soup_session_queue_message +soup_session_redirect_message +soup_session_remove_feature +soup_session_remove_feature_by_type +soup_session_request +soup_session_request_http +soup_session_request_http_uri +soup_session_request_uri +soup_session_requeue_message +soup_session_send +soup_session_send_async +soup_session_send_finish +soup_session_send_message +soup_session_sync_get_type +soup_session_sync_new +soup_session_sync_new_with_options +soup_session_unpause_message +soup_session_would_redirect +soup_socket_connect_async +soup_socket_connect_sync +soup_socket_disconnect +soup_socket_get_fd +soup_socket_get_local_address +soup_socket_get_remote_address +soup_socket_get_type +soup_socket_io_status_get_type +soup_socket_is_connected +soup_socket_is_ssl +soup_socket_listen +soup_socket_new +soup_socket_read +soup_socket_read_until +soup_socket_start_proxy_ssl +soup_socket_start_ssl +soup_socket_write +soup_ssl_supported +soup_status_get_phrase +soup_status_get_type +soup_status_proxify +soup_str_case_equal +soup_str_case_hash +soup_tld_domain_is_public_suffix +soup_tld_error_get_type +soup_tld_error_quark +soup_tld_get_base_domain +soup_uri_copy +soup_uri_copy_host +soup_uri_decode +soup_uri_encode +soup_uri_equal +soup_uri_free +soup_uri_get_fragment +soup_uri_get_host +soup_uri_get_password +soup_uri_get_path +soup_uri_get_port +soup_uri_get_query +soup_uri_get_scheme +soup_uri_get_type +soup_uri_get_user +soup_uri_host_equal +soup_uri_host_hash +soup_uri_new +soup_uri_new_with_base +soup_uri_normalize +soup_uri_set_fragment +soup_uri_set_host +soup_uri_set_password +soup_uri_set_path +soup_uri_set_port +soup_uri_set_query +soup_uri_set_query_from_fields +soup_uri_set_query_from_form +soup_uri_set_scheme +soup_uri_set_user +soup_uri_to_string +soup_uri_uses_default_port +soup_value_array_append +soup_value_array_append_vals +soup_value_array_from_args +soup_value_array_get_nth +soup_value_array_insert +soup_value_array_new +soup_value_array_new_with_vals +soup_value_array_to_args +soup_value_hash_insert +soup_value_hash_insert_vals +soup_value_hash_insert_value +soup_value_hash_lookup +soup_value_hash_lookup_vals +soup_value_hash_new +soup_value_hash_new_with_vals +soup_xmlrpc_build_fault +soup_xmlrpc_build_method_call +soup_xmlrpc_build_method_response +soup_xmlrpc_error_get_type +soup_xmlrpc_error_quark +soup_xmlrpc_extract_method_call +soup_xmlrpc_extract_method_response +soup_xmlrpc_fault_get_type +soup_xmlrpc_fault_quark +soup_xmlrpc_parse_method_call +soup_xmlrpc_parse_method_response +soup_xmlrpc_request_new +soup_xmlrpc_set_fault +soup_xmlrpc_set_response +_SOUP_METHOD_CONNECT +_SOUP_METHOD_COPY +_SOUP_METHOD_DELETE +_SOUP_METHOD_GET +_SOUP_METHOD_HEAD +_SOUP_METHOD_LOCK +_SOUP_METHOD_MKCOL +_SOUP_METHOD_MOVE +_SOUP_METHOD_OPTIONS +_SOUP_METHOD_POST +_SOUP_METHOD_PROPFIND +_SOUP_METHOD_PROPPATCH +_SOUP_METHOD_PUT +_SOUP_METHOD_TRACE +_SOUP_METHOD_UNLOCK +_SOUP_URI_SCHEME_DATA +_SOUP_URI_SCHEME_FILE +_SOUP_URI_SCHEME_FTP +_SOUP_URI_SCHEME_HTTP +_SOUP_URI_SCHEME_HTTPS +_SOUP_URI_SCHEME_RESOURCE diff --git a/libsoup/libsoup-gnome-2.4.sym b/libsoup/libsoup-gnome-2.4.sym new file mode 100644 index 00000000..67f62dec --- /dev/null +++ b/libsoup/libsoup-gnome-2.4.sym @@ -0,0 +1,5 @@ +soup_cookie_jar_sqlite_get_type +soup_cookie_jar_sqlite_new +soup_gnome_features_2_26_get_type +soup_password_manager_gnome_get_type +soup_proxy_resolver_gnome_get_type diff --git a/libsoup/soup-address.c b/libsoup/soup-address.c index f2e698bc..b2d1647a 100644 --- a/libsoup/soup-address.c +++ b/libsoup/soup-address.c @@ -11,9 +11,11 @@ #include <string.h> +#include <gio/gnetworking.h> + #include "soup-address.h" #include "soup.h" -#include "soup-marshal.h" +#include "soup-misc-private.h" /** * SECTION:soup-address @@ -22,6 +24,10 @@ * #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.) + * + * Although #SoupAddress is still used in some libsoup API's, it + * should not be used in new code; use GLib's #GNetworkAddress or + * #GSocketAddress instead. **/ enum { @@ -46,7 +52,6 @@ typedef struct { const char *protocol; GMutex lock; - GSList *async_lookups; } SoupAddressPrivate; #define SOUP_ADDRESS_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_ADDRESS, SoupAddressPrivate)) @@ -555,6 +560,59 @@ soup_address_get_port (SoupAddress *addr) } +/* Tries to resolve priv->name as an IP address, possibly including an + * IPv6 scope id. + */ +static void +maybe_resolve_ip (SoupAddress *addr) +{ + SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr); + const char *pct, *ip; + char *tmp = NULL; + GSocketConnectable *gaddr; + GSocketAddressEnumerator *sa_enum; + GSocketAddress *saddr; + + if (priv->sockaddr || !priv->name) + return; + + pct = strchr (priv->name, '%'); + if (pct) + ip = tmp = g_strndup (priv->name, pct - priv->name); + else + ip = priv->name; + + if (!g_hostname_is_ip_address (ip)) { + g_free (tmp); + return; + } + g_free (tmp); + + gaddr = g_network_address_new (priv->name, priv->port); + if (!gaddr) + return; + + sa_enum = g_socket_connectable_enumerate (gaddr); + saddr = g_socket_address_enumerator_next (sa_enum, NULL, NULL); + if (saddr) { + priv->n_addrs = 1; + priv->sockaddr = g_new (struct sockaddr_storage, 1); + if (!g_socket_address_to_native (saddr, priv->sockaddr, + sizeof (struct sockaddr_storage), + NULL)) { + /* can't happen: We know the address format is supported + * and the buffer is large enough + */ + g_warn_if_reached (); + } + g_object_unref (saddr); + } + + g_object_unref (sa_enum); + g_object_unref (gaddr); +} + + static guint update_addrs (SoupAddress *addr, GList *addrs, GError *error) { @@ -615,55 +673,47 @@ update_name (SoupAddress *addr, const char *name, GError *error) } typedef struct { + SoupAddress *addr; SoupAddressCallback callback; gpointer callback_data; } SoupAddressResolveAsyncData; static void -complete_resolve_async (SoupAddress *addr, guint status) +complete_resolve_async (SoupAddressResolveAsyncData *res_data, guint status) { - SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr); - SoupAddressResolveAsyncData *res_data; - GSList *lookups, *l; GSource *current_source; GMainContext *current_context; - lookups = priv->async_lookups; - priv->async_lookups = NULL; - - /* Awful hack; to make soup_socket_connect_async() with an - * non-default async_context work correctly, we need to ensure - * that the non-default context (which we're now running in) - * is the thread-default when the callbacks are run... - */ - current_source = g_main_current_source (); - if (current_source && !g_source_is_destroyed (current_source)) - current_context = g_source_get_context (current_source); - else - current_context = NULL; - g_main_context_push_thread_default (current_context); + if (res_data->callback) { + /* Awful hack; to make soup_socket_connect_async() + * with an non-default async_context work correctly, + * we need to ensure that the non-default context + * (which we're now running in) is the thread-default + * when the callbacks are run... + */ + current_source = g_main_current_source (); + if (current_source && !g_source_is_destroyed (current_source)) + current_context = g_source_get_context (current_source); + else + current_context = NULL; + g_main_context_push_thread_default (current_context); - for (l = lookups; l; l = l->next) { - res_data = l->data; + res_data->callback (res_data->addr, status, + res_data->callback_data); - if (res_data->callback) { - res_data->callback (addr, status, - res_data->callback_data); - } - g_slice_free (SoupAddressResolveAsyncData, res_data); + g_main_context_pop_thread_default (current_context); } - g_slist_free (lookups); - g_main_context_pop_thread_default (current_context); - - g_object_unref (addr); + g_object_unref (res_data->addr); + g_slice_free (SoupAddressResolveAsyncData, res_data); } static void lookup_resolved (GObject *source, GAsyncResult *result, gpointer user_data) { GResolver *resolver = G_RESOLVER (source); - SoupAddress *addr = user_data; + SoupAddressResolveAsyncData *res_data = user_data; + SoupAddress *addr = res_data->addr; SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr); GError *error = NULL; guint status; @@ -689,7 +739,7 @@ lookup_resolved (GObject *source, GAsyncResult *result, gpointer user_data) g_object_ref (addr); g_object_set_data (G_OBJECT (addr), "async-resolved-error", error); - complete_resolve_async (addr, status); + complete_resolve_async (res_data, status); g_object_set_data (G_OBJECT (addr), "async-resolved-error", NULL); g_object_unref (addr); @@ -698,9 +748,9 @@ lookup_resolved (GObject *source, GAsyncResult *result, gpointer user_data) } static gboolean -idle_complete_resolve (gpointer addr) +idle_complete_resolve (gpointer res_data) { - complete_resolve_async (addr, SOUP_STATUS_OK); + complete_resolve_async (res_data, SOUP_STATUS_OK); return FALSE; } @@ -746,7 +796,6 @@ soup_address_resolve_async (SoupAddress *addr, GMainContext *async_context, SoupAddressPrivate *priv; SoupAddressResolveAsyncData *res_data; GResolver *resolver; - gboolean already_started; g_return_if_fail (SOUP_IS_ADDRESS (addr)); priv = SOUP_ADDRESS_GET_PRIVATE (addr); @@ -756,47 +805,43 @@ soup_address_resolve_async (SoupAddress *addr, GMainContext *async_context, * not intended to be thread-safe. */ + if (priv->name && !priv->sockaddr) + maybe_resolve_ip (addr); if (priv->name && priv->sockaddr && !callback) return; res_data = g_slice_new0 (SoupAddressResolveAsyncData); + res_data->addr = g_object_ref (addr); res_data->callback = callback; res_data->callback_data = user_data; - already_started = priv->async_lookups != NULL; - priv->async_lookups = g_slist_prepend (priv->async_lookups, res_data); - - if (already_started) - return; - - g_object_ref (addr); - - if (priv->name && priv->sockaddr) { - soup_add_completion (async_context, idle_complete_resolve, addr); - return; - } - - resolver = g_resolver_get_default (); if (async_context) g_main_context_push_thread_default (async_context); - if (priv->name) { - g_resolver_lookup_by_name_async (resolver, priv->name, - cancellable, - lookup_resolved, addr); - } else { - GInetAddress *gia; + if (priv->name && priv->sockaddr) + soup_add_completion (async_context, idle_complete_resolve, res_data); + else { + resolver = g_resolver_get_default (); + + if (priv->name) { + g_resolver_lookup_by_name_async (resolver, priv->name, + cancellable, + lookup_resolved, res_data); + } else { + GInetAddress *gia; + + gia = soup_address_make_inet_address (addr); + g_resolver_lookup_by_address_async (resolver, gia, + cancellable, + lookup_resolved, res_data); + g_object_unref (gia); + } - gia = soup_address_make_inet_address (addr); - g_resolver_lookup_by_address_async (resolver, gia, - cancellable, - lookup_resolved, addr); - g_object_unref (gia); + g_object_unref (resolver); } if (async_context) g_main_context_pop_thread_default (async_context); - g_object_unref (resolver); } static guint @@ -816,6 +861,10 @@ resolve_sync_internal (SoupAddress *addr, GCancellable *cancellable, GError **er * blocking op, and then re-lock it to modify @addr. */ g_mutex_lock (&priv->lock); + + if (priv->name && !priv->sockaddr) + maybe_resolve_ip (addr); + if (!priv->sockaddr) { GList *addrs; @@ -841,6 +890,7 @@ resolve_sync_internal (SoupAddress *addr, GCancellable *cancellable, GError **er g_free (name); } else status = SOUP_STATUS_OK; + g_mutex_unlock (&priv->lock); if (my_err) @@ -1111,15 +1161,19 @@ soup_address_address_enumerator_next (GSocketAddressEnumerator *enumerator, static void got_addresses (SoupAddress *addr, guint status, gpointer user_data) { - GSimpleAsyncResult *simple = user_data; + GTask *task = user_data; GError *error; error = g_object_get_data (G_OBJECT (addr), "async-resolved-error"); if (error) - g_simple_async_result_set_from_error (simple, error); + g_task_return_error (task, g_error_copy (error)); + else { + GSocketAddress *addr; - g_simple_async_result_complete (simple); - g_object_unref (simple); + addr = next_address (g_task_get_source_object (task)); + g_task_return_pointer (task, addr, g_object_unref); + } + g_object_unref (task); } static void @@ -1131,18 +1185,16 @@ soup_address_address_enumerator_next_async (GSocketAddressEnumerator *enumerato SoupAddressAddressEnumerator *addr_enum = SOUP_ADDRESS_ADDRESS_ENUMERATOR (enumerator); SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr_enum->addr); - GSimpleAsyncResult *simple; - - simple = g_simple_async_result_new (G_OBJECT (enumerator), - callback, user_data, - soup_address_address_enumerator_next_async); + GTask *task; + task = g_task_new (enumerator, cancellable, callback, user_data); if (!priv->sockaddr) { - soup_address_resolve_async (addr_enum->addr, NULL, cancellable, - got_addresses, simple); + soup_address_resolve_async (addr_enum->addr, + g_main_context_get_thread_default (), + cancellable, got_addresses, task); } else { - g_simple_async_result_complete_in_idle (simple); - g_object_unref (simple); + g_task_return_pointer (task, next_address (addr_enum), g_object_unref); + g_object_unref (task); } } @@ -1151,14 +1203,7 @@ soup_address_address_enumerator_next_finish (GSocketAddressEnumerator *enumerat GAsyncResult *result, GError **error) { - SoupAddressAddressEnumerator *addr_enum = - SOUP_ADDRESS_ADDRESS_ENUMERATOR (enumerator); - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); - - if (g_simple_async_result_propagate_error (simple, error)) - return NULL; - else - return next_address (addr_enum); + return g_task_propagate_pointer (G_TASK (result), error); } static void @@ -1200,20 +1245,25 @@ soup_address_connectable_proxy_enumerate (GSocketConnectable *connectable) SoupAddress *addr = SOUP_ADDRESS (connectable); SoupAddressPrivate *priv = SOUP_ADDRESS_GET_PRIVATE (addr); GSocketAddressEnumerator *proxy_enum; - char *uri; + SoupURI *uri; + char *uri_string; /* We cheerily assume "http" here because you shouldn't be * using SoupAddress any more if you're not doing HTTP anyway. */ - uri = g_strdup_printf ("%s://%s:%u", - priv->protocol ? priv->protocol : "http", - priv->name ? priv->name : soup_address_get_physical (addr), - priv->port); + uri = soup_uri_new (NULL); + soup_uri_set_scheme (uri, priv->protocol ? priv->protocol : "http"); + soup_uri_set_host (uri, priv->name ? priv->name : soup_address_get_physical (addr)); + soup_uri_set_port (uri, priv->port); + soup_uri_set_path (uri, ""); + uri_string = soup_uri_to_string_internal (uri, FALSE, TRUE); + proxy_enum = g_object_new (G_TYPE_PROXY_ADDRESS_ENUMERATOR, "connectable", connectable, - "uri", uri, + "uri", uri_string, NULL); - g_free (uri); + g_free (uri_string); + soup_uri_free (uri); return proxy_enum; } diff --git a/libsoup/soup-address.h b/libsoup/soup-address.h index 579d1d3c..797a5506 100644 --- a/libsoup/soup-address.h +++ b/libsoup/soup-address.h @@ -8,7 +8,6 @@ #include <sys/types.h> -#include <libsoup/soup-portability.h> #include <libsoup/soup-types.h> G_BEGIN_DECLS @@ -51,6 +50,8 @@ typedef enum { #define SOUP_ADDRESS_ANY_PORT 0 +struct sockaddr; + typedef void (*SoupAddressCallback) (SoupAddress *addr, guint status, gpointer user_data); @@ -77,13 +78,18 @@ const char *soup_address_get_physical (SoupAddress *addr); guint soup_address_get_port (SoupAddress *addr); struct sockaddr *soup_address_get_sockaddr (SoupAddress *addr, int *len); +SOUP_AVAILABLE_IN_2_32 GSocketAddress *soup_address_get_gsockaddr (SoupAddress *addr); gboolean soup_address_is_resolved (SoupAddress *addr); +SOUP_AVAILABLE_IN_2_26 guint soup_address_hash_by_name (gconstpointer addr); +SOUP_AVAILABLE_IN_2_26 gboolean soup_address_equal_by_name (gconstpointer addr1, gconstpointer addr2); +SOUP_AVAILABLE_IN_2_26 guint soup_address_hash_by_ip (gconstpointer addr); +SOUP_AVAILABLE_IN_2_26 gboolean soup_address_equal_by_ip (gconstpointer addr1, gconstpointer addr2); diff --git a/libsoup/soup-auth-basic.c b/libsoup/soup-auth-basic.c index fb491c71..f298e36c 100644 --- a/libsoup/soup-auth-basic.c +++ b/libsoup/soup-auth-basic.c @@ -19,6 +19,17 @@ typedef struct { } SoupAuthBasicPrivate; #define SOUP_AUTH_BASIC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_BASIC, SoupAuthBasicPrivate)) +/** + * SOUP_TYPE_AUTH_BASIC: + * + * A #GType corresponding to HTTP "Basic" authentication. + * #SoupSessions support this by default; if you want to disable + * support for it, call soup_session_remove_feature_by_type(), + * passing %SOUP_TYPE_AUTH_BASIC. + * + * Since: 2.34 + */ + G_DEFINE_TYPE (SoupAuthBasic, soup_auth_basic, SOUP_TYPE_AUTH) static void @@ -62,7 +73,7 @@ soup_auth_basic_get_protection_space (SoupAuth *auth, SoupURI *source_uri) space = g_strdup (source_uri->path); - /* Strip query and filename component */ + /* Strip filename component */ p = strrchr (space, '/'); if (p && p != space && p[1]) *p = '\0'; diff --git a/libsoup/soup-auth-digest.c b/libsoup/soup-auth-digest.c index 5fe75610..1fbb639d 100644 --- a/libsoup/soup-auth-digest.c +++ b/libsoup/soup-auth-digest.c @@ -15,6 +15,10 @@ #include "soup.h" #include "soup-message-private.h" +#ifdef G_OS_WIN32 +#include <process.h> +#endif + typedef struct { char *user; char hex_urp[33]; @@ -36,6 +40,17 @@ typedef struct { static void recompute_hex_a1 (SoupAuthDigestPrivate *priv); +/** + * SOUP_TYPE_AUTH_DIGEST: + * + * A #GType corresponding to HTTP "Digest" authentication. + * #SoupSessions support this by default; if you want to disable + * support for it, call soup_session_remove_feature_by_type(), + * passing %SOUP_TYPE_AUTH_DIGEST. + * + * Since: 2.34 + */ + G_DEFINE_TYPE (SoupAuthDigest, soup_auth_digest, SOUP_TYPE_AUTH) static void @@ -278,6 +293,9 @@ soup_auth_digest_authenticate (SoupAuth *auth, const char *username, SoupAuthDigestPrivate *priv = SOUP_AUTH_DIGEST_GET_PRIVATE (auth); char *bgen; + g_clear_pointer (&priv->cnonce, g_free); + g_clear_pointer (&priv->user, g_free); + /* Create client nonce */ bgen = g_strdup_printf ("%p:%lu:%lu", auth, diff --git a/libsoup/soup-auth-domain-basic.c b/libsoup/soup-auth-domain-basic.c index 9e8afe8f..b843ad72 100644 --- a/libsoup/soup-auth-domain-basic.c +++ b/libsoup/soup-auth-domain-basic.c @@ -13,7 +13,6 @@ #include "soup-auth-domain-basic.h" #include "soup.h" -#include "soup-marshal.h" /** * SECTION:soup-auth-domain-basic diff --git a/libsoup/soup-auth-domain-digest.c b/libsoup/soup-auth-domain-digest.c index 985919f4..ebf3ddb6 100644 --- a/libsoup/soup-auth-domain-digest.c +++ b/libsoup/soup-auth-domain-digest.c @@ -15,13 +15,12 @@ #include "soup-auth-domain-digest.h" #include "soup.h" #include "soup-auth-digest.h" -#include "soup-marshal.h" /** * SECTION:soup-auth-domain-digest * @short_description: Server-side "Digest" authentication * - * #SoupAuthDomainBasic handles the server side of HTTP "Digest" + * #SoupAuthDomainDigest handles the server side of HTTP "Digest" * authentication. **/ diff --git a/libsoup/soup-auth-manager-ntlm.c b/libsoup/soup-auth-manager-ntlm.c deleted file mode 100644 index f2b67d1d..00000000 --- a/libsoup/soup-auth-manager-ntlm.c +++ /dev/null @@ -1,1360 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-auth-manager-ntlm.c: NTLM auth manager - * - * Copyright (C) 2001-2007 Novell, Inc. - * Copyright (C) 2008 Red Hat, Inc. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <ctype.h> -#include <errno.h> -#include <stdlib.h> -#include <string.h> - -#include "soup-auth-manager-ntlm.h" -#include "soup.h" -#include "soup-auth-ntlm.h" -#include "soup-message-private.h" - -static void soup_auth_manager_ntlm_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); -static SoupSessionFeatureInterface *soup_auth_manager_parent_feature_interface; - -G_DEFINE_TYPE_WITH_CODE (SoupAuthManagerNTLM, soup_auth_manager_ntlm, SOUP_TYPE_AUTH_MANAGER, - G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, - soup_auth_manager_ntlm_session_feature_init)) - -typedef enum { - SOUP_NTLM_NEW, -#ifdef USE_NTLM_AUTH - SOUP_NTLM_SENT_SSO_REQUEST, - SOUP_NTLM_RECEIVED_SSO_CHALLENGE, - SOUP_NTLM_SENT_SSO_RESPONSE, - SOUP_NTLM_SSO_UNAVAILABLE, - SOUP_NTLM_SSO_FAILED, -#endif - SOUP_NTLM_SENT_REQUEST, - SOUP_NTLM_RECEIVED_CHALLENGE, - SOUP_NTLM_SENT_RESPONSE, - SOUP_NTLM_FAILED -} SoupNTLMState; - -typedef struct { - SoupSocket *socket; - SoupNTLMState state; - char *response_header; - - char *nonce, *domain; - SoupAuth *auth; -#ifdef USE_NTLM_AUTH - char *challenge_header; - int fd_in; - int fd_out; -#endif -} SoupNTLMConnection; - -static void free_ntlm_connection (SoupNTLMConnection *conn); - -typedef struct { - gboolean use_ntlm; - - SoupSession *session; - GHashTable *connections_by_msg; - GHashTable *connections_by_id; -#ifdef USE_NTLM_AUTH - gboolean ntlm_auth_accessible; -#endif -} SoupAuthManagerNTLMPrivate; -#define SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER_NTLM, SoupAuthManagerNTLMPrivate)) - -static char *soup_ntlm_request (void); -static gboolean soup_ntlm_parse_challenge (const char *challenge, - char **nonce, - char **default_domain); -static char *soup_ntlm_response (const char *nonce, - const char *user, - const char *password, - const char *host, - const char *domain); -#ifdef USE_NTLM_AUTH -static void sso_ntlm_close (SoupNTLMConnection *conn); -#endif - -static void -soup_auth_manager_ntlm_init (SoupAuthManagerNTLM *ntlm) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - - priv->connections_by_id = g_hash_table_new_full (NULL, NULL, NULL, - (GDestroyNotify)free_ntlm_connection); - priv->connections_by_msg = g_hash_table_new (NULL, NULL); -#ifdef USE_NTLM_AUTH - priv->ntlm_auth_accessible = (access (NTLM_AUTH, X_OK) == 0); -#endif -} - -static void -free_ntlm_connection (SoupNTLMConnection *conn) -{ - g_free (conn->response_header); - g_free (conn->nonce); - g_free (conn->domain); - g_clear_object (&conn->auth); -#ifdef USE_NTLM_AUTH - g_free (conn->challenge_header); - sso_ntlm_close (conn); -#endif - g_slice_free (SoupNTLMConnection, conn); -} - -static void -soup_auth_manager_ntlm_finalize (GObject *object) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (object); - - g_hash_table_destroy (priv->connections_by_id); - g_hash_table_destroy (priv->connections_by_msg); - - G_OBJECT_CLASS (soup_auth_manager_ntlm_parent_class)->finalize (object); -} - -static void -soup_auth_manager_ntlm_class_init (SoupAuthManagerNTLMClass *auth_manager_ntlm_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (auth_manager_ntlm_class); - - g_type_class_add_private (auth_manager_ntlm_class, sizeof (SoupAuthManagerNTLMPrivate)); - - object_class->finalize = soup_auth_manager_ntlm_finalize; -} - -static void -soup_auth_manager_ntlm_attach (SoupSessionFeature *manager, SoupSession *session) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (manager); - - /* FIXME: should support multiple sessions */ - priv->session = session; - - soup_auth_manager_parent_feature_interface->attach (manager, session); -} - -static void -delete_conn (SoupSocket *socket, gpointer user_data) -{ - SoupAuthManagerNTLMPrivate *priv = user_data; - - g_hash_table_remove (priv->connections_by_id, socket); - g_signal_handlers_disconnect_by_func (socket, delete_conn, priv); -} - -static SoupNTLMConnection * -get_connection (SoupAuthManagerNTLMPrivate *priv, SoupSocket *socket) -{ - SoupNTLMConnection *conn; - - conn = g_hash_table_lookup (priv->connections_by_id, socket); - if (conn) - return conn; - - conn = g_slice_new0 (SoupNTLMConnection); - conn->socket = socket; - conn->state = SOUP_NTLM_NEW; -#ifdef USE_NTLM_AUTH - conn->fd_in = -1; - conn->fd_out = -1; -#endif - g_hash_table_insert (priv->connections_by_id, socket, conn); - - g_signal_connect (socket, "disconnected", - G_CALLBACK (delete_conn), priv); - return conn; -} - -static void -unset_conn (SoupMessage *msg, gpointer user_data) -{ - SoupAuthManagerNTLMPrivate *priv = user_data; - - g_hash_table_remove (priv->connections_by_msg, msg); - g_signal_handlers_disconnect_by_func (msg, unset_conn, priv); -} - -static SoupNTLMConnection * -set_connection_for_msg (SoupAuthManagerNTLMPrivate *priv, SoupMessage *msg, - SoupNTLMConnection *conn) -{ - if (!g_hash_table_lookup (priv->connections_by_msg, msg)) { - g_signal_connect (msg, "finished", - G_CALLBACK (unset_conn), priv); - g_signal_connect (msg, "restarted", - G_CALLBACK (unset_conn), priv); - } - g_hash_table_insert (priv->connections_by_msg, msg, conn); - - return conn; -} - -static SoupNTLMConnection * -get_connection_for_msg (SoupAuthManagerNTLMPrivate *priv, SoupMessage *msg) -{ - return g_hash_table_lookup (priv->connections_by_msg, msg); -} - -#ifdef USE_NTLM_AUTH -static void -sso_ntlm_close (SoupNTLMConnection *conn) -{ - if (conn->fd_in != -1) { - close (conn->fd_in); - conn->fd_in = -1; - } - - if (conn->fd_out != -1) { - close (conn->fd_out); - conn->fd_out = -1; - } -} - -static gboolean -sso_ntlm_initiate (SoupNTLMConnection *conn, SoupAuthManagerNTLMPrivate *priv) -{ - char *username = NULL, *slash, *domain = NULL; - char *argv[9]; - gboolean ret; - - /* Return if ntlm_auth execution process exist already */ - if (conn->fd_in != -1 && conn->fd_out != -1) - return TRUE; - else - /* Clean all sso data before re-initiate */ - sso_ntlm_close (conn); - - if (!priv->ntlm_auth_accessible) - goto done; - - username = getenv ("NTLMUSER"); - if (!username) - username = getenv ("USER"); - if (!username) - goto done; - - slash = strpbrk (username, "\\/"); - if (slash) { - domain = g_strdup (username); - slash = domain + (slash - username); - *slash = '\0'; - username = slash + 1; - } - - argv[0] = NTLM_AUTH; - argv[1] = "--helper-protocol"; - argv[2] = "ntlmssp-client-1"; - argv[3] = "--use-cached-creds"; - argv[4] = "--username"; - argv[5] = username; - argv[6] = domain ? "--domain" : NULL; - argv[7] = domain; - argv[8] = NULL; - /* Spawn child process */ - ret = g_spawn_async_with_pipes (NULL, argv, NULL, - G_SPAWN_FILE_AND_ARGV_ZERO | - G_SPAWN_STDERR_TO_DEV_NULL, - NULL, NULL, - NULL, &conn->fd_in, &conn->fd_out, - NULL, NULL); - if (!ret) - goto done; - g_free (domain); - return TRUE; -done: - g_free (domain); - return FALSE; -} - -static char * -sso_ntlm_response (SoupNTLMConnection *conn, const char *input, SoupNTLMState conn_state) -{ - ssize_t size; - char buf[1024], *response = NULL; - char *tmpbuf = buf; - size_t len_in = strlen (input), len_out = sizeof (buf); - - while (len_in > 0) { - int written = write (conn->fd_in, input, len_in); - if (written == -1) { - /* Interrupted by a signal, retry it */ - if (errno == EINTR) - continue; - /* write failed if other errors happen */ - goto done; - } - input += written; - len_in -= written; - } - /* Read one line */ - while (len_out > 0) { - size = read (conn->fd_out, tmpbuf, len_out); - if (size == -1) { - if (errno == EINTR) - continue; - goto done; - } else if (size == 0) - goto done; - else if (tmpbuf[size - 1] == '\n') { - tmpbuf[size - 1] = '\0'; - goto wrfinish; - } - tmpbuf += size; - len_out -= size; - } - goto done; -wrfinish: - if (g_ascii_strcasecmp (buf, "PW") == 0) { - /* Samba/winbind installed but not configured */ - response = g_strdup ("PW"); - goto done; - } - if (conn_state == SOUP_NTLM_NEW && - g_ascii_strncasecmp (buf, "YR ", 3) != 0) - /* invalid response for type 1 message */ - goto done; - if (conn_state == SOUP_NTLM_RECEIVED_SSO_CHALLENGE && - g_ascii_strncasecmp (buf, "KK ", 3) != 0 && - g_ascii_strncasecmp (buf, "AF ", 3) != 0) - /* invalid response for type 3 message */ - goto done; - - response = g_strdup_printf ("NTLM %.*s", (int)(size - 4), buf + 3); - goto done; -done: - return response; -} -#endif /* USE_NTLM_AUTH */ - -static void -ntlm_authorize_pre (SoupMessage *msg, gpointer ntlm) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - SoupNTLMConnection *conn; - const char *val; - char *challenge = NULL; - SoupURI *uri; - - conn = get_connection_for_msg (priv, msg); - if (!conn) - return; - - val = soup_message_headers_get_list (msg->response_headers, - "WWW-Authenticate"); - if (!val) - return; - challenge = soup_auth_manager_extract_challenge (val, "NTLM"); - if (!challenge) - return; - - if (conn->state > SOUP_NTLM_SENT_REQUEST) { - /* We already authenticated, but then got another 401. - * That means "permission denied", so don't try to - * authenticate again. - */ - conn->state = SOUP_NTLM_FAILED; - goto done; - } - - if (!soup_ntlm_parse_challenge (challenge, &conn->nonce, &conn->domain)) { - conn->state = SOUP_NTLM_FAILED; - goto done; - } - - conn->auth = soup_auth_ntlm_new (conn->domain, - soup_message_get_uri (msg)->host); -#ifdef USE_NTLM_AUTH - conn->challenge_header = g_strdup (challenge + 5); - if (conn->state == SOUP_NTLM_SENT_SSO_REQUEST) { - conn->state = SOUP_NTLM_RECEIVED_SSO_CHALLENGE; - goto done; - } -#endif - conn->state = SOUP_NTLM_RECEIVED_CHALLENGE; - - uri = soup_message_get_uri (msg); - if (uri->password) - soup_auth_authenticate (conn->auth, uri->user, uri->password); - else { - soup_auth_manager_emit_authenticate (SOUP_AUTH_MANAGER (ntlm), - msg, conn->auth, FALSE); - } - - done: - g_free (challenge); - - /* Remove the WWW-Authenticate headers so the session won't try - * to do Basic auth too. - */ - soup_message_headers_remove (msg->response_headers, "WWW-Authenticate"); -} - -static void -ntlm_authorize_post (SoupMessage *msg, gpointer ntlm) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - SoupNTLMConnection *conn; - const char *username = NULL, *password = NULL; - char *slash, *domain = NULL; - SoupMessageFlags flags; - - conn = get_connection_for_msg (priv, msg); - if (!conn || !conn->auth) - return; - -#ifdef USE_NTLM_AUTH - if (conn->state == SOUP_NTLM_RECEIVED_SSO_CHALLENGE) { - char *input, *header; - input = g_strdup_printf ("TT %s\n", conn->challenge_header); - /* Re-Initiate ntlm_auth process in case it was closed/killed abnormally */ - if (sso_ntlm_initiate (conn, priv)) { - header = sso_ntlm_response (conn, input, conn->state); - g_free (input); - /* Close ntlm_auth as it is no longer needed for current connection */ - sso_ntlm_close (conn); - if (!header) { - conn->state = SOUP_NTLM_SSO_FAILED; - g_free (header); - goto ssofailure; - } - if (!g_ascii_strcasecmp (header, "PW")) { - conn->state = SOUP_NTLM_SSO_UNAVAILABLE; - g_free (header); - goto ssofailure; - } - - conn->response_header = header; - soup_session_requeue_message (priv->session, msg); - goto done; - } - conn->state = SOUP_NTLM_SSO_FAILED; -ssofailure: - soup_session_requeue_message (priv->session, msg); - goto done; - } -#endif - username = soup_auth_ntlm_get_username (conn->auth); - password = soup_auth_ntlm_get_password (conn->auth); - if (!username || !password) - goto done; - - slash = strpbrk (username, "\\/"); - if (slash) { - domain = g_strdup (username); - slash = domain + (slash - username); - *slash = '\0'; - username = slash + 1; - } else - domain = conn->domain; - - conn->response_header = soup_ntlm_response (conn->nonce, - username, password, - NULL, domain); - - flags = soup_message_get_flags (msg); - soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION); - soup_session_requeue_message (priv->session, msg); - -done: - if (domain != conn->domain) - g_free (domain); - g_free (conn->domain); - conn->domain = NULL; - g_free (conn->nonce); - conn->nonce = NULL; - g_object_unref (conn->auth); - conn->auth = NULL; -} - -static void -soup_auth_manager_ntlm_request_queued (SoupSessionFeature *ntlm, - SoupSession *session, - SoupMessage *msg) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - - if (priv->use_ntlm) { - soup_message_add_status_code_handler ( - msg, "got_headers", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (ntlm_authorize_pre), ntlm); - soup_message_add_status_code_handler ( - msg, "got_body", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (ntlm_authorize_post), ntlm); - } - - soup_auth_manager_parent_feature_interface->request_queued (ntlm, session, msg); -} - -static void -soup_auth_manager_ntlm_request_started (SoupSessionFeature *ntlm, - SoupSession *session, - SoupMessage *msg, - SoupSocket *socket) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (ntlm); - SoupNTLMConnection *conn; - char *header = NULL; - - if (!priv->use_ntlm) - goto super; - - conn = get_connection (priv, socket); - set_connection_for_msg (priv, msg, conn); - - switch (conn->state) { - case SOUP_NTLM_NEW: -#ifdef USE_NTLM_AUTH - /* Use Samba's 'winbind' daemon to support NTLM single-sign-on, - * by delegating the NTLM challenge/response protocal to a helper - * in ntlm_auth. - * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html - * http://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html - * http://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html - * The preprocessor variable 'USE_NTLM_AUTH' indicates whether - * this feature is enabled. Another one 'NTLM_AUTH' contains absolute - * path of it. - * If NTLM single-sign-on fails, go back to original request handling process. - */ - if (sso_ntlm_initiate (conn, priv)) { - header = sso_ntlm_response (conn, "YR\n", conn->state); - if (header) { - if (g_ascii_strcasecmp (header, "PW") != 0) { - conn->state = SOUP_NTLM_SENT_SSO_REQUEST; - break; - } else { - g_free (header); - header = NULL; - goto ssounavailable; - } - } else { - g_warning ("NTLM single-sign-on by using %s failed", NTLM_AUTH); - goto ssounavailable; - } - } - case SOUP_NTLM_SSO_UNAVAILABLE: - ssounavailable: -#endif - header = soup_ntlm_request (); - conn->state = SOUP_NTLM_SENT_REQUEST; - break; -#ifdef USE_NTLM_AUTH - case SOUP_NTLM_RECEIVED_SSO_CHALLENGE: - header = conn->response_header; - conn->response_header = NULL; - conn->state = SOUP_NTLM_SENT_SSO_RESPONSE; - break; - case SOUP_NTLM_SSO_FAILED: - /* Restart request without SSO */ - g_warning ("NTLM single-sign-on by using %s failed", NTLM_AUTH); - header = soup_ntlm_request (); - conn->state = SOUP_NTLM_SENT_REQUEST; - break; -#endif - case SOUP_NTLM_RECEIVED_CHALLENGE: - header = conn->response_header; - conn->response_header = NULL; - conn->state = SOUP_NTLM_SENT_RESPONSE; - break; - default: - break; - } - - if (header && !soup_message_get_auth (msg)) { - soup_message_headers_replace (msg->request_headers, - "Authorization", header); - g_free (header); - } - -super: - soup_auth_manager_parent_feature_interface->request_started (ntlm, session, msg, socket); -} - -static void -soup_auth_manager_ntlm_request_unqueued (SoupSessionFeature *ntlm, - SoupSession *session, - SoupMessage *msg) -{ - g_signal_handlers_disconnect_by_func (msg, ntlm_authorize_pre, ntlm); - g_signal_handlers_disconnect_by_func (msg, ntlm_authorize_post, ntlm); - - soup_auth_manager_parent_feature_interface->request_unqueued (ntlm, session, msg); -} - -static gboolean -soup_auth_manager_ntlm_add_feature (SoupSessionFeature *feature, GType type) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (feature); - - if (type == SOUP_TYPE_AUTH_NTLM) { - priv->use_ntlm = TRUE; - return TRUE; - } - - return soup_auth_manager_parent_feature_interface->add_feature (feature, type); -} - -static gboolean -soup_auth_manager_ntlm_remove_feature (SoupSessionFeature *feature, GType type) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (feature); - - if (type == SOUP_TYPE_AUTH_NTLM) { - priv->use_ntlm = FALSE; - return TRUE; - } - - return soup_auth_manager_parent_feature_interface->remove_feature (feature, type); -} - -static gboolean -soup_auth_manager_ntlm_has_feature (SoupSessionFeature *feature, GType type) -{ - SoupAuthManagerNTLMPrivate *priv = - SOUP_AUTH_MANAGER_NTLM_GET_PRIVATE (feature); - - if (type == SOUP_TYPE_AUTH_NTLM) - return priv->use_ntlm; - - return soup_auth_manager_parent_feature_interface->has_feature (feature, type); -} - -static void -soup_auth_manager_ntlm_session_feature_init (SoupSessionFeatureInterface *feature_interface, - gpointer interface_data) -{ - soup_auth_manager_parent_feature_interface = - g_type_interface_peek_parent (feature_interface); - - feature_interface->attach = soup_auth_manager_ntlm_attach; - feature_interface->request_queued = soup_auth_manager_ntlm_request_queued; - feature_interface->request_started = soup_auth_manager_ntlm_request_started; - feature_interface->request_unqueued = soup_auth_manager_ntlm_request_unqueued; - feature_interface->add_feature = soup_auth_manager_ntlm_add_feature; - feature_interface->remove_feature = soup_auth_manager_ntlm_remove_feature; - feature_interface->has_feature = soup_auth_manager_ntlm_has_feature; -} - -/* NTLM code */ - -static void md4sum (const unsigned char *in, - int nbytes, - unsigned char digest[16]); - -typedef guint32 DES_KS[16][2]; /* Single-key DES key schedule */ - -static void deskey (DES_KS, unsigned char *, int); - -static void des (DES_KS, unsigned char *); - -static void setup_schedule (const guchar *key_56, DES_KS ks); - -static void calc_response (const guchar *key, - const guchar *plaintext, - guchar *results); - -#define LM_PASSWORD_MAGIC "\x4B\x47\x53\x21\x40\x23\x24\x25" \ - "\x4B\x47\x53\x21\x40\x23\x24\x25" \ - "\x00\x00\x00\x00\x00" - -static void -lanmanager_hash (const char *password, guchar hash[21]) -{ - guchar lm_password [15]; - DES_KS ks; - int i; - - for (i = 0; i < 14 && password [i]; i++) - lm_password [i] = toupper ((unsigned char) password [i]); - - for (; i < 15; i++) - lm_password [i] = '\0'; - - memcpy (hash, LM_PASSWORD_MAGIC, 21); - - setup_schedule (lm_password, ks); - des (ks, hash); - - setup_schedule (lm_password + 7, ks); - des (ks, hash + 8); -} - -static void -nt_hash (const char *password, guchar hash[21]) -{ - unsigned char *buf, *p; - - p = buf = g_malloc (strlen (password) * 2); - - while (*password) { - *p++ = *password++; - *p++ = '\0'; - } - - md4sum (buf, p - buf, hash); - memset (hash + 16, 0, 5); - - g_free (buf); -} - -typedef struct { - guint16 length; - guint16 length2; - guint16 offset; - guchar zero_pad[2]; -} NTLMString; - -#define NTLM_CHALLENGE_NONCE_OFFSET 24 -#define NTLM_CHALLENGE_NONCE_LENGTH 8 -#define NTLM_CHALLENGE_DOMAIN_STRING_OFFSET 12 - -#define NTLM_RESPONSE_HEADER "NTLMSSP\x00\x03\x00\x00\x00" -#define NTLM_RESPONSE_FLAGS 0x8201 - -typedef struct { - guchar header[12]; - - NTLMString lm_resp; - NTLMString nt_resp; - NTLMString domain; - NTLMString user; - NTLMString host; - NTLMString session_key; - - guint32 flags; -} NTLMResponse; - -static void -ntlm_set_string (NTLMString *string, int *offset, int len) -{ - string->offset = GUINT16_TO_LE (*offset); - string->length = string->length2 = GUINT16_TO_LE (len); - *offset += len; -} - -static char * -soup_ntlm_request (void) -{ - return g_strdup ("NTLM TlRMTVNTUAABAAAABYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA"); -} - -static gboolean -soup_ntlm_parse_challenge (const char *challenge, - char **nonce, - char **default_domain) -{ - gsize clen; - NTLMString domain; - guchar *chall; - - if (strncmp (challenge, "NTLM ", 5) != 0) - return FALSE; - - chall = g_base64_decode (challenge + 5, &clen); - if (clen < NTLM_CHALLENGE_DOMAIN_STRING_OFFSET || - clen < NTLM_CHALLENGE_NONCE_OFFSET + NTLM_CHALLENGE_NONCE_LENGTH) { - g_free (chall); - return FALSE; - } - - if (default_domain) { - memcpy (&domain, chall + NTLM_CHALLENGE_DOMAIN_STRING_OFFSET, sizeof (domain)); - domain.length = GUINT16_FROM_LE (domain.length); - domain.offset = GUINT16_FROM_LE (domain.offset); - - if (clen < domain.length + domain.offset) { - g_free (chall); - return FALSE; - } - - *default_domain = g_convert ((char *)chall + domain.offset, - domain.length, "UTF-8", "UCS-2LE", - NULL, NULL, NULL); - } - - if (nonce) { - *nonce = g_memdup (chall + NTLM_CHALLENGE_NONCE_OFFSET, - NTLM_CHALLENGE_NONCE_LENGTH); - } - - g_free (chall); - return TRUE; -} - -static char * -soup_ntlm_response (const char *nonce, - const char *user, - const char *password, - const char *host, - const char *domain) -{ - int offset; - gsize hlen, dlen, ulen; - guchar hash[21], lm_resp[24], nt_resp[24]; - char *user_conv, *host_conv, *domain_conv; - NTLMResponse resp; - char *out, *p; - int state, save; - - nt_hash (password, hash); - calc_response (hash, (guchar *)nonce, nt_resp); - lanmanager_hash (password, hash); - calc_response (hash, (guchar *)nonce, lm_resp); - - memset (&resp, 0, sizeof (resp)); - memcpy (resp.header, NTLM_RESPONSE_HEADER, sizeof (resp.header)); - resp.flags = GUINT32_TO_LE (NTLM_RESPONSE_FLAGS); - - offset = sizeof (resp); - - if (!host) - host = "UNKNOWN"; - - domain_conv = g_convert (domain, -1, "UCS-2LE", "UTF-8", NULL, &dlen, NULL); - user_conv = g_convert (user, -1, "UCS-2LE", "UTF-8", NULL, &ulen, NULL); - host_conv = g_convert (host, -1, "UCS-2LE", "UTF-8", NULL, &hlen, NULL); - - ntlm_set_string (&resp.domain, &offset, dlen); - ntlm_set_string (&resp.user, &offset, ulen); - ntlm_set_string (&resp.host, &offset, hlen); - ntlm_set_string (&resp.lm_resp, &offset, sizeof (lm_resp)); - ntlm_set_string (&resp.nt_resp, &offset, sizeof (nt_resp)); - - out = g_malloc (((offset + 3) * 4) / 3 + 6); - strncpy (out, "NTLM ", 5); - p = out + 5; - - state = save = 0; - - p += g_base64_encode_step ((const guchar *) &resp, sizeof (resp), - FALSE, p, &state, &save); - p += g_base64_encode_step ((const guchar *) domain_conv, dlen, - FALSE, p, &state, &save); - p += g_base64_encode_step ((const guchar *) user_conv, ulen, - FALSE, p, &state, &save); - p += g_base64_encode_step ((const guchar *) host_conv, hlen, - FALSE, p, &state, &save); - p += g_base64_encode_step (lm_resp, sizeof (lm_resp), - FALSE, p, &state, &save); - p += g_base64_encode_step (nt_resp, sizeof (nt_resp), - FALSE, p, &state, &save); - p += g_base64_encode_close (FALSE, p, &state, &save); - *p = '\0'; - - g_free (domain_conv); - g_free (user_conv); - g_free (host_conv); - - return out; -} - -/* DES utils */ -/* Set up a key schedule based on a 56bit key */ -static void -setup_schedule (const guchar *key_56, DES_KS ks) -{ - guchar key[8]; - int i, c, bit; - - key[0] = (key_56[0]) ; - key[1] = (key_56[1] >> 1) | ((key_56[0] << 7) & 0xFF); - key[2] = (key_56[2] >> 2) | ((key_56[1] << 6) & 0xFF); - key[3] = (key_56[3] >> 3) | ((key_56[2] << 5) & 0xFF); - key[4] = (key_56[4] >> 4) | ((key_56[3] << 4) & 0xFF); - key[5] = (key_56[5] >> 5) | ((key_56[4] << 3) & 0xFF); - key[6] = (key_56[6] >> 6) | ((key_56[5] << 2) & 0xFF); - key[7] = ((key_56[6] << 1) & 0xFF); - - /* Fix parity */ - for (i = 0; i < 8; i++) { - for (c = bit = 0; bit < 8; bit++) - if (key[i] & (1 << bit)) - c++; - if (!(c & 1)) - key[i] ^= 0x01; - } - - deskey (ks, key, 0); -} - -static void -calc_response (const guchar *key, const guchar *plaintext, guchar *results) -{ - DES_KS ks; - - memcpy (results, plaintext, 8); - memcpy (results + 8, plaintext, 8); - memcpy (results + 16, plaintext, 8); - - setup_schedule (key, ks); - des (ks, results); - - setup_schedule (key + 7, ks); - des (ks, results + 8); - - setup_schedule (key + 14, ks); - des (ks, results + 16); -} - - -/* - * MD4 encoder. (The one everyone else uses is not GPL-compatible; - * this is a reimplementation from spec.) This doesn't need to be - * efficient for our purposes, although it would be nice to fix - * it to not malloc()... - */ - -#define F(X,Y,Z) ( ((X)&(Y)) | ((~(X))&(Z)) ) -#define G(X,Y,Z) ( ((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)) ) -#define H(X,Y,Z) ( (X)^(Y)^(Z) ) -#define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) ) - -static void -md4sum (const unsigned char *in, int nbytes, unsigned char digest[16]) -{ - unsigned char *M; - guint32 A, B, C, D, AA, BB, CC, DD, X[16]; - int pbytes, nbits = nbytes * 8, i, j; - - pbytes = (120 - (nbytes % 64)) % 64; - M = alloca (nbytes + pbytes + 8); - memcpy (M, in, nbytes); - memset (M + nbytes, 0, pbytes + 8); - M[nbytes] = 0x80; - M[nbytes + pbytes] = nbits & 0xFF; - M[nbytes + pbytes + 1] = (nbits >> 8) & 0xFF; - M[nbytes + pbytes + 2] = (nbits >> 16) & 0xFF; - M[nbytes + pbytes + 3] = (nbits >> 24) & 0xFF; - - A = 0x67452301; - B = 0xEFCDAB89; - C = 0x98BADCFE; - D = 0x10325476; - - for (i = 0; i < nbytes + pbytes + 8; i += 64) { - for (j = 0; j < 16; j++) { - X[j] = (M[i + j*4]) | - (M[i + j*4 + 1] << 8) | - (M[i + j*4 + 2] << 16) | - (M[i + j*4 + 3] << 24); - } - - AA = A; - BB = B; - CC = C; - DD = D; - - A = ROT (A + F(B, C, D) + X[0], 3); - D = ROT (D + F(A, B, C) + X[1], 7); - C = ROT (C + F(D, A, B) + X[2], 11); - B = ROT (B + F(C, D, A) + X[3], 19); - A = ROT (A + F(B, C, D) + X[4], 3); - D = ROT (D + F(A, B, C) + X[5], 7); - C = ROT (C + F(D, A, B) + X[6], 11); - B = ROT (B + F(C, D, A) + X[7], 19); - A = ROT (A + F(B, C, D) + X[8], 3); - D = ROT (D + F(A, B, C) + X[9], 7); - C = ROT (C + F(D, A, B) + X[10], 11); - B = ROT (B + F(C, D, A) + X[11], 19); - A = ROT (A + F(B, C, D) + X[12], 3); - D = ROT (D + F(A, B, C) + X[13], 7); - C = ROT (C + F(D, A, B) + X[14], 11); - B = ROT (B + F(C, D, A) + X[15], 19); - - A = ROT (A + G(B, C, D) + X[0] + 0x5A827999, 3); - D = ROT (D + G(A, B, C) + X[4] + 0x5A827999, 5); - C = ROT (C + G(D, A, B) + X[8] + 0x5A827999, 9); - B = ROT (B + G(C, D, A) + X[12] + 0x5A827999, 13); - A = ROT (A + G(B, C, D) + X[1] + 0x5A827999, 3); - D = ROT (D + G(A, B, C) + X[5] + 0x5A827999, 5); - C = ROT (C + G(D, A, B) + X[9] + 0x5A827999, 9); - B = ROT (B + G(C, D, A) + X[13] + 0x5A827999, 13); - A = ROT (A + G(B, C, D) + X[2] + 0x5A827999, 3); - D = ROT (D + G(A, B, C) + X[6] + 0x5A827999, 5); - C = ROT (C + G(D, A, B) + X[10] + 0x5A827999, 9); - B = ROT (B + G(C, D, A) + X[14] + 0x5A827999, 13); - A = ROT (A + G(B, C, D) + X[3] + 0x5A827999, 3); - D = ROT (D + G(A, B, C) + X[7] + 0x5A827999, 5); - C = ROT (C + G(D, A, B) + X[11] + 0x5A827999, 9); - B = ROT (B + G(C, D, A) + X[15] + 0x5A827999, 13); - - A = ROT (A + H(B, C, D) + X[0] + 0x6ED9EBA1, 3); - D = ROT (D + H(A, B, C) + X[8] + 0x6ED9EBA1, 9); - C = ROT (C + H(D, A, B) + X[4] + 0x6ED9EBA1, 11); - B = ROT (B + H(C, D, A) + X[12] + 0x6ED9EBA1, 15); - A = ROT (A + H(B, C, D) + X[2] + 0x6ED9EBA1, 3); - D = ROT (D + H(A, B, C) + X[10] + 0x6ED9EBA1, 9); - C = ROT (C + H(D, A, B) + X[6] + 0x6ED9EBA1, 11); - B = ROT (B + H(C, D, A) + X[14] + 0x6ED9EBA1, 15); - A = ROT (A + H(B, C, D) + X[1] + 0x6ED9EBA1, 3); - D = ROT (D + H(A, B, C) + X[9] + 0x6ED9EBA1, 9); - C = ROT (C + H(D, A, B) + X[5] + 0x6ED9EBA1, 11); - B = ROT (B + H(C, D, A) + X[13] + 0x6ED9EBA1, 15); - A = ROT (A + H(B, C, D) + X[3] + 0x6ED9EBA1, 3); - D = ROT (D + H(A, B, C) + X[11] + 0x6ED9EBA1, 9); - C = ROT (C + H(D, A, B) + X[7] + 0x6ED9EBA1, 11); - B = ROT (B + H(C, D, A) + X[15] + 0x6ED9EBA1, 15); - - A += AA; - B += BB; - C += CC; - D += DD; - } - - digest[0] = A & 0xFF; - digest[1] = (A >> 8) & 0xFF; - digest[2] = (A >> 16) & 0xFF; - digest[3] = (A >> 24) & 0xFF; - digest[4] = B & 0xFF; - digest[5] = (B >> 8) & 0xFF; - digest[6] = (B >> 16) & 0xFF; - digest[7] = (B >> 24) & 0xFF; - digest[8] = C & 0xFF; - digest[9] = (C >> 8) & 0xFF; - digest[10] = (C >> 16) & 0xFF; - digest[11] = (C >> 24) & 0xFF; - digest[12] = D & 0xFF; - digest[13] = (D >> 8) & 0xFF; - digest[14] = (D >> 16) & 0xFF; - digest[15] = (D >> 24) & 0xFF; -} - - -/* Public domain DES implementation from Phil Karn */ -static const guint32 Spbox[8][64] = { - { 0x01010400,0x00000000,0x00010000,0x01010404, - 0x01010004,0x00010404,0x00000004,0x00010000, - 0x00000400,0x01010400,0x01010404,0x00000400, - 0x01000404,0x01010004,0x01000000,0x00000004, - 0x00000404,0x01000400,0x01000400,0x00010400, - 0x00010400,0x01010000,0x01010000,0x01000404, - 0x00010004,0x01000004,0x01000004,0x00010004, - 0x00000000,0x00000404,0x00010404,0x01000000, - 0x00010000,0x01010404,0x00000004,0x01010000, - 0x01010400,0x01000000,0x01000000,0x00000400, - 0x01010004,0x00010000,0x00010400,0x01000004, - 0x00000400,0x00000004,0x01000404,0x00010404, - 0x01010404,0x00010004,0x01010000,0x01000404, - 0x01000004,0x00000404,0x00010404,0x01010400, - 0x00000404,0x01000400,0x01000400,0x00000000, - 0x00010004,0x00010400,0x00000000,0x01010004 }, - { 0x80108020,0x80008000,0x00008000,0x00108020, - 0x00100000,0x00000020,0x80100020,0x80008020, - 0x80000020,0x80108020,0x80108000,0x80000000, - 0x80008000,0x00100000,0x00000020,0x80100020, - 0x00108000,0x00100020,0x80008020,0x00000000, - 0x80000000,0x00008000,0x00108020,0x80100000, - 0x00100020,0x80000020,0x00000000,0x00108000, - 0x00008020,0x80108000,0x80100000,0x00008020, - 0x00000000,0x00108020,0x80100020,0x00100000, - 0x80008020,0x80100000,0x80108000,0x00008000, - 0x80100000,0x80008000,0x00000020,0x80108020, - 0x00108020,0x00000020,0x00008000,0x80000000, - 0x00008020,0x80108000,0x00100000,0x80000020, - 0x00100020,0x80008020,0x80000020,0x00100020, - 0x00108000,0x00000000,0x80008000,0x00008020, - 0x80000000,0x80100020,0x80108020,0x00108000 }, - { 0x00000208,0x08020200,0x00000000,0x08020008, - 0x08000200,0x00000000,0x00020208,0x08000200, - 0x00020008,0x08000008,0x08000008,0x00020000, - 0x08020208,0x00020008,0x08020000,0x00000208, - 0x08000000,0x00000008,0x08020200,0x00000200, - 0x00020200,0x08020000,0x08020008,0x00020208, - 0x08000208,0x00020200,0x00020000,0x08000208, - 0x00000008,0x08020208,0x00000200,0x08000000, - 0x08020200,0x08000000,0x00020008,0x00000208, - 0x00020000,0x08020200,0x08000200,0x00000000, - 0x00000200,0x00020008,0x08020208,0x08000200, - 0x08000008,0x00000200,0x00000000,0x08020008, - 0x08000208,0x00020000,0x08000000,0x08020208, - 0x00000008,0x00020208,0x00020200,0x08000008, - 0x08020000,0x08000208,0x00000208,0x08020000, - 0x00020208,0x00000008,0x08020008,0x00020200 }, - { 0x00802001,0x00002081,0x00002081,0x00000080, - 0x00802080,0x00800081,0x00800001,0x00002001, - 0x00000000,0x00802000,0x00802000,0x00802081, - 0x00000081,0x00000000,0x00800080,0x00800001, - 0x00000001,0x00002000,0x00800000,0x00802001, - 0x00000080,0x00800000,0x00002001,0x00002080, - 0x00800081,0x00000001,0x00002080,0x00800080, - 0x00002000,0x00802080,0x00802081,0x00000081, - 0x00800080,0x00800001,0x00802000,0x00802081, - 0x00000081,0x00000000,0x00000000,0x00802000, - 0x00002080,0x00800080,0x00800081,0x00000001, - 0x00802001,0x00002081,0x00002081,0x00000080, - 0x00802081,0x00000081,0x00000001,0x00002000, - 0x00800001,0x00002001,0x00802080,0x00800081, - 0x00002001,0x00002080,0x00800000,0x00802001, - 0x00000080,0x00800000,0x00002000,0x00802080 }, - { 0x00000100,0x02080100,0x02080000,0x42000100, - 0x00080000,0x00000100,0x40000000,0x02080000, - 0x40080100,0x00080000,0x02000100,0x40080100, - 0x42000100,0x42080000,0x00080100,0x40000000, - 0x02000000,0x40080000,0x40080000,0x00000000, - 0x40000100,0x42080100,0x42080100,0x02000100, - 0x42080000,0x40000100,0x00000000,0x42000000, - 0x02080100,0x02000000,0x42000000,0x00080100, - 0x00080000,0x42000100,0x00000100,0x02000000, - 0x40000000,0x02080000,0x42000100,0x40080100, - 0x02000100,0x40000000,0x42080000,0x02080100, - 0x40080100,0x00000100,0x02000000,0x42080000, - 0x42080100,0x00080100,0x42000000,0x42080100, - 0x02080000,0x00000000,0x40080000,0x42000000, - 0x00080100,0x02000100,0x40000100,0x00080000, - 0x00000000,0x40080000,0x02080100,0x40000100 }, - { 0x20000010,0x20400000,0x00004000,0x20404010, - 0x20400000,0x00000010,0x20404010,0x00400000, - 0x20004000,0x00404010,0x00400000,0x20000010, - 0x00400010,0x20004000,0x20000000,0x00004010, - 0x00000000,0x00400010,0x20004010,0x00004000, - 0x00404000,0x20004010,0x00000010,0x20400010, - 0x20400010,0x00000000,0x00404010,0x20404000, - 0x00004010,0x00404000,0x20404000,0x20000000, - 0x20004000,0x00000010,0x20400010,0x00404000, - 0x20404010,0x00400000,0x00004010,0x20000010, - 0x00400000,0x20004000,0x20000000,0x00004010, - 0x20000010,0x20404010,0x00404000,0x20400000, - 0x00404010,0x20404000,0x00000000,0x20400010, - 0x00000010,0x00004000,0x20400000,0x00404010, - 0x00004000,0x00400010,0x20004010,0x00000000, - 0x20404000,0x20000000,0x00400010,0x20004010 }, - { 0x00200000,0x04200002,0x04000802,0x00000000, - 0x00000800,0x04000802,0x00200802,0x04200800, - 0x04200802,0x00200000,0x00000000,0x04000002, - 0x00000002,0x04000000,0x04200002,0x00000802, - 0x04000800,0x00200802,0x00200002,0x04000800, - 0x04000002,0x04200000,0x04200800,0x00200002, - 0x04200000,0x00000800,0x00000802,0x04200802, - 0x00200800,0x00000002,0x04000000,0x00200800, - 0x04000000,0x00200800,0x00200000,0x04000802, - 0x04000802,0x04200002,0x04200002,0x00000002, - 0x00200002,0x04000000,0x04000800,0x00200000, - 0x04200800,0x00000802,0x00200802,0x04200800, - 0x00000802,0x04000002,0x04200802,0x04200000, - 0x00200800,0x00000000,0x00000002,0x04200802, - 0x00000000,0x00200802,0x04200000,0x00000800, - 0x04000002,0x04000800,0x00000800,0x00200002 }, - { 0x10001040,0x00001000,0x00040000,0x10041040, - 0x10000000,0x10001040,0x00000040,0x10000000, - 0x00040040,0x10040000,0x10041040,0x00041000, - 0x10041000,0x00041040,0x00001000,0x00000040, - 0x10040000,0x10000040,0x10001000,0x00001040, - 0x00041000,0x00040040,0x10040040,0x10041000, - 0x00001040,0x00000000,0x00000000,0x10040040, - 0x10000040,0x10001000,0x00041040,0x00040000, - 0x00041040,0x00040000,0x10041000,0x00001000, - 0x00000040,0x10040040,0x00001000,0x00041040, - 0x10001000,0x00000040,0x10000040,0x10040000, - 0x10040040,0x10000000,0x00040000,0x10001040, - 0x00000000,0x10041040,0x00040040,0x10000040, - 0x10040000,0x10001000,0x10001040,0x00000000, - 0x10041040,0x00041000,0x00041000,0x00001040, - 0x00001040,0x00040040,0x10000000,0x10041000 } -}; - -#undef F -#define F(l,r,key){\ - work = ((r >> 4) | (r << 28)) ^ key[0];\ - l ^= Spbox[6][work & 0x3f];\ - l ^= Spbox[4][(work >> 8) & 0x3f];\ - l ^= Spbox[2][(work >> 16) & 0x3f];\ - l ^= Spbox[0][(work >> 24) & 0x3f];\ - work = r ^ key[1];\ - l ^= Spbox[7][work & 0x3f];\ - l ^= Spbox[5][(work >> 8) & 0x3f];\ - l ^= Spbox[3][(work >> 16) & 0x3f];\ - l ^= Spbox[1][(work >> 24) & 0x3f];\ -} -/* Encrypt or decrypt a block of data in ECB mode */ -static void -des (guint32 ks[16][2], unsigned char block[8]) -{ - guint32 left,right,work; - - /* Read input block and place in left/right in big-endian order */ - left = ((guint32)block[0] << 24) - | ((guint32)block[1] << 16) - | ((guint32)block[2] << 8) - | (guint32)block[3]; - right = ((guint32)block[4] << 24) - | ((guint32)block[5] << 16) - | ((guint32)block[6] << 8) - | (guint32)block[7]; - - /* Hoey's clever initial permutation algorithm, from Outerbridge - * (see Schneier p 478) - * - * The convention here is the same as Outerbridge: rotate each - * register left by 1 bit, i.e., so that "left" contains permuted - * input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32 - * (using origin-1 numbering as in the FIPS). This allows us to avoid - * one of the two rotates that would otherwise be required in each of - * the 16 rounds. - */ - work = ((left >> 4) ^ right) & 0x0f0f0f0f; - right ^= work; - left ^= work << 4; - work = ((left >> 16) ^ right) & 0xffff; - right ^= work; - left ^= work << 16; - work = ((right >> 2) ^ left) & 0x33333333; - left ^= work; - right ^= (work << 2); - work = ((right >> 8) ^ left) & 0xff00ff; - left ^= work; - right ^= (work << 8); - right = (right << 1) | (right >> 31); - work = (left ^ right) & 0xaaaaaaaa; - left ^= work; - right ^= work; - left = (left << 1) | (left >> 31); - - /* Now do the 16 rounds */ - F(left,right,ks[0]); - F(right,left,ks[1]); - F(left,right,ks[2]); - F(right,left,ks[3]); - F(left,right,ks[4]); - F(right,left,ks[5]); - F(left,right,ks[6]); - F(right,left,ks[7]); - F(left,right,ks[8]); - F(right,left,ks[9]); - F(left,right,ks[10]); - F(right,left,ks[11]); - F(left,right,ks[12]); - F(right,left,ks[13]); - F(left,right,ks[14]); - F(right,left,ks[15]); - - /* Inverse permutation, also from Hoey via Outerbridge and Schneier */ - right = (right << 31) | (right >> 1); - work = (left ^ right) & 0xaaaaaaaa; - left ^= work; - right ^= work; - left = (left >> 1) | (left << 31); - work = ((left >> 8) ^ right) & 0xff00ff; - right ^= work; - left ^= work << 8; - work = ((left >> 2) ^ right) & 0x33333333; - right ^= work; - left ^= work << 2; - work = ((right >> 16) ^ left) & 0xffff; - left ^= work; - right ^= work << 16; - work = ((right >> 4) ^ left) & 0x0f0f0f0f; - left ^= work; - right ^= work << 4; - - /* Put the block back into the user's buffer with final swap */ - block[0] = right >> 24; - block[1] = right >> 16; - block[2] = right >> 8; - block[3] = right; - block[4] = left >> 24; - block[5] = left >> 16; - block[6] = left >> 8; - block[7] = left; -} - -/* Key schedule-related tables from FIPS-46 */ - -/* permuted choice table (key) */ -static const unsigned char pc1[] = { - 57, 49, 41, 33, 25, 17, 9, - 1, 58, 50, 42, 34, 26, 18, - 10, 2, 59, 51, 43, 35, 27, - 19, 11, 3, 60, 52, 44, 36, - - 63, 55, 47, 39, 31, 23, 15, - 7, 62, 54, 46, 38, 30, 22, - 14, 6, 61, 53, 45, 37, 29, - 21, 13, 5, 28, 20, 12, 4 -}; - -/* number left rotations of pc1 */ -static const unsigned char totrot[] = { - 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 -}; - -/* permuted choice key (table) */ -static const unsigned char pc2[] = { - 14, 17, 11, 24, 1, 5, - 3, 28, 15, 6, 21, 10, - 23, 19, 12, 4, 26, 8, - 16, 7, 27, 20, 13, 2, - 41, 52, 31, 37, 47, 55, - 30, 40, 51, 45, 33, 48, - 44, 49, 39, 56, 34, 53, - 46, 42, 50, 36, 29, 32 -}; - -/* End of DES-defined tables */ - - -/* bit 0 is left-most in byte */ -static const int bytebit[] = { - 0200,0100,040,020,010,04,02,01 -}; - - -/* Generate key schedule for encryption or decryption - * depending on the value of "decrypt" - */ -static void -deskey (DES_KS k, unsigned char *key, int decrypt) -{ - unsigned char pc1m[56]; /* place to modify pc1 into */ - unsigned char pcr[56]; /* place to rotate pc1 into */ - register int i,j,l; - int m; - unsigned char ks[8]; - - for (j=0; j<56; j++) { /* convert pc1 to bits of key */ - l=pc1[j]-1; /* integer bit location */ - m = l & 07; /* find bit */ - pc1m[j]=(key[l>>3] & /* find which key byte l is in */ - bytebit[m]) /* and which bit of that byte */ - ? 1 : 0; /* and store 1-bit result */ - } - for (i=0; i<16; i++) { /* key chunk for each iteration */ - memset(ks,0,sizeof(ks)); /* Clear key schedule */ - for (j=0; j<56; j++) /* rotate pc1 the right amount */ - pcr[j] = pc1m[(l=j+totrot[decrypt? 15-i : i])<(j<28? 28 : 56) ? l: l-28]; - /* rotate left and right halves independently */ - for (j=0; j<48; j++){ /* select bits individually */ - /* check bit that goes to ks[j] */ - if (pcr[pc2[j]-1]){ - /* mask it in if it's there */ - l= j % 6; - ks[j/6] |= bytebit[l] >> 2; - } - } - /* Now convert to packed odd/even interleaved form */ - k[i][0] = ((guint32)ks[0] << 24) - | ((guint32)ks[2] << 16) - | ((guint32)ks[4] << 8) - | ((guint32)ks[6]); - k[i][1] = ((guint32)ks[1] << 24) - | ((guint32)ks[3] << 16) - | ((guint32)ks[5] << 8) - | ((guint32)ks[7]); - } -} diff --git a/libsoup/soup-auth-manager-ntlm.h b/libsoup/soup-auth-manager-ntlm.h deleted file mode 100644 index f0b4f575..00000000 --- a/libsoup/soup-auth-manager-ntlm.h +++ /dev/null @@ -1,36 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2008 Red Hat, Inc. - */ - -#ifndef SOUP_AUTH_MANAGER_NTLM_H -#define SOUP_AUTH_MANAGER_NTLM_H 1 - -#include "soup-auth-manager.h" - -G_BEGIN_DECLS - -#define SOUP_TYPE_AUTH_MANAGER_NTLM (soup_auth_manager_ntlm_get_type ()) -#define SOUP_AUTH_MANAGER_NTLM(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_AUTH_MANAGER_NTLM, SoupAuthManagerNTLM)) -#define SOUP_AUTH_MANAGER_NTLM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_AUTH_MANAGER_NTLM, SoupAuthManagerNTLMClass)) -#define SOUP_IS_AUTH_MANAGER_NTLM(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_AUTH_MANAGER_NTLM)) -#define SOUP_IS_AUTH_MANAGER_NTLM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_MANAGER_NTLM)) -#define SOUP_AUTH_MANAGER_NTLM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_MANAGER_NTLM, SoupAuthManagerNTLMClass)) - -typedef struct { - SoupAuthManager parent; - -} SoupAuthManagerNTLM; - -typedef struct { - SoupAuthManagerClass parent_class; - -} SoupAuthManagerNTLMClass; - -#define SOUP_AUTH_MANAGER_NTLM_USE_NTLM "use-ntlm" - -GType soup_auth_manager_ntlm_get_type (void); - -G_END_DECLS - -#endif /* SOUP_AUTH_MANAGER_NTLM_NTLM_H */ diff --git a/libsoup/soup-auth-manager.c b/libsoup/soup-auth-manager.c index b3ef9f65..f40a9282 100644 --- a/libsoup/soup-auth-manager.c +++ b/libsoup/soup-auth-manager.c @@ -13,12 +13,43 @@ #include "soup-auth-manager.h" #include "soup.h" -#include "soup-marshal.h" +#include "soup-connection-auth.h" #include "soup-message-private.h" #include "soup-message-queue.h" #include "soup-path-map.h" #include "soup-session-private.h" +/** + * SECTION:soup-auth-manager + * @short_description: HTTP client-side authentication handler + * @see_also: #SoupSession, #SoupAuth + * + * #SoupAuthManager is the #SoupSessionFeature that handles HTTP + * authentication for a #SoupSession. + * + * A #SoupAuthManager is added to the session by default, and normally + * you don't need to worry about it at all. However, if you want to + * disable HTTP authentication, you can remove the feature from the + * session with soup_session_remove_feature_by_type(), or disable it on + * individual requests with soup_message_disable_feature(). + * + * Since: 2.42 + **/ + +/** + * SOUP_TYPE_AUTH_MANAGER: + * + * The #GType of #SoupAuthManager; you can use this with + * soup_session_remove_feature_by_type() or + * soup_message_disable_feature(). + * + * (Although this type has only been publicly visible since libsoup + * 2.42, it has always existed in the background, and you can use + * <literal><code>g_type_from_name ("SoupAuthManager")</code></literal> + * to get its #GType in earlier releases.) + * + * Since: 2.42 + */ static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); static SoupSessionFeatureInterface *soup_session_feature_default_interface; @@ -33,14 +64,15 @@ G_DEFINE_TYPE_WITH_CODE (SoupAuthManager, soup_auth_manager, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, soup_auth_manager_session_feature_init)) -typedef struct { +struct SoupAuthManagerPrivate { SoupSession *session; GPtrArray *auth_types; + gboolean auto_ntlm; + GMutex lock; SoupAuth *proxy_auth; GHashTable *auth_hosts; -} SoupAuthManagerPrivate; -#define SOUP_AUTH_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate)) +}; typedef struct { SoupURI *uri; @@ -49,23 +81,29 @@ typedef struct { } SoupAuthHost; static void soup_auth_host_free (SoupAuthHost *host); +static SoupAuth *record_auth_for_uri (SoupAuthManagerPrivate *priv, + SoupURI *uri, SoupAuth *auth, + gboolean prior_auth_failed); static void soup_auth_manager_init (SoupAuthManager *manager) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv; + + priv = manager->priv = G_TYPE_INSTANCE_GET_PRIVATE (manager, SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerPrivate); priv->auth_types = g_ptr_array_new_with_free_func ((GDestroyNotify)g_type_class_unref); priv->auth_hosts = g_hash_table_new_full (soup_uri_host_hash, soup_uri_host_equal, NULL, (GDestroyNotify)soup_auth_host_free); + g_mutex_init (&priv->lock); } static void soup_auth_manager_finalize (GObject *object) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (object); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (object)->priv; g_ptr_array_free (priv->auth_types, TRUE); @@ -73,6 +111,8 @@ soup_auth_manager_finalize (GObject *object) g_clear_object (&priv->proxy_auth); + g_mutex_clear (&priv->lock); + G_OBJECT_CLASS (soup_auth_manager_parent_class)->finalize (object); } @@ -85,13 +125,27 @@ soup_auth_manager_class_init (SoupAuthManagerClass *auth_manager_class) object_class->finalize = soup_auth_manager_finalize; + /** + * SoupAuthManager::authenticate: + * @manager: the #SoupAuthManager + * @msg: the #SoupMessage being sent + * @auth: the #SoupAuth to authenticate + * @retrying: %TRUE if this is the second (or later) attempt + * + * Emitted when the manager requires the application to + * provide authentication credentials. + * + * #SoupSession connects to this signal and emits its own + * #SoupSession::authenticate signal when it is emitted, so + * you shouldn't need to use this signal directly. + */ signals[AUTHENTICATE] = g_signal_new ("authenticate", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupAuthManagerClass, authenticate), NULL, NULL, - _soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN, + NULL, G_TYPE_NONE, 3, SOUP_TYPE_MESSAGE, SOUP_TYPE_AUTH, @@ -111,7 +165,7 @@ auth_type_compare_func (gconstpointer a, gconstpointer b) static gboolean soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv; SoupAuthClass *auth_class; if (!g_type_is_a (type, SOUP_TYPE_AUTH)) @@ -120,13 +174,21 @@ soup_auth_manager_add_feature (SoupSessionFeature *feature, GType type) auth_class = g_type_class_ref (type); g_ptr_array_add (priv->auth_types, auth_class); g_ptr_array_sort (priv->auth_types, auth_type_compare_func); + + /* Plain SoupSession does not get the backward-compat + * auto-NTLM behavior; SoupSession subclasses do. + */ + if (type == SOUP_TYPE_AUTH_NTLM && + G_TYPE_FROM_INSTANCE (priv->session) != SOUP_TYPE_SESSION) + priv->auto_ntlm = TRUE; + return TRUE; } static gboolean soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv; SoupAuthClass *auth_class; int i; @@ -134,8 +196,12 @@ soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type) return FALSE; auth_class = g_type_class_peek (type); + for (i = 0; i < priv->auth_types->len; i++) { if (priv->auth_types->pdata[i] == (gpointer)auth_class) { + if (type == SOUP_TYPE_AUTH_NTLM) + priv->auto_ntlm = FALSE; + g_ptr_array_remove_index (priv->auth_types, i); return TRUE; } @@ -147,7 +213,7 @@ soup_auth_manager_remove_feature (SoupSessionFeature *feature, GType type) static gboolean soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (feature); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv; SoupAuthClass *auth_class; int i; @@ -162,22 +228,15 @@ soup_auth_manager_has_feature (SoupSessionFeature *feature, GType type) return FALSE; } -void -soup_auth_manager_emit_authenticate (SoupAuthManager *manager, SoupMessage *msg, - SoupAuth *auth, gboolean retrying) -{ - g_signal_emit (manager, signals[AUTHENTICATE], 0, msg, auth, retrying); -} - static void -soup_auth_manager_attach (SoupSessionFeature *manager, SoupSession *session) +soup_auth_manager_attach (SoupSessionFeature *feature, SoupSession *session) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (feature)->priv; /* FIXME: should support multiple sessions */ priv->session = session; - soup_session_feature_default_interface->attach (manager, session); + soup_session_feature_default_interface->attach (feature, session); } static inline const char * @@ -252,7 +311,7 @@ next_challenge_start (GSList *items) return NULL; } -char * +static char * soup_auth_manager_extract_challenge (const char *challenges, const char *scheme) { GSList *items, *i, *next; @@ -319,28 +378,30 @@ create_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) static gboolean check_auth (SoupMessage *msg, SoupAuth *auth) { - const char *header; - char *challenge; - gboolean ok; + const char *header, *scheme; + char *challenge = NULL; + gboolean ok = TRUE; - header = auth_header_for_message (msg); - if (!header) - return FALSE; + scheme = soup_auth_get_scheme_name (auth); - challenge = soup_auth_manager_extract_challenge (header, soup_auth_get_scheme_name (auth)); - if (!challenge) - return FALSE; + header = auth_header_for_message (msg); + if (header) + challenge = soup_auth_manager_extract_challenge (header, scheme); + if (!challenge) { + ok = FALSE; + challenge = g_strdup (scheme); + } - ok = soup_auth_update (auth, msg, challenge); + if (!soup_auth_update (auth, msg, challenge)) + ok = FALSE; g_free (challenge); return ok; } static SoupAuthHost * -get_auth_host_for_message (SoupAuthManagerPrivate *priv, SoupMessage *msg) +get_auth_host_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri) { SoupAuthHost *host; - SoupURI *uri = soup_message_get_uri (msg); host = g_hash_table_lookup (priv->auth_hosts, uri); if (host) @@ -363,14 +424,30 @@ soup_auth_host_free (SoupAuthHost *host) g_slice_free (SoupAuthHost, host); } +static gboolean +make_auto_ntlm_auth (SoupAuthManagerPrivate *priv, SoupAuthHost *host) +{ + SoupAuth *auth; + + if (!priv->auto_ntlm) + return FALSE; + + auth = g_object_new (SOUP_TYPE_AUTH_NTLM, + SOUP_AUTH_HOST, host->uri->host, + NULL); + record_auth_for_uri (priv, host->uri, auth, FALSE); + g_object_unref (auth); + return TRUE; +} + static SoupAuth * lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) { SoupAuthHost *host; const char *path, *realm; - host = get_auth_host_for_message (priv, msg); - if (!host->auth_realms) + host = get_auth_host_for_uri (priv, soup_message_get_uri (msg)); + if (!host->auth_realms && !make_auto_ntlm_auth (priv, host)) return NULL; path = soup_message_get_uri (msg)->path; @@ -383,12 +460,12 @@ lookup_auth (SoupAuthManagerPrivate *priv, SoupMessage *msg) return NULL; } -static gboolean +static void authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, SoupMessage *msg, gboolean prior_auth_failed, gboolean proxy, gboolean can_interact) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv = manager->priv; SoupURI *uri; if (proxy) { @@ -404,48 +481,34 @@ authenticate_auth (SoupAuthManager *manager, SoupAuth *auth, uri = NULL; if (!uri) - return FALSE; + return; } else uri = soup_message_get_uri (msg); /* If a password is specified explicitly in the URI, use it * even if the auth had previously already been authenticated. */ - if (uri->password) { - if (!prior_auth_failed) - soup_auth_authenticate (auth, uri->user, uri->password); + if (uri->password && uri->user) { + soup_auth_authenticate (auth, uri->user, uri->password); + soup_uri_set_password (uri, NULL); + soup_uri_set_user (uri, NULL); } else if (!soup_auth_is_authenticated (auth) && can_interact) { - soup_auth_manager_emit_authenticate (manager, msg, auth, - prior_auth_failed); + g_signal_emit (manager, signals[AUTHENTICATE], 0, + msg, auth, prior_auth_failed); } - - return soup_auth_is_authenticated (auth); } -static void -update_auth (SoupMessage *msg, gpointer manager) +static SoupAuth * +record_auth_for_uri (SoupAuthManagerPrivate *priv, SoupURI *uri, + SoupAuth *auth, gboolean prior_auth_failed) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); SoupAuthHost *host; - SoupAuth *auth, *prior_auth, *old_auth; + SoupAuth *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 (priv, msg); - /* See if we used auth last time */ - prior_auth = soup_message_get_auth (msg); - if (prior_auth && check_auth (msg, prior_auth)) { - auth = prior_auth; - if (!soup_auth_is_authenticated (auth)) - prior_auth_failed = TRUE; - } else { - auth = create_auth (priv, msg); - if (!auth) - return; - } + host = get_auth_host_for_uri (priv, uri); auth_info = soup_auth_get_info (auth); if (!host->auth_realms) { @@ -455,7 +518,7 @@ update_auth (SoupMessage *msg, gpointer manager) } /* Record where this auth realm is used. */ - pspace = soup_auth_get_protection_space (auth, soup_message_get_uri (msg)); + pspace = soup_auth_get_protection_space (auth, uri); for (p = pspace; p; p = p->next) { path = p->data; old_auth_info = soup_path_map_lookup (host->auth_realms, path); @@ -471,68 +534,116 @@ update_auth (SoupMessage *msg, gpointer manager) 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, + * pre-existing good 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) { + if (old_auth && (old_auth != auth || !prior_auth_failed)) { g_free (auth_info); - if (auth != old_auth && auth != prior_auth) { - g_object_unref (auth); - auth = old_auth; - } + return old_auth; + } else { + g_hash_table_insert (host->auths, auth_info, + g_object_ref (auth)); + return auth; + } +} + +static void +auth_got_headers (SoupMessage *msg, gpointer manager) +{ + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv; + SoupAuth *auth, *prior_auth, *new_auth; + gboolean prior_auth_failed = FALSE; + + g_mutex_lock (&priv->lock); + + /* See if we used auth last time */ + prior_auth = soup_message_get_auth (msg); + if (prior_auth && check_auth (msg, prior_auth)) { + auth = g_object_ref (prior_auth); + if (!soup_auth_is_ready (auth, msg)) + prior_auth_failed = TRUE; } else { - g_hash_table_insert (host->auths, auth_info, auth); + auth = create_auth (priv, msg); + if (!auth) { + g_mutex_unlock (&priv->lock); + return; + } } + new_auth = record_auth_for_uri (priv, soup_message_get_uri (msg), + auth, prior_auth_failed); + g_object_unref (auth); + /* If we need to authenticate, try to do it. */ - authenticate_auth (manager, auth, msg, + authenticate_auth (manager, new_auth, msg, prior_auth_failed, FALSE, TRUE); + g_mutex_unlock (&priv->lock); } static void -requeue_if_authenticated (SoupMessage *msg, gpointer manager) +auth_got_body (SoupMessage *msg, gpointer manager) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); - SoupAuth *auth = lookup_auth (priv, msg); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv; + SoupAuth *auth; + + g_mutex_lock (&priv->lock); + auth = lookup_auth (priv, msg); + if (auth && soup_auth_is_ready (auth, msg)) { + if (SOUP_IS_CONNECTION_AUTH (auth)) { + SoupMessageFlags flags; + + flags = soup_message_get_flags (msg); + soup_message_set_flags (msg, flags & ~SOUP_MESSAGE_NEW_CONNECTION); + } - if (auth && soup_auth_is_authenticated (auth)) soup_session_requeue_message (priv->session, msg); + } + g_mutex_unlock (&priv->lock); } static void -update_proxy_auth (SoupMessage *msg, gpointer manager) +proxy_auth_got_headers (SoupMessage *msg, gpointer manager) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv; SoupAuth *prior_auth; gboolean prior_auth_failed = FALSE; + g_mutex_lock (&priv->lock); + /* See if we used auth last time */ prior_auth = soup_message_get_proxy_auth (msg); if (prior_auth && check_auth (msg, prior_auth)) { - if (!soup_auth_is_authenticated (prior_auth)) + if (!soup_auth_is_ready (prior_auth, msg)) prior_auth_failed = TRUE; } if (!priv->proxy_auth) { priv->proxy_auth = create_auth (priv, msg); - if (!priv->proxy_auth) + if (!priv->proxy_auth) { + g_mutex_unlock (&priv->lock); return; + } } /* If we need to authenticate, try to do it. */ authenticate_auth (manager, priv->proxy_auth, msg, prior_auth_failed, TRUE, TRUE); + g_mutex_unlock (&priv->lock); } static void -requeue_if_proxy_authenticated (SoupMessage *msg, gpointer manager) +proxy_auth_got_body (SoupMessage *msg, gpointer manager) { - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); - SoupAuth *auth = priv->proxy_auth; + SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER (manager)->priv; + SoupAuth *auth; + + g_mutex_lock (&priv->lock); + auth = priv->proxy_auth; - if (auth && soup_auth_is_authenticated (auth)) + if (auth && soup_auth_is_ready (auth, msg)) soup_session_requeue_message (priv->session, msg); + g_mutex_unlock (&priv->lock); } static void @@ -542,17 +653,17 @@ soup_auth_manager_request_queued (SoupSessionFeature *manager, { soup_message_add_status_code_handler ( msg, "got_headers", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (update_auth), manager); + G_CALLBACK (auth_got_headers), manager); soup_message_add_status_code_handler ( msg, "got_body", SOUP_STATUS_UNAUTHORIZED, - G_CALLBACK (requeue_if_authenticated), manager); + G_CALLBACK (auth_got_body), manager); soup_message_add_status_code_handler ( msg, "got_headers", SOUP_STATUS_PROXY_UNAUTHORIZED, - G_CALLBACK (update_proxy_auth), manager); + G_CALLBACK (proxy_auth_got_headers), manager); soup_message_add_status_code_handler ( msg, "got_body", SOUP_STATUS_PROXY_UNAUTHORIZED, - G_CALLBACK (requeue_if_proxy_authenticated), manager); + G_CALLBACK (proxy_auth_got_body), manager); } static void @@ -562,18 +673,30 @@ soup_auth_manager_request_started (SoupSessionFeature *feature, SoupSocket *socket) { SoupAuthManager *manager = SOUP_AUTH_MANAGER (feature); - SoupAuthManagerPrivate *priv = SOUP_AUTH_MANAGER_GET_PRIVATE (manager); + SoupAuthManagerPrivate *priv = manager->priv; SoupAuth *auth; - auth = lookup_auth (priv, msg); - if (!auth || !authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE)) - auth = NULL; - soup_message_set_auth (msg, auth); + g_mutex_lock (&priv->lock); + + if (msg->method != SOUP_METHOD_CONNECT) { + auth = lookup_auth (priv, msg); + if (auth) { + authenticate_auth (manager, auth, msg, FALSE, FALSE, FALSE); + if (!soup_auth_is_ready (auth, msg)) + auth = NULL; + } + soup_message_set_auth (msg, auth); + } auth = priv->proxy_auth; - if (!auth || !authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE)) - auth = NULL; + if (auth) { + authenticate_auth (manager, auth, msg, FALSE, TRUE, FALSE); + if (!soup_auth_is_ready (auth, msg)) + auth = NULL; + } soup_message_set_proxy_auth (msg, auth); + + g_mutex_unlock (&priv->lock); } static void @@ -585,6 +708,36 @@ soup_auth_manager_request_unqueued (SoupSessionFeature *manager, 0, 0, NULL, NULL, manager); } +/** + * soup_auth_manager_use_auth: + * @manager: a #SoupAuthManager + * @uri: the #SoupURI under which @auth is to be used + * @auth: the #SoupAuth to use + * + * Records that @auth is to be used under @uri, as though a + * WWW-Authenticate header had been received at that URI. This can be + * used to "preload" @manager's auth cache, to avoid an extra HTTP + * round trip in the case where you know ahead of time that a 401 + * response will be returned. + * + * This is only useful for authentication types where the initial + * Authorization header does not depend on any additional information + * from the server. (Eg, Basic or NTLM, but not Digest.) + * + * Since: 2.42 + */ +void +soup_auth_manager_use_auth (SoupAuthManager *manager, + SoupURI *uri, + SoupAuth *auth) +{ + SoupAuthManagerPrivate *priv = manager->priv; + + g_mutex_lock (&priv->lock); + record_auth_for_uri (priv, uri, auth, FALSE); + g_mutex_unlock (&priv->lock); +} + static void soup_auth_manager_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data) diff --git a/libsoup/soup-auth-manager.h b/libsoup/soup-auth-manager.h index d82fbb1d..c1fcc6e6 100644 --- a/libsoup/soup-auth-manager.h +++ b/libsoup/soup-auth-manager.h @@ -18,9 +18,12 @@ G_BEGIN_DECLS #define SOUP_IS_AUTH_MANAGER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_AUTH_MANAGER)) #define SOUP_AUTH_MANAGER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_MANAGER, SoupAuthManagerClass)) +typedef struct SoupAuthManagerPrivate SoupAuthManagerPrivate; + typedef struct { GObject parent; + SoupAuthManagerPrivate *priv; } SoupAuthManager; typedef struct { @@ -32,13 +35,9 @@ typedef struct { GType soup_auth_manager_get_type (void); -void soup_auth_manager_emit_authenticate (SoupAuthManager *manager, - SoupMessage *msg, - SoupAuth *auth, - gboolean retrying); - -char *soup_auth_manager_extract_challenge (const char *challenges, - const char *scheme); +void soup_auth_manager_use_auth (SoupAuthManager *manager, + SoupURI *uri, + SoupAuth *auth); G_END_DECLS diff --git a/libsoup/soup-auth-ntlm.c b/libsoup/soup-auth-ntlm.c index 391c1c28..05541973 100644 --- a/libsoup/soup-auth-ntlm.c +++ b/libsoup/soup-auth-ntlm.c @@ -9,21 +9,114 @@ #include <config.h> #endif +#include <errno.h> +#include <stdlib.h> #include <string.h> +#include <glib.h> #include "soup-auth-ntlm.h" #include "soup.h" +#include "soup-message-private.h" + +static void soup_ntlm_lanmanager_hash (const char *password, + guchar hash[21]); +static void soup_ntlm_nt_hash (const char *password, + guchar hash[21]); +static char *soup_ntlm_request (void); +static gboolean soup_ntlm_parse_challenge (const char *challenge, + char **nonce, + char **default_domain); +static char *soup_ntlm_response (const char *nonce, + const char *user, + guchar nt_hash[21], + guchar lm_hash[21], + const char *host, + const char *domain); + +typedef enum { + SOUP_NTLM_NEW, + SOUP_NTLM_SSO_FAILED, + SOUP_NTLM_SENT_REQUEST, + SOUP_NTLM_RECEIVED_CHALLENGE, + SOUP_NTLM_SENT_RESPONSE, + SOUP_NTLM_FAILED +} SoupNTLMState; + +typedef struct { + SoupNTLMState state; + char *nonce; + char *response_header; +} SoupNTLMConnectionState; + +typedef enum { + SOUP_NTLM_PASSWORD_NONE, + SOUP_NTLM_PASSWORD_PROVIDED, + SOUP_NTLM_PASSWORD_ACCEPTED, + SOUP_NTLM_PASSWORD_REJECTED +} SoupNTLMPasswordState; typedef struct { - char *username, *password; + char *username, *domain; + guchar nt_hash[21], lm_hash[21]; + SoupNTLMPasswordState password_state; + +#ifdef USE_NTLM_AUTH + /* Use Samba's 'winbind' daemon to support NTLM single-sign-on, + * by delegating the NTLM challenge/response protocal to a helper + * in ntlm_auth. + * http://devel.squid-cache.org/ntlm/squid_helper_protocol.html + * http://www.samba.org/samba/docs/man/manpages-3/winbindd.8.html + * http://www.samba.org/samba/docs/man/manpages-3/ntlm_auth.1.html + */ + gboolean sso_available; + int fd_in; + int fd_out; +#endif } 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) +#ifdef USE_NTLM_AUTH +static gboolean ntlm_auth_available, ntlm_auth_debug; +static void sso_ntlm_close (SoupAuthNTLMPrivate *priv); +#endif + +/** + * SOUP_TYPE_AUTH_NTLM: + * + * A #GType corresponding to HTTP-based NTLM authentication. + * #SoupSessions do not support this type by default; if you want to + * enable support for it, call soup_session_add_feature_by_type(), + * passing %SOUP_TYPE_AUTH_NTLM. + * + * Since: 2.34 + */ + +G_DEFINE_TYPE (SoupAuthNTLM, soup_auth_ntlm, SOUP_TYPE_CONNECTION_AUTH) static void soup_auth_ntlm_init (SoupAuthNTLM *ntlm) { +#ifdef USE_NTLM_AUTH + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (ntlm); + const char *username = NULL, *slash; + + priv->sso_available = TRUE; + priv->fd_in = -1; + priv->fd_out = -1; + + username = getenv ("NTLMUSER"); + if (!username) + username = g_get_user_name (); + + slash = strpbrk (username, "\\/"); + if (slash) { + priv->username = g_strdup (slash + 1); + priv->domain = g_strndup (username, slash - username); + } else { + priv->username = g_strdup (username); + priv->domain = NULL; + } +#endif } static void @@ -32,37 +125,267 @@ soup_auth_ntlm_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_free (priv->domain); + + memset (priv->nt_hash, 0, sizeof (priv->nt_hash)); + memset (priv->lm_hash, 0, sizeof (priv->lm_hash)); + +#ifdef USE_NTLM_AUTH + sso_ntlm_close (priv); +#endif G_OBJECT_CLASS (soup_auth_ntlm_parent_class)->finalize (object); } -SoupAuth * -soup_auth_ntlm_new (const char *realm, const char *host) +#ifdef USE_NTLM_AUTH +static void +sso_ntlm_close (SoupAuthNTLMPrivate *priv) { - SoupAuth *auth; + if (priv->fd_in != -1) { + close (priv->fd_in); + priv->fd_in = -1; + } - auth = g_object_new (SOUP_TYPE_AUTH_NTLM, - SOUP_AUTH_REALM, realm, - SOUP_AUTH_HOST, host, - NULL); - return auth; + if (priv->fd_out != -1) { + close (priv->fd_out); + priv->fd_out = -1; + } } static gboolean -soup_auth_ntlm_update (SoupAuth *auth, SoupMessage *msg, - GHashTable *auth_params) +sso_ntlm_initiate (SoupAuthNTLMPrivate *priv) { - g_return_val_if_reached (FALSE); + char *argv[9]; + gboolean ret; + + if (!priv->sso_available) + return FALSE; + + if (!ntlm_auth_available && !ntlm_auth_debug) { + priv->sso_available = FALSE; + return FALSE; + } + + /* Return if ntlm_auth execution process exist already */ + if (priv->fd_in != -1 && priv->fd_out != -1) + return TRUE; + else { + /* Clean all sso data before re-initiate */ + sso_ntlm_close (priv); + } + + if (ntlm_auth_debug) { + argv[0] = (char *) g_getenv ("SOUP_NTLM_AUTH_DEBUG"); + if (!*argv[0]) { + priv->sso_available = FALSE; + return FALSE; + } + } else + argv[0] = NTLM_AUTH; + argv[1] = "--helper-protocol"; + argv[2] = "ntlmssp-client-1"; + argv[3] = "--use-cached-creds"; + argv[4] = "--username"; + argv[5] = priv->username; + argv[6] = priv->domain ? "--domain" : NULL; + argv[7] = priv->domain; + argv[8] = NULL; + + ret = g_spawn_async_with_pipes (NULL, argv, NULL, + G_SPAWN_STDERR_TO_DEV_NULL, + NULL, NULL, + NULL, &priv->fd_in, &priv->fd_out, + NULL, NULL); + if (!ret) + priv->sso_available = FALSE; + return ret; +} + +static char * +sso_ntlm_response (SoupAuthNTLMPrivate *priv, const char *input, SoupNTLMState conn_state) +{ + ssize_t size; + char buf[1024]; + char *tmpbuf = buf; + size_t len_in = strlen (input), len_out = sizeof (buf); + + while (len_in > 0) { + int written = write (priv->fd_in, input, len_in); + if (written == -1) { + if (errno == EINTR) + continue; + /* write failed if other errors happen */ + return NULL; + } + input += written; + len_in -= written; + } + /* Read one line */ + while (len_out > 0) { + size = read (priv->fd_out, tmpbuf, len_out); + if (size == -1) { + if (errno == EINTR) + continue; + return NULL; + } else if (size == 0) + return NULL; + else if (tmpbuf[size - 1] == '\n') { + tmpbuf[size - 1] = '\0'; + goto wrfinish; + } + tmpbuf += size; + len_out -= size; + } + return NULL; + +wrfinish: + if (g_ascii_strcasecmp (buf, "PW") == 0) { + /* Samba/winbind installed but not configured */ + return g_strdup ("PW"); + } + if (conn_state == SOUP_NTLM_NEW && + g_ascii_strncasecmp (buf, "YR ", 3) != 0) { + /* invalid response for type 1 message */ + return NULL; + } + if (conn_state == SOUP_NTLM_RECEIVED_CHALLENGE && + g_ascii_strncasecmp (buf, "KK ", 3) != 0 && + g_ascii_strncasecmp (buf, "AF ", 3) != 0) { + /* invalid response for type 3 message */ + return NULL; + } + + return g_strdup_printf ("NTLM %.*s", (int)(size - 4), buf + 3); +} +#endif /* USE_NTLM_AUTH */ + +static gpointer +soup_auth_ntlm_create_connection_state (SoupConnectionAuth *auth) +{ + SoupNTLMConnectionState *conn; + + conn = g_slice_new0 (SoupNTLMConnectionState); + conn->state = SOUP_NTLM_NEW; + + return conn; +} + +static void +soup_auth_ntlm_free_connection_state (SoupConnectionAuth *auth, + gpointer state) +{ + SoupNTLMConnectionState *conn = state; + + g_free (conn->nonce); + g_free (conn->response_header); + g_slice_free (SoupNTLMConnectionState, conn); +} + +static gboolean +soup_auth_ntlm_update_connection (SoupConnectionAuth *auth, SoupMessage *msg, + const char *auth_header, gpointer state) +{ + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + SoupNTLMConnectionState *conn = state; + gboolean success = TRUE; + + /* Note that we only return FALSE if some sort of parsing error + * occurs. Otherwise, the SoupAuth is still reusable (though it may + * no longer be _ready or _authenticated). + */ + + if (!g_str_has_prefix (auth_header, "NTLM")) + return FALSE; + + if (conn->state > SOUP_NTLM_SENT_REQUEST) { + if (priv->password_state == SOUP_NTLM_PASSWORD_ACCEPTED) { + /* We know our password is correct, so a 401 + * means "permission denied". Since the conn + * state is now FAILED, the auth is no longer + * is_ready() for this message, so this will + * cause a "retrying" authenticate signal. + */ + conn->state = SOUP_NTLM_FAILED; + return TRUE; + } + +#ifdef USE_NTLM_AUTH + if (priv->sso_available) { + conn->state = SOUP_NTLM_SSO_FAILED; + priv->password_state = SOUP_NTLM_PASSWORD_NONE; + } else { +#endif + conn->state = SOUP_NTLM_FAILED; + priv->password_state = SOUP_NTLM_PASSWORD_REJECTED; +#ifdef USE_NTLM_AUTH + } +#endif + return TRUE; + } + + if (conn->state == SOUP_NTLM_NEW && !auth_header[4]) + return TRUE; + + if (!soup_ntlm_parse_challenge (auth_header + 5, &conn->nonce, + priv->domain ? NULL : &priv->domain)) { + conn->state = SOUP_NTLM_FAILED; + return FALSE; + } + +#ifdef USE_NTLM_AUTH + if (priv->sso_available && conn->state == SOUP_NTLM_SENT_REQUEST) { + char *input, *response; + + /* Re-Initiate ntlm_auth process in case it was closed/killed abnormally */ + if (!sso_ntlm_initiate (priv)) { + conn->state = SOUP_NTLM_SSO_FAILED; + success = FALSE; + goto out; + } + + input = g_strdup_printf ("TT %s\n", auth_header + 5); + response = sso_ntlm_response (priv, input, conn->state); + sso_ntlm_close (priv); + g_free (input); + + if (!response) { + conn->state = SOUP_NTLM_SSO_FAILED; + success = FALSE; + } else if (!g_ascii_strcasecmp (response, "PW")) { + priv->sso_available = FALSE; + g_free (response); + } else { + conn->response_header = response; + if (priv->password_state != SOUP_NTLM_PASSWORD_ACCEPTED) + priv->password_state = SOUP_NTLM_PASSWORD_PROVIDED; + } + } + out: +#endif + + if (conn->state == SOUP_NTLM_SENT_REQUEST) + conn->state = SOUP_NTLM_RECEIVED_CHALLENGE; + + g_object_set (G_OBJECT (auth), + SOUP_AUTH_REALM, priv->domain, + SOUP_AUTH_HOST, soup_message_get_uri (msg)->host, + NULL); + return success; } static GSList * soup_auth_ntlm_get_protection_space (SoupAuth *auth, SoupURI *source_uri) { - g_return_val_if_reached (NULL); + char *space, *p; + + space = g_strdup (source_uri->path); + + /* Strip filename component */ + p = strrchr (space, '/'); + if (p && p != space && p[1]) + *p = '\0'; + + return g_slist_prepend (NULL, space); } static void @@ -70,30 +393,148 @@ soup_auth_ntlm_authenticate (SoupAuth *auth, const char *username, const char *password) { SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + const char *slash; g_return_if_fail (username != NULL); g_return_if_fail (password != NULL); - priv->username = g_strdup (username); - priv->password = g_strdup (password); + if (priv->username) + g_free (priv->username); + if (priv->domain) + g_free (priv->domain); + + slash = strpbrk (username, "\\/"); + if (slash) { + priv->domain = g_strndup (username, slash - username); + priv->username = g_strdup (slash + 1); + } else { + priv->domain = g_strdup (""); + priv->username = g_strdup (username); + } + + soup_ntlm_nt_hash (password, priv->nt_hash); + soup_ntlm_lanmanager_hash (password, priv->lm_hash); + + priv->password_state = SOUP_NTLM_PASSWORD_PROVIDED; } static gboolean soup_auth_ntlm_is_authenticated (SoupAuth *auth) { - return SOUP_AUTH_NTLM_GET_PRIVATE (auth)->password != NULL; + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + + return (priv->password_state != SOUP_NTLM_PASSWORD_NONE && + priv->password_state != SOUP_NTLM_PASSWORD_REJECTED); +} + +static gboolean +soup_auth_ntlm_is_connection_ready (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer state) +{ + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + SoupNTLMConnectionState *conn = state; + + if (priv->password_state == SOUP_NTLM_PASSWORD_REJECTED) + return FALSE; + + if (priv->password_state == SOUP_NTLM_PASSWORD_PROVIDED) + return TRUE; + + return conn->state != SOUP_NTLM_FAILED; +} + +static void +got_final_auth_result (SoupMessage *msg, gpointer data) +{ + SoupAuth *auth = data; + + g_signal_handlers_disconnect_by_func (msg, G_CALLBACK (got_final_auth_result), auth); + + if (auth != soup_message_get_auth (msg)) + return; + + if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) + SOUP_AUTH_NTLM_GET_PRIVATE (auth)->password_state = SOUP_NTLM_PASSWORD_ACCEPTED; } static char * -soup_auth_ntlm_get_authorization (SoupAuth *auth, SoupMessage *msg) +soup_auth_ntlm_get_connection_authorization (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer state) { - g_return_val_if_reached (NULL); + SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + SoupNTLMConnectionState *conn = state; + char *header = NULL; + + switch (conn->state) { + case SOUP_NTLM_NEW: +#ifdef USE_NTLM_AUTH + if (sso_ntlm_initiate (priv)) { + header = sso_ntlm_response (priv, "YR\n", conn->state); + if (header) { + if (g_ascii_strcasecmp (header, "PW") != 0) { + conn->state = SOUP_NTLM_SENT_REQUEST; + break; + } else { + g_free (header); + header = NULL; + priv->sso_available = FALSE; + } + } else { + g_warning ("NTLM single-sign-on using %s failed", NTLM_AUTH); + } + } + /* If NTLM single-sign-on fails, go back to original + * request handling process. + */ +#endif + header = soup_ntlm_request (); + conn->state = SOUP_NTLM_SENT_REQUEST; + break; + case SOUP_NTLM_RECEIVED_CHALLENGE: + if (conn->response_header) { + header = conn->response_header; + conn->response_header = NULL; + } else { + header = soup_ntlm_response (conn->nonce, + priv->username, + priv->nt_hash, + priv->lm_hash, + NULL, + priv->domain); + } + g_clear_pointer (&conn->nonce, g_free); + conn->state = SOUP_NTLM_SENT_RESPONSE; + + if (priv->password_state != SOUP_NTLM_PASSWORD_ACCEPTED) { + /* We need to know if this worked */ + g_signal_connect (msg, "got-headers", + G_CALLBACK (got_final_auth_result), + auth); + } + break; +#ifdef USE_NTLM_AUTH + case SOUP_NTLM_SSO_FAILED: + /* Restart request without SSO */ + g_warning ("NTLM single-sign-on by using %s failed", NTLM_AUTH); + priv->sso_available = FALSE; + header = soup_ntlm_request (); + conn->state = SOUP_NTLM_SENT_REQUEST; + break; +#endif + default: + break; + } + + return header; } static void soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class) { SoupAuthClass *auth_class = SOUP_AUTH_CLASS (auth_ntlm_class); + SoupConnectionAuthClass *connauth_class = SOUP_CONNECTION_AUTH_CLASS (auth_ntlm_class); GObjectClass *object_class = G_OBJECT_CLASS (auth_ntlm_class); g_type_class_add_private (auth_ntlm_class, sizeof (SoupAuthNTLMPrivate)); @@ -101,27 +542,721 @@ soup_auth_ntlm_class_init (SoupAuthNTLMClass *auth_ntlm_class) auth_class->scheme_name = "NTLM"; auth_class->strength = 3; - auth_class->update = soup_auth_ntlm_update; auth_class->get_protection_space = soup_auth_ntlm_get_protection_space; auth_class->authenticate = soup_auth_ntlm_authenticate; auth_class->is_authenticated = soup_auth_ntlm_is_authenticated; - auth_class->get_authorization = soup_auth_ntlm_get_authorization; + + connauth_class->create_connection_state = soup_auth_ntlm_create_connection_state; + connauth_class->free_connection_state = soup_auth_ntlm_free_connection_state; + connauth_class->update_connection = soup_auth_ntlm_update_connection; + connauth_class->get_connection_authorization = soup_auth_ntlm_get_connection_authorization; + connauth_class->is_connection_ready = soup_auth_ntlm_is_connection_ready; object_class->finalize = soup_auth_ntlm_finalize; + +#ifdef USE_NTLM_AUTH + ntlm_auth_available = g_file_test (NTLM_AUTH, G_FILE_TEST_IS_EXECUTABLE); + ntlm_auth_debug = (g_getenv ("SOUP_NTLM_AUTH_DEBUG") != NULL); +#endif +} + +static void md4sum (const unsigned char *in, + int nbytes, + unsigned char digest[16]); + +typedef guint32 DES_KS[16][2]; /* Single-key DES key schedule */ + +static void deskey (DES_KS, unsigned char *, int); + +static void des (DES_KS, unsigned char *); + +static void setup_schedule (const guchar *key_56, DES_KS ks); + +static void calc_response (const guchar *key, + const guchar *plaintext, + guchar *results); + +#define LM_PASSWORD_MAGIC "\x4B\x47\x53\x21\x40\x23\x24\x25" \ + "\x4B\x47\x53\x21\x40\x23\x24\x25" \ + "\x00\x00\x00\x00\x00" + +static void +soup_ntlm_lanmanager_hash (const char *password, guchar hash[21]) +{ + guchar lm_password [15]; + DES_KS ks; + int i; + + for (i = 0; i < 14 && password [i]; i++) + lm_password [i] = g_ascii_toupper ((unsigned char) password [i]); + + for (; i < 15; i++) + lm_password [i] = '\0'; + + memcpy (hash, LM_PASSWORD_MAGIC, 21); + + setup_schedule (lm_password, ks); + des (ks, hash); + + setup_schedule (lm_password + 7, ks); + des (ks, hash + 8); } -const char * -soup_auth_ntlm_get_username (SoupAuth *auth) +static void +soup_ntlm_nt_hash (const char *password, guchar hash[21]) { - SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + unsigned char *buf, *p; + + p = buf = g_malloc (strlen (password) * 2); + + while (*password) { + *p++ = *password++; + *p++ = '\0'; + } + + md4sum (buf, p - buf, hash); + memset (hash + 16, 0, 5); - return priv->username; + g_free (buf); } -const char * -soup_auth_ntlm_get_password (SoupAuth *auth) +typedef struct { + guint16 length; + guint16 length2; + guint16 offset; + guchar zero_pad[2]; +} NTLMString; + +#define NTLM_CHALLENGE_NONCE_OFFSET 24 +#define NTLM_CHALLENGE_NONCE_LENGTH 8 +#define NTLM_CHALLENGE_DOMAIN_STRING_OFFSET 12 + +#define NTLM_RESPONSE_HEADER "NTLMSSP\x00\x03\x00\x00\x00" +#define NTLM_RESPONSE_FLAGS 0x8201 + +typedef struct { + guchar header[12]; + + NTLMString lm_resp; + NTLMString nt_resp; + NTLMString domain; + NTLMString user; + NTLMString host; + NTLMString session_key; + + guint32 flags; +} NTLMResponse; + +static void +ntlm_set_string (NTLMString *string, int *offset, int len) { - SoupAuthNTLMPrivate *priv = SOUP_AUTH_NTLM_GET_PRIVATE (auth); + string->offset = GUINT16_TO_LE (*offset); + string->length = string->length2 = GUINT16_TO_LE (len); + *offset += len; +} + +static char * +soup_ntlm_request (void) +{ + return g_strdup ("NTLM TlRMTVNTUAABAAAABYIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA"); +} + +static gboolean +soup_ntlm_parse_challenge (const char *challenge, + char **nonce, + char **default_domain) +{ + gsize clen; + NTLMString domain; + guchar *chall; + + chall = g_base64_decode (challenge, &clen); + if (clen < NTLM_CHALLENGE_DOMAIN_STRING_OFFSET || + clen < NTLM_CHALLENGE_NONCE_OFFSET + NTLM_CHALLENGE_NONCE_LENGTH) { + g_free (chall); + return FALSE; + } + + if (default_domain) { + memcpy (&domain, chall + NTLM_CHALLENGE_DOMAIN_STRING_OFFSET, sizeof (domain)); + domain.length = GUINT16_FROM_LE (domain.length); + domain.offset = GUINT16_FROM_LE (domain.offset); + + if (clen < domain.length + domain.offset) { + g_free (chall); + return FALSE; + } + + *default_domain = g_convert ((char *)chall + domain.offset, + domain.length, "UTF-8", "UCS-2LE", + NULL, NULL, NULL); + } + + if (nonce) { + *nonce = g_memdup (chall + NTLM_CHALLENGE_NONCE_OFFSET, + NTLM_CHALLENGE_NONCE_LENGTH); + } + + g_free (chall); + return TRUE; +} + +static char * +soup_ntlm_response (const char *nonce, + const char *user, + guchar nt_hash[21], + guchar lm_hash[21], + const char *host, + const char *domain) +{ + int offset; + gsize hlen, dlen, ulen; + guchar lm_resp[24], nt_resp[24]; + char *user_conv, *host_conv, *domain_conv; + NTLMResponse resp; + char *out, *p; + int state, save; + + calc_response (nt_hash, (guchar *)nonce, nt_resp); + calc_response (lm_hash, (guchar *)nonce, lm_resp); + + memset (&resp, 0, sizeof (resp)); + memcpy (resp.header, NTLM_RESPONSE_HEADER, sizeof (resp.header)); + resp.flags = GUINT32_TO_LE (NTLM_RESPONSE_FLAGS); + + offset = sizeof (resp); + + if (!host) + host = "UNKNOWN"; + + domain_conv = g_convert (domain, -1, "UCS-2LE", "UTF-8", NULL, &dlen, NULL); + user_conv = g_convert (user, -1, "UCS-2LE", "UTF-8", NULL, &ulen, NULL); + host_conv = g_convert (host, -1, "UCS-2LE", "UTF-8", NULL, &hlen, NULL); + + ntlm_set_string (&resp.domain, &offset, dlen); + ntlm_set_string (&resp.user, &offset, ulen); + ntlm_set_string (&resp.host, &offset, hlen); + ntlm_set_string (&resp.lm_resp, &offset, sizeof (lm_resp)); + ntlm_set_string (&resp.nt_resp, &offset, sizeof (nt_resp)); + + out = g_malloc (((offset + 3) * 4) / 3 + 6); + strncpy (out, "NTLM ", 5); + p = out + 5; + + state = save = 0; + + p += g_base64_encode_step ((const guchar *) &resp, sizeof (resp), + FALSE, p, &state, &save); + p += g_base64_encode_step ((const guchar *) domain_conv, dlen, + FALSE, p, &state, &save); + p += g_base64_encode_step ((const guchar *) user_conv, ulen, + FALSE, p, &state, &save); + p += g_base64_encode_step ((const guchar *) host_conv, hlen, + FALSE, p, &state, &save); + p += g_base64_encode_step (lm_resp, sizeof (lm_resp), + FALSE, p, &state, &save); + p += g_base64_encode_step (nt_resp, sizeof (nt_resp), + FALSE, p, &state, &save); + p += g_base64_encode_close (FALSE, p, &state, &save); + *p = '\0'; + + g_free (domain_conv); + g_free (user_conv); + g_free (host_conv); + + return out; +} + +/* DES utils */ +/* Set up a key schedule based on a 56bit key */ +static void +setup_schedule (const guchar *key_56, DES_KS ks) +{ + guchar key[8]; + int i, c, bit; + + key[0] = (key_56[0]) ; + key[1] = (key_56[1] >> 1) | ((key_56[0] << 7) & 0xFF); + key[2] = (key_56[2] >> 2) | ((key_56[1] << 6) & 0xFF); + key[3] = (key_56[3] >> 3) | ((key_56[2] << 5) & 0xFF); + key[4] = (key_56[4] >> 4) | ((key_56[3] << 4) & 0xFF); + key[5] = (key_56[5] >> 5) | ((key_56[4] << 3) & 0xFF); + key[6] = (key_56[6] >> 6) | ((key_56[5] << 2) & 0xFF); + key[7] = ((key_56[6] << 1) & 0xFF); + + /* Fix parity */ + for (i = 0; i < 8; i++) { + for (c = bit = 0; bit < 8; bit++) + if (key[i] & (1 << bit)) + c++; + if (!(c & 1)) + key[i] ^= 0x01; + } + + deskey (ks, key, 0); +} + +static void +calc_response (const guchar *key, const guchar *plaintext, guchar *results) +{ + DES_KS ks; + + memcpy (results, plaintext, 8); + memcpy (results + 8, plaintext, 8); + memcpy (results + 16, plaintext, 8); + + setup_schedule (key, ks); + des (ks, results); + + setup_schedule (key + 7, ks); + des (ks, results + 8); + + setup_schedule (key + 14, ks); + des (ks, results + 16); +} - return priv->password; + +/* + * MD4 encoder. (The one everyone else uses is not GPL-compatible; + * this is a reimplementation from spec.) This doesn't need to be + * efficient for our purposes, although it would be nice to fix + * it to not malloc()... + */ + +#define F(X,Y,Z) ( ((X)&(Y)) | ((~(X))&(Z)) ) +#define G(X,Y,Z) ( ((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z)) ) +#define H(X,Y,Z) ( (X)^(Y)^(Z) ) +#define ROT(val, n) ( ((val) << (n)) | ((val) >> (32 - (n))) ) + +static void +md4sum (const unsigned char *in, int nbytes, unsigned char digest[16]) +{ + unsigned char *M; + guint32 A, B, C, D, AA, BB, CC, DD, X[16]; + int pbytes, nbits = nbytes * 8, i, j; + + pbytes = (120 - (nbytes % 64)) % 64; + M = alloca (nbytes + pbytes + 8); + memcpy (M, in, nbytes); + memset (M + nbytes, 0, pbytes + 8); + M[nbytes] = 0x80; + M[nbytes + pbytes] = nbits & 0xFF; + M[nbytes + pbytes + 1] = (nbits >> 8) & 0xFF; + M[nbytes + pbytes + 2] = (nbits >> 16) & 0xFF; + M[nbytes + pbytes + 3] = (nbits >> 24) & 0xFF; + + A = 0x67452301; + B = 0xEFCDAB89; + C = 0x98BADCFE; + D = 0x10325476; + + for (i = 0; i < nbytes + pbytes + 8; i += 64) { + for (j = 0; j < 16; j++) { + X[j] = (M[i + j*4]) | + (M[i + j*4 + 1] << 8) | + (M[i + j*4 + 2] << 16) | + (M[i + j*4 + 3] << 24); + } + + AA = A; + BB = B; + CC = C; + DD = D; + + A = ROT (A + F(B, C, D) + X[0], 3); + D = ROT (D + F(A, B, C) + X[1], 7); + C = ROT (C + F(D, A, B) + X[2], 11); + B = ROT (B + F(C, D, A) + X[3], 19); + A = ROT (A + F(B, C, D) + X[4], 3); + D = ROT (D + F(A, B, C) + X[5], 7); + C = ROT (C + F(D, A, B) + X[6], 11); + B = ROT (B + F(C, D, A) + X[7], 19); + A = ROT (A + F(B, C, D) + X[8], 3); + D = ROT (D + F(A, B, C) + X[9], 7); + C = ROT (C + F(D, A, B) + X[10], 11); + B = ROT (B + F(C, D, A) + X[11], 19); + A = ROT (A + F(B, C, D) + X[12], 3); + D = ROT (D + F(A, B, C) + X[13], 7); + C = ROT (C + F(D, A, B) + X[14], 11); + B = ROT (B + F(C, D, A) + X[15], 19); + + A = ROT (A + G(B, C, D) + X[0] + 0x5A827999, 3); + D = ROT (D + G(A, B, C) + X[4] + 0x5A827999, 5); + C = ROT (C + G(D, A, B) + X[8] + 0x5A827999, 9); + B = ROT (B + G(C, D, A) + X[12] + 0x5A827999, 13); + A = ROT (A + G(B, C, D) + X[1] + 0x5A827999, 3); + D = ROT (D + G(A, B, C) + X[5] + 0x5A827999, 5); + C = ROT (C + G(D, A, B) + X[9] + 0x5A827999, 9); + B = ROT (B + G(C, D, A) + X[13] + 0x5A827999, 13); + A = ROT (A + G(B, C, D) + X[2] + 0x5A827999, 3); + D = ROT (D + G(A, B, C) + X[6] + 0x5A827999, 5); + C = ROT (C + G(D, A, B) + X[10] + 0x5A827999, 9); + B = ROT (B + G(C, D, A) + X[14] + 0x5A827999, 13); + A = ROT (A + G(B, C, D) + X[3] + 0x5A827999, 3); + D = ROT (D + G(A, B, C) + X[7] + 0x5A827999, 5); + C = ROT (C + G(D, A, B) + X[11] + 0x5A827999, 9); + B = ROT (B + G(C, D, A) + X[15] + 0x5A827999, 13); + + A = ROT (A + H(B, C, D) + X[0] + 0x6ED9EBA1, 3); + D = ROT (D + H(A, B, C) + X[8] + 0x6ED9EBA1, 9); + C = ROT (C + H(D, A, B) + X[4] + 0x6ED9EBA1, 11); + B = ROT (B + H(C, D, A) + X[12] + 0x6ED9EBA1, 15); + A = ROT (A + H(B, C, D) + X[2] + 0x6ED9EBA1, 3); + D = ROT (D + H(A, B, C) + X[10] + 0x6ED9EBA1, 9); + C = ROT (C + H(D, A, B) + X[6] + 0x6ED9EBA1, 11); + B = ROT (B + H(C, D, A) + X[14] + 0x6ED9EBA1, 15); + A = ROT (A + H(B, C, D) + X[1] + 0x6ED9EBA1, 3); + D = ROT (D + H(A, B, C) + X[9] + 0x6ED9EBA1, 9); + C = ROT (C + H(D, A, B) + X[5] + 0x6ED9EBA1, 11); + B = ROT (B + H(C, D, A) + X[13] + 0x6ED9EBA1, 15); + A = ROT (A + H(B, C, D) + X[3] + 0x6ED9EBA1, 3); + D = ROT (D + H(A, B, C) + X[11] + 0x6ED9EBA1, 9); + C = ROT (C + H(D, A, B) + X[7] + 0x6ED9EBA1, 11); + B = ROT (B + H(C, D, A) + X[15] + 0x6ED9EBA1, 15); + + A += AA; + B += BB; + C += CC; + D += DD; + } + + digest[0] = A & 0xFF; + digest[1] = (A >> 8) & 0xFF; + digest[2] = (A >> 16) & 0xFF; + digest[3] = (A >> 24) & 0xFF; + digest[4] = B & 0xFF; + digest[5] = (B >> 8) & 0xFF; + digest[6] = (B >> 16) & 0xFF; + digest[7] = (B >> 24) & 0xFF; + digest[8] = C & 0xFF; + digest[9] = (C >> 8) & 0xFF; + digest[10] = (C >> 16) & 0xFF; + digest[11] = (C >> 24) & 0xFF; + digest[12] = D & 0xFF; + digest[13] = (D >> 8) & 0xFF; + digest[14] = (D >> 16) & 0xFF; + digest[15] = (D >> 24) & 0xFF; +} + + +/* Public domain DES implementation from Phil Karn */ +static const guint32 Spbox[8][64] = { + { 0x01010400,0x00000000,0x00010000,0x01010404, + 0x01010004,0x00010404,0x00000004,0x00010000, + 0x00000400,0x01010400,0x01010404,0x00000400, + 0x01000404,0x01010004,0x01000000,0x00000004, + 0x00000404,0x01000400,0x01000400,0x00010400, + 0x00010400,0x01010000,0x01010000,0x01000404, + 0x00010004,0x01000004,0x01000004,0x00010004, + 0x00000000,0x00000404,0x00010404,0x01000000, + 0x00010000,0x01010404,0x00000004,0x01010000, + 0x01010400,0x01000000,0x01000000,0x00000400, + 0x01010004,0x00010000,0x00010400,0x01000004, + 0x00000400,0x00000004,0x01000404,0x00010404, + 0x01010404,0x00010004,0x01010000,0x01000404, + 0x01000004,0x00000404,0x00010404,0x01010400, + 0x00000404,0x01000400,0x01000400,0x00000000, + 0x00010004,0x00010400,0x00000000,0x01010004 }, + { 0x80108020,0x80008000,0x00008000,0x00108020, + 0x00100000,0x00000020,0x80100020,0x80008020, + 0x80000020,0x80108020,0x80108000,0x80000000, + 0x80008000,0x00100000,0x00000020,0x80100020, + 0x00108000,0x00100020,0x80008020,0x00000000, + 0x80000000,0x00008000,0x00108020,0x80100000, + 0x00100020,0x80000020,0x00000000,0x00108000, + 0x00008020,0x80108000,0x80100000,0x00008020, + 0x00000000,0x00108020,0x80100020,0x00100000, + 0x80008020,0x80100000,0x80108000,0x00008000, + 0x80100000,0x80008000,0x00000020,0x80108020, + 0x00108020,0x00000020,0x00008000,0x80000000, + 0x00008020,0x80108000,0x00100000,0x80000020, + 0x00100020,0x80008020,0x80000020,0x00100020, + 0x00108000,0x00000000,0x80008000,0x00008020, + 0x80000000,0x80100020,0x80108020,0x00108000 }, + { 0x00000208,0x08020200,0x00000000,0x08020008, + 0x08000200,0x00000000,0x00020208,0x08000200, + 0x00020008,0x08000008,0x08000008,0x00020000, + 0x08020208,0x00020008,0x08020000,0x00000208, + 0x08000000,0x00000008,0x08020200,0x00000200, + 0x00020200,0x08020000,0x08020008,0x00020208, + 0x08000208,0x00020200,0x00020000,0x08000208, + 0x00000008,0x08020208,0x00000200,0x08000000, + 0x08020200,0x08000000,0x00020008,0x00000208, + 0x00020000,0x08020200,0x08000200,0x00000000, + 0x00000200,0x00020008,0x08020208,0x08000200, + 0x08000008,0x00000200,0x00000000,0x08020008, + 0x08000208,0x00020000,0x08000000,0x08020208, + 0x00000008,0x00020208,0x00020200,0x08000008, + 0x08020000,0x08000208,0x00000208,0x08020000, + 0x00020208,0x00000008,0x08020008,0x00020200 }, + { 0x00802001,0x00002081,0x00002081,0x00000080, + 0x00802080,0x00800081,0x00800001,0x00002001, + 0x00000000,0x00802000,0x00802000,0x00802081, + 0x00000081,0x00000000,0x00800080,0x00800001, + 0x00000001,0x00002000,0x00800000,0x00802001, + 0x00000080,0x00800000,0x00002001,0x00002080, + 0x00800081,0x00000001,0x00002080,0x00800080, + 0x00002000,0x00802080,0x00802081,0x00000081, + 0x00800080,0x00800001,0x00802000,0x00802081, + 0x00000081,0x00000000,0x00000000,0x00802000, + 0x00002080,0x00800080,0x00800081,0x00000001, + 0x00802001,0x00002081,0x00002081,0x00000080, + 0x00802081,0x00000081,0x00000001,0x00002000, + 0x00800001,0x00002001,0x00802080,0x00800081, + 0x00002001,0x00002080,0x00800000,0x00802001, + 0x00000080,0x00800000,0x00002000,0x00802080 }, + { 0x00000100,0x02080100,0x02080000,0x42000100, + 0x00080000,0x00000100,0x40000000,0x02080000, + 0x40080100,0x00080000,0x02000100,0x40080100, + 0x42000100,0x42080000,0x00080100,0x40000000, + 0x02000000,0x40080000,0x40080000,0x00000000, + 0x40000100,0x42080100,0x42080100,0x02000100, + 0x42080000,0x40000100,0x00000000,0x42000000, + 0x02080100,0x02000000,0x42000000,0x00080100, + 0x00080000,0x42000100,0x00000100,0x02000000, + 0x40000000,0x02080000,0x42000100,0x40080100, + 0x02000100,0x40000000,0x42080000,0x02080100, + 0x40080100,0x00000100,0x02000000,0x42080000, + 0x42080100,0x00080100,0x42000000,0x42080100, + 0x02080000,0x00000000,0x40080000,0x42000000, + 0x00080100,0x02000100,0x40000100,0x00080000, + 0x00000000,0x40080000,0x02080100,0x40000100 }, + { 0x20000010,0x20400000,0x00004000,0x20404010, + 0x20400000,0x00000010,0x20404010,0x00400000, + 0x20004000,0x00404010,0x00400000,0x20000010, + 0x00400010,0x20004000,0x20000000,0x00004010, + 0x00000000,0x00400010,0x20004010,0x00004000, + 0x00404000,0x20004010,0x00000010,0x20400010, + 0x20400010,0x00000000,0x00404010,0x20404000, + 0x00004010,0x00404000,0x20404000,0x20000000, + 0x20004000,0x00000010,0x20400010,0x00404000, + 0x20404010,0x00400000,0x00004010,0x20000010, + 0x00400000,0x20004000,0x20000000,0x00004010, + 0x20000010,0x20404010,0x00404000,0x20400000, + 0x00404010,0x20404000,0x00000000,0x20400010, + 0x00000010,0x00004000,0x20400000,0x00404010, + 0x00004000,0x00400010,0x20004010,0x00000000, + 0x20404000,0x20000000,0x00400010,0x20004010 }, + { 0x00200000,0x04200002,0x04000802,0x00000000, + 0x00000800,0x04000802,0x00200802,0x04200800, + 0x04200802,0x00200000,0x00000000,0x04000002, + 0x00000002,0x04000000,0x04200002,0x00000802, + 0x04000800,0x00200802,0x00200002,0x04000800, + 0x04000002,0x04200000,0x04200800,0x00200002, + 0x04200000,0x00000800,0x00000802,0x04200802, + 0x00200800,0x00000002,0x04000000,0x00200800, + 0x04000000,0x00200800,0x00200000,0x04000802, + 0x04000802,0x04200002,0x04200002,0x00000002, + 0x00200002,0x04000000,0x04000800,0x00200000, + 0x04200800,0x00000802,0x00200802,0x04200800, + 0x00000802,0x04000002,0x04200802,0x04200000, + 0x00200800,0x00000000,0x00000002,0x04200802, + 0x00000000,0x00200802,0x04200000,0x00000800, + 0x04000002,0x04000800,0x00000800,0x00200002 }, + { 0x10001040,0x00001000,0x00040000,0x10041040, + 0x10000000,0x10001040,0x00000040,0x10000000, + 0x00040040,0x10040000,0x10041040,0x00041000, + 0x10041000,0x00041040,0x00001000,0x00000040, + 0x10040000,0x10000040,0x10001000,0x00001040, + 0x00041000,0x00040040,0x10040040,0x10041000, + 0x00001040,0x00000000,0x00000000,0x10040040, + 0x10000040,0x10001000,0x00041040,0x00040000, + 0x00041040,0x00040000,0x10041000,0x00001000, + 0x00000040,0x10040040,0x00001000,0x00041040, + 0x10001000,0x00000040,0x10000040,0x10040000, + 0x10040040,0x10000000,0x00040000,0x10001040, + 0x00000000,0x10041040,0x00040040,0x10000040, + 0x10040000,0x10001000,0x10001040,0x00000000, + 0x10041040,0x00041000,0x00041000,0x00001040, + 0x00001040,0x00040040,0x10000000,0x10041000 } +}; + +#undef F +#define F(l,r,key){\ + work = ((r >> 4) | (r << 28)) ^ key[0];\ + l ^= Spbox[6][work & 0x3f];\ + l ^= Spbox[4][(work >> 8) & 0x3f];\ + l ^= Spbox[2][(work >> 16) & 0x3f];\ + l ^= Spbox[0][(work >> 24) & 0x3f];\ + work = r ^ key[1];\ + l ^= Spbox[7][work & 0x3f];\ + l ^= Spbox[5][(work >> 8) & 0x3f];\ + l ^= Spbox[3][(work >> 16) & 0x3f];\ + l ^= Spbox[1][(work >> 24) & 0x3f];\ +} +/* Encrypt or decrypt a block of data in ECB mode */ +static void +des (guint32 ks[16][2], unsigned char block[8]) +{ + guint32 left,right,work; + + /* Read input block and place in left/right in big-endian order */ + left = ((guint32)block[0] << 24) + | ((guint32)block[1] << 16) + | ((guint32)block[2] << 8) + | (guint32)block[3]; + right = ((guint32)block[4] << 24) + | ((guint32)block[5] << 16) + | ((guint32)block[6] << 8) + | (guint32)block[7]; + + /* Hoey's clever initial permutation algorithm, from Outerbridge + * (see Schneier p 478) + * + * The convention here is the same as Outerbridge: rotate each + * register left by 1 bit, i.e., so that "left" contains permuted + * input bits 2, 3, 4, ... 1 and "right" contains 33, 34, 35, ... 32 + * (using origin-1 numbering as in the FIPS). This allows us to avoid + * one of the two rotates that would otherwise be required in each of + * the 16 rounds. + */ + work = ((left >> 4) ^ right) & 0x0f0f0f0f; + right ^= work; + left ^= work << 4; + work = ((left >> 16) ^ right) & 0xffff; + right ^= work; + left ^= work << 16; + work = ((right >> 2) ^ left) & 0x33333333; + left ^= work; + right ^= (work << 2); + work = ((right >> 8) ^ left) & 0xff00ff; + left ^= work; + right ^= (work << 8); + right = (right << 1) | (right >> 31); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left << 1) | (left >> 31); + + /* Now do the 16 rounds */ + F(left,right,ks[0]); + F(right,left,ks[1]); + F(left,right,ks[2]); + F(right,left,ks[3]); + F(left,right,ks[4]); + F(right,left,ks[5]); + F(left,right,ks[6]); + F(right,left,ks[7]); + F(left,right,ks[8]); + F(right,left,ks[9]); + F(left,right,ks[10]); + F(right,left,ks[11]); + F(left,right,ks[12]); + F(right,left,ks[13]); + F(left,right,ks[14]); + F(right,left,ks[15]); + + /* Inverse permutation, also from Hoey via Outerbridge and Schneier */ + right = (right << 31) | (right >> 1); + work = (left ^ right) & 0xaaaaaaaa; + left ^= work; + right ^= work; + left = (left >> 1) | (left << 31); + work = ((left >> 8) ^ right) & 0xff00ff; + right ^= work; + left ^= work << 8; + work = ((left >> 2) ^ right) & 0x33333333; + right ^= work; + left ^= work << 2; + work = ((right >> 16) ^ left) & 0xffff; + left ^= work; + right ^= work << 16; + work = ((right >> 4) ^ left) & 0x0f0f0f0f; + left ^= work; + right ^= work << 4; + + /* Put the block back into the user's buffer with final swap */ + block[0] = right >> 24; + block[1] = right >> 16; + block[2] = right >> 8; + block[3] = right; + block[4] = left >> 24; + block[5] = left >> 16; + block[6] = left >> 8; + block[7] = left; +} + +/* Key schedule-related tables from FIPS-46 */ + +/* permuted choice table (key) */ +static const unsigned char pc1[] = { + 57, 49, 41, 33, 25, 17, 9, + 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, + 19, 11, 3, 60, 52, 44, 36, + + 63, 55, 47, 39, 31, 23, 15, + 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, + 21, 13, 5, 28, 20, 12, 4 +}; + +/* number left rotations of pc1 */ +static const unsigned char totrot[] = { + 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 +}; + +/* permuted choice key (table) */ +static const unsigned char pc2[] = { + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 +}; + +/* End of DES-defined tables */ + + +/* bit 0 is left-most in byte */ +static const int bytebit[] = { + 0200,0100,040,020,010,04,02,01 +}; + + +/* Generate key schedule for encryption or decryption + * depending on the value of "decrypt" + */ +static void +deskey (DES_KS k, unsigned char *key, int decrypt) +{ + unsigned char pc1m[56]; /* place to modify pc1 into */ + unsigned char pcr[56]; /* place to rotate pc1 into */ + register int i,j,l; + int m; + unsigned char ks[8]; + + for (j=0; j<56; j++) { /* convert pc1 to bits of key */ + l=pc1[j]-1; /* integer bit location */ + m = l & 07; /* find bit */ + pc1m[j]=(key[l>>3] & /* find which key byte l is in */ + bytebit[m]) /* and which bit of that byte */ + ? 1 : 0; /* and store 1-bit result */ + } + for (i=0; i<16; i++) { /* key chunk for each iteration */ + memset(ks,0,sizeof(ks)); /* Clear key schedule */ + for (j=0; j<56; j++) /* rotate pc1 the right amount */ + pcr[j] = pc1m[(l=j+totrot[decrypt? 15-i : i])<(j<28? 28 : 56) ? l: l-28]; + /* rotate left and right halves independently */ + for (j=0; j<48; j++){ /* select bits individually */ + /* check bit that goes to ks[j] */ + if (pcr[pc2[j]-1]){ + /* mask it in if it's there */ + l= j % 6; + ks[j/6] |= bytebit[l] >> 2; + } + } + /* Now convert to packed odd/even interleaved form */ + k[i][0] = ((guint32)ks[0] << 24) + | ((guint32)ks[2] << 16) + | ((guint32)ks[4] << 8) + | ((guint32)ks[6]); + k[i][1] = ((guint32)ks[1] << 24) + | ((guint32)ks[3] << 16) + | ((guint32)ks[5] << 8) + | ((guint32)ks[7]); + } } diff --git a/libsoup/soup-auth-ntlm.h b/libsoup/soup-auth-ntlm.h index 1f56976b..43c40856 100644 --- a/libsoup/soup-auth-ntlm.h +++ b/libsoup/soup-auth-ntlm.h @@ -6,7 +6,7 @@ #ifndef SOUP_AUTH_NTLM_H #define SOUP_AUTH_NTLM_H 1 -#include "soup-auth.h" +#include "soup-connection-auth.h" #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)) @@ -15,18 +15,13 @@ #define SOUP_AUTH_NTLM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_AUTH_NTLM, SoupAuthNTLMClass)) typedef struct { - SoupAuth parent; + SoupConnectionAuth parent; } SoupAuthNTLM; typedef struct { - SoupAuthClass parent_class; + SoupConnectionAuthClass parent_class; } SoupAuthNTLMClass; -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 6960e520..7c34dcfa 100644 --- a/libsoup/soup-auth.c +++ b/libsoup/soup-auth.c @@ -9,14 +9,11 @@ #include <config.h> #endif -#define LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY - #include <string.h> #include "soup-auth.h" -#include "soup-headers.h" -#include "soup-marshal.h" -#include "soup-uri.h" +#include "soup.h" +#include "soup-connection-auth.h" /** * SECTION:soup-auth @@ -40,21 +37,12 @@ typedef struct { gboolean proxy; char *host; - - GHashTable *saved_passwords; } SoupAuthPrivate; #define SOUP_AUTH_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_AUTH, SoupAuthPrivate)) G_DEFINE_ABSTRACT_TYPE (SoupAuth, soup_auth, G_TYPE_OBJECT) enum { - SAVE_PASSWORD, - LAST_SIGNAL -}; - -static guint signals[LAST_SIGNAL] = { 0 }; - -enum { PROP_0, PROP_SCHEME_NAME, @@ -79,8 +67,6 @@ soup_auth_finalize (GObject *object) g_free (auth->realm); g_free (priv->host); - if (priv->saved_passwords) - g_hash_table_destroy (priv->saved_passwords); G_OBJECT_CLASS (soup_auth_parent_class)->finalize (object); } @@ -120,9 +106,13 @@ soup_auth_get_property (GObject *object, guint prop_id, g_value_set_string (value, soup_auth_get_scheme_name (auth)); break; case PROP_REALM: + if (auth->realm) + g_free (auth->realm); g_value_set_string (value, soup_auth_get_realm (auth)); break; case PROP_HOST: + if (priv->host) + g_free (priv->host); g_value_set_string (value, soup_auth_get_host (auth)); break; case PROP_IS_FOR_PROXY: @@ -149,30 +139,6 @@ soup_auth_class_init (SoupAuthClass *auth_class) object_class->set_property = soup_auth_set_property; object_class->get_property = soup_auth_get_property; - /** - * SoupAuth::save-password: - * @auth: the auth - * @username: the username to save - * @password: the password to save - * - * Emitted to request that the @username/@password pair be - * saved. If the session supports password-saving, it will - * connect to this signal before emitting - * #SoupSession::authenticate, so that it record the password - * if requested by the caller. - * - * Since: 2.28 - **/ - signals[SAVE_PASSWORD] = - g_signal_new ("save-password", - G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_RUN_FIRST, - 0, NULL, NULL, - _soup_marshal_NONE__STRING_STRING, - G_TYPE_NONE, 2, - G_TYPE_STRING, - G_TYPE_STRING); - /* properties */ /** * SOUP_AUTH_SCHEME_NAME: @@ -199,7 +165,7 @@ soup_auth_class_init (SoupAuthClass *auth_class) "Realm", "Authentication realm", NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE)); /** * SOUP_AUTH_HOST: * @@ -212,7 +178,7 @@ soup_auth_class_init (SoupAuthClass *auth_class) "Host", "Authentication host", NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE)); /** * SOUP_AUTH_IS_FOR_PROXY: * @@ -225,7 +191,7 @@ soup_auth_class_init (SoupAuthClass *auth_class) "For Proxy", "Whether or not the auth is for a proxy server", FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + G_PARAM_READWRITE)); /** * SOUP_AUTH_IS_AUTHENTICATED: * @@ -279,19 +245,12 @@ soup_auth_new (GType type, SoupMessage *msg, const char *auth_header) } params = soup_header_parse_param_list (auth_header + strlen (scheme)); - if (!params) { - g_object_unref (auth); - return NULL; - } + if (!params) + params = g_hash_table_new (NULL, NULL); realm = g_hash_table_lookup (params, "realm"); - if (!realm) { - soup_header_free_param_list (params); - g_object_unref (auth); - return NULL; - } - - auth->realm = g_strdup (realm); + if (realm) + auth->realm = g_strdup (realm); if (!SOUP_AUTH_GET_CLASS (auth)->update (auth, msg, params)) { g_object_unref (auth); @@ -332,10 +291,10 @@ soup_auth_update (SoupAuth *auth, SoupMessage *msg, const char *auth_header) params = soup_header_parse_param_list (auth_header + strlen (scheme)); if (!params) - return FALSE; + params = g_hash_table_new (NULL, NULL); realm = g_hash_table_lookup (params, "realm"); - if (!realm || strcmp (realm, auth->realm) != 0) { + if (realm && auth->realm && strcmp (realm, auth->realm) != 0) { soup_header_free_param_list (params); return FALSE; } @@ -356,9 +315,6 @@ soup_auth_update (SoupAuth *auth, SoupMessage *msg, const char *auth_header) * * Call this on an auth to authenticate it; normally this will cause * the auth's message to be requeued with the new authentication info. - * - * This does not cause the password to be saved to persistent storage; - * see soup_auth_save_password() for that. **/ void soup_auth_authenticate (SoupAuth *auth, const char *username, const char *password) @@ -460,9 +416,13 @@ soup_auth_get_info (SoupAuth *auth) { g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL); - return g_strdup_printf ("%s:%s", - SOUP_AUTH_GET_CLASS (auth)->scheme_name, - auth->realm); + if (SOUP_IS_CONNECTION_AUTH (auth)) + return g_strdup (SOUP_AUTH_GET_CLASS (auth)->scheme_name); + else { + return g_strdup_printf ("%s:%s", + SOUP_AUTH_GET_CLASS (auth)->scheme_name, + auth->realm); + } } /** @@ -502,6 +462,33 @@ soup_auth_get_authorization (SoupAuth *auth, SoupMessage *msg) } /** + * soup_auth_is_ready: + * @auth: a #SoupAuth + * @msg: a #SoupMessage + * + * Tests if @auth is ready to make a request for @msg with. For most + * auths, this is equivalent to soup_auth_is_authenticated(), but for + * some auth types (eg, NTLM), the auth may be sendable (eg, as an + * authentication request) even before it is authenticated. + * + * Return value: %TRUE if @auth is ready to make a request with. + * + * Since: 2.42 + **/ +gboolean +soup_auth_is_ready (SoupAuth *auth, + SoupMessage *msg) +{ + g_return_val_if_fail (SOUP_IS_AUTH (auth), TRUE); + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), TRUE); + + if (SOUP_AUTH_GET_CLASS (auth)->is_ready) + return SOUP_AUTH_GET_CLASS (auth)->is_ready (auth, msg); + else + return SOUP_AUTH_GET_CLASS (auth)->is_authenticated (auth); +} + +/** * soup_auth_get_protection_space: * @auth: a #SoupAuth * @source_uri: the URI of the request that @auth was generated in @@ -539,134 +526,29 @@ soup_auth_free_protection_space (SoupAuth *auth, GSList *space) /** * soup_auth_get_saved_users: - * @auth: a #SoupAuth - * - * Gets a list of usernames for which a saved password is available. - * (If the session is not configured to save passwords, this will - * always be %NULL.) - * - * Return value: (transfer container): the list of usernames. You must - * free the list with g_slist_free(), but do not free or modify the - * contents. * - * Since: 2.28 - **/ + * Return value: (transfer full) (element-type utf8): + */ GSList * soup_auth_get_saved_users (SoupAuth *auth) { - SoupAuthPrivate *priv; - GSList *users; - - g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL); - - priv = SOUP_AUTH_GET_PRIVATE (auth); - users = NULL; - - if (priv->saved_passwords) { - GHashTableIter iter; - gpointer key, value; - - g_hash_table_iter_init (&iter, priv->saved_passwords); - while (g_hash_table_iter_next (&iter, &key, &value)) - users = g_slist_prepend (users, key); - } - return users; + return NULL; } -/** - * soup_auth_get_saved_password: - * @auth: a #SoupAuth - * @user: a username from the list returned from - * soup_auth_get_saved_users(). - * - * Given a username for which @auth has a saved password, this returns - * that password. If @auth doesn't have a passwords saved for @user, it - * returns %NULL. - * - * Return value: the saved password, or %NULL. - * - * Since: 2.28 - **/ const char * soup_auth_get_saved_password (SoupAuth *auth, const char *user) { - SoupAuthPrivate *priv; - - g_return_val_if_fail (SOUP_IS_AUTH (auth), NULL); - g_return_val_if_fail (user != NULL, NULL); - - priv = SOUP_AUTH_GET_PRIVATE (auth); - if (!priv->saved_passwords) - return NULL; - return g_hash_table_lookup (priv->saved_passwords, user); -} - -static void -free_password (gpointer password) -{ - memset (password, 0, strlen (password)); - g_free (password); + return NULL; } -static inline void -init_saved_passwords (SoupAuthPrivate *priv) -{ - priv->saved_passwords = g_hash_table_new_full ( - g_str_hash, g_str_equal, g_free, free_password); -} - -/** - * soup_auth_has_saved_password: - * @auth: a #SoupAuth - * @username: a username - * @password: a password - * - * Updates @auth to be aware of an already-saved username/password - * combination. This method <emphasis>does not</emphasis> cause the - * given @username and @password to be saved; use - * soup_auth_save_password() for that. (soup_auth_has_saved_password() - * is an internal method, which is used by the code that actually - * saves and restores the passwords.) - * - * Since: 2.28 - **/ void soup_auth_has_saved_password (SoupAuth *auth, const char *username, const char *password) { - SoupAuthPrivate *priv; - - g_return_if_fail (SOUP_IS_AUTH (auth)); - g_return_if_fail (username != NULL); - g_return_if_fail (password != NULL); - - priv = SOUP_AUTH_GET_PRIVATE (auth); - - if (!priv->saved_passwords) - init_saved_passwords (priv); - g_hash_table_insert (priv->saved_passwords, - g_strdup (username), g_strdup (password)); } -/** - * soup_auth_save_password: - * @auth: a #SoupAuth - * @username: the username provided by the user or client - * @password: the password provided by the user or client - * - * Requests that the username/password pair be saved to whatever form - * of persistent password storage the session supports. - * - * Since: 2.28 - **/ void soup_auth_save_password (SoupAuth *auth, const char *username, const char *password) { - g_return_if_fail (SOUP_IS_AUTH (auth)); - g_return_if_fail (username != NULL); - g_return_if_fail (password != NULL); - - g_signal_emit (auth, signals[SAVE_PASSWORD], 0, - username, password); } diff --git a/libsoup/soup-auth.h b/libsoup/soup-auth.h index 18f11c7b..824857e2 100644 --- a/libsoup/soup-auth.h +++ b/libsoup/soup-auth.h @@ -44,8 +44,11 @@ typedef struct { char * (*get_authorization) (SoupAuth *auth, SoupMessage *msg); + + gboolean (*is_ready) (SoupAuth *auth, + SoupMessage *msg); + /* Padding for future expansion */ - void (*_libsoup_reserved1) (void); void (*_libsoup_reserved2) (void); void (*_libsoup_reserved3) (void); void (*_libsoup_reserved4) (void); @@ -72,19 +75,13 @@ const char *soup_auth_get_host (SoupAuth *auth); const char *soup_auth_get_realm (SoupAuth *auth); char *soup_auth_get_info (SoupAuth *auth); -#ifdef LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY -GSList *soup_auth_get_saved_users (SoupAuth *auth); -const char *soup_auth_get_saved_password (SoupAuth *auth, - const char *user); -void soup_auth_save_password (SoupAuth *auth, - const char *username, - const char *password); -#endif - void soup_auth_authenticate (SoupAuth *auth, const char *username, const char *password); gboolean soup_auth_is_authenticated (SoupAuth *auth); +SOUP_AVAILABLE_IN_2_42 +gboolean soup_auth_is_ready (SoupAuth *auth, + SoupMessage *msg); char *soup_auth_get_authorization (SoupAuth *auth, SoupMessage *msg); @@ -94,12 +91,6 @@ GSList *soup_auth_get_protection_space (SoupAuth *auth, void soup_auth_free_protection_space (SoupAuth *auth, GSList *space); -#ifdef LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY -void soup_auth_has_saved_password (SoupAuth *auth, - const char *username, - const char *password); -#endif - /* The actual auth types, which can be added/removed as features */ #define SOUP_TYPE_AUTH_BASIC (soup_auth_basic_get_type ()) @@ -109,6 +100,25 @@ GType soup_auth_digest_get_type (void); #define SOUP_TYPE_AUTH_NTLM (soup_auth_ntlm_get_type ()) GType soup_auth_ntlm_get_type (void); +/* Deprecated SoupPasswordManager-related APIs: all are now no-ops */ +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_28 +GSList *soup_auth_get_saved_users (SoupAuth *auth); +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_28 +const char *soup_auth_get_saved_password (SoupAuth *auth, + const char *user); +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_28 +void soup_auth_save_password (SoupAuth *auth, + const char *username, + const char *password); +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_28 +void soup_auth_has_saved_password (SoupAuth *auth, + const char *username, + const char *password); + G_END_DECLS #endif /* SOUP_AUTH_H */ diff --git a/libsoup/soup-body-input-stream.c b/libsoup/soup-body-input-stream.c index ba4ce6a6..62747446 100644 --- a/libsoup/soup-body-input-stream.c +++ b/libsoup/soup-body-input-stream.c @@ -16,7 +16,6 @@ #include "soup-body-input-stream.h" #include "soup.h" #include "soup-filter-input-stream.h" -#include "soup-marshal.h" typedef enum { SOUP_BODY_INPUT_STREAM_STATE_CHUNK_SIZE, @@ -33,6 +32,8 @@ struct _SoupBodyInputStreamPrivate { goffset read_length; SoupBodyInputStreamState chunked_state; gboolean eof; + + goffset pos; }; enum { @@ -50,10 +51,13 @@ enum { }; static void soup_body_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data); +static void soup_body_input_stream_seekable_init (GSeekableIface *seekable_interface); G_DEFINE_TYPE_WITH_CODE (SoupBodyInputStream, soup_body_input_stream, G_TYPE_FILTER_INPUT_STREAM, G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM, - soup_body_input_stream_pollable_init)) + soup_body_input_stream_pollable_init) + G_IMPLEMENT_INTERFACE (G_TYPE_SEEKABLE, + soup_body_input_stream_seekable_init)) static void soup_body_input_stream_init (SoupBodyInputStream *bistream) @@ -258,6 +262,9 @@ read_internal (GInputStream *stream, blocking, cancellable, error); if (bistream->priv->read_length != -1 && nread > 0) bistream->priv->read_length -= nread; + + if (bistream->priv->encoding == SOUP_ENCODING_CONTENT_LENGTH) + bistream->priv->pos += nread; return nread; default: @@ -266,6 +273,27 @@ read_internal (GInputStream *stream, } static gssize +soup_body_input_stream_skip (GInputStream *stream, + gsize count, + GCancellable *cancellable, + GError **error) +{ + SoupBodyInputStreamPrivate *priv = SOUP_BODY_INPUT_STREAM(stream)->priv; + gssize skipped; + + skipped = g_input_stream_skip (G_FILTER_INPUT_STREAM (stream)->base_stream, + MIN (count, priv->read_length), + cancellable, error); + + if (skipped == 0) + priv->eof = TRUE; + else if (skipped > 0) + priv->pos += skipped; + + return skipped; +} + +static gssize soup_body_input_stream_read_fn (GInputStream *stream, void *buffer, gsize count, @@ -295,6 +323,15 @@ soup_body_input_stream_is_readable (GPollableInputStream *stream) g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (bistream->priv->base_stream)); } +static gboolean +soup_body_input_stream_can_poll (GPollableInputStream *pollable) +{ + GInputStream *base_stream = SOUP_BODY_INPUT_STREAM (pollable)->priv->base_stream; + + return G_IS_POLLABLE_INPUT_STREAM (base_stream) && + g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (base_stream)); +} + static gssize soup_body_input_stream_read_nonblocking (GPollableInputStream *stream, void *buffer, @@ -337,6 +374,7 @@ soup_body_input_stream_class_init (SoupBodyInputStreamClass *stream_class) object_class->set_property = soup_body_input_stream_set_property; object_class->get_property = soup_body_input_stream_get_property; + input_stream_class->skip = soup_body_input_stream_skip; input_stream_class->read_fn = soup_body_input_stream_read_fn; input_stream_class->close_fn = soup_body_input_stream_close_fn; @@ -346,7 +384,7 @@ soup_body_input_stream_class_init (SoupBodyInputStreamClass *stream_class) G_SIGNAL_RUN_LAST, 0, NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); g_object_class_install_property ( @@ -370,16 +408,107 @@ static void soup_body_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data) { + pollable_interface->can_poll = soup_body_input_stream_can_poll; pollable_interface->is_readable = soup_body_input_stream_is_readable; pollable_interface->read_nonblocking = soup_body_input_stream_read_nonblocking; pollable_interface->create_source = soup_body_input_stream_create_source; } +static goffset +soup_body_input_stream_tell (GSeekable *seekable) +{ + return SOUP_BODY_INPUT_STREAM (seekable)->priv->pos; +} + +static gboolean +soup_body_input_stream_can_seek (GSeekable *seekable) +{ + SoupBodyInputStreamPrivate *priv = SOUP_BODY_INPUT_STREAM (seekable)->priv; + + return priv->encoding == SOUP_ENCODING_CONTENT_LENGTH + && G_IS_SEEKABLE (priv->base_stream) + && g_seekable_can_seek (G_SEEKABLE (priv->base_stream)); +} + +static gboolean +soup_body_input_stream_seek (GSeekable *seekable, + goffset offset, + GSeekType type, + GCancellable *cancellable, + GError **error) +{ + SoupBodyInputStreamPrivate *priv = SOUP_BODY_INPUT_STREAM (seekable)->priv; + goffset position, end_position; + + end_position = priv->pos + priv->read_length; + switch (type) { + case G_SEEK_CUR: + position = priv->pos + offset; + break; + case G_SEEK_SET: + position = offset; + break; + case G_SEEK_END: + position = end_position + offset; + break; + default: + g_return_val_if_reached (FALSE); + } + + if (position < 0 || position >= end_position) { + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + _("Invalid seek request")); + return FALSE; + } + + if (!g_seekable_seek (G_SEEKABLE (priv->base_stream), position - priv->pos, + G_SEEK_CUR, cancellable, error)) + return FALSE; + + priv->pos = position; + + return TRUE; +} + +static gboolean +soup_body_input_stream_can_truncate (GSeekable *seekable) +{ + return FALSE; +} + +static gboolean +soup_body_input_stream_truncate_fn (GSeekable *seekable, + goffset offset, + GCancellable *cancellable, + GError **error) +{ + g_set_error_literal (error, + G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Cannot truncate SoupBodyInputStream")); + return FALSE; +} + +static void +soup_body_input_stream_seekable_init (GSeekableIface *seekable_interface) +{ + seekable_interface->tell = soup_body_input_stream_tell; + seekable_interface->can_seek = soup_body_input_stream_can_seek; + seekable_interface->seek = soup_body_input_stream_seek; + seekable_interface->can_truncate = soup_body_input_stream_can_truncate; + seekable_interface->truncate_fn = soup_body_input_stream_truncate_fn; +} + GInputStream * -soup_body_input_stream_new (SoupFilterInputStream *base_stream, - SoupEncoding encoding, - goffset content_length) +soup_body_input_stream_new (GInputStream *base_stream, + SoupEncoding encoding, + goffset content_length) { + if (encoding == SOUP_ENCODING_CHUNKED) + g_return_val_if_fail (SOUP_IS_FILTER_INPUT_STREAM (base_stream), NULL); + return g_object_new (SOUP_TYPE_BODY_INPUT_STREAM, "base-stream", base_stream, "close-base-stream", FALSE, diff --git a/libsoup/soup-body-input-stream.h b/libsoup/soup-body-input-stream.h index 9e0c08e3..7732e5e5 100644 --- a/libsoup/soup-body-input-stream.h +++ b/libsoup/soup-body-input-stream.h @@ -39,9 +39,9 @@ typedef struct { GType soup_body_input_stream_get_type (void); -GInputStream *soup_body_input_stream_new (SoupFilterInputStream *base_stream, - SoupEncoding encoding, - goffset content_length); +GInputStream *soup_body_input_stream_new (GInputStream *base_stream, + SoupEncoding encoding, + goffset content_length); G_END_DECLS diff --git a/libsoup/soup-body-output-stream.c b/libsoup/soup-body-output-stream.c index 86f131ab..03353ef5 100644 --- a/libsoup/soup-body-output-stream.c +++ b/libsoup/soup-body-output-stream.c @@ -104,6 +104,7 @@ static gssize soup_body_output_stream_write_raw (SoupBodyOutputStream *bostream, const void *buffer, gsize count, + gboolean blocking, GCancellable *cancellable, GError **error) { @@ -122,9 +123,9 @@ soup_body_output_stream_write_raw (SoupBodyOutputStream *bostream, } else my_count = count; - nwrote = g_output_stream_write (bostream->priv->base_stream, - buffer, my_count, - cancellable, error); + nwrote = g_pollable_stream_write (bostream->priv->base_stream, + buffer, my_count, + blocking, cancellable, error); if (nwrote > 0 && bostream->priv->write_length) bostream->priv->written += nwrote; @@ -139,6 +140,7 @@ static gssize soup_body_output_stream_write_chunked (SoupBodyOutputStream *bostream, const void *buffer, gsize count, + gboolean blocking, GCancellable *cancellable, GError **error) { @@ -148,8 +150,9 @@ soup_body_output_stream_write_chunked (SoupBodyOutputStream *bostream, again: len = strlen (buf); if (len) { - nwrote = g_output_stream_write (bostream->priv->base_stream, - buf, len, cancellable, error); + nwrote = g_pollable_stream_write (bostream->priv->base_stream, + buf, len, blocking, + cancellable, error); if (nwrote < 0) return nwrote; memmove (buf, buf + nwrote, len + 1 - nwrote); @@ -169,8 +172,9 @@ again: break; case SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK: - nwrote = g_output_stream_write (bostream->priv->base_stream, - buffer, count, cancellable, error); + nwrote = g_pollable_stream_write (bostream->priv->base_stream, + buffer, count, blocking, + cancellable, error); if (nwrote < (gssize)count) return nwrote; @@ -212,11 +216,11 @@ soup_body_output_stream_write_fn (GOutputStream *stream, switch (bostream->priv->encoding) { case SOUP_ENCODING_CHUNKED: return soup_body_output_stream_write_chunked (bostream, buffer, count, - cancellable, error); + TRUE, cancellable, error); default: return soup_body_output_stream_write_raw (bostream, buffer, count, - cancellable, error); + TRUE, cancellable, error); } } @@ -227,8 +231,9 @@ soup_body_output_stream_close_fn (GOutputStream *stream, { SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (stream); - if (bostream->priv->encoding == SOUP_ENCODING_CHUNKED) { - if (soup_body_output_stream_write_chunked (bostream, NULL, 0, cancellable, error) == -1) + if (bostream->priv->encoding == SOUP_ENCODING_CHUNKED && + bostream->priv->chunked_state == SOUP_BODY_OUTPUT_STREAM_STATE_CHUNK_SIZE) { + if (soup_body_output_stream_write_chunked (bostream, NULL, 0, TRUE, cancellable, error) == -1) return FALSE; } @@ -244,6 +249,28 @@ soup_body_output_stream_is_writable (GPollableOutputStream *stream) g_pollable_output_stream_is_writable (G_POLLABLE_OUTPUT_STREAM (bostream->priv->base_stream)); } +static gssize +soup_body_output_stream_write_nonblocking (GPollableOutputStream *stream, + const void *buffer, + gsize count, + GError **error) +{ + SoupBodyOutputStream *bostream = SOUP_BODY_OUTPUT_STREAM (stream); + + if (bostream->priv->eof) + return count; + + switch (bostream->priv->encoding) { + case SOUP_ENCODING_CHUNKED: + return soup_body_output_stream_write_chunked (bostream, buffer, count, + FALSE, NULL, error); + + default: + return soup_body_output_stream_write_raw (bostream, buffer, count, + FALSE, NULL, error); + } +} + static GSource * soup_body_output_stream_create_source (GPollableOutputStream *stream, GCancellable *cancellable) @@ -301,6 +328,7 @@ soup_body_output_stream_pollable_init (GPollableOutputStreamInterface *pollable_ gpointer interface_data) { pollable_interface->is_writable = soup_body_output_stream_is_writable; + pollable_interface->write_nonblocking = soup_body_output_stream_write_nonblocking; pollable_interface->create_source = soup_body_output_stream_create_source; } diff --git a/libsoup/soup-cache-input-stream.c b/libsoup/soup-cache-input-stream.c new file mode 100644 index 00000000..679531d0 --- /dev/null +++ b/libsoup/soup-cache-input-stream.c @@ -0,0 +1,351 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012 Igalia, S.L. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib/gi18n-lib.h> +#include "soup-cache-input-stream.h" +#include "soup-message-body.h" + +static void soup_cache_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data); + +G_DEFINE_TYPE_WITH_CODE (SoupCacheInputStream, soup_cache_input_stream, SOUP_TYPE_FILTER_INPUT_STREAM, + G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM, + soup_cache_input_stream_pollable_init)) + +/* properties */ +enum { + PROP_0, + + PROP_OUTPUT_STREAM, + + LAST_PROP +}; + +enum { + CACHING_FINISHED, + + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct _SoupCacheInputStreamPrivate +{ + GOutputStream *output_stream; + GCancellable *cancellable; + gsize bytes_written; + + gboolean read_finished; + SoupBuffer *current_writing_buffer; + GQueue *buffer_queue; +}; + +static void soup_cache_input_stream_write_next_buffer (SoupCacheInputStream *istream); + +static inline void +notify_and_clear (SoupCacheInputStream *istream, GError *error) +{ + SoupCacheInputStreamPrivate *priv = istream->priv; + + g_signal_emit (istream, signals[CACHING_FINISHED], 0, priv->bytes_written, error); + + g_clear_object (&priv->cancellable); + g_clear_object (&priv->output_stream); +} + +static inline void +try_write_next_buffer (SoupCacheInputStream *istream) +{ + SoupCacheInputStreamPrivate *priv = istream->priv; + + if (priv->current_writing_buffer == NULL && priv->buffer_queue->length) + soup_cache_input_stream_write_next_buffer (istream); + else if (priv->read_finished) + notify_and_clear (istream, NULL); + else if (g_input_stream_is_closed (G_INPUT_STREAM (istream))) { + GError *error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_CLOSED, + _("Network stream unexpectedly closed")); + notify_and_clear (istream, error); + } +} + +static void +file_replaced_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + SoupCacheInputStream *istream = SOUP_CACHE_INPUT_STREAM (user_data); + SoupCacheInputStreamPrivate *priv = istream->priv; + GError *error = NULL; + + priv->output_stream = (GOutputStream *) g_file_replace_finish (G_FILE (source), res, &error); + + if (error) + notify_and_clear (istream, error); + else + try_write_next_buffer (istream); + + g_object_unref (istream); +} + +static void +soup_cache_input_stream_init (SoupCacheInputStream *self) +{ + SoupCacheInputStreamPrivate *priv = + G_TYPE_INSTANCE_GET_PRIVATE (self, SOUP_TYPE_CACHE_INPUT_STREAM, + SoupCacheInputStreamPrivate); + + priv->buffer_queue = g_queue_new (); + self->priv = priv; +} + +static void +soup_cache_input_stream_get_property (GObject *object, + guint property_id, GValue *value, GParamSpec *pspec) +{ + SoupCacheInputStream *self = SOUP_CACHE_INPUT_STREAM (object); + SoupCacheInputStreamPrivate *priv = self->priv; + + switch (property_id) { + case PROP_OUTPUT_STREAM: + g_value_set_object (value, priv->output_stream); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +soup_cache_input_stream_set_property (GObject *object, + guint property_id, const GValue *value, GParamSpec *pspec) +{ + SoupCacheInputStream *self = SOUP_CACHE_INPUT_STREAM (object); + SoupCacheInputStreamPrivate *priv = self->priv; + + switch (property_id) { + case PROP_OUTPUT_STREAM: + priv->output_stream = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +soup_cache_input_stream_finalize (GObject *object) +{ + SoupCacheInputStream *self = (SoupCacheInputStream *)object; + SoupCacheInputStreamPrivate *priv = self->priv; + + g_clear_object (&priv->cancellable); + g_clear_object (&priv->output_stream); + g_clear_pointer (&priv->current_writing_buffer, soup_buffer_free); + g_queue_free_full (priv->buffer_queue, (GDestroyNotify) soup_buffer_free); + + G_OBJECT_CLASS (soup_cache_input_stream_parent_class)->finalize (object); +} + +static void +write_ready_cb (GObject *source, GAsyncResult *result, SoupCacheInputStream *istream) +{ + GOutputStream *ostream = G_OUTPUT_STREAM (source); + SoupCacheInputStreamPrivate *priv = istream->priv; + gssize write_size; + gsize pending; + GError *error = NULL; + + write_size = g_output_stream_write_finish (ostream, result, &error); + if (error) { + notify_and_clear (istream, error); + g_object_unref (istream); + return; + } + + /* Check that we have written everything */ + pending = priv->current_writing_buffer->length - write_size; + if (pending) { + SoupBuffer *subbuffer = soup_buffer_new_subbuffer (priv->current_writing_buffer, + write_size, pending); + g_queue_push_head (priv->buffer_queue, subbuffer); + } + + priv->bytes_written += write_size; + g_clear_pointer (&priv->current_writing_buffer, soup_buffer_free); + + try_write_next_buffer (istream); + g_object_unref (istream); +} + +static void +soup_cache_input_stream_write_next_buffer (SoupCacheInputStream *istream) +{ + SoupCacheInputStreamPrivate *priv = istream->priv; + SoupBuffer *buffer = g_queue_pop_head (priv->buffer_queue); + int priority; + + g_assert (priv->output_stream && !g_output_stream_is_closed (priv->output_stream)); + + g_clear_pointer (&priv->current_writing_buffer, soup_buffer_free); + priv->current_writing_buffer = buffer; + + if (priv->buffer_queue->length > 10) + priority = G_PRIORITY_DEFAULT; + else + priority = G_PRIORITY_LOW; + + g_output_stream_write_async (priv->output_stream, buffer->data, buffer->length, + priority, priv->cancellable, + (GAsyncReadyCallback) write_ready_cb, + g_object_ref (istream)); +} + +static gssize +read_internal (GInputStream *stream, + void *buffer, + gsize count, + gboolean blocking, + GCancellable *cancellable, + GError **error) +{ + SoupCacheInputStream *istream = SOUP_CACHE_INPUT_STREAM (stream); + SoupCacheInputStreamPrivate *priv = istream->priv; + GInputStream *base_stream; + gssize nread; + + base_stream = g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (stream)); + nread = g_pollable_stream_read (base_stream, buffer, count, blocking, + cancellable, error); + + if (G_UNLIKELY (nread == -1 || priv->read_finished)) + return nread; + + if (nread == 0) { + priv->read_finished = TRUE; + + if (priv->current_writing_buffer == NULL && priv->output_stream) + notify_and_clear (istream, NULL); + } else { + SoupBuffer *soup_buffer = soup_buffer_new (SOUP_MEMORY_COPY, buffer, nread); + g_queue_push_tail (priv->buffer_queue, soup_buffer); + + if (priv->current_writing_buffer == NULL && priv->output_stream) + soup_cache_input_stream_write_next_buffer (istream); + } + + return nread; +} + +static gssize +soup_cache_input_stream_read_fn (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + return read_internal (stream, buffer, count, TRUE, + cancellable, error); +} + +static gssize +soup_cache_input_stream_read_nonblocking (GPollableInputStream *stream, + void *buffer, + gsize count, + GError **error) +{ + return read_internal (G_INPUT_STREAM (stream), buffer, count, FALSE, + NULL, error); +} + +static void +soup_cache_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, + gpointer interface_data) +{ + pollable_interface->read_nonblocking = soup_cache_input_stream_read_nonblocking; +} + +static gboolean +soup_cache_input_stream_close_fn (GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + SoupCacheInputStream *istream = SOUP_CACHE_INPUT_STREAM (stream); + SoupCacheInputStreamPrivate *priv = istream->priv; + + if (!priv->read_finished) { + if (priv->output_stream) { + /* Cancel any pending write operation or return an error if none. */ + if (g_output_stream_has_pending (priv->output_stream)) + g_cancellable_cancel (priv->cancellable); + else { + GError *error = NULL; + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT, + _("Failed to completely cache the resource")); + notify_and_clear (istream, error); + } + } else if (priv->cancellable) + /* The file_replace_async() hasn't finished yet */ + g_cancellable_cancel (priv->cancellable); + } + + return G_INPUT_STREAM_CLASS (soup_cache_input_stream_parent_class)->close_fn (stream, cancellable, error); +} + +static void +soup_cache_input_stream_class_init (SoupCacheInputStreamClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS (klass); + + g_type_class_add_private (klass, sizeof (SoupCacheInputStreamPrivate)); + + gobject_class->get_property = soup_cache_input_stream_get_property; + gobject_class->set_property = soup_cache_input_stream_set_property; + gobject_class->finalize = soup_cache_input_stream_finalize; + + istream_class->read_fn = soup_cache_input_stream_read_fn; + istream_class->close_fn = soup_cache_input_stream_close_fn; + + g_object_class_install_property (gobject_class, PROP_OUTPUT_STREAM, + g_param_spec_object ("output-stream", "Output stream", + "the output stream where to write.", + G_TYPE_OUTPUT_STREAM, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS)); + + signals[CACHING_FINISHED] = + g_signal_new ("caching-finished", + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (SoupCacheInputStreamClass, caching_finished), + NULL, NULL, + NULL, + G_TYPE_NONE, 2, + G_TYPE_INT, G_TYPE_ERROR); +} + +GInputStream * +soup_cache_input_stream_new (GInputStream *base_stream, + GFile *file) +{ + SoupCacheInputStream *istream = g_object_new (SOUP_TYPE_CACHE_INPUT_STREAM, + "base-stream", base_stream, + "close-base-stream", FALSE, + NULL); + + istream->priv->cancellable = g_cancellable_new (); + g_file_replace_async (file, NULL, FALSE, + G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION, + G_PRIORITY_DEFAULT, istream->priv->cancellable, + file_replaced_cb, g_object_ref (istream)); + + return (GInputStream *) istream; +} diff --git a/libsoup/soup-cache-input-stream.h b/libsoup/soup-cache-input-stream.h new file mode 100644 index 00000000..92b1d7b6 --- /dev/null +++ b/libsoup/soup-cache-input-stream.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-cache-input-stream.h - Header for SoupCacheInputStream + */ + +#ifndef __SOUP_CACHE_INPUT_STREAM_H__ +#define __SOUP_CACHE_INPUT_STREAM_H__ + +#include "soup-filter-input-stream.h" + +G_BEGIN_DECLS + +#define SOUP_TYPE_CACHE_INPUT_STREAM (soup_cache_input_stream_get_type()) +#define SOUP_CACHE_INPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_CACHE_INPUT_STREAM, SoupCacheInputStream)) +#define SOUP_CACHE_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CACHE_INPUT_STREAM, SoupCacheInputStreamClass)) +#define SOUP_IS_CACHE_INPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_CACHE_INPUT_STREAM)) +#define SOUP_IS_CACHE_INPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_CACHE_INPUT_STREAM)) +#define SOUP_CACHE_INPUT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CACHE_INPUT_STREAM, SoupCacheInputStreamClass)) + +typedef struct _SoupCacheInputStream SoupCacheInputStream; +typedef struct _SoupCacheInputStreamClass SoupCacheInputStreamClass; +typedef struct _SoupCacheInputStreamPrivate SoupCacheInputStreamPrivate; + +struct _SoupCacheInputStreamClass +{ + SoupFilterInputStreamClass parent_class; + + /* signals */ + void (*caching_finished) (SoupCacheInputStream *istream, gsize bytes_written, GError *error); +}; + +struct _SoupCacheInputStream +{ + SoupFilterInputStream parent; + + SoupCacheInputStreamPrivate *priv; +}; + +GType soup_cache_input_stream_get_type (void) G_GNUC_CONST; + +GInputStream *soup_cache_input_stream_new (GInputStream *base_stream, + GFile *file); + +G_END_DECLS + +#endif /* __SOUP_CACHE_INPUT_STREAM_H__ */ diff --git a/libsoup/soup-cache-private.h b/libsoup/soup-cache-private.h index 3843e8e9..e17fc0de 100644 --- a/libsoup/soup-cache-private.h +++ b/libsoup/soup-cache-private.h @@ -28,14 +28,18 @@ G_BEGIN_DECLS -SoupCacheResponse soup_cache_has_response (SoupCache *cache, - SoupMessage *msg); -GInputStream *soup_cache_send_response (SoupCache *cache, - SoupMessage *msg); -SoupCacheability soup_cache_get_cacheability (SoupCache *cache, - SoupMessage *msg); -SoupMessage *soup_cache_generate_conditional_request (SoupCache *cache, - SoupMessage *original); +SoupCacheResponse soup_cache_has_response (SoupCache *cache, + SoupMessage *msg); +GInputStream *soup_cache_send_response (SoupCache *cache, + SoupMessage *msg); +SoupCacheability soup_cache_get_cacheability (SoupCache *cache, + SoupMessage *msg); +SoupMessage *soup_cache_generate_conditional_request (SoupCache *cache, + SoupMessage *original); +void soup_cache_cancel_conditional_request (SoupCache *cache, + SoupMessage *msg); +void soup_cache_update_from_conditional_request (SoupCache *cache, + SoupMessage *msg); G_END_DECLS diff --git a/libsoup/soup-cache.c b/libsoup/soup-cache.c index 8b26478d..c17e537c 100644 --- a/libsoup/soup-cache.c +++ b/libsoup/soup-cache.c @@ -31,15 +31,28 @@ #include <string.h> -#define LIBSOUP_USE_UNSTABLE_REQUEST_API - #include "soup-cache.h" +#include "soup-body-input-stream.h" +#include "soup-cache-input-stream.h" #include "soup-cache-private.h" +#include "soup-content-processor.h" +#include "soup-message-private.h" #include "soup.h" +#include "soup-message-private.h" + +/** + * SECTION:soup-cache + * @short_description: Caching support + * + * #SoupCache implements a file-based cache for HTTP resources. + */ static SoupSessionFeatureInterface *soup_cache_default_feature_interface; static void soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); +static SoupContentProcessorInterface *soup_cache_default_content_processor_interface; +static void soup_cache_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data); + #define DEFAULT_MAX_SIZE 50 * 1024 * 1024 #define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size of the cache that can be @@ -65,6 +78,19 @@ static void soup_cache_session_feature_init (SoupSessionFeatureInterface *featur */ #define SOUP_CACHE_CURRENT_VERSION 5 +#define OLD_SOUP_CACHE_FILE "soup.cache" +#define SOUP_CACHE_FILE "soup.cache2" + +#define SOUP_CACHE_HEADERS_FORMAT "{ss}" +#define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqa" SOUP_CACHE_HEADERS_FORMAT ")" +#define SOUP_CACHE_ENTRIES_FORMAT "(qa" SOUP_CACHE_PHEADERS_FORMAT ")" + +/* Basically the same format than above except that some strings are + prepended with &. This way the GVariant returns a pointer to the + data instead of duplicating the string */ +#define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}" + + typedef struct _SoupCacheEntry { guint32 key; char *uri; @@ -73,13 +99,9 @@ typedef struct _SoupCacheEntry { gsize length; guint32 corrected_initial_age; guint32 response_time; - SoupBuffer *current_writing_buffer; gboolean dirty; - gboolean got_body; gboolean being_validated; SoupMessageHeaders *headers; - GOutputStream *stream; - GError *error; guint32 hits; GCancellable *cancellable; guint16 status_code; @@ -97,17 +119,6 @@ struct _SoupCachePrivate { GList *lru_start; }; -typedef struct { - SoupCache *cache; - SoupCacheEntry *entry; - SoupMessage *msg; - gulong content_sniffed_handler; - gulong got_chunk_handler; - gulong got_body_handler; - gulong restarted_handler; - GQueue *buffer_queue; -} SoupCacheWritingFixture; - enum { PROP_0, PROP_CACHE_DIR, @@ -118,12 +129,13 @@ enum { G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, - soup_cache_session_feature_init)) + soup_cache_session_feature_init) + G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR, + soup_cache_content_processor_init)) -static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry); +static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge); static void make_room_for_new_entry (SoupCache *cache, guint length_to_add); static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add); -static gboolean write_next_buffer (SoupCacheEntry *entry, SoupCacheWritingFixture *fixture); static GFile * get_file_from_entry (SoupCache *cache, SoupCacheEntry *entry) @@ -260,17 +272,10 @@ get_cacheability (SoupCache *cache, SoupMessage *msg) * and also unref's the GFile object representing it. */ static void -soup_cache_entry_free (SoupCacheEntry *entry, GFile *file) +soup_cache_entry_free (SoupCacheEntry *entry) { - if (file) { - g_file_delete (file, NULL, NULL); - g_object_unref (file); - } - g_free (entry->uri); - g_clear_pointer (&entry->current_writing_buffer, soup_buffer_free); g_clear_pointer (&entry->headers, soup_message_headers_free); - g_clear_error (&entry->error); g_clear_object (&entry->cancellable); g_slice_free (SoupCacheEntry, entry); @@ -282,6 +287,12 @@ copy_headers (const char *name, const char *value, SoupMessageHeaders *headers) soup_message_headers_append (headers, name, value); } +static void +remove_headers (const char *name, const char *value, SoupMessageHeaders *headers) +{ + soup_message_headers_remove (headers, name); +} + static char *hop_by_hop_headers[] = {"Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailer", "Transfer-Encoding", "Upgrade"}; static void @@ -324,8 +335,14 @@ soup_cache_entry_set_freshness (SoupCacheEntry *entry, SoupMessage *msg, SoupCac const char *cache_control; const char *expires, *date, *last_modified; + /* Reset these values. We have to do this to ensure that + * revalidations overwrite previous values for the headers. + */ + entry->must_revalidate = FALSE; + entry->freshness_lifetime = 0; + cache_control = soup_message_headers_get_list (entry->headers, "Cache-Control"); - if (cache_control) { + if (cache_control && *cache_control) { const char *max_age, *s_maxage; gint64 freshness_lifetime = 0; GHashTable *hash; @@ -442,10 +459,7 @@ soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, t entry = g_slice_new0 (SoupCacheEntry); entry->dirty = FALSE; - entry->current_writing_buffer = NULL; - entry->got_body = FALSE; entry->being_validated = FALSE; - entry->error = NULL; entry->status_code = msg->status_code; entry->response_time = response_time; entry->uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE); @@ -488,243 +502,11 @@ soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, t return entry; } -static void -soup_cache_writing_fixture_free (SoupCacheWritingFixture *fixture) -{ - /* Free fixture. And disconnect signals, we don't want to - listen to more SoupMessage events as we're finished with - this resource */ - if (g_signal_handler_is_connected (fixture->msg, fixture->content_sniffed_handler)) - g_signal_handler_disconnect (fixture->msg, fixture->content_sniffed_handler); - if (g_signal_handler_is_connected (fixture->msg, fixture->got_chunk_handler)) - g_signal_handler_disconnect (fixture->msg, fixture->got_chunk_handler); - if (g_signal_handler_is_connected (fixture->msg, fixture->got_body_handler)) - g_signal_handler_disconnect (fixture->msg, fixture->got_body_handler); - if (g_signal_handler_is_connected (fixture->msg, fixture->restarted_handler)) - g_signal_handler_disconnect (fixture->msg, fixture->restarted_handler); - g_queue_foreach (fixture->buffer_queue, (GFunc) soup_buffer_free, NULL); - g_queue_free (fixture->buffer_queue); - g_object_unref (fixture->msg); - g_object_unref (fixture->cache); - g_slice_free (SoupCacheWritingFixture, fixture); -} - -static void -msg_content_sniffed_cb (SoupMessage *msg, gchar *content_type, GHashTable *params, SoupCacheWritingFixture *fixture) -{ - soup_message_headers_set_content_type (fixture->entry->headers, content_type, params); -} - -static void -close_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture) -{ - SoupCacheEntry *entry = fixture->entry; - SoupCache *cache = fixture->cache; - GOutputStream *stream = G_OUTPUT_STREAM (source); - goffset content_length; - - g_warn_if_fail (entry->error == NULL); - - /* FIXME: what do we do on error ? */ - - if (stream) { - g_output_stream_close_finish (stream, result, NULL); - g_object_unref (stream); - } - entry->stream = NULL; - - content_length = soup_message_headers_get_content_length (entry->headers); - - /* If the process was cancelled, then delete the entry from - the cache. Do it also if the size of a chunked resource is - too much for the cache */ - if (g_cancellable_is_cancelled (entry->cancellable)) { - entry->dirty = FALSE; - soup_cache_entry_remove (cache, entry); - soup_cache_entry_free (entry, get_file_from_entry (cache, entry)); - entry = NULL; - } else if ((soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CHUNKED) || - entry->length != (gsize) content_length) { - /* Two options here: - * - * 1. "chunked" data, entry was temporarily added to - * cache (as content-length is 0) and now that we have - * the actual size we have to evaluate if we want it - * in the cache or not - * - * 2. Content-Length has a different value than actual - * length, means that the content was encoded for - * transmission (typically compressed) and thus we - * have to substract the content-length value that was - * added to the cache and add the unencoded length - */ - gint length_to_add = entry->length - content_length; - - /* Make room in cache if needed */ - if (cache_accepts_entries_of_size (cache, length_to_add)) { - make_room_for_new_entry (cache, length_to_add); - - cache->priv->size += length_to_add; - } else { - entry->dirty = FALSE; - soup_cache_entry_remove (cache, entry); - soup_cache_entry_free (entry, get_file_from_entry (cache, entry)); - entry = NULL; - } - } - - if (entry) { - entry->dirty = FALSE; - entry->got_body = FALSE; - - if (entry->current_writing_buffer) { - soup_buffer_free (entry->current_writing_buffer); - entry->current_writing_buffer = NULL; - } - - g_object_unref (entry->cancellable); - entry->cancellable = NULL; - } - - cache->priv->n_pending--; - - /* Frees */ - soup_cache_writing_fixture_free (fixture); -} - -static void -write_ready_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture) -{ - GOutputStream *stream = G_OUTPUT_STREAM (source); - GError *error = NULL; - gssize write_size; - SoupCacheEntry *entry = fixture->entry; - - if (g_cancellable_is_cancelled (entry->cancellable)) { - g_output_stream_close_async (stream, - G_PRIORITY_LOW, - entry->cancellable, - (GAsyncReadyCallback)close_ready_cb, - fixture); - return; - } - - write_size = g_output_stream_write_finish (stream, result, &error); - if (write_size <= 0 || error) { - if (error) - entry->error = error; - g_output_stream_close_async (stream, - G_PRIORITY_LOW, - entry->cancellable, - (GAsyncReadyCallback)close_ready_cb, - fixture); - /* FIXME: We should completely stop caching the - resource at this point */ - } else { - /* Are we still writing and is there new data to write - already ? */ - if (fixture->buffer_queue->length > 0) - write_next_buffer (entry, fixture); - else { - soup_buffer_free (entry->current_writing_buffer); - entry->current_writing_buffer = NULL; - - if (entry->got_body) { - /* If we already received 'got-body' - and we have written all the data, - we can close the stream */ - g_output_stream_close_async (entry->stream, - G_PRIORITY_LOW, - entry->cancellable, - (GAsyncReadyCallback)close_ready_cb, - fixture); - } - } - } -} - static gboolean -write_next_buffer (SoupCacheEntry *entry, SoupCacheWritingFixture *fixture) -{ - SoupBuffer *buffer = g_queue_pop_head (fixture->buffer_queue); - - if (buffer == NULL) - return FALSE; - - /* Free the old buffer */ - if (entry->current_writing_buffer) { - soup_buffer_free (entry->current_writing_buffer); - entry->current_writing_buffer = NULL; - } - entry->current_writing_buffer = buffer; - - g_output_stream_write_async (entry->stream, buffer->data, buffer->length, - G_PRIORITY_LOW, entry->cancellable, - (GAsyncReadyCallback) write_ready_cb, - fixture); - return TRUE; -} - -static void -msg_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, SoupCacheWritingFixture *fixture) -{ - SoupCacheEntry *entry = fixture->entry; - - /* Ignore this if the writing or appending was cancelled */ - if (!g_cancellable_is_cancelled (entry->cancellable)) { - g_queue_push_tail (fixture->buffer_queue, soup_buffer_copy (chunk)); - entry->length += chunk->length; - - if (!cache_accepts_entries_of_size (fixture->cache, entry->length)) { - /* Quickly cancel the caching of the resource */ - g_cancellable_cancel (entry->cancellable); - } - } - - /* FIXME: remove the error check when we cancel the caching at - the first write error */ - /* Only write if the entry stream is ready */ - if (entry->current_writing_buffer == NULL && entry->error == NULL && entry->stream) - write_next_buffer (entry, fixture); -} - -static void -msg_got_body_cb (SoupMessage *msg, SoupCacheWritingFixture *fixture) -{ - SoupCacheEntry *entry = fixture->entry; - g_return_if_fail (entry); - - entry->got_body = TRUE; - - if (!entry->stream && fixture->buffer_queue->length > 0) - /* The stream is not ready to be written but we still - have data to write, we'll write it when the stream - is opened for writing */ - return; - - - if (fixture->buffer_queue->length > 0) { - /* If we still have data to write, write it, - write_ready_cb will close the stream */ - if (entry->current_writing_buffer == NULL && entry->error == NULL && entry->stream) - write_next_buffer (entry, fixture); - return; - } - - if (entry->stream && entry->current_writing_buffer == NULL) - g_output_stream_close_async (entry->stream, - G_PRIORITY_LOW, - entry->cancellable, - (GAsyncReadyCallback)close_ready_cb, - fixture); -} - -static gboolean -soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry) +soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge) { GList *lru_item; - /* if (entry->dirty && !g_cancellable_is_cancelled (entry->cancellable)) { */ if (entry->dirty) { g_cancellable_cancel (entry->cancellable); return FALSE; @@ -745,6 +527,14 @@ soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry) g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache)); + /* Free resources */ + if (purge) { + GFile *file = get_file_from_entry (cache, entry); + g_file_delete (file, NULL, NULL); + g_object_unref (file); + } + soup_cache_entry_free (entry); + return TRUE; } @@ -803,10 +593,9 @@ make_room_for_new_entry (SoupCache *cache, guint length_to_add) /* Discard entries. Once cancelled resources will be * freed in close_ready_cb */ - if (soup_cache_entry_remove (cache, old_entry)) { - soup_cache_entry_free (old_entry, get_file_from_entry (cache, old_entry)); + if (soup_cache_entry_remove (cache, old_entry, TRUE)) lru_entry = cache->priv->lru_start; - } else + else lru_entry = g_list_next (lru_entry); } } @@ -822,7 +611,7 @@ soup_cache_entry_insert (SoupCache *cache, /* Fill the key */ entry->key = get_cache_key_from_uri ((const char *) entry->uri); - if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CHUNKED) + if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH) length_to_add = soup_message_headers_get_content_length (entry->headers); /* Check if we are going to store the resource depending on its size */ @@ -836,9 +625,7 @@ soup_cache_entry_insert (SoupCache *cache, /* Remove any previous entry */ if ((old_entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (entry->key))) != NULL) { - if (soup_cache_entry_remove (cache, old_entry)) - soup_cache_entry_free (old_entry, get_file_from_entry (cache, old_entry)); - else + if (!soup_cache_entry_remove (cache, old_entry, TRUE)) return FALSE; } @@ -879,157 +666,12 @@ soup_cache_entry_lookup (SoupCache *cache, return entry; } -static void -msg_restarted_cb (SoupMessage *msg, SoupCacheEntry *entry) -{ - /* FIXME: What should we do here exactly? */ -} - -static void -replace_cb (GObject *source, GAsyncResult *result, SoupCacheWritingFixture *fixture) -{ - SoupCacheEntry *entry = fixture->entry; - GOutputStream *stream = (GOutputStream *) g_file_replace_finish (G_FILE (source), - result, &entry->error); - - if (g_cancellable_is_cancelled (entry->cancellable) || entry->error) { - if (stream) - g_object_unref (stream); - fixture->cache->priv->n_pending--; - entry->dirty = FALSE; - soup_cache_entry_remove (fixture->cache, entry); - soup_cache_entry_free (entry, get_file_from_entry (fixture->cache, entry)); - soup_cache_writing_fixture_free (fixture); - return; - } - - entry->stream = stream; - - /* If we already got all the data we have to initiate the - * writing here, since we won't get more 'got-chunk' - * signals - */ - if (!entry->got_body) - return; - - /* It could happen that reading the data from server - * was completed before this happens. In that case - * there is no data - */ - if (!write_next_buffer (entry, fixture)) - /* Could happen if the resource is empty */ - g_output_stream_close_async (stream, G_PRIORITY_LOW, entry->cancellable, - (GAsyncReadyCallback) close_ready_cb, - fixture); -} - -typedef struct { - time_t request_time; - SoupSessionFeature *feature; - gulong got_headers_handler; -} RequestHelper; - -static void -msg_got_headers_cb (SoupMessage *msg, gpointer user_data) -{ - SoupCache *cache; - SoupCacheability cacheable; - RequestHelper *helper; - time_t request_time, response_time; - SoupCacheEntry *entry; - - response_time = time (NULL); - - helper = (RequestHelper *)user_data; - cache = SOUP_CACHE (helper->feature); - request_time = helper->request_time; - g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data); - g_slice_free (RequestHelper, helper); - - cacheable = soup_cache_get_cacheability (cache, msg); - - if (cacheable & SOUP_CACHE_CACHEABLE) { - GFile *file; - SoupCacheWritingFixture *fixture; - - /* Check if we are already caching this resource */ - entry = soup_cache_entry_lookup (cache, msg); - - if (entry && (entry->dirty || entry->being_validated)) - return; - - /* Create a new entry, deleting any old one if present */ - if (entry) { - soup_cache_entry_remove (cache, entry); - soup_cache_entry_free (entry, get_file_from_entry (cache, entry)); - } - - entry = soup_cache_entry_new (cache, msg, request_time, response_time); - entry->hits = 1; - - /* Do not continue if it can not be stored */ - if (!soup_cache_entry_insert (cache, entry, TRUE)) { - soup_cache_entry_free (entry, get_file_from_entry (cache, entry)); - return; - } - - fixture = g_slice_new0 (SoupCacheWritingFixture); - fixture->cache = g_object_ref (cache); - fixture->entry = entry; - fixture->msg = g_object_ref (msg); - fixture->buffer_queue = g_queue_new (); - - /* We connect now to these signals and buffer the data - if it comes before the file is ready for writing */ - fixture->content_sniffed_handler = - g_signal_connect (msg, "content-sniffed", G_CALLBACK (msg_content_sniffed_cb), fixture); - fixture->got_chunk_handler = - g_signal_connect (msg, "got-chunk", G_CALLBACK (msg_got_chunk_cb), fixture); - fixture->got_body_handler = - g_signal_connect (msg, "got-body", G_CALLBACK (msg_got_body_cb), fixture); - fixture->restarted_handler = - g_signal_connect (msg, "restarted", G_CALLBACK (msg_restarted_cb), entry); - - /* Prepare entry */ - cache->priv->n_pending++; - - entry->dirty = TRUE; - entry->cancellable = g_cancellable_new (); - file = get_file_from_entry (cache, entry); - g_file_replace_async (file, NULL, FALSE, - G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION, - G_PRIORITY_LOW, entry->cancellable, - (GAsyncReadyCallback) replace_cb, fixture); - g_object_unref (file); - } else if (cacheable & SOUP_CACHE_INVALIDATES) { - entry = soup_cache_entry_lookup (cache, msg); - - if (entry) { - if (soup_cache_entry_remove (cache, entry)) - soup_cache_entry_free (entry, get_file_from_entry (cache, entry)); - } - } else if (cacheable & SOUP_CACHE_VALIDATES) { - entry = soup_cache_entry_lookup (cache, msg); - - /* It's possible to get a CACHE_VALIDATES with no - * entry in the hash table. This could happen if for - * example the soup client is the one creating the - * conditional request. - */ - if (entry) { - entry->being_validated = FALSE; - copy_end_to_end_headers (msg->response_headers, entry->headers); - soup_cache_entry_set_freshness (entry, msg, cache); - } - } -} - GInputStream * soup_cache_send_response (SoupCache *cache, SoupMessage *msg) { SoupCacheEntry *entry; char *current_age; - GInputStream *stream = NULL; + GInputStream *file_stream, *body_stream, *cache_stream; GFile *file; g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL); @@ -1038,18 +680,19 @@ soup_cache_send_response (SoupCache *cache, SoupMessage *msg) entry = soup_cache_entry_lookup (cache, msg); g_return_val_if_fail (entry, NULL); - /* TODO: the original idea was to save reads, but current code - assumes that a stream is always returned. Need to reach - some agreement here. Also we have to handle the situation - were the file was no longer there (for example files - removed without notifying the cache */ file = get_file_from_entry (cache, entry); - stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL)); + file_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL)); g_object_unref (file); /* Do not change the original message if there is no resource */ - if (stream == NULL) - return stream; + if (!file_stream) + return NULL; + + body_stream = soup_body_input_stream_new (file_stream, SOUP_ENCODING_CONTENT_LENGTH, entry->length); + g_object_unref (file_stream); + + if (!body_stream) + return NULL; /* If we are told to send a response from cache any validation in course is over by now */ @@ -1068,19 +711,29 @@ soup_cache_send_response (SoupCache *cache, SoupMessage *msg) current_age); g_free (current_age); - return stream; + /* Create the cache stream. */ + soup_message_disable_feature (msg, SOUP_TYPE_CACHE); + cache_stream = soup_message_setup_body_istream (body_stream, msg, + cache->priv->session, + SOUP_STAGE_ENTITY_BODY); + g_object_unref (body_stream); + + return cache_stream; +} + +static void +msg_got_headers_cb (SoupMessage *msg, gpointer user_data) +{ + g_object_set_data (G_OBJECT (msg), "response-time", GINT_TO_POINTER (time (NULL))); + g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data); } static void request_started (SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg, SoupSocket *socket) { - RequestHelper *helper = g_slice_new0 (RequestHelper); - helper->request_time = time (NULL); - helper->feature = feature; - helper->got_headers_handler = g_signal_connect (msg, "got-headers", - G_CALLBACK (msg_got_headers_cb), - helper); + g_object_set_data (G_OBJECT (msg), "request-time", GINT_TO_POINTER (time (NULL))); + g_signal_connect (msg, "got-headers", G_CALLBACK (msg_got_headers_cb), NULL); } static void @@ -1103,6 +756,138 @@ soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, feature_interface->request_started = request_started; } +typedef struct { + SoupCache *cache; + SoupCacheEntry *entry; +} StreamHelper; + +static void +istream_caching_finished (SoupCacheInputStream *istream, + gsize bytes_written, + GError *error, + gpointer user_data) +{ + StreamHelper *helper = (StreamHelper *) user_data; + SoupCache *cache = helper->cache; + SoupCacheEntry *entry = helper->entry; + + --cache->priv->n_pending; + + entry->dirty = FALSE; + entry->length = bytes_written; + g_clear_object (&entry->cancellable); + + if (error) { + /* Update cache size */ + if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH) + cache->priv->size -= soup_message_headers_get_content_length (entry->headers); + + soup_cache_entry_remove (cache, entry, TRUE); + helper->entry = entry = NULL; + goto cleanup; + } + + if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CONTENT_LENGTH) { + + if (cache_accepts_entries_of_size (cache, entry->length)) { + make_room_for_new_entry (cache, entry->length); + cache->priv->size += entry->length; + } else { + soup_cache_entry_remove (cache, entry, TRUE); + helper->entry = entry = NULL; + } + } + + cleanup: + g_object_unref (helper->cache); + g_slice_free (StreamHelper, helper); +} + +static GInputStream* +soup_cache_content_processor_wrap_input (SoupContentProcessor *processor, + GInputStream *base_stream, + SoupMessage *msg, + GError **error) +{ + SoupCache *cache = (SoupCache*) processor; + SoupCacheEntry *entry; + SoupCacheability cacheability; + GInputStream *istream; + GFile *file; + StreamHelper *helper; + time_t request_time, response_time; + + /* First of all, check if we should cache the resource. */ + cacheability = soup_cache_get_cacheability (cache, msg); + entry = soup_cache_entry_lookup (cache, msg); + + if (cacheability & SOUP_CACHE_INVALIDATES) { + if (entry) + soup_cache_entry_remove (cache, entry, TRUE); + return NULL; + } + + if (cacheability & SOUP_CACHE_VALIDATES) { + /* It's possible to get a CACHE_VALIDATES with no + * entry in the hash table. This could happen if for + * example the soup client is the one creating the + * conditional request. + */ + if (entry) + soup_cache_update_from_conditional_request (cache, msg); + return NULL; + } + + if (!(cacheability & SOUP_CACHE_CACHEABLE)) + return NULL; + + /* Check if we are already caching this resource */ + if (entry && (entry->dirty || entry->being_validated)) + return NULL; + + /* Create a new entry, deleting any old one if present */ + if (entry) + soup_cache_entry_remove (cache, entry, TRUE); + + request_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time")); + response_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time")); + entry = soup_cache_entry_new (cache, msg, request_time, response_time); + entry->hits = 1; + entry->dirty = TRUE; + + /* Do not continue if it can not be stored */ + if (!soup_cache_entry_insert (cache, entry, TRUE)) { + soup_cache_entry_free (entry); + return NULL; + } + + entry->cancellable = g_cancellable_new (); + ++cache->priv->n_pending; + + helper = g_slice_new (StreamHelper); + helper->cache = g_object_ref (cache); + helper->entry = entry; + + file = get_file_from_entry (cache, entry); + istream = soup_cache_input_stream_new (base_stream, file); + g_object_unref (file); + + g_signal_connect (istream, "caching-finished", G_CALLBACK (istream_caching_finished), helper); + + return istream; +} + +static void +soup_cache_content_processor_init (SoupContentProcessorInterface *processor_interface, + gpointer interface_data) +{ + soup_cache_default_content_processor_interface = + g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR); + + processor_interface->processing_stage = SOUP_STAGE_ENTITY_BODY; + processor_interface->wrap_input = soup_cache_content_processor_wrap_input; +} + static void soup_cache_init (SoupCache *cache) { @@ -1127,11 +912,7 @@ static void remove_cache_item (gpointer data, gpointer user_data) { - SoupCache *cache = (SoupCache *) user_data; - SoupCacheEntry *entry = (SoupCacheEntry *) data; - - if (soup_cache_entry_remove (cache, entry)) - soup_cache_entry_free (entry, NULL); + soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, FALSE); } static void @@ -1361,7 +1142,7 @@ soup_cache_has_response (SoupCache *cache, SoupMessage *msg) return SOUP_CACHE_RESPONSE_STALE; cache_control = soup_message_headers_get_list (msg->request_headers, "Cache-Control"); - if (cache_control) { + if (cache_control && *cache_control) { GHashTable *hash = soup_header_parse_param_list (cache_control); if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) { @@ -1470,6 +1251,9 @@ force_flush_timeout (gpointer data) * committed to disk. For doing so it will iterate the #GMainContext * associated with @cache's session as long as needed. * + * Contrast with soup_cache_dump(), which writes out the cache index + * file. + * * Since: 2.34 */ void @@ -1502,19 +1286,39 @@ static void clear_cache_item (gpointer data, gpointer user_data) { - SoupCache *cache = (SoupCache *) user_data; - SoupCacheEntry *entry = (SoupCacheEntry *) data; + soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, TRUE); +} + +static void +clear_cache_files (SoupCache *cache) +{ + GFileInfo *file_info; + GFileEnumerator *file_enumerator; + GFile *cache_dir_file = g_file_new_for_path (cache->priv->cache_dir); + + file_enumerator = g_file_enumerate_children (cache_dir_file, G_FILE_ATTRIBUTE_STANDARD_NAME, + G_FILE_QUERY_INFO_NONE, NULL, NULL); + if (file_enumerator) { + while ((file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL)) != NULL) { + const char *filename = g_file_info_get_name (file_info); - if (soup_cache_entry_remove (cache, entry)) - soup_cache_entry_free (entry, get_file_from_entry (cache, entry)); + if (strcmp (filename, SOUP_CACHE_FILE) != 0) { + GFile *cache_file = g_file_get_child (cache_dir_file, filename); + g_file_delete (cache_file, NULL, NULL); + g_object_unref (cache_file); + } + g_object_unref (file_info); + } + g_object_unref (file_enumerator); + } + g_object_unref (cache_dir_file); } /** * soup_cache_clear: * @cache: a #SoupCache * - * Will remove all entries in the @cache plus all the cache files - * associated with them. + * Will remove all entries in the @cache plus all the cache files. * * Since: 2.34 */ @@ -1530,6 +1334,9 @@ soup_cache_clear (SoupCache *cache) entries = g_hash_table_get_values (cache->priv->cache); g_list_foreach (entries, clear_cache_item, cache); g_list_free (entries); + + /* Remove also any file not associated with a cache entry. */ + clear_cache_files (cache); } SoupMessage * @@ -1539,6 +1346,8 @@ soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original SoupURI *uri; SoupCacheEntry *entry; const char *last_modified, *etag; + SoupMessagePrivate *origpriv; + GSList *f; g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL); g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL); @@ -1558,11 +1367,16 @@ soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original /* Copy the data we need from the original message */ uri = soup_message_get_uri (original); msg = soup_message_new_from_uri (original->method, uri); + soup_message_disable_feature (msg, SOUP_TYPE_CACHE); soup_message_headers_foreach (original->request_headers, (SoupMessageHeadersForeachFunc)copy_headers, msg->request_headers); + origpriv = SOUP_MESSAGE_GET_PRIVATE (original); + for (f = origpriv->disabled_features; f; f = f->next) + soup_message_disable_feature (msg, (GType) GPOINTER_TO_SIZE (f->data)); + if (last_modified) soup_message_headers_append (msg->request_headers, "If-Modified-Since", @@ -1575,17 +1389,38 @@ soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original return msg; } -#define OLD_SOUP_CACHE_FILE "soup.cache" -#define SOUP_CACHE_FILE "soup.cache2" +void +soup_cache_cancel_conditional_request (SoupCache *cache, + SoupMessage *msg) +{ + SoupCacheEntry *entry; -#define SOUP_CACHE_HEADERS_FORMAT "{ss}" -#define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqa" SOUP_CACHE_HEADERS_FORMAT ")" -#define SOUP_CACHE_ENTRIES_FORMAT "(qa" SOUP_CACHE_PHEADERS_FORMAT ")" + entry = soup_cache_entry_lookup (cache, msg); + if (entry) + entry->being_validated = FALSE; -/* Basically the same format than above except that some strings are - prepended with &. This way the GVariant returns a pointer to the - data instead of duplicating the string */ -#define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}" + soup_session_cancel_message (cache->priv->session, msg, SOUP_STATUS_CANCELLED); +} + +void +soup_cache_update_from_conditional_request (SoupCache *cache, + SoupMessage *msg) +{ + SoupCacheEntry *entry = soup_cache_entry_lookup (cache, msg); + if (!entry) + return; + + entry->being_validated = FALSE; + + if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) { + soup_message_headers_foreach (msg->response_headers, + (SoupMessageHeadersForeachFunc) remove_headers, + entry->headers); + copy_end_to_end_headers (msg->response_headers, entry->headers); + + soup_cache_entry_set_freshness (entry, msg, cache); + } +} static void pack_entry (gpointer data, @@ -1597,7 +1432,7 @@ pack_entry (gpointer data, GVariantBuilder *entries_builder = (GVariantBuilder *)user_data; /* Do not store non-consolidated entries */ - if (entry->dirty || entry->current_writing_buffer != NULL || !entry->key) + if (entry->dirty || !entry->key) return; g_variant_builder_open (entries_builder, G_VARIANT_TYPE (SOUP_CACHE_PHEADERS_FORMAT)); @@ -1622,6 +1457,19 @@ pack_entry (gpointer data, g_variant_builder_close (entries_builder); /* SOUP_CACHE_PHEADERS_FORMAT */ } +/** + * soup_cache_dump: + * @cache: a #SoupCache + * + * Synchronously writes the cache index out to disk. Contrast with + * soup_cache_flush(), which writes pending cache + * <emphasis>entries</emphasis> to disk. + * + * You must call this before exiting if you want your cache data to + * persist between sessions. + * + * Since: 2.34. + */ void soup_cache_dump (SoupCache *cache) { @@ -1650,30 +1498,14 @@ soup_cache_dump (SoupCache *cache) g_variant_unref (cache_variant); } -static void -clear_cache_files (SoupCache *cache) -{ - GFileInfo *file_info; - GFileEnumerator *file_enumerator; - GFile *cache_dir_file = g_file_new_for_path (cache->priv->cache_dir); - - file_enumerator = g_file_enumerate_children (cache_dir_file, G_FILE_ATTRIBUTE_STANDARD_NAME, - G_FILE_QUERY_INFO_NONE, NULL, NULL); - if (file_enumerator) { - while ((file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL)) != NULL) { - const char *filename = g_file_info_get_name (file_info); - - if (strcmp (filename, SOUP_CACHE_FILE) != 0) { - GFile *cache_file = g_file_get_child (cache_dir_file, filename); - g_file_delete (cache_file, NULL, NULL); - g_object_unref (cache_file); - } - } - g_object_unref (file_enumerator); - } - g_object_unref (cache_dir_file); -} - +/** + * soup_cache_load: + * @cache: a #SoupCache + * + * Loads the contents of @cache's index into memory. + * + * Since: 2.34 + */ void soup_cache_load (SoupCache *cache) { @@ -1741,7 +1573,7 @@ soup_cache_load (SoupCache *cache) entry->status_code = status_code; if (!soup_cache_entry_insert (cache, entry, FALSE)) - soup_cache_entry_free (entry, get_file_from_entry (cache, entry)); + soup_cache_entry_free (entry); } cache->priv->lru_start = g_list_reverse (cache->priv->lru_start); @@ -1751,6 +1583,15 @@ soup_cache_load (SoupCache *cache) g_variant_unref (cache_variant); } +/** + * soup_cache_set_max_size: + * @cache: a #SoupCache + * @max_size: the maximum size of the cache, in bytes + * + * Sets the maximum size of the cache. + * + * Since: 2.34 + */ void soup_cache_set_max_size (SoupCache *cache, guint max_size) @@ -1759,6 +1600,16 @@ soup_cache_set_max_size (SoupCache *cache, cache->priv->max_entry_data_size = cache->priv->max_size / MAX_ENTRY_DATA_PERCENTAGE; } +/** + * soup_cache_get_max_size: + * @cache: a #SoupCache + * + * Gets the maximum size of the cache. + * + * Return value: the maximum size of the cache, in bytes. + * + * Since: 2.34 + */ guint soup_cache_get_max_size (SoupCache *cache) { diff --git a/libsoup/soup-cache.h b/libsoup/soup-cache.h index 8585d51f..ce19b5e1 100644 --- a/libsoup/soup-cache.h +++ b/libsoup/soup-cache.h @@ -23,8 +23,6 @@ #ifndef SOUP_CACHE_H #define SOUP_CACHE_H 1 -#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API - #include <libsoup/soup-types.h> #include <gio/gio.h> @@ -77,22 +75,28 @@ typedef struct { void (*_libsoup_reserved3)(void); } SoupCacheClass; +SOUP_AVAILABLE_IN_2_34 GType soup_cache_get_type (void); +SOUP_AVAILABLE_IN_2_34 SoupCache *soup_cache_new (const char *cache_dir, SoupCacheType cache_type); +SOUP_AVAILABLE_IN_2_34 void soup_cache_flush (SoupCache *cache); +SOUP_AVAILABLE_IN_2_34 void soup_cache_clear (SoupCache *cache); +SOUP_AVAILABLE_IN_2_34 void soup_cache_dump (SoupCache *cache); +SOUP_AVAILABLE_IN_2_34 void soup_cache_load (SoupCache *cache); +SOUP_AVAILABLE_IN_2_34 void soup_cache_set_max_size (SoupCache *cache, guint max_size); +SOUP_AVAILABLE_IN_2_34 guint soup_cache_get_max_size (SoupCache *cache); G_END_DECLS -#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */ - #endif /* SOUP_CACHE_H */ diff --git a/libsoup/soup-client-input-stream.c b/libsoup/soup-client-input-stream.c index 5c18eaa0..0264cb79 100644 --- a/libsoup/soup-client-input-stream.c +++ b/libsoup/soup-client-input-stream.c @@ -11,7 +11,6 @@ #include "soup-client-input-stream.h" #include "soup.h" -#include "soup-marshal.h" #include "soup-message-private.h" struct _SoupClientInputStreamPrivate { @@ -47,6 +46,16 @@ soup_client_input_stream_init (SoupClientInputStream *stream) } static void +soup_client_input_stream_finalize (GObject *object) +{ + SoupClientInputStream *cistream = SOUP_CLIENT_INPUT_STREAM (object); + + g_clear_object (&cistream->priv->msg); + + G_OBJECT_CLASS (soup_client_input_stream_parent_class)->finalize (object); +} + +static void soup_client_input_stream_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { @@ -78,17 +87,6 @@ soup_client_input_stream_get_property (GObject *object, guint prop_id, } } -/* Temporary HACK to keep SoupCache working. See soup_client_input_stream_read_fn() - * and soup_client_input_stream_read_nonblocking(). - */ -static void -soup_client_input_stream_emit_got_chunk (SoupClientInputStream *stream, void *data, gssize nread) -{ - SoupBuffer *buffer = soup_buffer_new (SOUP_MEMORY_TEMPORARY, data, nread); - soup_message_got_chunk (stream->priv->msg, buffer); - soup_buffer_free (buffer); -} - static gssize soup_client_input_stream_read_fn (GInputStream *stream, void *buffer, @@ -104,12 +102,6 @@ soup_client_input_stream_read_fn (GInputStream *stream, if (nread == 0) g_signal_emit (stream, signals[EOF], 0); - /* Temporary HACK to keep SoupCache working */ - if (nread > 0) { - soup_client_input_stream_emit_got_chunk (SOUP_CLIENT_INPUT_STREAM (stream), - buffer, nread); - } - return nread; } @@ -127,12 +119,6 @@ soup_client_input_stream_read_nonblocking (GPollableInputStream *stream, if (nread == 0) g_signal_emit (stream, signals[EOF], 0); - /* Temporary HACK to keep SoupCache working */ - if (nread > 0) { - soup_client_input_stream_emit_got_chunk (SOUP_CLIENT_INPUT_STREAM (stream), - buffer, nread); - } - return nread; } @@ -142,45 +128,53 @@ soup_client_input_stream_close_fn (GInputStream *stream, GError **error) { SoupClientInputStream *cistream = SOUP_CLIENT_INPUT_STREAM (stream); + gboolean success; - return soup_message_io_run_until_finish (cistream->priv->msg, - cancellable, error); + success = soup_message_io_run_until_finish (cistream->priv->msg, TRUE, + NULL, error); + soup_message_io_finished (cistream->priv->msg); + return success; } -typedef struct { - SoupClientInputStream *cistream; - gint priority; - GCancellable *cancellable; - GSimpleAsyncResult *result; -} CloseAsyncData; - -static void -close_async_data_free (CloseAsyncData *cad) +static gboolean +idle_finish_close (gpointer user_data) { - g_clear_object (&cad->cancellable); - g_object_unref (cad->result); - g_slice_free (CloseAsyncData, cad); + GTask *task = user_data; + + g_task_return_boolean (task, TRUE); + g_object_unref (task); + return FALSE; } static gboolean close_async_ready (SoupMessage *msg, gpointer user_data) { - CloseAsyncData *cad = user_data; + GTask *task = user_data; + SoupClientInputStream *cistream = g_task_get_source_object (task); GError *error = NULL; - if (!soup_message_io_run_until_finish (cad->cistream->priv->msg, - cad->cancellable, &error) && + if (!soup_message_io_run_until_finish (cistream->priv->msg, FALSE, + g_task_get_cancellable (task), + &error) && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_error_free (error); return TRUE; } - if (error) - g_simple_async_result_take_error (cad->result, error); - else - g_simple_async_result_set_op_res_gboolean (cad->result, TRUE); - g_simple_async_result_complete_in_idle (cad->result); - close_async_data_free (cad); + soup_message_io_finished (cistream->priv->msg); + + if (error) { + g_task_return_error (task, error); + g_object_unref (task); + return FALSE; + } + + /* Due to a historical accident, SoupSessionAsync relies on us + * waiting one extra cycle after run_until_finish() returns. + * Ugh. FIXME later when it's easier to do. + */ + soup_add_idle (g_main_context_get_thread_default (), + idle_finish_close, task); return FALSE; } @@ -191,23 +185,20 @@ soup_client_input_stream_close_async (GInputStream *stream, GAsyncReadyCallback callback, gpointer user_data) { - CloseAsyncData *cad; + SoupClientInputStream *cistream = SOUP_CLIENT_INPUT_STREAM (stream); + GTask *task; GSource *source; - cad = g_slice_new (CloseAsyncData); - cad->cistream = SOUP_CLIENT_INPUT_STREAM (stream); - cad->result = g_simple_async_result_new (G_OBJECT (stream), - callback, user_data, - soup_client_input_stream_close_async); - cad->priority = priority; - cad->cancellable = cancellable ? g_object_ref (cancellable) : NULL; - - source = soup_message_io_get_source (cad->cistream->priv->msg, - cancellable, - close_async_ready, cad); - g_source_set_priority (source, priority); - g_source_attach (source, g_main_context_get_thread_default ()); - g_source_unref (source); + task = g_task_new (stream, cancellable, callback, user_data); + g_task_set_priority (task, priority); + + if (close_async_ready (cistream->priv->msg, task) == G_SOURCE_CONTINUE) { + source = soup_message_io_get_source (cistream->priv->msg, + cancellable, NULL, NULL); + + g_task_attach_source (task, source, (GSourceFunc) close_async_ready); + g_source_unref (source); + } } static gboolean @@ -215,12 +206,7 @@ soup_client_input_stream_close_finish (GInputStream *stream, GAsyncResult *result, GError **error) { - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); - - if (g_simple_async_result_propagate_error (simple, error)) - return FALSE; - else - return g_simple_async_result_get_op_res_gboolean (simple); + return g_task_propagate_boolean (G_TASK (result), error); } static void @@ -231,6 +217,7 @@ soup_client_input_stream_class_init (SoupClientInputStreamClass *stream_class) g_type_class_add_private (stream_class, sizeof (SoupClientInputStreamPrivate)); + object_class->finalize = soup_client_input_stream_finalize; object_class->set_property = soup_client_input_stream_set_property; object_class->get_property = soup_client_input_stream_get_property; @@ -245,7 +232,7 @@ soup_client_input_stream_class_init (SoupClientInputStreamClass *stream_class) G_SIGNAL_RUN_LAST, 0, NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); g_object_class_install_property ( diff --git a/libsoup/soup-connection-auth.c b/libsoup/soup-connection-auth.c new file mode 100644 index 00000000..fc327335 --- /dev/null +++ b/libsoup/soup-connection-auth.c @@ -0,0 +1,173 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-connection-auth.c: Abstract base class for hacky Microsoft + * connection-based auth mechanisms (NTLM and Negotiate) + * + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <ctype.h> +#include <string.h> + +#include "soup-connection-auth.h" +#include "soup.h" +#include "soup-connection.h" +#include "soup-message-private.h" + +G_DEFINE_ABSTRACT_TYPE (SoupConnectionAuth, soup_connection_auth, SOUP_TYPE_AUTH) + +struct SoupConnectionAuthPrivate { + GHashTable *conns; +}; + +static void +soup_connection_auth_init (SoupConnectionAuth *auth) +{ + auth->priv = G_TYPE_INSTANCE_GET_PRIVATE (auth, SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthPrivate); + + auth->priv->conns = g_hash_table_new (NULL, NULL); +} + +static void connection_disconnected (SoupConnection *conn, gpointer user_data); + +static void +soup_connection_auth_free_connection_state (SoupConnectionAuth *auth, + SoupConnection *conn, + gpointer state) +{ + g_signal_handlers_disconnect_by_func (conn, G_CALLBACK (connection_disconnected), auth); + SOUP_CONNECTION_AUTH_GET_CLASS (auth)->free_connection_state (auth, state); +} + +static void +connection_disconnected (SoupConnection *conn, gpointer user_data) +{ + SoupConnectionAuth *auth = user_data; + gpointer state; + + state = g_hash_table_lookup (auth->priv->conns, conn); + g_hash_table_remove (auth->priv->conns, conn); + soup_connection_auth_free_connection_state (auth, conn, state); +} + +static void +soup_connection_auth_finalize (GObject *object) +{ + SoupConnectionAuth *auth = SOUP_CONNECTION_AUTH (object); + GHashTableIter iter; + gpointer conn, state; + + g_hash_table_iter_init (&iter, auth->priv->conns); + while (g_hash_table_iter_next (&iter, &conn, &state)) { + soup_connection_auth_free_connection_state (auth, conn, state); + g_hash_table_iter_remove (&iter); + } + g_hash_table_destroy (auth->priv->conns); + + G_OBJECT_CLASS (soup_connection_auth_parent_class)->finalize (object); +} + +static gpointer +get_connection_state_for_message (SoupConnectionAuth *auth, SoupMessage *msg) +{ + SoupConnection *conn; + gpointer state; + + conn = soup_message_get_connection (msg); + state = g_hash_table_lookup (auth->priv->conns, conn); + if (state) + return state; + + state = SOUP_CONNECTION_AUTH_GET_CLASS (auth)->create_connection_state (auth); + if (conn) { + g_signal_connect (conn, "disconnected", + G_CALLBACK (connection_disconnected), auth); + } + + g_hash_table_insert (auth->priv->conns, conn, state); + return state; +} + +static gboolean +soup_connection_auth_update (SoupAuth *auth, + SoupMessage *msg, + GHashTable *auth_params) +{ + SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth); + gpointer conn = get_connection_state_for_message (cauth, msg); + GHashTableIter iter; + GString *auth_header; + gpointer key, value; + gboolean result; + + /* Recreate @auth_header out of @auth_params. If the + * base64 data ended with 1 or more "="s, then it + * will have been parsed as key=value. Otherwise + * it will all have been parsed as key, and value + * will be %NULL. + */ + auth_header = g_string_new (soup_auth_get_scheme_name (auth)); + g_hash_table_iter_init (&iter, auth_params); + if (g_hash_table_iter_next (&iter, &key, &value)) { + if (value) { + g_string_append_printf (auth_header, " %s=%s", + (char *)key, + (char *)value); + } else { + g_string_append_printf (auth_header, " %s", + (char *)key); + } + + if (g_hash_table_iter_next (&iter, &key, &value)) { + g_string_free (auth_header, TRUE); + return FALSE; + } + } + + result = SOUP_CONNECTION_AUTH_GET_CLASS (auth)-> + update_connection (cauth, msg, auth_header->str, conn); + + g_string_free (auth_header, TRUE); + return result; +} + +static char * +soup_connection_auth_get_authorization (SoupAuth *auth, + SoupMessage *msg) +{ + SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth); + gpointer conn = get_connection_state_for_message (cauth, msg); + + return SOUP_CONNECTION_AUTH_GET_CLASS (auth)-> + get_connection_authorization (cauth, msg, conn); +} + +static gboolean +soup_connection_auth_is_ready (SoupAuth *auth, + SoupMessage *msg) +{ + SoupConnectionAuth *cauth = SOUP_CONNECTION_AUTH (auth); + gpointer conn = get_connection_state_for_message (cauth, msg); + + return SOUP_CONNECTION_AUTH_GET_CLASS (auth)-> + is_connection_ready (SOUP_CONNECTION_AUTH (auth), msg, conn); +} + +static void +soup_connection_auth_class_init (SoupConnectionAuthClass *connauth_class) +{ + SoupAuthClass *auth_class = SOUP_AUTH_CLASS (connauth_class); + GObjectClass *object_class = G_OBJECT_CLASS (connauth_class); + + g_type_class_add_private (connauth_class, sizeof (SoupConnectionAuthPrivate)); + + auth_class->update = soup_connection_auth_update; + auth_class->get_authorization = soup_connection_auth_get_authorization; + auth_class->is_ready = soup_connection_auth_is_ready; + + object_class->finalize = soup_connection_auth_finalize; +} diff --git a/libsoup/soup-connection-auth.h b/libsoup/soup-connection-auth.h new file mode 100644 index 00000000..251ca359 --- /dev/null +++ b/libsoup/soup-connection-auth.h @@ -0,0 +1,52 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2010 Red Hat, Inc. + */ + +#ifndef SOUP_CONNECTION_AUTH_H +#define SOUP_CONNECTION_AUTH_H 1 + +#include <libsoup/soup-auth.h> + +G_BEGIN_DECLS + +#define SOUP_TYPE_CONNECTION_AUTH (soup_connection_auth_get_type ()) +#define SOUP_CONNECTION_AUTH(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuth)) +#define SOUP_CONNECTION_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthClass)) +#define SOUP_IS_CONNECTION_AUTH(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_CONNECTION_AUTH)) +#define SOUP_IS_CONNECTION_AUTH_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_CONNECTION_AUTH)) +#define SOUP_CONNECTION_AUTH_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_CONNECTION_AUTH, SoupConnectionAuthClass)) + +typedef struct SoupConnectionAuthPrivate SoupConnectionAuthPrivate; + +typedef struct { + SoupAuth parent; + + SoupConnectionAuthPrivate *priv; +} SoupConnectionAuth; + +typedef struct { + SoupAuthClass parent_class; + + gpointer (*create_connection_state) (SoupConnectionAuth *auth); + void (*free_connection_state) (SoupConnectionAuth *auth, + gpointer conn); + + gboolean (*update_connection) (SoupConnectionAuth *auth, + SoupMessage *msg, + const char *auth_header, + gpointer conn); + char *(*get_connection_authorization) (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer conn); + gboolean (*is_connection_ready) (SoupConnectionAuth *auth, + SoupMessage *msg, + gpointer conn); +} SoupConnectionAuthClass; + +SOUP_AVAILABLE_IN_2_42 +GType soup_connection_auth_get_type (void); + +G_END_DECLS + +#endif /* SOUP_CONNECTION_AUTH_H */ diff --git a/libsoup/soup-connection.c b/libsoup/soup-connection.c index 48c3d00c..fce589fa 100644 --- a/libsoup/soup-connection.c +++ b/libsoup/soup-connection.c @@ -11,23 +11,22 @@ #include "soup-connection.h" #include "soup.h" -#include "soup-marshal.h" #include "soup-message-queue.h" #include "soup-misc-private.h" typedef struct { SoupSocket *socket; + SoupAddress *local_addr; SoupURI *remote_uri, *proxy_uri; - SoupProxyURIResolver *proxy_resolver; - gboolean use_gproxyresolver; + GProxyResolver *proxy_resolver; GTlsDatabase *tlsdb; gboolean ssl, ssl_strict, ssl_fallback; GMainContext *async_context; gboolean use_thread_context; - SoupMessageQueueItem *cur_item; + SoupMessage *current_msg; SoupConnectionState state; time_t unused_timeout; guint io_timeout, idle_timeout; @@ -49,6 +48,7 @@ static guint signals[LAST_SIGNAL] = { 0 }; enum { PROP_0, + PROP_LOCAL_ADDRESS, PROP_REMOTE_URI, PROP_PROXY_RESOLVER, PROP_SSL, @@ -60,13 +60,11 @@ enum { PROP_TIMEOUT, PROP_IDLE_TIMEOUT, PROP_STATE, - PROP_MESSAGE, LAST_PROP }; static void stop_idle_timer (SoupConnectionPrivate *priv); -static void clear_current_item (SoupConnection *conn); /* Number of seconds after which we close a connection that hasn't yet * been used. @@ -87,6 +85,7 @@ soup_connection_finalize (GObject *object) g_clear_pointer (&priv->proxy_uri, soup_uri_free); g_clear_object (&priv->tlsdb); g_clear_object (&priv->proxy_resolver); + g_clear_object (&priv->local_addr); g_clear_pointer (&priv->async_context, g_main_context_unref); G_OBJECT_CLASS (soup_connection_parent_class)->finalize (object); @@ -99,13 +98,7 @@ soup_connection_dispose (GObject *object) SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); stop_idle_timer (priv); - /* Make sure clear_current_item doesn't re-establish the timeout */ - priv->idle_timeout = 0; - if (priv->cur_item) { - g_warning ("Disposing connection with cur_item set"); - clear_current_item (conn); - } if (priv->socket) { g_warning ("Disposing connection while connected"); soup_connection_disconnect (conn); @@ -119,18 +112,16 @@ soup_connection_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object); - SoupProxyURIResolver *proxy_resolver; switch (prop_id) { + case PROP_LOCAL_ADDRESS: + priv->local_addr = g_value_dup_object (value); + break; case PROP_REMOTE_URI: priv->remote_uri = g_value_dup_boxed (value); break; case PROP_PROXY_RESOLVER: - proxy_resolver = g_value_get_object (value); - if (proxy_resolver && SOUP_IS_PROXY_RESOLVER_DEFAULT (proxy_resolver)) - priv->use_gproxyresolver = TRUE; - else if (proxy_resolver) - priv->proxy_resolver = g_object_ref (proxy_resolver); + priv->proxy_resolver = g_value_dup_object (value); break; case PROP_SSL: priv->ssl = g_value_get_boolean (value); @@ -176,6 +167,9 @@ soup_connection_get_property (GObject *object, guint prop_id, SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (object); switch (prop_id) { + case PROP_LOCAL_ADDRESS: + g_value_set_object (value, priv->local_addr); + break; case PROP_REMOTE_URI: g_value_set_boxed (value, priv->remote_uri); break; @@ -206,12 +200,6 @@ soup_connection_get_property (GObject *object, guint prop_id, case PROP_STATE: g_value_set_enum (value, priv->state); break; - case PROP_MESSAGE: - if (priv->cur_item) - g_value_set_object (value, priv->cur_item->msg); - else - g_value_set_object (value, NULL); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -248,11 +236,18 @@ soup_connection_class_init (SoupConnectionClass *connection_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupConnectionClass, disconnected), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /* properties */ g_object_class_install_property ( + object_class, PROP_LOCAL_ADDRESS, + g_param_spec_object (SOUP_CONNECTION_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_URI, g_param_spec_boxed (SOUP_CONNECTION_REMOTE_URI, "Remote URI", @@ -263,8 +258,8 @@ soup_connection_class_init (SoupConnectionClass *connection_class) object_class, PROP_PROXY_RESOLVER, g_param_spec_object (SOUP_CONNECTION_PROXY_RESOLVER, "Proxy resolver", - "SoupProxyURIResolver to use", - SOUP_TYPE_PROXY_URI_RESOLVER, + "GProxyResolver to use", + G_TYPE_PROXY_RESOLVER, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property ( object_class, PROP_SSL, @@ -328,13 +323,20 @@ soup_connection_class_init (SoupConnectionClass *connection_class) "Current state of connection", SOUP_TYPE_CONNECTION_STATE, SOUP_CONNECTION_NEW, G_PARAM_READWRITE)); - g_object_class_install_property ( - object_class, PROP_MESSAGE, - g_param_spec_object (SOUP_CONNECTION_MESSAGE, - "Message", - "Message being processed", - SOUP_TYPE_MESSAGE, - G_PARAM_READABLE)); +} + +static void +soup_connection_event (SoupConnection *conn, + GSocketClientEvent event, + GIOStream *connection) +{ + SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); + + if (!connection && priv->socket) + connection = soup_socket_get_connection (priv->socket); + + g_signal_emit (conn, signals[EVENT], 0, + event, connection); } static gboolean @@ -367,99 +369,71 @@ stop_idle_timer (SoupConnectionPrivate *priv) } static void -current_item_restarted (SoupMessage *msg, gpointer user_data) +current_msg_got_body (SoupMessage *msg, gpointer user_data) { SoupConnection *conn = user_data; SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); priv->unused_timeout = 0; + + if (priv->proxy_uri && + msg->method == SOUP_METHOD_CONNECT && + SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { + soup_connection_event (conn, G_SOCKET_CLIENT_PROXY_NEGOTIATED, NULL); + + /* We're now effectively no longer proxying */ + g_clear_pointer (&priv->proxy_uri, soup_uri_free); + } + + priv->reusable = soup_message_is_keepalive (msg); } static void -set_current_item (SoupConnection *conn, SoupMessageQueueItem *item) +clear_current_msg (SoupConnection *conn) { SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); + SoupMessage *msg; - g_return_if_fail (priv->cur_item == NULL); - - g_object_freeze_notify (G_OBJECT (conn)); - - stop_idle_timer (priv); - - item->state = SOUP_MESSAGE_RUNNING; - priv->cur_item = item; - g_object_notify (G_OBJECT (conn), "message"); - priv->reusable = FALSE; - - g_signal_connect (item->msg, "restarted", - G_CALLBACK (current_item_restarted), conn); - - if (item->msg->method == SOUP_METHOD_CONNECT) { - g_signal_emit (conn, signals[EVENT], 0, - G_SOCKET_CLIENT_PROXY_NEGOTIATING, - soup_socket_get_connection (priv->socket)); - } else if (priv->state == SOUP_CONNECTION_IDLE) - soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE); + msg = priv->current_msg; + priv->current_msg = NULL; - g_object_thaw_notify (G_OBJECT (conn)); + g_signal_handlers_disconnect_by_func (msg, G_CALLBACK (current_msg_got_body), conn); + g_object_unref (msg); } static void -clear_current_item (SoupConnection *conn) +set_current_msg (SoupConnection *conn, SoupMessage *msg) { SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); - g_object_freeze_notify (G_OBJECT (conn)); + g_return_if_fail (priv->state == SOUP_CONNECTION_IN_USE); - priv->unused_timeout = 0; - start_idle_timer (conn); + g_object_freeze_notify (G_OBJECT (conn)); - if (priv->cur_item) { - SoupMessageQueueItem *item; + if (priv->current_msg) { + g_return_if_fail (priv->current_msg->method == SOUP_METHOD_CONNECT); + clear_current_msg (conn); + } - item = priv->cur_item; - priv->cur_item = NULL; - g_object_notify (G_OBJECT (conn), "message"); + stop_idle_timer (priv); - g_signal_handlers_disconnect_by_func (item->msg, G_CALLBACK (current_item_restarted), conn); + priv->current_msg = g_object_ref (msg); + priv->reusable = FALSE; - if (item->msg->method == SOUP_METHOD_CONNECT && - SOUP_STATUS_IS_SUCCESSFUL (item->msg->status_code)) { - g_signal_emit (conn, signals[EVENT], 0, - G_SOCKET_CLIENT_PROXY_NEGOTIATED, - soup_socket_get_connection (priv->socket)); + g_signal_connect (msg, "got-body", + G_CALLBACK (current_msg_got_body), conn); - /* We're now effectively no longer proxying */ - soup_uri_free (priv->proxy_uri); - priv->proxy_uri = NULL; - } - - if (!soup_message_is_keepalive (item->msg) || !priv->reusable) - soup_connection_disconnect (conn); - } + if (priv->proxy_uri && msg->method == SOUP_METHOD_CONNECT) + soup_connection_event (conn, G_SOCKET_CLIENT_PROXY_NEGOTIATING, NULL); g_object_thaw_notify (G_OBJECT (conn)); } static void -soup_connection_event (SoupConnection *conn, - GSocketClientEvent event, - GIOStream *connection) -{ - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); - - if (!connection && priv->socket) - connection = soup_socket_get_connection (priv->socket); - - g_signal_emit (conn, signals[EVENT], 0, - event, connection); -} - -static void -proxy_socket_event (SoupSocket *socket, - GSocketClientEvent event, - GIOStream *connection, - gpointer user_data) +re_emit_socket_event (SoupSocket *socket, + GSocketClientEvent event, + GIOStream *connection, + gpointer user_data) { SoupConnection *conn = user_data; @@ -469,98 +443,103 @@ proxy_socket_event (SoupSocket *socket, } static void -socket_disconnected (SoupSocket *sock, gpointer conn) -{ - soup_connection_disconnect (conn); -} - -typedef struct { - SoupConnection *conn; - SoupConnectionCallback callback; - gpointer callback_data; - GCancellable *cancellable; - guint event_id; -} SoupConnectionAsyncConnectData; - -static void -socket_connect_finished (SoupSocket *socket, guint status, gpointer user_data) +socket_connect_finished (GTask *task, SoupSocket *sock, GError *error) { - SoupConnectionAsyncConnectData *data = user_data; - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (data->conn); + SoupConnection *conn = g_task_get_source_object (task); + SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); - g_signal_handler_disconnect (socket, data->event_id); + if (priv->async_context && !priv->use_thread_context) + g_main_context_pop_thread_default (priv->async_context); - if (SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_signal_connect (priv->socket, "disconnected", - G_CALLBACK (socket_disconnected), data->conn); + g_signal_handlers_disconnect_by_func (sock, G_CALLBACK (re_emit_socket_event), conn); + if (!error) { if (priv->ssl && !priv->proxy_uri) { - soup_connection_event (data->conn, + soup_connection_event (conn, G_SOCKET_CLIENT_TLS_HANDSHAKED, NULL); } if (!priv->ssl || !priv->proxy_uri) { - soup_connection_event (data->conn, + soup_connection_event (conn, G_SOCKET_CLIENT_COMPLETE, NULL); } - soup_connection_set_state (data->conn, SOUP_CONNECTION_IN_USE); + soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE); priv->unused_timeout = time (NULL) + SOUP_CONNECTION_UNUSED_TIMEOUT; - start_idle_timer (data->conn); - } else if (status == SOUP_STATUS_TLS_FAILED) { - priv->ssl_fallback = TRUE; - status = SOUP_STATUS_TRY_AGAIN; - } + start_idle_timer (conn); - if (data->callback) { - if (priv->proxy_uri != NULL) - status = soup_status_proxify (status); - data->callback (data->conn, status, data->callback_data); - } - g_object_unref (data->conn); - if (data->cancellable) - g_object_unref (data->cancellable); - g_slice_free (SoupConnectionAsyncConnectData, data); + g_task_return_boolean (task, TRUE); + } else + g_task_return_error (task, error); + g_object_unref (task); +} + +static void +socket_handshake_complete (GObject *object, GAsyncResult *result, gpointer user_data) +{ + SoupSocket *sock = SOUP_SOCKET (object); + GTask *task = user_data; + GError *error = NULL; + + soup_socket_handshake_finish (sock, result, &error); + socket_connect_finished (task, sock, error); } static void -socket_connect_result (SoupSocket *sock, guint status, gpointer user_data) +socket_connect_complete (GObject *object, GAsyncResult *result, gpointer user_data) { - SoupConnectionAsyncConnectData *data = user_data; - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (data->conn); + SoupSocket *sock = SOUP_SOCKET (object); + GTask *task = user_data; + SoupConnection *conn = g_task_get_source_object (task); + SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); + GError *error = NULL; - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - socket_connect_finished (sock, status, data); + if (!soup_socket_connect_finish_internal (sock, result, &error)) { + socket_connect_finished (task, sock, error); return; } - if (priv->use_gproxyresolver) - priv->proxy_uri = soup_socket_get_http_proxy_uri (priv->socket); + priv->proxy_uri = soup_socket_get_http_proxy_uri (sock); if (priv->ssl && !priv->proxy_uri) { - if (soup_socket_start_ssl (sock, data->cancellable)) { - soup_connection_event (data->conn, - G_SOCKET_CLIENT_TLS_HANDSHAKING, - NULL); - soup_socket_handshake_async (sock, data->cancellable, - socket_connect_finished, data); - return; - } + soup_connection_event (conn, + G_SOCKET_CLIENT_TLS_HANDSHAKING, + NULL); - status = SOUP_STATUS_SSL_FAILED; + soup_socket_handshake_async (sock, priv->remote_uri->host, + g_task_get_cancellable (task), + socket_handshake_complete, task); + return; } - socket_connect_finished (sock, status, data); + socket_connect_finished (task, sock, NULL); } -static void -connect_async_to_uri (SoupConnectionAsyncConnectData *data, SoupURI *uri) +void +soup_connection_connect_async (SoupConnection *conn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (data->conn); + SoupConnectionPrivate *priv; SoupAddress *remote_addr; + GTask *task; + + g_return_if_fail (SOUP_IS_CONNECTION (conn)); + priv = SOUP_CONNECTION_GET_PRIVATE (conn); + g_return_if_fail (priv->socket == NULL); + + soup_connection_set_state (conn, SOUP_CONNECTION_CONNECTING); + + /* Set the protocol to ensure correct proxy resolution. */ + remote_addr = + g_object_new (SOUP_TYPE_ADDRESS, + SOUP_ADDRESS_NAME, priv->remote_uri->host, + SOUP_ADDRESS_PORT, priv->remote_uri->port, + SOUP_ADDRESS_PROTOCOL, priv->remote_uri->scheme, + NULL); - remote_addr = soup_address_new (uri->host, uri->port); priv->socket = soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, remote_addr, SOUP_SOCKET_SSL_CREDENTIALS, priv->tlsdb, @@ -568,178 +547,107 @@ connect_async_to_uri (SoupConnectionAsyncConnectData *data, SoupURI *uri) SOUP_SOCKET_SSL_FALLBACK, priv->ssl_fallback, SOUP_SOCKET_ASYNC_CONTEXT, priv->async_context, SOUP_SOCKET_USE_THREAD_CONTEXT, priv->use_thread_context, - SOUP_SOCKET_USE_PROXY, priv->use_gproxyresolver, + SOUP_SOCKET_PROXY_RESOLVER, priv->proxy_resolver, SOUP_SOCKET_TIMEOUT, priv->io_timeout, SOUP_SOCKET_CLEAN_DISPOSE, TRUE, + SOUP_SOCKET_LOCAL_ADDRESS, priv->local_addr, NULL); g_object_unref (remote_addr); - data->event_id = g_signal_connect (priv->socket, "event", - G_CALLBACK (proxy_socket_event), - data->conn); - - soup_socket_connect_async (priv->socket, data->cancellable, - socket_connect_result, data); -} - -static void -proxy_resolver_result (SoupProxyURIResolver *resolver, - guint status, SoupURI *proxy_uri, - gpointer user_data) -{ - SoupConnectionAsyncConnectData *data = user_data; - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (data->conn); + g_signal_connect (priv->socket, "event", + G_CALLBACK (re_emit_socket_event), conn); - if (status != SOUP_STATUS_OK) { - socket_connect_finished (NULL, status, data); - return; - } + if (priv->async_context && !priv->use_thread_context) + g_main_context_push_thread_default (priv->async_context); + task = g_task_new (conn, cancellable, callback, user_data); - if (proxy_uri) { - priv->proxy_uri = soup_uri_copy (proxy_uri); - connect_async_to_uri (data, proxy_uri); - } else - connect_async_to_uri (data, priv->remote_uri); + soup_socket_connect_async_internal (priv->socket, cancellable, + socket_connect_complete, task); } -void -soup_connection_connect_async (SoupConnection *conn, - GCancellable *cancellable, - SoupConnectionCallback callback, - gpointer user_data) +gboolean +soup_connection_connect_finish (SoupConnection *conn, + GAsyncResult *result, + GError **error) { - SoupConnectionAsyncConnectData *data; - SoupConnectionPrivate *priv; - GMainContext *async_context; - - g_return_if_fail (SOUP_IS_CONNECTION (conn)); - priv = SOUP_CONNECTION_GET_PRIVATE (conn); - g_return_if_fail (priv->socket == NULL); - - soup_connection_set_state (conn, SOUP_CONNECTION_CONNECTING); - - data = g_slice_new (SoupConnectionAsyncConnectData); - data->conn = g_object_ref (conn); - data->callback = callback; - data->callback_data = user_data; - data->cancellable = cancellable ? g_object_ref (cancellable) : NULL; - - if (!priv->proxy_resolver) { - connect_async_to_uri (data, priv->remote_uri); - return; - } - - if (priv->use_thread_context) - async_context = g_main_context_get_thread_default (); - else - async_context = priv->async_context; - - soup_proxy_uri_resolver_get_proxy_uri_async (priv->proxy_resolver, - priv->remote_uri, - async_context, - cancellable, - proxy_resolver_result, - data); + return g_task_propagate_boolean (G_TASK (result), error); } -guint -soup_connection_connect_sync (SoupConnection *conn, GCancellable *cancellable) +gboolean +soup_connection_connect_sync (SoupConnection *conn, + GCancellable *cancellable, + GError **error) { SoupConnectionPrivate *priv; - guint status, event_id; - SoupURI *connect_uri; + guint event_id = 0; SoupAddress *remote_addr; + gboolean success = TRUE; - g_return_val_if_fail (SOUP_IS_CONNECTION (conn), SOUP_STATUS_MALFORMED); + g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE); priv = SOUP_CONNECTION_GET_PRIVATE (conn); - g_return_val_if_fail (priv->socket == NULL, SOUP_STATUS_MALFORMED); + g_return_val_if_fail (priv->socket == NULL, FALSE); soup_connection_set_state (conn, SOUP_CONNECTION_CONNECTING); - if (priv->proxy_resolver) { - status = soup_proxy_uri_resolver_get_proxy_uri_sync (priv->proxy_resolver, - priv->remote_uri, - cancellable, - &priv->proxy_uri); - if (status != SOUP_STATUS_OK) - goto fail; - - if (priv->proxy_uri) - connect_uri = priv->proxy_uri; - else - connect_uri = priv->remote_uri; - } else - connect_uri = priv->remote_uri; + /* Set the protocol to ensure correct proxy resolution. */ + remote_addr = + g_object_new (SOUP_TYPE_ADDRESS, + SOUP_ADDRESS_NAME, priv->remote_uri->host, + SOUP_ADDRESS_PORT, priv->remote_uri->port, + SOUP_ADDRESS_PROTOCOL, priv->remote_uri->scheme, + NULL); - remote_addr = soup_address_new (connect_uri->host, connect_uri->port); priv->socket = soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, remote_addr, - SOUP_SOCKET_USE_PROXY, priv->use_gproxyresolver, + SOUP_SOCKET_PROXY_RESOLVER, priv->proxy_resolver, SOUP_SOCKET_SSL_CREDENTIALS, priv->tlsdb, SOUP_SOCKET_SSL_STRICT, priv->ssl_strict, SOUP_SOCKET_SSL_FALLBACK, priv->ssl_fallback, SOUP_SOCKET_FLAG_NONBLOCKING, FALSE, SOUP_SOCKET_TIMEOUT, priv->io_timeout, SOUP_SOCKET_CLEAN_DISPOSE, TRUE, + SOUP_SOCKET_LOCAL_ADDRESS, priv->local_addr, NULL); g_object_unref (remote_addr); event_id = g_signal_connect (priv->socket, "event", - G_CALLBACK (proxy_socket_event), conn); - status = soup_socket_connect_sync (priv->socket, cancellable); - - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) - goto fail; + G_CALLBACK (re_emit_socket_event), conn); + if (!soup_socket_connect_sync_internal (priv->socket, cancellable, error)) { + success = FALSE; + goto done; + } - if (priv->use_gproxyresolver) - priv->proxy_uri = soup_socket_get_http_proxy_uri (priv->socket); + priv->proxy_uri = soup_socket_get_http_proxy_uri (priv->socket); if (priv->ssl && !priv->proxy_uri) { - if (!soup_socket_start_ssl (priv->socket, cancellable)) - status = SOUP_STATUS_SSL_FAILED; - else { - soup_connection_event (conn, - G_SOCKET_CLIENT_TLS_HANDSHAKING, - NULL); - status = soup_socket_handshake_sync (priv->socket, cancellable); - if (status == SOUP_STATUS_OK) { - soup_connection_event (conn, - G_SOCKET_CLIENT_TLS_HANDSHAKED, - NULL); - } else if (status == SOUP_STATUS_TLS_FAILED) { - priv->ssl_fallback = TRUE; - status = SOUP_STATUS_TRY_AGAIN; - } + soup_connection_event (conn, + G_SOCKET_CLIENT_TLS_HANDSHAKING, + NULL); + if (!soup_socket_handshake_sync (priv->socket, + priv->remote_uri->host, + cancellable, error)) { + success = FALSE; + goto done; } + soup_connection_event (conn, + G_SOCKET_CLIENT_TLS_HANDSHAKED, + NULL); } - if (SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_signal_connect (priv->socket, "disconnected", - G_CALLBACK (socket_disconnected), conn); - - if (!priv->ssl || !priv->proxy_uri) { - soup_connection_event (conn, - G_SOCKET_CLIENT_COMPLETE, - NULL); - } - soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE); - priv->unused_timeout = time (NULL) + SOUP_CONNECTION_UNUSED_TIMEOUT; - start_idle_timer (conn); - } else { - fail: - if (priv->socket) { - soup_socket_disconnect (priv->socket); - g_object_unref (priv->socket); - priv->socket = NULL; - } + if (!priv->ssl || !priv->proxy_uri) { + soup_connection_event (conn, + G_SOCKET_CLIENT_COMPLETE, + NULL); } + soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE); + priv->unused_timeout = time (NULL) + SOUP_CONNECTION_UNUSED_TIMEOUT; + start_idle_timer (conn); - if (priv->socket) + done: + if (priv->socket && event_id) g_signal_handler_disconnect (priv->socket, event_id); - if (priv->proxy_uri != NULL) - status = soup_status_proxify (status); - return status; + return success; } gboolean @@ -753,94 +661,74 @@ soup_connection_is_tunnelled (SoupConnection *conn) return priv->ssl && priv->proxy_uri != NULL; } -guint -soup_connection_start_ssl_sync (SoupConnection *conn, - GCancellable *cancellable) +gboolean +soup_connection_start_ssl_sync (SoupConnection *conn, + GCancellable *cancellable, + GError **error) { SoupConnectionPrivate *priv; - guint status; g_return_val_if_fail (SOUP_IS_CONNECTION (conn), FALSE); priv = SOUP_CONNECTION_GET_PRIVATE (conn); - if (!soup_socket_start_proxy_ssl (priv->socket, - priv->remote_uri->host, - cancellable)) - return SOUP_STATUS_SSL_FAILED; - soup_connection_event (conn, G_SOCKET_CLIENT_TLS_HANDSHAKING, NULL); - status = soup_socket_handshake_sync (priv->socket, cancellable); - if (status == SOUP_STATUS_OK) + if (soup_socket_handshake_sync (priv->socket, priv->remote_uri->host, + cancellable, error)) { soup_connection_event (conn, G_SOCKET_CLIENT_TLS_HANDSHAKED, NULL); - else if (status == SOUP_STATUS_TLS_FAILED) { - priv->ssl_fallback = TRUE; - status = SOUP_STATUS_TRY_AGAIN; - } - - return status; + soup_connection_event (conn, G_SOCKET_CLIENT_COMPLETE, NULL); + return TRUE; + } else + return FALSE; } static void -start_ssl_completed (SoupSocket *socket, guint status, gpointer user_data) +start_ssl_completed (GObject *object, GAsyncResult *result, gpointer user_data) { - SoupConnectionAsyncConnectData *data = user_data; - SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (data->conn); - - if (status == SOUP_STATUS_OK) - soup_connection_event (data->conn, G_SOCKET_CLIENT_TLS_HANDSHAKED, NULL); - else if (status == SOUP_STATUS_TLS_FAILED) { - priv->ssl_fallback = TRUE; - status = SOUP_STATUS_TRY_AGAIN; - } - - data->callback (data->conn, status, data->callback_data); - g_object_unref (data->conn); - g_slice_free (SoupConnectionAsyncConnectData, data); -} + GTask *task = user_data; + SoupConnection *conn = g_task_get_source_object (task); + SoupConnectionPrivate *priv = SOUP_CONNECTION_GET_PRIVATE (conn); + GError *error = NULL; -static gboolean -idle_start_ssl_completed (gpointer user_data) -{ - SoupConnectionAsyncConnectData *data = user_data; + if (priv->async_context && !priv->use_thread_context) + g_main_context_pop_thread_default (priv->async_context); - start_ssl_completed (NULL, SOUP_STATUS_SSL_FAILED, data); - return FALSE; + if (soup_socket_handshake_finish (priv->socket, result, &error)) { + soup_connection_event (conn, G_SOCKET_CLIENT_TLS_HANDSHAKED, NULL); + soup_connection_event (conn, G_SOCKET_CLIENT_COMPLETE, NULL); + g_task_return_boolean (task, TRUE); + } else + g_task_return_error (task, error); + g_object_unref (task); } void -soup_connection_start_ssl_async (SoupConnection *conn, - GCancellable *cancellable, - SoupConnectionCallback callback, - gpointer user_data) +soup_connection_start_ssl_async (SoupConnection *conn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { SoupConnectionPrivate *priv; - SoupConnectionAsyncConnectData *data; - GMainContext *async_context; + GTask *task; g_return_if_fail (SOUP_IS_CONNECTION (conn)); priv = SOUP_CONNECTION_GET_PRIVATE (conn); - data = g_slice_new (SoupConnectionAsyncConnectData); - data->conn = g_object_ref (conn); - data->callback = callback; - data->callback_data = user_data; + soup_connection_event (conn, G_SOCKET_CLIENT_TLS_HANDSHAKING, NULL); - if (priv->use_thread_context) - async_context = g_main_context_get_thread_default (); - else - async_context = priv->async_context; + if (priv->async_context && !priv->use_thread_context) + g_main_context_push_thread_default (priv->async_context); + task = g_task_new (conn, cancellable, callback, user_data); - if (!soup_socket_start_proxy_ssl (priv->socket, - priv->remote_uri->host, - cancellable)) { - soup_add_completion (async_context, - idle_start_ssl_completed, data); - return; - } + soup_socket_handshake_async (priv->socket, priv->remote_uri->host, + cancellable, start_ssl_completed, task); +} - soup_connection_event (conn, G_SOCKET_CLIENT_TLS_HANDSHAKING, NULL); - soup_socket_handshake_async (priv->socket, cancellable, - start_ssl_completed, data); +gboolean +soup_connection_start_ssl_finish (SoupConnection *conn, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); } /** @@ -864,14 +752,9 @@ soup_connection_disconnect (SoupConnection *conn) soup_connection_set_state (conn, SOUP_CONNECTION_DISCONNECTED); if (priv->socket) { - /* Set the socket to NULL at the beginning to avoid reentrancy - * issues. soup_socket_disconnect() could trigger a reentrant - * call unref'ing and disconnecting the socket twice. - */ SoupSocket *socket = priv->socket; + priv->socket = NULL; - g_signal_handlers_disconnect_by_func (socket, - socket_disconnected, conn); soup_socket_disconnect (socket); g_object_unref (socket); } @@ -936,7 +819,6 @@ void soup_connection_set_state (SoupConnection *conn, SoupConnectionState state) { SoupConnectionPrivate *priv; - SoupConnectionState old_state; g_return_if_fail (SOUP_IS_CONNECTION (conn)); g_return_if_fail (state >= SOUP_CONNECTION_NEW && @@ -945,21 +827,26 @@ soup_connection_set_state (SoupConnection *conn, SoupConnectionState state) g_object_freeze_notify (G_OBJECT (conn)); priv = SOUP_CONNECTION_GET_PRIVATE (conn); - old_state = priv->state; - priv->state = state; - if ((state == SOUP_CONNECTION_IDLE || - state == SOUP_CONNECTION_DISCONNECTED) && - old_state == SOUP_CONNECTION_IN_USE) - clear_current_item (conn); - g_object_notify (G_OBJECT (conn), "state"); - g_object_thaw_notify (G_OBJECT (conn)); -} + if (priv->current_msg) { + g_warn_if_fail (state == SOUP_CONNECTION_IDLE || + state == SOUP_CONNECTION_DISCONNECTED); + clear_current_msg (conn); + } -void -soup_connection_set_reusable (SoupConnection *conn) -{ - SOUP_CONNECTION_GET_PRIVATE (conn)->reusable = TRUE; + if (state == SOUP_CONNECTION_IDLE && !priv->reusable) { + /* This will recursively call set_state() */ + soup_connection_disconnect (conn); + } else { + priv->state = state; + + if (priv->state == SOUP_CONNECTION_IDLE) + start_idle_timer (conn); + + g_object_notify (G_OBJECT (conn), "state"); + } + + g_object_thaw_notify (G_OBJECT (conn)); } gboolean @@ -987,9 +874,13 @@ soup_connection_send_request (SoupConnection *conn, g_return_if_fail (SOUP_IS_CONNECTION (conn)); g_return_if_fail (item != NULL); priv = SOUP_CONNECTION_GET_PRIVATE (conn); - g_return_if_fail (priv->state != SOUP_CONNECTION_NEW && priv->state != SOUP_CONNECTION_DISCONNECTED); + g_return_if_fail (priv->state != SOUP_CONNECTION_NEW && + priv->state != SOUP_CONNECTION_DISCONNECTED); + + if (item->msg != priv->current_msg) + set_current_msg (conn, item->msg); + else + priv->reusable = FALSE; - if (item != priv->cur_item) - set_current_item (conn, item); soup_message_send_request (item, completion_cb, user_data); } diff --git a/libsoup/soup-connection.h b/libsoup/soup-connection.h index ad3a1b13..b70a8a36 100644 --- a/libsoup/soup-connection.h +++ b/libsoup/soup-connection.h @@ -35,10 +35,7 @@ typedef struct { GType soup_connection_get_type (void); -typedef void (*SoupConnectionCallback) (SoupConnection *conn, - guint status, - gpointer data); - +#define SOUP_CONNECTION_LOCAL_ADDRESS "local-address" #define SOUP_CONNECTION_REMOTE_URI "remote-uri" #define SOUP_CONNECTION_PROXY_RESOLVER "proxy-resolver" #define SOUP_CONNECTION_SSL "ssl" @@ -52,18 +49,26 @@ typedef void (*SoupConnectionCallback) (SoupConnection *conn, #define SOUP_CONNECTION_STATE "state" #define SOUP_CONNECTION_MESSAGE "message" -void soup_connection_connect_async (SoupConnection *conn, - GCancellable *cancellable, - SoupConnectionCallback callback, - gpointer user_data); -guint soup_connection_connect_sync (SoupConnection *conn, - GCancellable *cancellable); -guint soup_connection_start_ssl_sync (SoupConnection *conn, - GCancellable *cancellable); -void soup_connection_start_ssl_async (SoupConnection *conn, - GCancellable *cancellable, - SoupConnectionCallback callback, - gpointer user_data); +void soup_connection_connect_async (SoupConnection *conn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean soup_connection_connect_finish (SoupConnection *conn, + GAsyncResult *result, + GError **error); +gboolean soup_connection_connect_sync (SoupConnection *conn, + GCancellable *cancellable, + GError **error); +gboolean soup_connection_start_ssl_sync (SoupConnection *conn, + GCancellable *cancellable, + GError **error); +void soup_connection_start_ssl_async (SoupConnection *conn, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean soup_connection_start_ssl_finish (SoupConnection *conn, + GAsyncResult *result, + GError **error); void soup_connection_disconnect (SoupConnection *conn); @@ -76,7 +81,6 @@ gboolean soup_connection_is_tunnelled (SoupConnection *conn); SoupConnectionState soup_connection_get_state (SoupConnection *conn); void soup_connection_set_state (SoupConnection *conn, SoupConnectionState state); -void soup_connection_set_reusable (SoupConnection *conn); gboolean soup_connection_get_ever_used (SoupConnection *conn); diff --git a/libsoup/soup-content-decoder.c b/libsoup/soup-content-decoder.c index 85dcef49..bce78c01 100644 --- a/libsoup/soup-content-decoder.c +++ b/libsoup/soup-content-decoder.c @@ -10,6 +10,7 @@ #endif #include "soup-content-decoder.h" +#include "soup-converter-wrapper.h" #include "soup.h" #include "soup-message-private.h" @@ -17,15 +18,18 @@ * SECTION:soup-content-decoder * @short_description: Content-Encoding handler * - * #SoupContentDecoder handles the "Accept-Encoding" header on - * outgoing messages, and the "Content-Encoding" header on incoming - * ones. If you add it to a session with soup_session_add_feature() or - * soup_session_add_feature_by_type(), the session will automatically - * use Content-Encoding as appropriate. + * #SoupContentDecoder handles adding the "Accept-Encoding" header on + * outgoing messages, and processing the "Content-Encoding" header on + * incoming ones. Currently it supports the "gzip" and "deflate" + * content codings. * - * (Note that currently there is no way to (automatically) use - * Content-Encoding when sending a request body, or to pick specific - * encoding types to support.) + * If you are using a plain #SoupSession (ie, not #SoupSessionAsync or + * #SoupSessionSync), then a #SoupContentDecoder will automatically be + * added to the session by default. (You can use + * %SOUP_SESSION_REMOVE_FEATURE_BY_TYPE at construct time if you don't + * want this.) If you are using one of the deprecated #SoupSession + * subclasses, you can add a #SoupContentDecoder to your session with + * soup_session_add_feature() or soup_session_add_feature_by_type(). * * If #SoupContentDecoder successfully decodes the Content-Encoding, * it will set the %SOUP_MESSAGE_CONTENT_DECODED flag on the message, @@ -39,7 +43,11 @@ * will be decoded (and the %SOUP_MESSAGE_CONTENT_DECODED flag will * not be set). * - * Since: 2.28.2 + * (Note that currently there is no way to (automatically) use + * Content-Encoding when sending a request body, or to pick specific + * encoding types to support.) + * + * Since: 2.30 **/ struct _SoupContentDecoderPrivate { @@ -50,9 +58,114 @@ typedef GConverter * (*SoupContentDecoderCreator) (void); static void soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); +static SoupContentProcessorInterface *soup_content_decoder_default_content_processor_interface; +static void soup_content_decoder_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data); + + G_DEFINE_TYPE_WITH_CODE (SoupContentDecoder, soup_content_decoder, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, - soup_content_decoder_session_feature_init)) + soup_content_decoder_session_feature_init) + G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR, + soup_content_decoder_content_processor_init)) + +static GSList * +soup_content_decoder_get_decoders_for_msg (SoupContentDecoder *decoder, SoupMessage *msg) +{ + const char *header; + GSList *encodings, *e, *decoders = NULL; + SoupContentDecoderCreator converter_creator; + GConverter *converter; + + header = soup_message_headers_get_list (msg->response_headers, + "Content-Encoding"); + if (!header) + return NULL; + + /* Workaround for an apache bug (bgo 613361) */ + if (!g_ascii_strcasecmp (header, "gzip") || + !g_ascii_strcasecmp (header, "x-gzip")) { + const char *content_type = soup_message_headers_get_content_type (msg->response_headers, NULL); + + if (content_type && + (!g_ascii_strcasecmp (content_type, "application/gzip") || + !g_ascii_strcasecmp (content_type, "application/x-gzip"))) + return NULL; + } + + /* OK, really, no one is ever going to use more than one + * encoding, but we'll be robust. + */ + encodings = soup_header_parse_list (header); + if (!encodings) + return NULL; + + for (e = encodings; e; e = e->next) { + if (!g_hash_table_lookup (decoder->priv->decoders, e->data)) { + soup_header_free_list (encodings); + return NULL; + } + } + + for (e = encodings; e; e = e->next) { + converter_creator = g_hash_table_lookup (decoder->priv->decoders, e->data); + converter = converter_creator (); + + /* Content-Encoding lists the codings in the order + * they were applied in, so we put decoders in reverse + * order so the last-applied will be the first + * decoded. + */ + decoders = g_slist_prepend (decoders, converter); + } + soup_header_free_list (encodings); + + return decoders; +} + +static GInputStream* +soup_content_decoder_content_processor_wrap_input (SoupContentProcessor *processor, + GInputStream *base_stream, + SoupMessage *msg, + GError **error) +{ + GSList *decoders, *d; + GInputStream *istream; + + decoders = soup_content_decoder_get_decoders_for_msg (SOUP_CONTENT_DECODER (processor), msg); + if (!decoders) + return NULL; + + istream = g_object_ref (base_stream); + for (d = decoders; d; d = d->next) { + GConverter *decoder, *wrapper; + GInputStream *filter; + + decoder = d->data; + wrapper = soup_converter_wrapper_new (decoder, msg); + filter = g_object_new (G_TYPE_CONVERTER_INPUT_STREAM, + "base-stream", istream, + "converter", wrapper, + NULL); + g_object_unref (istream); + g_object_unref (wrapper); + istream = filter; + } + + g_slist_free_full (decoders, g_object_unref); + + return istream; +} + +static void +soup_content_decoder_content_processor_init (SoupContentProcessorInterface *processor_interface, + gpointer interface_data) +{ + soup_content_decoder_default_content_processor_interface = + g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR); + + processor_interface->processing_stage = SOUP_STAGE_CONTENT_ENCODING; + processor_interface->wrap_input = soup_content_decoder_content_processor_wrap_input; +} /* This is constant for now */ #define ACCEPT_ENCODING_HEADER "gzip, deflate" @@ -107,89 +220,16 @@ soup_content_decoder_class_init (SoupContentDecoderClass *decoder_class) } static void -soup_content_decoder_got_headers_cb (SoupMessage *msg, SoupContentDecoder *decoder) -{ - SoupMessagePrivate *msgpriv = SOUP_MESSAGE_GET_PRIVATE (msg); - const char *header; - GSList *encodings, *e; - SoupContentDecoderCreator converter_creator; - GConverter *converter; - - header = soup_message_headers_get_list (msg->response_headers, - "Content-Encoding"); - if (!header) - return; - - /* Workaround for an apache bug (bgo 613361) */ - if (!g_ascii_strcasecmp (header, "gzip") || - !g_ascii_strcasecmp (header, "x-gzip")) { - const char *content_type = soup_message_headers_get_content_type (msg->response_headers, NULL); - - if (content_type && - (!g_ascii_strcasecmp (content_type, "application/gzip") || - !g_ascii_strcasecmp (content_type, "application/x-gzip"))) - return; - } - - /* OK, really, no one is ever going to use more than one - * encoding, but we'll be robust. - */ - encodings = soup_header_parse_list (header); - if (!encodings) - return; - - for (e = encodings; e; e = e->next) { - if (!g_hash_table_lookup (decoder->priv->decoders, e->data)) { - soup_header_free_list (encodings); - return; - } - } - - /* msgpriv->decoders should be empty at this point anyway, but - * clean it up if it's not. - */ - g_slist_free_full (msgpriv->decoders, g_object_unref); - msgpriv->decoders = NULL; - - for (e = encodings; e; e = e->next) { - converter_creator = g_hash_table_lookup (decoder->priv->decoders, e->data); - converter = converter_creator (); - - /* Content-Encoding lists the codings in the order - * they were applied in, so we put decoders in reverse - * order so the last-applied will be the first - * decoded. - */ - msgpriv->decoders = g_slist_prepend (msgpriv->decoders, converter); - } - soup_header_free_list (encodings); -} - -static void soup_content_decoder_request_queued (SoupSessionFeature *feature, SoupSession *session, SoupMessage *msg) { - SoupContentDecoder *decoder = SOUP_CONTENT_DECODER (feature); - if (!soup_message_headers_get_one (msg->request_headers, "Accept-Encoding")) { soup_message_headers_append (msg->request_headers, "Accept-Encoding", ACCEPT_ENCODING_HEADER); } - - g_signal_connect (msg, "got-headers", - G_CALLBACK (soup_content_decoder_got_headers_cb), - decoder); -} - -static void -soup_content_decoder_request_unqueued (SoupSessionFeature *feature, - SoupSession *session, - SoupMessage *msg) -{ - g_signal_handlers_disconnect_by_func (msg, soup_content_decoder_got_headers_cb, feature); } static void @@ -197,5 +237,4 @@ soup_content_decoder_session_feature_init (SoupSessionFeatureInterface *feature_ gpointer interface_data) { feature_interface->request_queued = soup_content_decoder_request_queued; - feature_interface->request_unqueued = soup_content_decoder_request_unqueued; } diff --git a/libsoup/soup-content-decoder.h b/libsoup/soup-content-decoder.h index e0b22383..471f8138 100644 --- a/libsoup/soup-content-decoder.h +++ b/libsoup/soup-content-decoder.h @@ -37,7 +37,8 @@ typedef struct { void (*_libsoup_reserved5) (void); } SoupContentDecoderClass; -GType soup_content_decoder_get_type (void); +SOUP_AVAILABLE_IN_2_30 +GType soup_content_decoder_get_type (void); G_END_DECLS diff --git a/libsoup/soup-content-processor.c b/libsoup/soup-content-processor.c new file mode 100644 index 00000000..8b959ebe --- /dev/null +++ b/libsoup/soup-content-processor.c @@ -0,0 +1,51 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012 Igalia, S.L. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "soup-content-processor.h" +#include "soup.h" + +static void soup_content_processor_default_init (SoupContentProcessorInterface *interface); + +G_DEFINE_INTERFACE (SoupContentProcessor, soup_content_processor, G_TYPE_OBJECT) + +static GInputStream * +soup_content_processor_real_wrap_input (SoupContentProcessor *processor, + GInputStream *base_stream, + SoupMessage *msg, + GError **error) +{ + g_return_val_if_reached (NULL); +} + +static void +soup_content_processor_default_init (SoupContentProcessorInterface *interface) +{ + interface->processing_stage = SOUP_STAGE_INVALID; + interface->wrap_input = soup_content_processor_real_wrap_input; +} + +GInputStream * +soup_content_processor_wrap_input (SoupContentProcessor *processor, + GInputStream *base_stream, + SoupMessage *msg, + GError **error) +{ + g_return_val_if_fail (SOUP_IS_CONTENT_PROCESSOR (processor), NULL); + + return SOUP_CONTENT_PROCESSOR_GET_INTERFACE (processor)->wrap_input (processor, base_stream, msg, error); +} + +SoupProcessingStage +soup_content_processor_get_processing_stage (SoupContentProcessor *processor) +{ + g_return_val_if_fail (SOUP_IS_CONTENT_PROCESSOR (processor), SOUP_STAGE_INVALID); + + return SOUP_CONTENT_PROCESSOR_GET_INTERFACE (processor)->processing_stage; +} diff --git a/libsoup/soup-content-processor.h b/libsoup/soup-content-processor.h new file mode 100644 index 00000000..8ee04b2f --- /dev/null +++ b/libsoup/soup-content-processor.h @@ -0,0 +1,56 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012 Igalia, S.L. + */ + + +#ifndef SOUP_CONTENT_PROCESSOR_H +#define SOUP_CONTENT_PROCESSOR_H 1 + +#include <libsoup/soup-types.h> +#include <gio/gio.h> + +G_BEGIN_DECLS + +#define SOUP_TYPE_CONTENT_PROCESSOR (soup_content_processor_get_type ()) +#define SOUP_CONTENT_PROCESSOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_CONTENT_PROCESSOR, SoupContentProcessor)) +#define SOUP_IS_CONTENT_PROCESSOR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_CONTENT_PROCESSOR)) +#define SOUP_CONTENT_PROCESSOR_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), SOUP_TYPE_CONTENT_PROCESSOR, SoupContentProcessorInterface)) + +typedef enum { + SOUP_STAGE_INVALID, + + SOUP_STAGE_MESSAGE_BODY, /* Raw network data */ + SOUP_STAGE_TRANSFER_ENCODING, /* SoupBodyInputStream is here */ + SOUP_STAGE_ENTITY_BODY, /* Has Transfer-Encoding removed */ + SOUP_STAGE_CONTENT_ENCODING, /* SoupContentDecoder works here */ + SOUP_STAGE_BODY_DATA /* Actual body data */ +} SoupProcessingStage; + +typedef struct _SoupContentProcessor SoupContentProcessor; +typedef struct _SoupContentProcessorInterface SoupContentProcessorInterface; + +struct _SoupContentProcessorInterface { + GTypeInterface parent; + + SoupProcessingStage processing_stage; + + /* methods */ + GInputStream* (*wrap_input) (SoupContentProcessor *processor, + GInputStream *base_stream, + SoupMessage *msg, + GError **error); +}; + +GType soup_content_processor_get_type (void); + +GInputStream *soup_content_processor_wrap_input (SoupContentProcessor *processor, + GInputStream *base_stream, + SoupMessage *msg, + GError **error); + +SoupProcessingStage soup_content_processor_get_processing_stage (SoupContentProcessor *processor); + +G_END_DECLS + +#endif /* SOUP_CONTENT_PROCESSOR_H */ diff --git a/libsoup/soup-content-sniffer-stream.c b/libsoup/soup-content-sniffer-stream.c index 42e92476..d358a19e 100644 --- a/libsoup/soup-content-sniffer-stream.c +++ b/libsoup/soup-content-sniffer-stream.c @@ -233,6 +233,16 @@ soup_content_sniffer_stream_skip (GInputStream *stream, } static gboolean +soup_content_sniffer_stream_can_poll (GPollableInputStream *pollable) +{ + GInputStream *base_stream = G_FILTER_INPUT_STREAM (pollable)->base_stream; + + return G_IS_POLLABLE_INPUT_STREAM (base_stream) && + g_pollable_input_stream_can_poll (G_POLLABLE_INPUT_STREAM (base_stream)); +} + + +static gboolean soup_content_sniffer_stream_is_readable (GPollableInputStream *stream) { SoupContentSnifferStream *sniffer = SOUP_CONTENT_SNIFFER_STREAM (stream); @@ -320,23 +330,12 @@ static void soup_content_sniffer_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data) { + pollable_interface->can_poll = soup_content_sniffer_stream_can_poll; pollable_interface->is_readable = soup_content_sniffer_stream_is_readable; pollable_interface->read_nonblocking = soup_content_sniffer_stream_read_nonblocking; pollable_interface->create_source = soup_content_sniffer_stream_create_source; } -GInputStream * -soup_content_sniffer_stream_new (SoupContentSniffer *sniffer, - SoupMessage *msg, - GInputStream *base_stream) -{ - return g_object_new (SOUP_TYPE_CONTENT_SNIFFER_STREAM, - "base-stream", base_stream, - "message", msg, - "sniffer", sniffer, - NULL); -} - gboolean soup_content_sniffer_stream_is_ready (SoupContentSnifferStream *sniffer, gboolean blocking, diff --git a/libsoup/soup-content-sniffer-stream.h b/libsoup/soup-content-sniffer-stream.h index fb4889ce..ab230f3e 100644 --- a/libsoup/soup-content-sniffer-stream.h +++ b/libsoup/soup-content-sniffer-stream.h @@ -36,10 +36,6 @@ struct _SoupContentSnifferStreamClass { GType soup_content_sniffer_stream_get_type (void) G_GNUC_CONST; -GInputStream *soup_content_sniffer_stream_new (SoupContentSniffer *sniffer, - SoupMessage *msg, - GInputStream *base_stream); - gboolean soup_content_sniffer_stream_is_ready (SoupContentSnifferStream *sniffer, gboolean blocking, GCancellable *cancellable, diff --git a/libsoup/soup-content-sniffer.c b/libsoup/soup-content-sniffer.c index b5fd125e..5659af9a 100644 --- a/libsoup/soup-content-sniffer.c +++ b/libsoup/soup-content-sniffer.c @@ -2,7 +2,11 @@ /* * soup-content-sniffer.c * - * Copyright (C) 2009 Gustavo Noronha Silva. + * Copyright (C) 2009, 2013 Gustavo Noronha Silva. + * + * This code implements the following specification: + * + * http://mimesniff.spec.whatwg.org/ as of 11 June 2013 */ #ifdef HAVE_CONFIG_H @@ -13,11 +17,13 @@ #include "soup-content-sniffer.h" #include "soup.h" +#include "soup-content-processor.h" +#include "soup-content-sniffer-stream.h" #include "soup-message-private.h" /** * SECTION:soup-content-sniffer - * @short_description: Content sniffing for #SoupSession + * @short_description: Content sniffing for SoupSession * * A #SoupContentSniffer tries to detect the actual content type of * the files that are being downloaded by looking at some of the data @@ -26,26 +32,257 @@ * content sniffing to a session with soup_session_add_feature() or * soup_session_add_feature_by_type(). * - * Since: 2.27.3 + * Since: 2.28 **/ static void soup_content_sniffer_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); +static SoupContentProcessorInterface *soup_content_sniffer_default_content_processor_interface; +static void soup_content_sniffer_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data); + + G_DEFINE_TYPE_WITH_CODE (SoupContentSniffer, soup_content_sniffer, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, - soup_content_sniffer_session_feature_init)) + soup_content_sniffer_session_feature_init) + G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR, + soup_content_sniffer_content_processor_init)) + + +static GInputStream * +soup_content_sniffer_content_processor_wrap_input (SoupContentProcessor *processor, + GInputStream *base_stream, + SoupMessage *msg, + GError **error) +{ + return g_object_new (SOUP_TYPE_CONTENT_SNIFFER_STREAM, + "base-stream", base_stream, + "message", msg, + "sniffer", SOUP_CONTENT_SNIFFER (processor), + NULL); +} + +static void +soup_content_sniffer_content_processor_init (SoupContentProcessorInterface *processor_interface, + gpointer interface_data) +{ + soup_content_sniffer_default_content_processor_interface = + g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR); + + processor_interface->processing_stage = SOUP_STAGE_BODY_DATA; + processor_interface->wrap_input = soup_content_sniffer_content_processor_wrap_input; +} static void soup_content_sniffer_init (SoupContentSniffer *content_sniffer) { } -/* This table is based on the HTML5 spec; - * See 2.7.4 Content-Type sniffing: unknown type +typedef struct { + const guchar *mask; + const guchar *pattern; + guint pattern_length; + const char *sniffed_type; +} SoupContentSnifferMediaPattern; + +static char* +sniff_media (SoupContentSniffer *sniffer, + SoupBuffer *buffer, + SoupContentSnifferMediaPattern table[], + int table_length) +{ + const guchar *resource = (const guchar *)buffer->data; + int resource_length = MIN (512, buffer->length); + int i; + + for (i = 0; i < table_length; i++) { + SoupContentSnifferMediaPattern *type_row = &(table[i]); + int j; + + if (resource_length < type_row->pattern_length) + continue; + + for (j = 0; j < type_row->pattern_length; j++) { + if ((type_row->mask[j] & resource[j]) != type_row->pattern[j]) + break; + } + + /* This means our comparison above matched completely */ + if (j == type_row->pattern_length) + return g_strdup (type_row->sniffed_type); + } + + return NULL; +} + +/* This table is based on the MIMESNIFF spec; + * See 6.1 Matching an image type pattern + */ +static SoupContentSnifferMediaPattern image_types_table[] = { + + /* Windows icon signature. */ + { (const guchar *)"\xFF\xFF\xFF\xFF", + (const guchar *)"\x00\x00\x01\x00", + 4, + "image/x-icon" }, + + /* Windows cursor signature. */ + { (const guchar *)"\xFF\xFF\xFF\xFF", + (const guchar *)"\x00\x00\x02\x00", + 4, + "image/x-icon" }, + + /* BMP. */ + { (const guchar *)"\xFF\xFF", + (const guchar *)"BM", + 2, + "image/bmp" }, + + /* GIFs. */ + { (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF", + (const guchar *)"GIF87a", + 6, + "image/gif" }, + + { (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF", + (const guchar *)"GIF89a", + 6, + "image/gif" }, + + /* WEBP. */ + { (const guchar *)"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF", + (const guchar *)"RIFF\x00\x00\x00\x00WEBPVP", + 14, + "image/webp" }, + + /* PNG. */ + { (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + (const guchar *)"\x89PNG\x0D\x0A\x1A\x0A", + 8, + "image/png" }, + + /* JPEG. */ + { (const guchar *)"\xFF\xFF\xFF", + (const guchar *)"\xFF\xD8\xFF", + 3, + "image/jpeg" }, +}; + +static char* +sniff_images (SoupContentSniffer *sniffer, SoupBuffer *buffer) +{ + return sniff_media (sniffer, + buffer, + image_types_table, + G_N_ELEMENTS (image_types_table)); +} + +/* This table is based on the MIMESNIFF spec; + * See 6.2 Matching an audio or video type pattern + */ +static SoupContentSnifferMediaPattern audio_video_types_table[] = { + { (const guchar *)"\xFF\xFF\xFF\xFF", + (const guchar *)"\x1A\x45\xDF\xA3", + 4, + "video/webm" }, + + { (const guchar *)"\xFF\xFF\xFF\xFF", + (const guchar *)".snd", + 4, + "audio/basic" }, + + + { (const guchar *)"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", + (const guchar *)"FORM\0\0\0\0AIFF", + 12, + "audio/aiff" }, + + { (const guchar *)"\xFF\xFF\xFF", + (const guchar *)"ID3", + 3, + "audio/mpeg" }, + + { (const guchar *)"\xFF\xFF\xFF\xFF\xFF", + (const guchar *)"OggS\0", + 5, + "application/ogg" }, + + { (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + (const guchar *)"MThd\x00\x00\x00\x06", + 8, + "audio/midi" }, + + { (const guchar *)"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", + (const guchar *)"RIFF\x00\x00\x00\x00AVI ", + 12, + "video/avi" }, + + { (const guchar *)"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF", + (const guchar *)"RIFF\x00\x00\x00\x00WAVE", + 12, + "audio/wave" }, +}; + +static gboolean +sniff_mp4 (SoupContentSniffer *sniffer, SoupBuffer *buffer) +{ + const char *resource = (const char *)buffer->data; + int resource_length = MIN (512, buffer->length); + guint32 box_size = *((guint32*)resource); + int i; + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + box_size = ((box_size >> 24) | + ((box_size << 8) & 0x00FF0000) | + ((box_size >> 8) & 0x0000FF00) | + (box_size << 24)); +#endif + + if (resource_length < 12 || resource_length < box_size || box_size % 4 != 0) + return FALSE; + + if (!g_str_has_prefix (resource + 4, "ftyp")) + return FALSE; + + if (!g_str_has_prefix (resource + 8, "mp4")) + return FALSE; + + for (i = 16; i < box_size && i < resource_length; i = i + 4) { + if (g_str_has_prefix (resource + i, "mp4")) + return TRUE; + } + + return FALSE; +} + +static char* +sniff_audio_video (SoupContentSniffer *sniffer, SoupBuffer *buffer) +{ + char *sniffed_type; + + sniffed_type = sniff_media (sniffer, + buffer, + audio_video_types_table, + G_N_ELEMENTS (audio_video_types_table)); + + if (sniffed_type != NULL) + return sniffed_type; + + if (sniff_mp4 (sniffer, buffer)) + return g_strdup ("video/mp4"); + + return NULL; +} + +/* This table is based on the MIMESNIFF spec; + * See 7.1 Identifying a resource with an unknown MIME type */ typedef struct { /* @has_ws is TRUE if @pattern contains "generic" whitespace */ gboolean has_ws; + /* @has_tag_termination is TRUE if we should check for a tag-terminating + * byte (0x20 " " or 0x3E ">") after the pattern match. + */ + gboolean has_tag_termination; const guchar *mask; const guchar *pattern; guint pattern_length; @@ -53,111 +290,174 @@ typedef struct { gboolean scriptable; } SoupContentSnifferPattern; + +/* When has_ws is TRUE, spaces in the pattern will indicate where insignificant space + * is allowed. Those spaces are marked with \x00 on the mask. + */ static SoupContentSnifferPattern types_table[] = { - { FALSE, - (const guchar *)"\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xDF\xFF\xDF\xDF\xDF\xDF", - (const guchar *)"\x3C\x21\x44\x4F\x43\x54\x59\x50\x45\x20\x48\x54\x4D\x4C", + /* Scriptable types. */ + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xDF\xFF\xDF\xDF\xDF\xDF", + (const guchar *)" <!DOCTYPE HTML", 14, "text/html", TRUE }, - { TRUE, - (const guchar *)"\xFF\xFF\xDF\xDF\xDF\xDF", - (const guchar *)" \x3C\x48\x54\x4D\x4C", + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF", + (const guchar *)" <HTML", 5, "text/html", TRUE }, - { TRUE, - (const guchar *)"\xFF\xFF\xDF\xDF\xDF\xDF", - (const guchar *)" \x3C\x48\x45\x41\x44", + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF", + (const guchar *)" <HEAD", 5, "text/html", TRUE }, - { TRUE, - (const guchar *)"\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF", - (const guchar *)" \x3C\x53\x43\x52\x49\x50\x54", + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF\xDF", + (const guchar *)" <SCRIPT", + 7, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF\xDF", + (const guchar *)" <IFRAME", 7, "text/html", TRUE }, - { FALSE, + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xFF", + (const guchar *)" <H1", + 3, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF", + (const guchar *)" <DIV", + 4, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF", + (const guchar *)" <FONT", + 5, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF", + (const guchar *)" <TABLE", + 6, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF", + (const guchar *)" <A", + 2, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF", + (const guchar *)" <STYLE", + 6, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF", + (const guchar *)" <TITLE", + 6, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF", + (const guchar *)" <B", + 2, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF", + (const guchar *)" <BODY", + 5, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF\xDF", + (const guchar *)" <BR", + 3, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xDF", + (const guchar *)" <P", + 2, + "text/html", + TRUE }, + + { TRUE, TRUE, + (const guchar *)"\x00\xFF\xFF\xFF\xFF", + (const guchar *)" <!--", + 4, + "text/html", + TRUE }, + + { TRUE, FALSE, + (const guchar *)"\x00\xFF\xFF\xFF\xFF\xFF", + (const guchar *)" <?xml", + 5, + "text/html", + TRUE }, + + { FALSE, FALSE, (const guchar *)"\xFF\xFF\xFF\xFF\xFF", - (const guchar *)"\x25\x50\x44\x46\x2D", + (const guchar *)"%PDF-", 5, "application/pdf", TRUE }, - { FALSE, + /* Non-scriptable types. */ + { FALSE, FALSE, (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", - (const guchar *)"\x25\x21\x50\x53\x2D\x41\x64\x6F\x62\x65\x2D", + (const guchar *)"%!PS-Adobe-", 11, "application/postscript", FALSE }, - { FALSE, + { FALSE, FALSE, /* UTF-16BE BOM */ (const guchar *)"\xFF\xFF\x00\x00", (const guchar *)"\xFE\xFF\x00\x00", 4, "text/plain", FALSE }, - { FALSE, - (const guchar *)"\xFF\xFF\x00\x00", + { FALSE, FALSE, /* UTF-16LE BOM */ (const guchar *)"\xFF\xFF\x00\x00", + (const guchar *)"\xFF\xFE\x00\x00", 4, "text/plain", FALSE }, - { FALSE, + { FALSE, FALSE, /* UTF-8 BOM */ (const guchar *)"\xFF\xFF\xFF\x00", (const guchar *)"\xEF\xBB\xBF\x00", 4, "text/plain", FALSE }, - - { FALSE, - (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF", - (const guchar *)"\x47\x49\x46\x38\x37\x61", - 6, - "image/gif", - FALSE }, - - { FALSE, - (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF", - (const guchar *)"\x47\x49\x46\x38\x39\x61", - 6, - "image/gif", - FALSE }, - - { FALSE, - (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", - (const guchar *)"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A", - 8, - "image/png", - FALSE }, - - { FALSE, - (const guchar *)"\xFF\xFF\xFF", - (const guchar *)"\xFF\xD8\xFF", - 3, - "image/jpeg", - FALSE }, - - { FALSE, - (const guchar *)"\xFF\xFF", - (const guchar *)"\x42\x4D", - 2, - "image/bmp", - FALSE }, - - { FALSE, - (const guchar *)"\xFF\xFF\xFF\xFF", - (const guchar *)"\x00\x00\x01\x00", - 4, - "image/vnd.microsoft.icon", - FALSE } }; /* Whether a given byte looks like it might be part of binary content. @@ -185,9 +485,10 @@ static char byte_looks_binary[] = { /* HTML5: 2.7.4 Content-Type sniffing: unknown type */ static char* -sniff_unknown (SoupContentSniffer *sniffer, SoupMessage *msg, - SoupBuffer *buffer, gboolean for_text_or_binary) +sniff_unknown (SoupContentSniffer *sniffer, SoupBuffer *buffer, + gboolean sniff_scriptable) { + char *sniffed_type = NULL; const guchar *resource = (const guchar *)buffer->data; int resource_length = MIN (512, buffer->length); int i; @@ -195,9 +496,7 @@ sniff_unknown (SoupContentSniffer *sniffer, SoupMessage *msg, for (i = 0; i < G_N_ELEMENTS (types_table); i++) { SoupContentSnifferPattern *type_row = &(types_table[i]); - /* The scriptable types should be skiped for the text - * or binary path, but considered for other paths */ - if (for_text_or_binary && type_row->scriptable) + if (!sniff_scriptable && type_row->scriptable) continue; if (type_row->has_ws) { @@ -230,8 +529,14 @@ sniff_unknown (SoupContentSniffer *sniffer, SoupMessage *msg, if (skip_row) continue; - if (index_pattern > type_row->pattern_length) + if (index_pattern > type_row->pattern_length) { + if (type_row->has_tag_termination && + resource[index_stream] != '\x20' && + resource[index_stream] != '\x3E') + continue; + return g_strdup (type_row->sniffed_type); + } } else { int j; @@ -249,8 +554,15 @@ sniff_unknown (SoupContentSniffer *sniffer, SoupMessage *msg, } } - if (for_text_or_binary) - return g_strdup ("application/octet-stream"); + sniffed_type = sniff_images (sniffer, buffer); + + if (sniffed_type != NULL) + return sniffed_type; + + sniffed_type = sniff_audio_video (sniffer, buffer); + + if (sniffed_type != NULL) + return sniffed_type; for (i = 0; i < resource_length; i++) { if (byte_looks_binary[resource[i]]) @@ -260,25 +572,29 @@ sniff_unknown (SoupContentSniffer *sniffer, SoupMessage *msg, return g_strdup ("text/plain"); } -/* HTML5: 2.7.3 Content-Type sniffing: text or binary */ +/* MIMESNIFF: 7.2 Sniffing a mislabeled binary resource */ static char* -sniff_text_or_binary (SoupContentSniffer *sniffer, SoupMessage *msg, - SoupBuffer *buffer) +sniff_text_or_binary (SoupContentSniffer *sniffer, SoupBuffer *buffer) { const guchar *resource = (const guchar *)buffer->data; int resource_length = MIN (512, buffer->length); gboolean looks_binary = FALSE; int i; - /* Detecting UTF-16BE, UTF-16LE, or UTF-8 BOMs means it's text/plain */ - if (resource_length >= 4) { + /* 2. Detecting UTF-16BE, UTF-16LE BOMs means it's text/plain */ + if (resource_length >= 2) { if ((resource[0] == 0xFE && resource[1] == 0xFF) || - (resource[0] == 0xFF && resource[1] == 0xFE) || - (resource[0] == 0xEF && resource[1] == 0xBB && resource[2] == 0xBF)) + (resource[0] == 0xFF && resource[1] == 0xFE)) + return g_strdup ("text/plain"); + } + + /* 3. UTF-8 BOM. */ + if (resource_length >= 3) { + if (resource[0] == 0xEF && resource[1] == 0xBB && resource[2] == 0xBF) return g_strdup ("text/plain"); } - /* Look to see if any of the first n bytes looks binary */ + /* 4. Look to see if any of the first n bytes looks binary */ for (i = 0; i < resource_length; i++) { if (byte_looks_binary[resource[i]]) { looks_binary = TRUE; @@ -289,40 +605,32 @@ sniff_text_or_binary (SoupContentSniffer *sniffer, SoupMessage *msg, if (!looks_binary) return g_strdup ("text/plain"); - return sniff_unknown (sniffer, msg, buffer, TRUE); + /* 5. Execute 7.1 Identifying a resource with an unknown MIME type. + * TODO: sniff-scriptable needs to be unset. + */ + return sniff_unknown (sniffer, buffer, TRUE); } -static char* -sniff_images (SoupContentSniffer *sniffer, SoupMessage *msg, - SoupBuffer *buffer, const char *content_type) +static gboolean +skip_insignificant_space (const char *resource, int *pos, int resource_length) { - const guchar *resource = (const guchar *)buffer->data; - int resource_length = MIN (512, buffer->length); - int i; - - for (i = 0; i < G_N_ELEMENTS (types_table); i++) { - SoupContentSnifferPattern *type_row = &(types_table[i]); - - if (resource_length < type_row->pattern_length) - continue; - - if (!g_str_has_prefix (type_row->sniffed_type, "image/")) - continue; - - /* All of the image types use all-\xFF for the mask, - * so we can just memcmp. - */ - if (memcmp (type_row->pattern, resource, type_row->pattern_length) == 0) - return g_strdup (type_row->sniffed_type); + while ((resource[*pos] == '\x09') || + (resource[*pos] == '\x20') || + (resource[*pos] == '\x0A') || + (resource[*pos] == '\x0D')) { + *pos = *pos + 1; + + if (*pos > resource_length) + return TRUE; } - return g_strdup (content_type); + return FALSE; } static char* -sniff_feed_or_html (SoupContentSniffer *sniffer, SoupMessage *msg, SoupBuffer *buffer) +sniff_feed_or_html (SoupContentSniffer *sniffer, SoupBuffer *buffer) { - const guchar *resource = (const guchar *)buffer->data; + const char *resource = (const char *)buffer->data; int resource_length = MIN (512, buffer->length); int pos = 0; @@ -337,19 +645,10 @@ sniff_feed_or_html (SoupContentSniffer *sniffer, SoupMessage *msg, SoupBuffer *b if (pos > resource_length) goto text_html; - /* Skip insignificant white space */ - while ((resource[pos] == '\x09') || - (resource[pos] == '\x20') || - (resource[pos] == '\x0A') || - (resource[pos] == '\x0D')) { - pos++; - - if (pos > resource_length) - goto text_html; - } + if (skip_insignificant_space (resource, &pos, resource_length)) + goto text_html; - /* != < */ - if (resource[pos] != '\x3C') + if (resource[pos] != '<') return g_strdup ("text/html"); pos++; @@ -357,73 +656,106 @@ sniff_feed_or_html (SoupContentSniffer *sniffer, SoupMessage *msg, SoupBuffer *b if ((pos + 2) > resource_length) goto text_html; - /* Skipping comments */ - if ((resource[pos] == '\x2D') || - (resource[pos+1] == '\x2D') || - (resource[pos+2] == '\x3E')) { + /* Skip comments. */ + if (g_str_has_prefix (resource + pos, "!--")) { pos = pos + 3; if ((pos + 2) > resource_length) goto text_html; - while ((resource[pos] != '\x2D') && - (resource[pos+1] != '\x2D') && - (resource[pos+2] != '\x3E')) { + while (!g_str_has_prefix (resource + pos, "-->")) { pos++; if ((pos + 2) > resource_length) goto text_html; } + pos = pos + 3; + goto look_for_tag; } if (pos > resource_length) goto text_html; - /* == ! */ - if (resource[pos] == '\x21') { + if (resource[pos] == '!') { do { pos++; if (pos > resource_length) goto text_html; - } while (resource[pos] != '\x3E'); + } while (resource[pos] != '>'); pos++; goto look_for_tag; - } else if (resource[pos] == '\x3F') { /* ? */ + } else if (resource[pos] == '?') { do { pos++; if ((pos + 1) > resource_length) goto text_html; - } while ((resource[pos] != '\x3F') && - (resource[pos+1] != '\x3E')); + } while (!g_str_has_prefix (resource + pos, "?>")); pos = pos + 2; goto look_for_tag; } - if ((pos + 2) > resource_length) + if ((pos + 3) > resource_length) goto text_html; - if ((resource[pos] == '\x72') && - (resource[pos+1] == '\x73') && - (resource[pos+2] == '\x73')) + if (g_str_has_prefix (resource + pos, "rss")) return g_strdup ("application/rss+xml"); - if ((pos + 3) > resource_length) + if ((pos + 4) > resource_length) goto text_html; - if ((resource[pos] == '\x66') && - (resource[pos+1] == '\x65') && - (resource[pos+2] == '\x65') && - (resource[pos+3] == '\x64')) + if (g_str_has_prefix (resource + pos, "feed")) return g_strdup ("application/atom+xml"); + if ((pos + 7) > resource_length) + goto text_html; + + if (g_str_has_prefix (resource + pos, "rdf:RDF")) { + pos = pos + 7; + + if (skip_insignificant_space (resource, &pos, resource_length)) + goto text_html; + + if ((pos + 32) > resource_length) + goto text_html; + + if (g_str_has_prefix (resource + pos, "xmlns=\"http://purl.org/rss/1.0/\"")) { + pos = pos + 32; + + if (skip_insignificant_space (resource, &pos, resource_length)) + goto text_html; + + if ((pos + 55) > resource_length) + goto text_html; + + if (g_str_has_prefix (resource + pos, "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"")) + return g_strdup ("application/rss+xml"); + } + + if ((pos + 55) > resource_length) + goto text_html; + + if (g_str_has_prefix (resource + pos, "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"")) { + pos = pos + 55; + + if (skip_insignificant_space (resource, &pos, resource_length)) + goto text_html; + + if ((pos + 32) > resource_length) + goto text_html; + + if (g_str_has_prefix (resource + pos, "xmlns=\"http://purl.org/rss/1.0/\"")) + return g_strdup ("application/rss+xml"); + } + } + text_html: return g_strdup ("text/html"); } @@ -433,43 +765,71 @@ soup_content_sniffer_real_sniff (SoupContentSniffer *sniffer, SoupMessage *msg, SoupBuffer *buffer, GHashTable **params) { const char *content_type; + const char *x_content_type_options; + char *sniffed_type = NULL; + gboolean no_sniff = FALSE; content_type = soup_message_headers_get_content_type (msg->response_headers, params); - /* These comparisons are done in an ASCII-case-insensitive - * manner because the spec requires it */ + /* MIMESNIFF: 7 Determining the sniffed MIME type of a resource. */ + + x_content_type_options = soup_message_headers_get_one (msg->response_headers, "X-Content-Type-Options"); + if (!g_strcmp0 (x_content_type_options, "nosniff")) + no_sniff = TRUE; + + /* 1. Unknown/undefined supplied type with sniff-scritable = !nosniff. */ if ((content_type == NULL) || !g_ascii_strcasecmp (content_type, "unknown/unknown") || !g_ascii_strcasecmp (content_type, "application/unknown") || !g_ascii_strcasecmp (content_type, "*/*")) - return sniff_unknown (sniffer, msg, buffer, FALSE); + return sniff_unknown (sniffer, buffer, !no_sniff); + + /* 2. If nosniff is specified in X-Content-Type-Options use the supplied MIME type. */ + if (no_sniff) + return g_strdup (content_type); + /* 3. check-for-apache-bug */ + if ((content_type != NULL) && + (g_str_equal (content_type, "text/plain") || + g_str_equal (content_type, "text/plain; charset=ISO-8859-1") || + g_str_equal (content_type, "text/plain; charset=iso-8859-1") || + g_str_equal (content_type, "text/plain; charset=UTF-8"))) + return sniff_text_or_binary (sniffer, buffer); + + /* 4. XML types sent by the server are always used. */ if (g_str_has_suffix (content_type, "+xml") || !g_ascii_strcasecmp (content_type, "text/xml") || !g_ascii_strcasecmp (content_type, "application/xml")) return g_strdup (content_type); - /* 2.7.5 Content-Type sniffing: image - * The spec says: - * - * If the resource's official type is "image/svg+xml", then - * the sniffed type of the resource is its official type (an - * XML type) - * - * The XML case is handled by the if above; if you refactor - * this code, keep this in mind. + /* 5. Distinguish feed from HTML. */ + if (!g_ascii_strcasecmp (content_type, "text/html")) + return sniff_feed_or_html (sniffer, buffer); + + /* 6. Image types. */ - if (!g_ascii_strncasecmp (content_type, "image/", 6)) - return sniff_images (sniffer, msg, buffer, content_type); + if (!g_ascii_strncasecmp (content_type, "image/", 6)) { + sniffed_type = sniff_images (sniffer, buffer); + if (sniffed_type != NULL) + return sniffed_type; + return g_strdup (content_type); + } + + /* 7. Audio and video types. */ + if (!g_ascii_strncasecmp (content_type, "audio/", 6) || + !g_ascii_strncasecmp (content_type, "video/", 6) || + !g_ascii_strcasecmp (content_type, "application/ogg")) { + sniffed_type = sniff_audio_video (sniffer, buffer); + if (sniffed_type != NULL) + return sniffed_type; + return g_strdup (content_type); + } /* If we got text/plain, use text_or_binary */ if (g_str_equal (content_type, "text/plain")) { - return sniff_text_or_binary (sniffer, msg, buffer); + return sniff_text_or_binary (sniffer, buffer); } - if (!g_ascii_strcasecmp (content_type, "text/html")) - return sniff_feed_or_html (sniffer, msg, buffer); - return g_strdup (content_type); } @@ -535,7 +895,7 @@ soup_content_sniffer_session_feature_init (SoupSessionFeatureInterface *feature_ * * Returns: a new #SoupContentSniffer * - * Since: 2.27.3 + * Since: 2.28 **/ SoupContentSniffer * soup_content_sniffer_new () @@ -557,6 +917,8 @@ soup_content_sniffer_new () * * Return value: the sniffed Content-Type of @buffer; this will never be %NULL, * but may be "application/octet-stream". + * + * Since: 2.28 */ char * soup_content_sniffer_sniff (SoupContentSniffer *sniffer, @@ -570,6 +932,17 @@ soup_content_sniffer_sniff (SoupContentSniffer *sniffer, return SOUP_CONTENT_SNIFFER_GET_CLASS (sniffer)->sniff (sniffer, msg, buffer, params); } +/** + * soup_content_sniffer_get_buffer_size: + * @sniffer: a #SoupContentSniffer + * + * Gets the number of bytes @sniffer needs in order to properly sniff + * a buffer. + * + * Return value: the number of bytes to sniff + * + * Since: 2.28 + */ gsize soup_content_sniffer_get_buffer_size (SoupContentSniffer *sniffer) { diff --git a/libsoup/soup-content-sniffer.h b/libsoup/soup-content-sniffer.h index 59c01546..eb1e4bd8 100644 --- a/libsoup/soup-content-sniffer.h +++ b/libsoup/soup-content-sniffer.h @@ -43,14 +43,18 @@ typedef struct { void (*_libsoup_reserved5) (void); } SoupContentSnifferClass; +SOUP_AVAILABLE_IN_2_28 GType soup_content_sniffer_get_type (void); +SOUP_AVAILABLE_IN_2_28 SoupContentSniffer *soup_content_sniffer_new (void); +SOUP_AVAILABLE_IN_2_28 char *soup_content_sniffer_sniff (SoupContentSniffer *sniffer, SoupMessage *msg, SoupBuffer *buffer, GHashTable **params); +SOUP_AVAILABLE_IN_2_28 gsize soup_content_sniffer_get_buffer_size (SoupContentSniffer *sniffer); G_END_DECLS diff --git a/libsoup/soup-converter-wrapper.c b/libsoup/soup-converter-wrapper.c index 8aa000b9..6fa19d91 100644 --- a/libsoup/soup-converter-wrapper.c +++ b/libsoup/soup-converter-wrapper.c @@ -15,12 +15,14 @@ #include "soup.h" /* SoupConverterWrapper is a GConverter that wraps another GConverter. - * Mostly it is transparent, but it implements three special fallbacks + * Mostly it is transparent, but it implements four special fallbacks * for Content-Encoding handling: (1) "deflate" can mean either raw - * deflate or zlib-encoded default, (2) the server may mistakenly + * deflate or zlib-encoded deflate, (2) the server may mistakenly * claim that a response is encoded when actually it isn't, (3) the * response may contain trailing junk after the end of the encoded - * portion that we want to ignore. + * portion that we want to ignore, (4) the response may be truncated + * at an arbitrary point rather than containing a complete compressed + * representation. * * If the wrapped conversion succeeds, then the wrapper will set the * %SOUP_MESSAGE_CONTENT_DECODED flag on its message. @@ -48,11 +50,20 @@ struct _SoupConverterWrapperPrivate }; static void +soup_converter_wrapper_init (SoupConverterWrapper *converter) +{ + converter->priv = G_TYPE_INSTANCE_GET_PRIVATE (converter, + SOUP_TYPE_CONVERTER_WRAPPER, + SoupConverterWrapperPrivate); +} + +static void soup_converter_wrapper_finalize (GObject *object) { SoupConverterWrapperPrivate *priv = SOUP_CONVERTER_WRAPPER (object)->priv; g_clear_object (&priv->base_converter); + g_clear_object (&priv->msg); G_OBJECT_CLASS (soup_converter_wrapper_parent_class)->finalize (object); } @@ -114,14 +125,6 @@ soup_converter_wrapper_get_property (GObject *object, } static void -soup_converter_wrapper_init (SoupConverterWrapper *converter) -{ - converter->priv = G_TYPE_INSTANCE_GET_PRIVATE (converter, - SOUP_TYPE_CONVERTER_WRAPPER, - SoupConverterWrapperPrivate); -} - -static void soup_converter_wrapper_class_init (SoupConverterWrapperClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); @@ -271,8 +274,7 @@ soup_converter_wrapper_real_convert (GConverter *converter, } if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT) && - !priv->started && inbuf_size == 0 && - (flags & G_CONVERTER_INPUT_AT_END)) { + inbuf_size == 0 && (flags & G_CONVERTER_INPUT_AT_END)) { /* Server claimed compression but there was no message body. */ g_error_free (my_error); *bytes_written = 0; diff --git a/libsoup/soup-cookie-jar-db.c b/libsoup/soup-cookie-jar-db.c new file mode 100644 index 00000000..8f21baab --- /dev/null +++ b/libsoup/soup-cookie-jar-db.c @@ -0,0 +1,337 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-cookie-jar-db.c: database-based cookie storage + * + * Using danw's soup-cookie-jar-text as template + * Copyright (C) 2008 Diego Escalante Urrelo + * Copyright (C) 2009 Collabora Ltd. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> + +#include <sqlite3.h> + +#include "soup-cookie-jar-db.h" +#include "soup.h" + +/** + * SECTION:soup-cookie-jar-db + * @short_description: Database-based Cookie Jar + * + * #SoupCookieJarDB is a #SoupCookieJar that reads cookies from and + * writes them to a sqlite database in the new Mozilla format. + * + * (This is identical to <literal>SoupCookieJarSqlite</literal> in + * libsoup-gnome; it has just been moved into libsoup proper, and + * renamed to avoid conflicting.) + **/ + +enum { + PROP_0, + + PROP_FILENAME, + + LAST_PROP +}; + +typedef struct { + char *filename; + sqlite3 *db; +} SoupCookieJarDBPrivate; + +#define SOUP_COOKIE_JAR_DB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_COOKIE_JAR_DB, SoupCookieJarDBPrivate)) + +G_DEFINE_TYPE (SoupCookieJarDB, soup_cookie_jar_db, SOUP_TYPE_COOKIE_JAR) + +static void load (SoupCookieJar *jar); + +static void +soup_cookie_jar_db_init (SoupCookieJarDB *db) +{ +} + +static void +soup_cookie_jar_db_finalize (GObject *object) +{ + SoupCookieJarDBPrivate *priv = + SOUP_COOKIE_JAR_DB_GET_PRIVATE (object); + + g_free (priv->filename); + g_clear_pointer (&priv->db, sqlite3_close); + + G_OBJECT_CLASS (soup_cookie_jar_db_parent_class)->finalize (object); +} + +static void +soup_cookie_jar_db_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + SoupCookieJarDBPrivate *priv = + SOUP_COOKIE_JAR_DB_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_FILENAME: + priv->filename = g_value_dup_string (value); + load (SOUP_COOKIE_JAR (object)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +soup_cookie_jar_db_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + SoupCookieJarDBPrivate *priv = + SOUP_COOKIE_JAR_DB_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_FILENAME: + g_value_set_string (value, priv->filename); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/** + * soup_cookie_jar_db_new: + * @filename: the filename to read to/write from, or %NULL + * @read_only: %TRUE if @filename is read-only + * + * Creates a #SoupCookieJarDB. + * + * @filename will be read in at startup to create an initial set of + * cookies. If @read_only is %FALSE, then the non-session cookies will + * be written to @filename when the 'changed' signal is emitted from + * the jar. (If @read_only is %TRUE, then the cookie jar will only be + * used for this session, and changes made to it will be lost when the + * jar is destroyed.) + * + * Return value: the new #SoupCookieJar + * + * Since: 2.42 + **/ +SoupCookieJar * +soup_cookie_jar_db_new (const char *filename, gboolean read_only) +{ + g_return_val_if_fail (filename != NULL, NULL); + + return g_object_new (SOUP_TYPE_COOKIE_JAR_DB, + SOUP_COOKIE_JAR_DB_FILENAME, filename, + SOUP_COOKIE_JAR_READ_ONLY, read_only, + NULL); +} + +#define QUERY_ALL "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly FROM moz_cookies;" +#define CREATE_TABLE "CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT,expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)" +#define QUERY_INSERT "INSERT INTO moz_cookies VALUES(NULL, %Q, %Q, %Q, %Q, %d, NULL, %d, %d);" +#define QUERY_DELETE "DELETE FROM moz_cookies WHERE name=%Q AND host=%Q;" + +enum { + COL_ID, + COL_NAME, + COL_VALUE, + COL_HOST, + COL_PATH, + COL_EXPIRY, + COL_LAST_ACCESS, + COL_SECURE, + COL_HTTP_ONLY, + N_COL, +}; + +static int +callback (void *data, int argc, char **argv, char **colname) +{ + SoupCookie *cookie = NULL; + SoupCookieJar *jar = SOUP_COOKIE_JAR (data); + + char *name, *value, *host, *path; + gulong expire_time; + time_t now; + int max_age; + gboolean http_only = FALSE, secure = FALSE; + + now = time (NULL); + + name = argv[COL_NAME]; + value = argv[COL_VALUE]; + host = argv[COL_HOST]; + path = argv[COL_PATH]; + expire_time = strtoul (argv[COL_EXPIRY], NULL, 10); + + if (now >= expire_time) + return 0; + max_age = (expire_time - now <= G_MAXINT ? expire_time - now : G_MAXINT); + + http_only = (g_strcmp0 (argv[COL_HTTP_ONLY], "1") == 0); + secure = (g_strcmp0 (argv[COL_SECURE], "1") == 0); + + cookie = soup_cookie_new (name, value, host, path, max_age); + + if (secure) + soup_cookie_set_secure (cookie, TRUE); + if (http_only) + soup_cookie_set_http_only (cookie, TRUE); + + soup_cookie_jar_add_cookie (jar, cookie); + + return 0; +} + +static void +try_create_table (sqlite3 *db) +{ + char *error = NULL; + + if (sqlite3_exec (db, CREATE_TABLE, NULL, NULL, &error)) { + g_warning ("Failed to execute query: %s", error); + sqlite3_free (error); + } +} + +static void +exec_query_with_try_create_table (sqlite3 *db, + const char *sql, + int (*callback)(void*,int,char**,char**), + void *argument) +{ + char *error = NULL; + gboolean try_create = TRUE; + +try_exec: + if (sqlite3_exec (db, sql, callback, argument, &error)) { + if (try_create) { + try_create = FALSE; + try_create_table (db); + sqlite3_free (error); + error = NULL; + goto try_exec; + } else { + g_warning ("Failed to execute query: %s", error); + sqlite3_free (error); + } + } +} + +/* Follows sqlite3 convention; returns TRUE on error */ +static gboolean +open_db (SoupCookieJar *jar) +{ + SoupCookieJarDBPrivate *priv = + SOUP_COOKIE_JAR_DB_GET_PRIVATE (jar); + + char *error = NULL; + + if (sqlite3_open (priv->filename, &priv->db)) { + sqlite3_close (priv->db); + priv->db = NULL; + g_warning ("Can't open %s", priv->filename); + return TRUE; + } + + if (sqlite3_exec (priv->db, "PRAGMA synchronous = OFF; PRAGMA secure_delete = 1;", NULL, NULL, &error)) { + g_warning ("Failed to execute query: %s", error); + sqlite3_free (error); + } + + return FALSE; +} + +static void +load (SoupCookieJar *jar) +{ + SoupCookieJarDBPrivate *priv = + SOUP_COOKIE_JAR_DB_GET_PRIVATE (jar); + + if (priv->db == NULL) { + if (open_db (jar)) + return; + } + + exec_query_with_try_create_table (priv->db, QUERY_ALL, callback, jar); +} + +static void +soup_cookie_jar_db_changed (SoupCookieJar *jar, + SoupCookie *old_cookie, + SoupCookie *new_cookie) +{ + SoupCookieJarDBPrivate *priv = + SOUP_COOKIE_JAR_DB_GET_PRIVATE (jar); + char *query; + + if (priv->db == NULL) { + if (open_db (jar)) + return; + } + + if (old_cookie) { + query = sqlite3_mprintf (QUERY_DELETE, + old_cookie->name, + old_cookie->domain); + exec_query_with_try_create_table (priv->db, query, NULL, NULL); + sqlite3_free (query); + } + + if (new_cookie && new_cookie->expires) { + gulong expires; + + expires = (gulong)soup_date_to_time_t (new_cookie->expires); + query = sqlite3_mprintf (QUERY_INSERT, + new_cookie->name, + new_cookie->value, + new_cookie->domain, + new_cookie->path, + expires, + new_cookie->secure, + new_cookie->http_only); + exec_query_with_try_create_table (priv->db, query, NULL, NULL); + sqlite3_free (query); + } +} + +static gboolean +soup_cookie_jar_db_is_persistent (SoupCookieJar *jar) +{ + return TRUE; +} + +static void +soup_cookie_jar_db_class_init (SoupCookieJarDBClass *db_class) +{ + SoupCookieJarClass *cookie_jar_class = + SOUP_COOKIE_JAR_CLASS (db_class); + GObjectClass *object_class = G_OBJECT_CLASS (db_class); + + g_type_class_add_private (db_class, sizeof (SoupCookieJarDBPrivate)); + + cookie_jar_class->is_persistent = soup_cookie_jar_db_is_persistent; + cookie_jar_class->changed = soup_cookie_jar_db_changed; + + object_class->finalize = soup_cookie_jar_db_finalize; + object_class->set_property = soup_cookie_jar_db_set_property; + object_class->get_property = soup_cookie_jar_db_get_property; + + /** + * SOUP_COOKIE_JAR_DB_FILENAME: + * + * Alias for the #SoupCookieJarDB:filename property. (The + * cookie-storage filename.) + **/ + g_object_class_install_property ( + object_class, PROP_FILENAME, + g_param_spec_string (SOUP_COOKIE_JAR_DB_FILENAME, + "Filename", + "Cookie-storage filename", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} diff --git a/libsoup/soup-cookie-jar-db.h b/libsoup/soup-cookie-jar-db.h new file mode 100644 index 00000000..1a989015 --- /dev/null +++ b/libsoup/soup-cookie-jar-db.h @@ -0,0 +1,46 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Diego Escalante Urrelo + */ + +#ifndef SOUP_COOKIE_JAR_DB_H +#define SOUP_COOKIE_JAR_DB_H 1 + +#include <libsoup/soup-cookie-jar.h> + +G_BEGIN_DECLS + +#define SOUP_TYPE_COOKIE_JAR_DB (soup_cookie_jar_db_get_type ()) +#define SOUP_COOKIE_JAR_DB(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SOUP_TYPE_COOKIE_JAR_DB, SoupCookieJarDB)) +#define SOUP_COOKIE_JAR_DB_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_COOKIE_JAR_DB, SoupCookieJarDBClass)) +#define SOUP_IS_COOKIE_JAR_DB(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SOUP_TYPE_COOKIE_JAR_DB)) +#define SOUP_IS_COOKIE_JAR_DB_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), SOUP_TYPE_COOKIE_JAR_DB)) +#define SOUP_COOKIE_JAR_DB_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_COOKIE_JAR_DB, SoupCookieJarDBClass)) + +typedef struct { + SoupCookieJar parent; + +} SoupCookieJarDB; + +typedef struct { + SoupCookieJarClass parent_class; + + /* Padding for future expansion */ + void (*_libsoup_reserved1) (void); + void (*_libsoup_reserved2) (void); + void (*_libsoup_reserved3) (void); + void (*_libsoup_reserved4) (void); +} SoupCookieJarDBClass; + +#define SOUP_COOKIE_JAR_DB_FILENAME "filename" + +SOUP_AVAILABLE_IN_2_42 +GType soup_cookie_jar_db_get_type (void); + +SOUP_AVAILABLE_IN_2_42 +SoupCookieJar *soup_cookie_jar_db_new (const char *filename, + gboolean read_only); + +G_END_DECLS + +#endif /* SOUP_COOKIE_JAR_DB_H */ diff --git a/libsoup/soup-cookie-jar-sqlite.c b/libsoup/soup-cookie-jar-sqlite.c index 97e6a0e6..705e0478 100644 --- a/libsoup/soup-cookie-jar-sqlite.c +++ b/libsoup/soup-cookie-jar-sqlite.c @@ -1,10 +1,8 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* - * soup-cookie-jar-sqlite.c: ff sqlite-based cookie storage + * soup-cookie-jar-sqlite.c: deprecated version of sqlite-based cookie storage * - * Using danw's soup-cookie-jar-text as template - * Copyright (C) 2008 Diego Escalante Urrelo - * Copyright (C) 2009 Collabora Ltd. + * Copyright 2012 Red Hat, Inc. */ #ifdef HAVE_CONFIG_H @@ -13,18 +11,11 @@ #include <stdlib.h> -#include <sqlite3.h> +/* Avoid deprecation warnings */ +#define SOUP_VERSION_MIN_REQUIRED SOUP_VERSION_2_40 -#include "soup-cookie-jar-sqlite.h" #include "soup.h" - -/** - * SECTION:soup-cookie-jar-sqlite - * @short_description: SQLite-based Cookie Jar - * - * #SoupCookieJarSqlite is a #SoupCookieJar that reads cookies from and - * writes them to an SQLite file in the new Mozilla format. - **/ +#include "soup-cookie-jar-sqlite.h" enum { PROP_0, @@ -34,89 +25,13 @@ enum { LAST_PROP }; -typedef struct { - char *filename; - sqlite3 *db; -} SoupCookieJarSqlitePrivate; - -#define SOUP_COOKIE_JAR_SQLITE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_COOKIE_JAR_SQLITE, SoupCookieJarSqlitePrivate)) - -G_DEFINE_TYPE (SoupCookieJarSqlite, soup_cookie_jar_sqlite, SOUP_TYPE_COOKIE_JAR) - -static void load (SoupCookieJar *jar); +G_DEFINE_TYPE (SoupCookieJarSqlite, soup_cookie_jar_sqlite, SOUP_TYPE_COOKIE_JAR_DB) static void soup_cookie_jar_sqlite_init (SoupCookieJarSqlite *sqlite) { } -static void -soup_cookie_jar_sqlite_finalize (GObject *object) -{ - SoupCookieJarSqlitePrivate *priv = - SOUP_COOKIE_JAR_SQLITE_GET_PRIVATE (object); - - g_free (priv->filename); - - if (priv->db) - sqlite3_close (priv->db); - - G_OBJECT_CLASS (soup_cookie_jar_sqlite_parent_class)->finalize (object); -} - -static void -soup_cookie_jar_sqlite_set_property (GObject *object, guint prop_id, - const GValue *value, GParamSpec *pspec) -{ - SoupCookieJarSqlitePrivate *priv = - SOUP_COOKIE_JAR_SQLITE_GET_PRIVATE (object); - - switch (prop_id) { - case PROP_FILENAME: - priv->filename = g_value_dup_string (value); - load (SOUP_COOKIE_JAR (object)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -soup_cookie_jar_sqlite_get_property (GObject *object, guint prop_id, - GValue *value, GParamSpec *pspec) -{ - SoupCookieJarSqlitePrivate *priv = - SOUP_COOKIE_JAR_SQLITE_GET_PRIVATE (object); - - switch (prop_id) { - case PROP_FILENAME: - g_value_set_string (value, priv->filename); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -/** - * soup_cookie_jar_sqlite_new: - * @filename: the filename to read to/write from, or %NULL - * @read_only: %TRUE if @filename is read-only - * - * Creates a #SoupCookieJarSqlite. - * - * @filename will be read in at startup to create an initial set of - * cookies. If @read_only is %FALSE, then the non-session cookies will - * be written to @filename when the 'changed' signal is emitted from - * the jar. (If @read_only is %TRUE, then the cookie jar will only be - * used for this session, and changes made to it will be lost when the - * jar is destroyed.) - * - * Return value: the new #SoupCookieJar - * - * Since: 2.26 - **/ SoupCookieJar * soup_cookie_jar_sqlite_new (const char *filename, gboolean read_only) { @@ -128,208 +43,7 @@ soup_cookie_jar_sqlite_new (const char *filename, gboolean read_only) NULL); } -#define QUERY_ALL "SELECT id, name, value, host, path, expiry, lastAccessed, isSecure, isHttpOnly FROM moz_cookies;" -#define CREATE_TABLE "CREATE TABLE moz_cookies (id INTEGER PRIMARY KEY, name TEXT, value TEXT, host TEXT, path TEXT,expiry INTEGER, lastAccessed INTEGER, isSecure INTEGER, isHttpOnly INTEGER)" -#define QUERY_INSERT "INSERT INTO moz_cookies VALUES(NULL, %Q, %Q, %Q, %Q, %d, NULL, %d, %d);" -#define QUERY_DELETE "DELETE FROM moz_cookies WHERE name=%Q AND host=%Q;" - -enum { - COL_ID, - COL_NAME, - COL_VALUE, - COL_HOST, - COL_PATH, - COL_EXPIRY, - COL_LAST_ACCESS, - COL_SECURE, - COL_HTTP_ONLY, - N_COL, -}; - -static int -callback (void *data, int argc, char **argv, char **colname) -{ - SoupCookie *cookie = NULL; - SoupCookieJar *jar = SOUP_COOKIE_JAR (data); - - char *name, *value, *host, *path; - gulong expire_time; - time_t now; - int max_age; - gboolean http_only = FALSE, secure = FALSE; - - now = time (NULL); - - name = argv[COL_NAME]; - value = argv[COL_VALUE]; - host = argv[COL_HOST]; - path = argv[COL_PATH]; - expire_time = strtoul (argv[COL_EXPIRY], NULL, 10); - - if (now >= expire_time) - return 0; - max_age = (expire_time - now <= G_MAXINT ? expire_time - now : G_MAXINT); - - http_only = (g_strcmp0 (argv[COL_HTTP_ONLY], "1") == 0); - secure = (g_strcmp0 (argv[COL_SECURE], "1") == 0); - - cookie = soup_cookie_new (name, value, host, path, max_age); - - if (secure) - soup_cookie_set_secure (cookie, TRUE); - if (http_only) - soup_cookie_set_http_only (cookie, TRUE); - - soup_cookie_jar_add_cookie (jar, cookie); - - return 0; -} - -static void -try_create_table (sqlite3 *db) -{ - char *error = NULL; - - if (sqlite3_exec (db, CREATE_TABLE, NULL, NULL, &error)) { - g_warning ("Failed to execute query: %s", error); - sqlite3_free (error); - } -} - -static void -exec_query_with_try_create_table (sqlite3 *db, - const char *sql, - int (*callback)(void*,int,char**,char**), - void *argument) -{ - char *error = NULL; - gboolean try_create = TRUE; - -try_exec: - if (sqlite3_exec (db, sql, callback, argument, &error)) { - if (try_create) { - try_create = FALSE; - try_create_table (db); - sqlite3_free (error); - error = NULL; - goto try_exec; - } else { - g_warning ("Failed to execute query: %s", error); - sqlite3_free (error); - } - } -} - -/* Follows sqlite3 convention; returns TRUE on error */ -static gboolean -open_db (SoupCookieJar *jar) -{ - SoupCookieJarSqlitePrivate *priv = - SOUP_COOKIE_JAR_SQLITE_GET_PRIVATE (jar); - - char *error = NULL; - - if (sqlite3_open (priv->filename, &priv->db)) { - sqlite3_close (priv->db); - priv->db = NULL; - g_warning ("Can't open %s", priv->filename); - return TRUE; - } - - if (sqlite3_exec (priv->db, "PRAGMA synchronous = OFF; PRAGMA secure_delete = 1;", NULL, NULL, &error)) { - g_warning ("Failed to execute query: %s", error); - sqlite3_free (error); - } - - return FALSE; -} - -static void -load (SoupCookieJar *jar) -{ - SoupCookieJarSqlitePrivate *priv = - SOUP_COOKIE_JAR_SQLITE_GET_PRIVATE (jar); - - if (priv->db == NULL) { - if (open_db (jar)) - return; - } - - exec_query_with_try_create_table (priv->db, QUERY_ALL, callback, jar); -} - -static void -soup_cookie_jar_sqlite_changed (SoupCookieJar *jar, - SoupCookie *old_cookie, - SoupCookie *new_cookie) -{ - SoupCookieJarSqlitePrivate *priv = - SOUP_COOKIE_JAR_SQLITE_GET_PRIVATE (jar); - char *query; - - if (priv->db == NULL) { - if (open_db (jar)) - return; - } - - if (old_cookie) { - query = sqlite3_mprintf (QUERY_DELETE, - old_cookie->name, - old_cookie->domain); - exec_query_with_try_create_table (priv->db, query, NULL, NULL); - sqlite3_free (query); - } - - if (new_cookie && new_cookie->expires) { - gulong expires; - - expires = (gulong)soup_date_to_time_t (new_cookie->expires); - query = sqlite3_mprintf (QUERY_INSERT, - new_cookie->name, - new_cookie->value, - new_cookie->domain, - new_cookie->path, - expires, - new_cookie->secure, - new_cookie->http_only); - exec_query_with_try_create_table (priv->db, query, NULL, NULL); - sqlite3_free (query); - } -} - -static gboolean -soup_cookie_jar_sqlite_is_persistent (SoupCookieJar *jar) -{ - return TRUE; -} - static void soup_cookie_jar_sqlite_class_init (SoupCookieJarSqliteClass *sqlite_class) { - SoupCookieJarClass *cookie_jar_class = - SOUP_COOKIE_JAR_CLASS (sqlite_class); - GObjectClass *object_class = G_OBJECT_CLASS (sqlite_class); - - g_type_class_add_private (sqlite_class, sizeof (SoupCookieJarSqlitePrivate)); - - cookie_jar_class->is_persistent = soup_cookie_jar_sqlite_is_persistent; - cookie_jar_class->changed = soup_cookie_jar_sqlite_changed; - - object_class->finalize = soup_cookie_jar_sqlite_finalize; - object_class->set_property = soup_cookie_jar_sqlite_set_property; - object_class->get_property = soup_cookie_jar_sqlite_get_property; - - /** - * SOUP_COOKIE_JAR_SQLITE_FILENAME: - * - * Alias for the #SoupCookieJarSqlite:filename property. (The - * cookie-storage filename.) - **/ - g_object_class_install_property ( - object_class, PROP_FILENAME, - g_param_spec_string (SOUP_COOKIE_JAR_SQLITE_FILENAME, - "Filename", - "Cookie-storage filename", - NULL, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } diff --git a/libsoup/soup-cookie-jar-sqlite.h b/libsoup/soup-cookie-jar-sqlite.h index 19dfbfa1..adc27868 100644 --- a/libsoup/soup-cookie-jar-sqlite.h +++ b/libsoup/soup-cookie-jar-sqlite.h @@ -6,7 +6,7 @@ #ifndef SOUP_COOKIE_JAR_SQLITE_H #define SOUP_COOKIE_JAR_SQLITE_H 1 -#include <libsoup/soup-cookie-jar.h> +#include <libsoup/soup-cookie-jar-db.h> G_BEGIN_DECLS @@ -18,24 +18,23 @@ G_BEGIN_DECLS #define SOUP_COOKIE_JAR_SQLITE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_COOKIE_JAR_SQLITE, SoupCookieJarSqliteClass)) typedef struct { - SoupCookieJar parent; + SoupCookieJarDB parent; } SoupCookieJarSqlite; typedef struct { - SoupCookieJarClass parent_class; + SoupCookieJarDBClass parent_class; - /* Padding for future expansion */ - void (*_libsoup_reserved1) (void); - void (*_libsoup_reserved2) (void); - void (*_libsoup_reserved3) (void); - void (*_libsoup_reserved4) (void); } SoupCookieJarSqliteClass; #define SOUP_COOKIE_JAR_SQLITE_FILENAME "filename" +SOUP_AVAILABLE_IN_2_26 +SOUP_DEPRECATED_IN_2_42_FOR(soup_cookie_jar_db_get_type) GType soup_cookie_jar_sqlite_get_type (void); +SOUP_AVAILABLE_IN_2_26 +SOUP_DEPRECATED_IN_2_42_FOR(soup_cookie_jar_db_new) SoupCookieJar *soup_cookie_jar_sqlite_new (const char *filename, gboolean read_only); diff --git a/libsoup/soup-cookie-jar-text.h b/libsoup/soup-cookie-jar-text.h index cc186dd1..83b8f280 100644 --- a/libsoup/soup-cookie-jar-text.h +++ b/libsoup/soup-cookie-jar-text.h @@ -34,8 +34,10 @@ typedef struct { #define SOUP_COOKIE_JAR_TEXT_FILENAME "filename" +SOUP_AVAILABLE_IN_2_26 GType soup_cookie_jar_text_get_type (void); +SOUP_AVAILABLE_IN_2_26 SoupCookieJar *soup_cookie_jar_text_new (const char *filename, gboolean read_only); diff --git a/libsoup/soup-cookie-jar.c b/libsoup/soup-cookie-jar.c index 433e7046..83229705 100644 --- a/libsoup/soup-cookie-jar.c +++ b/libsoup/soup-cookie-jar.c @@ -13,11 +13,10 @@ #include "soup-cookie-jar.h" #include "soup.h" -#include "soup-marshal.h" /** * SECTION:soup-cookie-jar - * @short_description: Automatic cookie handling for #SoupSession + * @short_description: Automatic cookie handling for SoupSession * * A #SoupCookieJar stores #SoupCookie<!-- -->s and arrange for them * to be sent with the appropriate #SoupMessage<!-- -->s. @@ -175,7 +174,7 @@ soup_cookie_jar_class_init (SoupCookieJarClass *jar_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupCookieJarClass, changed), NULL, NULL, - _soup_marshal_NONE__BOXED_BOXED, + NULL, G_TYPE_NONE, 2, SOUP_TYPE_COOKIE | G_SIGNAL_TYPE_STATIC_SCOPE, SOUP_TYPE_COOKIE | G_SIGNAL_TYPE_STATIC_SCOPE); @@ -241,6 +240,10 @@ soup_cookie_jar_new (void) * This function exists for backward compatibility, but does not do * anything any more; cookie jars are saved automatically when they * are changed. + * + * Since: 2.24 + * + * Deprecated: This is a no-op. */ void soup_cookie_jar_save (SoupCookieJar *jar) @@ -432,7 +435,7 @@ soup_cookie_jar_get_cookie_list (SoupCookieJar *jar, SoupURI *uri, gboolean for_ /** * soup_cookie_jar_add_cookie: * @jar: a #SoupCookieJar - * @cookie: a #SoupCookie + * @cookie: (transfer full): a #SoupCookie * * Adds @cookie to @jar, emitting the 'changed' signal if we are modifying * an existing cookie or adding a valid new cookie ('valid' means @@ -510,7 +513,7 @@ soup_cookie_jar_add_cookie (SoupCookieJar *jar, SoupCookie *cookie) * soup_cookie_jar_add_cookie_with_first_party: * @jar: a #SoupCookieJar * @first_party: the URI for the main document - * @cookie: a #SoupCookie + * @cookie: (transfer full): a #SoupCookie * * Adds @cookie to @jar, emitting the 'changed' signal if we are modifying * an existing cookie or adding a valid new cookie ('valid' means @@ -791,6 +794,9 @@ soup_cookie_jar_delete_cookie (SoupCookieJar *jar, * document. If no first party is set in a message when this policy is * in effect, cookies will be assumed to be third party by default. * + * The policy for accepting or rejecting cookies returned in + * responses. + * * Since: 2.30 */ diff --git a/libsoup/soup-cookie-jar.h b/libsoup/soup-cookie-jar.h index 7ef05066..e77f8ab3 100644 --- a/libsoup/soup-cookie-jar.h +++ b/libsoup/soup-cookie-jar.h @@ -47,37 +47,53 @@ typedef enum { SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY } SoupCookieJarAcceptPolicy; +SOUP_AVAILABLE_IN_2_24 GType soup_cookie_jar_get_type (void); +SOUP_AVAILABLE_IN_2_24 SoupCookieJar * soup_cookie_jar_new (void); -#ifndef LIBSOUP_DISABLE_DEPRECATED -void soup_cookie_jar_save (SoupCookieJar *jar); -#endif +SOUP_AVAILABLE_IN_2_24 char * soup_cookie_jar_get_cookies (SoupCookieJar *jar, SoupURI *uri, gboolean for_http); +SOUP_AVAILABLE_IN_2_40 GSList * soup_cookie_jar_get_cookie_list (SoupCookieJar *jar, SoupURI *uri, gboolean for_http); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_jar_set_cookie (SoupCookieJar *jar, SoupURI *uri, const char *cookie); +SOUP_AVAILABLE_IN_2_30 void soup_cookie_jar_set_cookie_with_first_party (SoupCookieJar *jar, SoupURI *uri, SoupURI *first_party, const char *cookie); +SOUP_AVAILABLE_IN_2_26 void soup_cookie_jar_add_cookie (SoupCookieJar *jar, SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_40 void soup_cookie_jar_add_cookie_with_first_party (SoupCookieJar *jar, SoupURI *first_party, SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_26 void soup_cookie_jar_delete_cookie (SoupCookieJar *jar, SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_26 GSList * soup_cookie_jar_all_cookies (SoupCookieJar *jar); +SOUP_AVAILABLE_IN_2_30 void soup_cookie_jar_set_accept_policy (SoupCookieJar *jar, SoupCookieJarAcceptPolicy policy); +SOUP_AVAILABLE_IN_2_30 SoupCookieJarAcceptPolicy soup_cookie_jar_get_accept_policy (SoupCookieJar *jar); +SOUP_AVAILABLE_IN_2_40 gboolean soup_cookie_jar_is_persistent (SoupCookieJar *jar); +#ifndef SOUP_DISABLE_DEPRECATED +SOUP_AVAILABLE_IN_2_24 +SOUP_DEPRECATED_IN_2_26 +void soup_cookie_jar_save (SoupCookieJar *jar); +#endif + G_END_DECLS #endif /* SOUP_COOKIE_JAR_H */ diff --git a/libsoup/soup-cookie.c b/libsoup/soup-cookie.c index c553c52e..de5efa13 100755 --- a/libsoup/soup-cookie.c +++ b/libsoup/soup-cookie.c @@ -18,17 +18,10 @@ /** * SECTION:soup-cookie * @short_description: HTTP Cookies - * @see_also: #SoupMessage - * - * #SoupCookie implements HTTP cookies, primarily as described by - * <ulink - * url="http://wp.netscape.com/newsref/std/cookie_spec.html">the - * original Netscape cookie specification</ulink>, but with slight - * modifications based on <ulink - * url="http://www.ietf.org/rfc/rfc2109.txt">RFC 2109</ulink>, <ulink - * url="http://msdn2.microsoft.com/en-us/library/ms533046.aspx">Microsoft's - * HttpOnly extension attribute</ulink>, and observed real-world usage - * (and, in particular, based on what Firefox does). + * @see_also: #SoupMessage, #SoupCookieJar + * + * #SoupCookie implements HTTP cookies, as described by <ulink + * url="http://tools.ietf.org/html/rfc6265.txt">RFC 6265</ulink>. * * To have a #SoupSession handle cookies for your appliction * automatically, use a #SoupCookieJar. @@ -58,10 +51,9 @@ * is a hostname and must match exactly. * * @expires will be non-%NULL if the cookie uses either the original - * "expires" attribute, or the "max-age" attribute specified in RFC - * 2109. If @expires is %NULL, it indicates that neither "expires" nor - * "max-age" was specified, and the cookie expires at the end of the - * session. + * "expires" attribute, or the newer "max-age" attribute. If @expires + * is %NULL, it indicates that neither "expires" nor "max-age" was + * specified, and the cookie expires at the end of the session. * * If @http_only is set, the cookie should not be exposed to untrusted * code (eg, javascript), so as to minimize the danger posed by @@ -358,6 +350,10 @@ cookie_new_internal (const char *name, const char *value, * soup_cookie_set_secure() and soup_cookie_set_http_only() if you * need to set those attributes on the returned cookie.) * + * If @domain starts with ".", that indicates a domain (which matches + * the string after the ".", or any hostname that has @domain as a + * suffix). Otherwise, it is a hostname and must match exactly. + * * @max_age is used to set the "expires" attribute on the cookie; pass * -1 to not include the attribute (indicating that the cookie expires * with the current session), 0 for an already-expired cookie, or a @@ -1065,6 +1061,8 @@ soup_cookie_applies_to_uri (SoupCookie *cookie, SoupURI *uri) * match. This may change in the future. * * Return value: whether the cookies are equal. + * + * Since: 2.24 */ gboolean soup_cookie_equal (SoupCookie *cookie1, SoupCookie *cookie2) diff --git a/libsoup/soup-cookie.h b/libsoup/soup-cookie.h index 3a3c3886..96242913 100644 --- a/libsoup/soup-cookie.h +++ b/libsoup/soup-cookie.h @@ -20,6 +20,7 @@ struct _SoupCookie { gboolean http_only; }; +SOUP_AVAILABLE_IN_2_24 GType soup_cookie_get_type (void); #define SOUP_TYPE_COOKIE (soup_cookie_get_type()) @@ -28,61 +29,91 @@ GType soup_cookie_get_type (void); #define SOUP_COOKIE_MAX_AGE_ONE_WEEK (SOUP_COOKIE_MAX_AGE_ONE_DAY * 7) #define SOUP_COOKIE_MAX_AGE_ONE_YEAR (SOUP_COOKIE_MAX_AGE_ONE_DAY * 365.2422) +SOUP_AVAILABLE_IN_2_24 SoupCookie *soup_cookie_new (const char *name, const char *value, const char *domain, const char *path, int max_age); +SOUP_AVAILABLE_IN_2_24 SoupCookie *soup_cookie_parse (const char *header, SoupURI *origin); +SOUP_AVAILABLE_IN_2_24 SoupCookie *soup_cookie_copy (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_32 const char *soup_cookie_get_name (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_set_name (SoupCookie *cookie, const char *name); +SOUP_AVAILABLE_IN_2_32 const char *soup_cookie_get_value (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_set_value (SoupCookie *cookie, const char *value); +SOUP_AVAILABLE_IN_2_32 const char *soup_cookie_get_domain (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_set_domain (SoupCookie *cookie, const char *domain); +SOUP_AVAILABLE_IN_2_32 const char *soup_cookie_get_path (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_set_path (SoupCookie *cookie, const char *path); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_set_max_age (SoupCookie *cookie, int max_age); +SOUP_AVAILABLE_IN_2_32 SoupDate *soup_cookie_get_expires (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_set_expires (SoupCookie *cookie, SoupDate *expires); +SOUP_AVAILABLE_IN_2_32 gboolean soup_cookie_get_secure (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_set_secure (SoupCookie *cookie, gboolean secure); +SOUP_AVAILABLE_IN_2_32 gboolean soup_cookie_get_http_only (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_set_http_only (SoupCookie *cookie, gboolean http_only); +SOUP_AVAILABLE_IN_2_24 char *soup_cookie_to_set_cookie_header (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 char *soup_cookie_to_cookie_header (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 gboolean soup_cookie_applies_to_uri (SoupCookie *cookie, SoupURI *uri); +SOUP_AVAILABLE_IN_2_24 gboolean soup_cookie_equal (SoupCookie *cookie1, SoupCookie *cookie2); +SOUP_AVAILABLE_IN_2_24 void soup_cookie_free (SoupCookie *cookie); +SOUP_AVAILABLE_IN_2_24 GSList *soup_cookies_from_response (SoupMessage *msg); +SOUP_AVAILABLE_IN_2_24 GSList *soup_cookies_from_request (SoupMessage *msg); +SOUP_AVAILABLE_IN_2_24 void soup_cookies_to_response (GSList *cookies, SoupMessage *msg); +SOUP_AVAILABLE_IN_2_24 void soup_cookies_to_request (GSList *cookies, SoupMessage *msg); +SOUP_AVAILABLE_IN_2_24 void soup_cookies_free (GSList *cookies); +SOUP_AVAILABLE_IN_2_24 char *soup_cookies_to_cookie_header (GSList *cookies); +SOUP_AVAILABLE_IN_2_30 gboolean soup_cookie_domain_matches (SoupCookie *cookie, const char *host); diff --git a/libsoup/soup-date.c b/libsoup/soup-date.c index 99730786..d3dcf6f4 100644 --- a/libsoup/soup-date.c +++ b/libsoup/soup-date.c @@ -188,7 +188,7 @@ soup_date_new (int year, int month, int day, * offset_seconds is 0, returns the current time. * * If @offset_seconds would indicate a time not expressible as a - * #time_t, the return value will be clamped into range. + * <type>time_t</type>, the return value will be clamped into range. * * Return value: a new #SoupDate **/ @@ -525,7 +525,7 @@ soup_date_new_from_string (const char *date_string) /** * soup_date_new_from_time_t: - * @when: a #time_t + * @when: a <type>time_t</type> * * Creates a #SoupDate corresponding to @when * @@ -680,13 +680,13 @@ soup_date_to_string (SoupDate *date, SoupDateFormat format) * soup_date_to_time_t: * @date: a #SoupDate * - * Converts @date to a %time_t. + * Converts @date to a <type>time_t</type>. * - * If @date is not representable as a %time_t, it will be clamped into - * range. (In particular, some HTTP cookies have expiration dates - * after "Y2.038k" (2038-01-19T03:14:07Z).) + * If @date is not representable as a <type>time_t</type>, it will be + * clamped into range. (In particular, some HTTP cookies have + * expiration dates after "Y2.038k" (2038-01-19T03:14:07Z).) * - * Return value: @date as a %time_t + * Return value: @date as a <type>time_t</type> **/ time_t soup_date_to_time_t (SoupDate *date) @@ -897,6 +897,8 @@ soup_date_get_offset (SoupDate *date) * @date: a #SoupDate * * Copies @date. + * + * Since: 2.24 **/ SoupDate * soup_date_copy (SoupDate *date) @@ -915,6 +917,8 @@ soup_date_copy (SoupDate *date) * @date: a #SoupDate * * Frees @date. + * + * Since: 2.24 **/ void soup_date_free (SoupDate *date) diff --git a/libsoup/soup-date.h b/libsoup/soup-date.h index 09246072..c6414c16 100644 --- a/libsoup/soup-date.h +++ b/libsoup/soup-date.h @@ -35,37 +35,56 @@ typedef enum { SOUP_DATE_ISO8601_XMLRPC } SoupDateFormat; +SOUP_AVAILABLE_IN_2_24 GType soup_date_get_type (void); #define SOUP_TYPE_DATE (soup_date_get_type ()) +SOUP_AVAILABLE_IN_2_24 SoupDate *soup_date_new (int year, int month, int day, int hour, int minute, int second); +SOUP_AVAILABLE_IN_2_24 SoupDate *soup_date_new_from_string (const char *date_string); +SOUP_AVAILABLE_IN_2_24 SoupDate *soup_date_new_from_time_t (time_t when); +SOUP_AVAILABLE_IN_2_24 SoupDate *soup_date_new_from_now (int offset_seconds); +SOUP_AVAILABLE_IN_2_24 char *soup_date_to_string (SoupDate *date, SoupDateFormat format); +SOUP_AVAILABLE_IN_2_24 time_t soup_date_to_time_t (SoupDate *date); +SOUP_AVAILABLE_IN_2_24 void soup_date_to_timeval (SoupDate *date, GTimeVal *time); +SOUP_AVAILABLE_IN_2_24 gboolean soup_date_is_past (SoupDate *date); +SOUP_AVAILABLE_IN_2_32 int soup_date_get_year (SoupDate *date); +SOUP_AVAILABLE_IN_2_32 int soup_date_get_month (SoupDate *date); +SOUP_AVAILABLE_IN_2_32 int soup_date_get_day (SoupDate *date); +SOUP_AVAILABLE_IN_2_32 int soup_date_get_hour (SoupDate *date); +SOUP_AVAILABLE_IN_2_32 int soup_date_get_minute (SoupDate *date); +SOUP_AVAILABLE_IN_2_32 int soup_date_get_second (SoupDate *date); +SOUP_AVAILABLE_IN_2_32 int soup_date_get_utc (SoupDate *date); +SOUP_AVAILABLE_IN_2_32 int soup_date_get_offset (SoupDate *date); +SOUP_AVAILABLE_IN_2_24 SoupDate *soup_date_copy (SoupDate *date); +SOUP_AVAILABLE_IN_2_24 void soup_date_free (SoupDate *date); G_END_DECLS diff --git a/libsoup/soup-filter-input-stream.c b/libsoup/soup-filter-input-stream.c index 79159c38..8067b882 100644 --- a/libsoup/soup-filter-input-stream.c +++ b/libsoup/soup-filter-input-stream.c @@ -22,6 +22,8 @@ struct _SoupFilterInputStreamPrivate { GByteArray *buf; + gboolean need_more; + gboolean in_read_until; }; static void soup_filter_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data); @@ -78,7 +80,10 @@ soup_filter_input_stream_read_fn (GInputStream *stream, { SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (stream); - if (fstream->priv->buf) { + if (!fstream->priv->in_read_until) + fstream->priv->need_more = FALSE; + + if (fstream->priv->buf && !fstream->priv->in_read_until) { return read_from_buf (fstream, buffer, count); } else { return g_pollable_stream_read (G_FILTER_INPUT_STREAM (fstream)->base_stream, @@ -92,7 +97,7 @@ soup_filter_input_stream_is_readable (GPollableInputStream *stream) { SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (stream); - if (fstream->priv->buf) + if (fstream->priv->buf && !fstream->priv->need_more) return TRUE; else return g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (fstream)->base_stream)); @@ -106,7 +111,10 @@ soup_filter_input_stream_read_nonblocking (GPollableInputStream *stream, { SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (stream); - if (fstream->priv->buf) { + if (!fstream->priv->in_read_until) + fstream->priv->need_more = FALSE; + + if (fstream->priv->buf && !fstream->priv->in_read_until) { return read_from_buf (fstream, buffer, count); } else { return g_pollable_stream_read (G_FILTER_INPUT_STREAM (fstream)->base_stream, @@ -122,7 +130,7 @@ soup_filter_input_stream_create_source (GPollableInputStream *stream, SoupFilterInputStream *fstream = SOUP_FILTER_INPUT_STREAM (stream); GSource *base_source, *pollable_source; - if (fstream->priv->buf) + if (fstream->priv->buf && !fstream->priv->need_more) base_source = g_timeout_source_new (0); else base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (G_FILTER_INPUT_STREAM (fstream)->base_stream), cancellable); @@ -177,7 +185,7 @@ soup_filter_input_stream_read_line (SoupFilterInputStream *fstream, { return soup_filter_input_stream_read_until (fstream, buffer, length, "\n", 1, blocking, - got_line, + TRUE, got_line, cancellable, error); } @@ -188,6 +196,7 @@ soup_filter_input_stream_read_until (SoupFilterInputStream *fstream, const void *boundary, gsize boundary_length, gboolean blocking, + gboolean include_boundary, gboolean *got_boundary, GCancellable *cancellable, GError **error) @@ -195,11 +204,13 @@ soup_filter_input_stream_read_until (SoupFilterInputStream *fstream, gssize nread; guint8 *p, *buf, *end; gboolean eof = FALSE; + GError *my_error = NULL; g_return_val_if_fail (SOUP_IS_FILTER_INPUT_STREAM (fstream), -1); - g_return_val_if_fail (boundary_length < length, -1); + g_return_val_if_fail (!include_boundary || (boundary_length < length), -1); *got_boundary = FALSE; + fstream->priv->need_more = FALSE; if (!fstream->priv->buf || fstream->priv->buf->len < boundary_length) { guint prev_len; @@ -211,10 +222,12 @@ soup_filter_input_stream_read_until (SoupFilterInputStream *fstream, g_byte_array_set_size (fstream->priv->buf, length); buf = fstream->priv->buf->data; - nread = g_pollable_stream_read (G_FILTER_INPUT_STREAM (fstream)->base_stream, + fstream->priv->in_read_until = TRUE; + nread = g_pollable_stream_read (G_INPUT_STREAM (fstream), buf + prev_len, length - prev_len, blocking, - cancellable, error); + cancellable, &my_error); + fstream->priv->in_read_until = FALSE; if (nread <= 0) { if (prev_len) fstream->priv->buf->len = prev_len; @@ -225,8 +238,17 @@ soup_filter_input_stream_read_until (SoupFilterInputStream *fstream, if (nread == 0 && prev_len) eof = TRUE; - else + else { + if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) + fstream->priv->need_more = TRUE; + if (my_error) + g_propagate_error (error, my_error); + return nread; + } + + if (my_error) + g_propagate_error (error, my_error); } else fstream->priv->buf->len = prev_len + nread; } else @@ -237,8 +259,10 @@ soup_filter_input_stream_read_until (SoupFilterInputStream *fstream, if (!eof) end -= boundary_length; for (p = buf; p <= end; p++) { - if (!memcmp (p, boundary, boundary_length)) { - p += boundary_length; + if (*p == *(guint8*)boundary && + !memcmp (p, boundary, boundary_length)) { + if (include_boundary) + p += boundary_length; *got_boundary = TRUE; break; } @@ -247,9 +271,10 @@ soup_filter_input_stream_read_until (SoupFilterInputStream *fstream, if (!*got_boundary && fstream->priv->buf->len < length && !eof) goto fill_buffer; - /* Return everything up to 'p' (which is either just after the - * boundary, @boundary_len - 1 bytes before the end of the - * buffer, or end-of-file). + /* Return everything up to 'p' (which is either just after the boundary if + * include_boundary is TRUE, just before the boundary if include_boundary is + * FALSE, @boundary_len - 1 bytes before the end of the buffer, or end-of- + * file). */ return read_from_buf (fstream, buffer, p - buf); } diff --git a/libsoup/soup-filter-input-stream.h b/libsoup/soup-filter-input-stream.h index b86a476f..276b60f7 100644 --- a/libsoup/soup-filter-input-stream.h +++ b/libsoup/soup-filter-input-stream.h @@ -47,6 +47,7 @@ gssize soup_filter_input_stream_read_until (SoupFilterInputStream *fstre const void *boundary, gsize boundary_len, gboolean blocking, + gboolean include_boundary, gboolean *got_boundary, GCancellable *cancellable, GError **error); diff --git a/libsoup/soup-form.c b/libsoup/soup-form.c index 5946daeb..4dee5647 100644 --- a/libsoup/soup-form.c +++ b/libsoup/soup-form.c @@ -218,7 +218,8 @@ append_form_encoded (GString *str, const char *in) if (*s == ' ') { g_string_append_c (str, '+'); s++; - } else if (!g_ascii_isalnum (*s)) + } else if (!g_ascii_isalnum (*s) && (*s != '-') && (*s != '_') + && (*s != '.')) g_string_append_printf (str, "%%%02X", (int)*s++); else g_string_append_c (str, *s++); diff --git a/libsoup/soup-form.h b/libsoup/soup-form.h index f2be1bcf..21b8db1e 100644 --- a/libsoup/soup-form.h +++ b/libsoup/soup-form.h @@ -15,6 +15,7 @@ G_BEGIN_DECLS #define SOUP_FORM_MIME_TYPE_MULTIPART "multipart/form-data" GHashTable *soup_form_decode (const char *encoded_form); +SOUP_AVAILABLE_IN_2_26 GHashTable *soup_form_decode_multipart (SoupMessage *msg, const char *file_control_name, char **filename, @@ -28,7 +29,7 @@ char *soup_form_encode_datalist (GData **form_data_set); char *soup_form_encode_valist (const char *first_field, va_list args); -#ifndef LIBSOUP_DISABLE_DEPRECATED +#ifndef SOUP_DISABLE_DEPRECATED /* Compatibility with libsoup 2.3.0 */ #define soup_form_decode_urlencoded soup_form_decode #define soup_form_encode_urlencoded soup_form_encode_hash @@ -45,6 +46,7 @@ SoupMessage *soup_form_request_new_from_hash (const char *method, SoupMessage *soup_form_request_new_from_datalist (const char *method, const char *uri, GData **form_data_set); +SOUP_AVAILABLE_IN_2_26 SoupMessage *soup_form_request_new_from_multipart (const char *uri, SoupMultipart *multipart); diff --git a/libsoup/soup-gnome-features.c b/libsoup/soup-gnome-features.c index 798712a2..4b40f34c 100644 --- a/libsoup/soup-gnome-features.c +++ b/libsoup/soup-gnome-features.c @@ -11,42 +11,11 @@ #include "soup-gnome-features.h" -/** - * SOUP_TYPE_PROXY_RESOLVER_GNOME: - * - * This returns the #GType of a #SoupProxyURIResolver that can be used to - * resolve HTTP proxies for GNOME applications. You can add this to - * a session using soup_session_add_feature_by_type() or by using the - * %SOUP_SESSION_ADD_FEATURE_BY_TYPE construct-time property. - * - * This feature is included in %SOUP_TYPE_GNOME_FEATURES_2_26, so if - * you are using that feature, you do not need to include this feature - * separately. - * - * Since: 2.26 - **/ -/* This is actually declared in soup-proxy-resolver-gnome now */ - -/** - * SOUP_TYPE_GNOME_FEATURES_2_26: - * - * This returns the #GType of a #SoupSessionFeature that automatically - * adds all of the GNOME features defined for libsoup 2.26 (which is - * just %SOUP_TYPE_PROXY_RESOLVER_GNOME). - * - * You can add this to a session using - * soup_session_add_feature_by_type() or by using the - * %SOUP_SESSION_ADD_FEATURE_BY_TYPE construct-time property. - * - * Since: 2.26 - **/ GType soup_gnome_features_2_26_get_type (void) { - /* Eventually this needs to be a special SoupSessionFeature - * class that registers other features. But for now we can - * just do this: - */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; return SOUP_TYPE_PROXY_RESOLVER_GNOME; + G_GNUC_END_IGNORE_DEPRECATIONS; } diff --git a/libsoup/soup-gnome-features.h b/libsoup/soup-gnome-features.h index 84d64597..18f37dea 100644 --- a/libsoup/soup-gnome-features.h +++ b/libsoup/soup-gnome-features.h @@ -10,18 +10,20 @@ G_BEGIN_DECLS +SOUP_AVAILABLE_IN_2_26 +SOUP_DEPRECATED_IN_2_42_FOR(SoupSession:proxy-resolver) GType soup_proxy_resolver_gnome_get_type (void); #define SOUP_TYPE_PROXY_RESOLVER_GNOME (soup_proxy_resolver_gnome_get_type ()) +SOUP_AVAILABLE_IN_2_26 +SOUP_DEPRECATED_IN_2_42 GType soup_gnome_features_2_26_get_type (void); #define SOUP_TYPE_GNOME_FEATURES_2_26 (soup_gnome_features_2_26_get_type ()) -#ifndef G_OS_WIN32 -#ifdef LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_28 GType soup_password_manager_gnome_get_type (void); #define SOUP_TYPE_PASSWORD_MANAGER_GNOME (soup_password_manager_gnome_get_type ()) -#endif /* LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY */ -#endif G_END_DECLS diff --git a/libsoup/soup-headers.c b/libsoup/soup-headers.c index ffa1e77a..9c4c8083 100644 --- a/libsoup/soup-headers.c +++ b/libsoup/soup-headers.c @@ -702,14 +702,11 @@ parse_param_list (const char *header, char delim) char *item, *eq, *name_end, *value; gboolean override; - list = parse_list (header, delim); - if (!list) - return NULL; - params = g_hash_table_new_full (soup_str_case_hash, soup_str_case_equal, g_free, NULL); + list = parse_list (header, delim); for (iter = list; iter; iter = iter->next) { item = iter->data; override = FALSE; diff --git a/libsoup/soup-headers.h b/libsoup/soup-headers.h index cc542c34..687ea41e 100644 --- a/libsoup/soup-headers.h +++ b/libsoup/soup-headers.h @@ -13,6 +13,7 @@ G_BEGIN_DECLS /* HTTP Header Parsing */ +SOUP_AVAILABLE_IN_2_26 gboolean soup_headers_parse (const char *str, int len, SoupMessageHeaders *dest); @@ -47,12 +48,15 @@ gboolean soup_header_contains (const char *header, const char *token); GHashTable *soup_header_parse_param_list (const char *header); +SOUP_AVAILABLE_IN_2_24 GHashTable *soup_header_parse_semi_param_list (const char *header); void soup_header_free_param_list (GHashTable *param_list); +SOUP_AVAILABLE_IN_2_26 void soup_header_g_string_append_param (GString *string, const char *name, const char *value); +SOUP_AVAILABLE_IN_2_30 void soup_header_g_string_append_param_quoted (GString *string, const char *name, const char *value); diff --git a/libsoup/soup-io-stream.c b/libsoup/soup-io-stream.c index 74c71580..ea5ddef1 100644 --- a/libsoup/soup-io-stream.c +++ b/libsoup/soup-io-stream.c @@ -103,6 +103,8 @@ soup_io_stream_finalize (GObject *object) SoupIOStream *siostream = SOUP_IO_STREAM (object); g_clear_object (&siostream->priv->base_iostream); + g_clear_object (&siostream->priv->istream); + g_clear_object (&siostream->priv->ostream); G_OBJECT_CLASS (soup_io_stream_parent_class)->finalize (object); } diff --git a/libsoup/soup-logger.c b/libsoup/soup-logger.c index 3183cced..34a55764 100644 --- a/libsoup/soup-logger.c +++ b/libsoup/soup-logger.c @@ -4,6 +4,7 @@ * * Copyright (C) 2001-2004 Novell, Inc. * Copyright (C) 2008 Red Hat, Inc. + * Copyright (C) 2013 Igalia, S.L. */ #ifdef HAVE_CONFIG_H @@ -52,7 +53,7 @@ * </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 + * a <type>time_t</type>) when the request was sent, or the response fully * received. * * The <literal>Soup-Debug</literal> line gives further debugging @@ -68,13 +69,21 @@ * the first byte of the request gets written to the network (from the * #SoupSession::request_started signal), which means that if you have * not made the complete request body available at that point, it will - * not be logged. The response is logged just after the last byte of - * the response body is read from the network (from the - * #SoupMessage::got_body or #SoupMessage::got_informational signal), - * which means that the #SoupMessage::got_headers signal, and anything - * triggered off it (such as #SoupSession::authenticate) will be - * emitted <emphasis>before</emphasis> the response headers are - * actually logged. + * not be logged. + * + * The response is logged just after the last byte of the response + * body is read from the network (from the #SoupMessage::got_body or + * #SoupMessage::got_informational signal), which means that the + * #SoupMessage::got_headers signal, and anything triggered off it + * (such as #SoupSession::authenticate) will be emitted + * <emphasis>before</emphasis> the response headers are actually + * logged. + * + * If the response doesn't happen to trigger the + * #SoupMessage::got_body nor #SoupMessage::got_informational signals + * due to, for example, a cancellation before receiving the last byte + * of the response body, the response will still be logged on the + * event of the #SoupMessage::finished signal. **/ static void soup_logger_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); @@ -365,6 +374,9 @@ soup_logger_detach (SoupLogger *logger, soup_session_remove_feature (session, SOUP_SESSION_FEATURE (logger)); } +static void soup_logger_print (SoupLogger *logger, SoupLoggerLogLevel level, + char direction, const char *format, ...) G_GNUC_PRINTF (4, 5); + static void soup_logger_print (SoupLogger *logger, SoupLoggerLogLevel level, char direction, const char *format, ...) @@ -425,7 +437,7 @@ soup_logger_print_basic_auth (SoupLogger *logger, const char *value) *p = '*'; } soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>', - "Authorization: Basic [%.*s]", len, decoded); + "Authorization: Basic [%.*s]", (int)len, decoded); g_free (decoded); } @@ -481,10 +493,22 @@ print_request (SoupLogger *logger, SoupMessage *msg, return; if (!soup_message_headers_get_one (msg->request_headers, "Host")) { + char *uri_host; + + if (strchr (uri->host, ':')) + uri_host = g_strdup_printf ("[%s]", uri->host); + else if (g_hostname_is_non_ascii (uri->host)) + uri_host = g_hostname_to_ascii (uri->host); + else + uri_host = uri->host; + soup_logger_print (logger, SOUP_LOGGER_LOG_HEADERS, '>', - "Host: %s%c%u", uri->host, + "Host: %s%c%u", uri_host, soup_uri_uses_default_port (uri) ? '\0' : ':', uri->port); + + if (uri_host != uri->host) + g_free (uri_host); } soup_message_headers_iter_init (&iter, msg->request_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) { @@ -562,6 +586,20 @@ print_response (SoupLogger *logger, SoupMessage *msg) } static void +finished (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); + soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', ""); + + g_mutex_unlock (&priv->lock); +} + +static void got_informational (SoupMessage *msg, gpointer user_data) { SoupLogger *logger = user_data; @@ -569,6 +607,7 @@ got_informational (SoupMessage *msg, gpointer user_data) g_mutex_lock (&priv->lock); + g_signal_handlers_disconnect_by_func (msg, finished, logger); print_response (logger, msg); soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', ""); @@ -603,6 +642,7 @@ got_body (SoupMessage *msg, gpointer user_data) g_mutex_lock (&priv->lock); + g_signal_handlers_disconnect_by_func (msg, finished, logger); print_response (logger, msg); soup_logger_print (logger, SOUP_LOGGER_LOG_MINIMAL, ' ', ""); @@ -622,6 +662,9 @@ soup_logger_request_queued (SoupSessionFeature *logger, g_signal_connect (msg, "got-body", G_CALLBACK (got_body), logger); + g_signal_connect (msg, "finished", + G_CALLBACK (finished), + logger); } static void @@ -665,6 +708,7 @@ soup_logger_request_unqueued (SoupSessionFeature *logger, 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, logger); } static void diff --git a/libsoup/soup-logger.h b/libsoup/soup-logger.h index 8ada23d0..d84c765e 100644 --- a/libsoup/soup-logger.h +++ b/libsoup/soup-logger.h @@ -53,12 +53,12 @@ GType soup_logger_get_type (void); SoupLogger *soup_logger_new (SoupLoggerLogLevel level, int max_body_size); -#ifndef LIBSOUP_DISABLE_DEPRECATED -/* Use soup_session_add/remove_feature */ -G_DEPRECATED_FOR(soup_session_add_feature) + +#ifndef SOUP_DISABLE_DEPRECATED +SOUP_DEPRECATED_IN_2_24_FOR(soup_session_add_feature) void soup_logger_attach (SoupLogger *logger, SoupSession *session); -G_DEPRECATED_FOR(soup_session_remove_feature) +SOUP_DEPRECATED_IN_2_24_FOR(soup_session_remove_feature) void soup_logger_detach (SoupLogger *logger, SoupSession *session); #endif diff --git a/libsoup/soup-message-body.c b/libsoup/soup-message-body.c index 0868d841..dfc29fae 100644 --- a/libsoup/soup-message-body.c +++ b/libsoup/soup-message-body.c @@ -313,6 +313,10 @@ soup_buffer_free (SoupBuffer *buffer) * soup_buffer_get_as_bytes: * @buffer: a #SoupBuffer * + * Creates a #GBytes pointing to the same memory as @buffer. The + * #GBytes will hold a reference on @buffer to ensure that it is not + * freed while the #GBytes is still valid. + * * Returns: (transfer full): a new #GBytes which has the same content * as the #SoupBuffer. * @@ -415,7 +419,7 @@ soup_message_body_new (void) * be discarded, and you will be responsible for recreating the * request body after the #SoupMessage::restarted signal is emitted. * - * Since: 2.4.1 + * Since: 2.24 **/ void soup_message_body_set_accumulate (SoupMessageBody *body, @@ -435,7 +439,7 @@ soup_message_body_set_accumulate (SoupMessageBody *body, * * Return value: the accumulate flag for @body. * - * Since: 2.4.1 + * Since: 2.24 **/ gboolean soup_message_body_get_accumulate (SoupMessageBody *body) @@ -664,7 +668,7 @@ soup_message_body_get_chunk (SoupMessageBody *body, goffset offset) * This is a low-level method which you should not normally need to * use. * - * Since: 2.4.1 + * Since: 2.24 **/ void soup_message_body_got_chunk (SoupMessageBody *body, SoupBuffer *chunk) @@ -691,7 +695,7 @@ soup_message_body_got_chunk (SoupMessageBody *body, SoupBuffer *chunk) * there are further restrictions on its proper use which are not * documented here. * - * Since: 2.4.1 + * Since: 2.24 **/ void soup_message_body_wrote_chunk (SoupMessageBody *body, SoupBuffer *chunk) diff --git a/libsoup/soup-message-body.h b/libsoup/soup-message-body.h index ee840837..885cfd00 100644 --- a/libsoup/soup-message-body.h +++ b/libsoup/soup-message-body.h @@ -28,6 +28,7 @@ GType soup_buffer_get_type (void); SoupBuffer *soup_buffer_new (SoupMemoryUse use, gconstpointer data, gsize length); +SOUP_AVAILABLE_IN_2_32 SoupBuffer *soup_buffer_new_take (guchar *data, gsize length); SoupBuffer *soup_buffer_new_subbuffer (SoupBuffer *parent, @@ -39,12 +40,15 @@ SoupBuffer *soup_buffer_new_with_owner (gconstpointer data, gpointer owner, GDestroyNotify owner_dnotify); gpointer soup_buffer_get_owner (SoupBuffer *buffer); +SOUP_AVAILABLE_IN_2_32 void soup_buffer_get_data (SoupBuffer *buffer, const guint8 **data, gsize *length); +SOUP_AVAILABLE_IN_2_40 +GBytes *soup_buffer_get_as_bytes (SoupBuffer *buffer); + SoupBuffer *soup_buffer_copy (SoupBuffer *buffer); void soup_buffer_free (SoupBuffer *buffer); -GBytes *soup_buffer_get_as_bytes (SoupBuffer *buffer); typedef struct { const char *data; @@ -56,14 +60,17 @@ GType soup_message_body_get_type (void); SoupMessageBody *soup_message_body_new (void); +SOUP_AVAILABLE_IN_2_24 void soup_message_body_set_accumulate(SoupMessageBody *body, gboolean accumulate); +SOUP_AVAILABLE_IN_2_24 gboolean soup_message_body_get_accumulate(SoupMessageBody *body); void soup_message_body_append (SoupMessageBody *body, SoupMemoryUse use, gconstpointer data, gsize length); +SOUP_AVAILABLE_IN_2_32 void soup_message_body_append_take (SoupMessageBody *body, guchar *data, gsize length); @@ -77,8 +84,10 @@ SoupBuffer *soup_message_body_flatten (SoupMessageBody *body); SoupBuffer *soup_message_body_get_chunk (SoupMessageBody *body, goffset offset); +SOUP_AVAILABLE_IN_2_24 void soup_message_body_got_chunk (SoupMessageBody *body, SoupBuffer *chunk); +SOUP_AVAILABLE_IN_2_24 void soup_message_body_wrote_chunk (SoupMessageBody *body, SoupBuffer *chunk); diff --git a/libsoup/soup-message-client-io.c b/libsoup/soup-message-client-io.c index 1310bb85..b145bbaf 100644 --- a/libsoup/soup-message-client-io.c +++ b/libsoup/soup-message-client-io.c @@ -11,6 +11,8 @@ #include <string.h> +#include <glib/gi18n-lib.h> + #include "soup.h" #include "soup-connection.h" #include "soup-message-private.h" @@ -18,67 +20,76 @@ #include "soup-misc-private.h" static guint -parse_response_headers (SoupMessage *req, +parse_response_headers (SoupMessage *msg, char *headers, guint headers_len, SoupEncoding *encoding, - gpointer user_data) + gpointer user_data, + GError **error) { - SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupHTTPVersion version; - g_free(req->reason_phrase); - req->reason_phrase = NULL; + g_free(msg->reason_phrase); + msg->reason_phrase = NULL; if (!soup_headers_parse_response (headers, headers_len, - req->response_headers, + msg->response_headers, &version, - &req->status_code, - &req->reason_phrase)) + &msg->status_code, + &msg->reason_phrase)) { + g_set_error_literal (error, SOUP_REQUEST_ERROR, + SOUP_REQUEST_ERROR_PARSING, + _("Could not parse HTTP response")); return SOUP_STATUS_MALFORMED; + } - g_object_notify (G_OBJECT (req), SOUP_MESSAGE_STATUS_CODE); - g_object_notify (G_OBJECT (req), SOUP_MESSAGE_REASON_PHRASE); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_STATUS_CODE); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_REASON_PHRASE); if (version < priv->http_version) { priv->http_version = version; - g_object_notify (G_OBJECT (req), SOUP_MESSAGE_HTTP_VERSION); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_HTTP_VERSION); } - 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))) + 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 = soup_message_headers_get_encoding (req->response_headers); + *encoding = soup_message_headers_get_encoding (msg->response_headers); - if (*encoding == SOUP_ENCODING_UNRECOGNIZED) + if (*encoding == SOUP_ENCODING_UNRECOGNIZED) { + g_set_error_literal (error, SOUP_REQUEST_ERROR, + SOUP_REQUEST_ERROR_ENCODING, + _("Unrecognized HTTP response encoding")); return SOUP_STATUS_MALFORMED; + } return SOUP_STATUS_OK; } static void -get_request_headers (SoupMessage *req, GString *header, +get_request_headers (SoupMessage *msg, GString *header, SoupEncoding *encoding, gpointer user_data) { - SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageQueueItem *item = user_data; - SoupURI *uri = soup_message_get_uri (req); + SoupURI *uri = soup_message_get_uri (msg); char *uri_host; char *uri_string; SoupMessageHeadersIter iter; const char *name, *value; if (strchr (uri->host, ':')) - uri_host = g_strdup_printf ("[%s]", uri->host); + uri_host = g_strdup_printf ("[%.*s]", (int) strcspn (uri->host, "%"), uri->host); else if (g_hostname_is_non_ascii (uri->host)) uri_host = g_hostname_to_ascii (uri->host); else uri_host = uri->host; - if (req->method == SOUP_METHOD_CONNECT) { + if (msg->method == SOUP_METHOD_CONNECT) { /* CONNECT URI is hostname:port for tunnel destination */ uri_string = g_strdup_printf ("%s:%d", uri_host, uri->port); } else { @@ -97,38 +108,35 @@ get_request_headers (SoupMessage *req, GString *header, } } - if (priv->http_version == SOUP_HTTP_1_0) { - g_string_append_printf (header, "%s %s HTTP/1.0\r\n", - req->method, uri_string); - } else { - g_string_append_printf (header, "%s %s HTTP/1.1\r\n", - req->method, uri_string); - if (!soup_message_headers_get_one (req->request_headers, "Host")) { - if (soup_uri_uses_default_port (uri)) { - g_string_append_printf (header, "Host: %s\r\n", - uri_host); - } else { - g_string_append_printf (header, "Host: %s:%d\r\n", - uri_host, uri->port); - } + g_string_append_printf (header, "%s %s HTTP/1.%d\r\n", + msg->method, uri_string, + (priv->http_version == SOUP_HTTP_1_0) ? 0 : 1); + + if (!soup_message_headers_get_one (msg->request_headers, "Host")) { + if (soup_uri_uses_default_port (uri)) { + g_string_append_printf (header, "Host: %s\r\n", + uri_host); + } else { + g_string_append_printf (header, "Host: %s:%d\r\n", + uri_host, uri->port); } } g_free (uri_string); if (uri_host != uri->host) g_free (uri_host); - *encoding = soup_message_headers_get_encoding (req->request_headers); + *encoding = soup_message_headers_get_encoding (msg->request_headers); if ((*encoding == SOUP_ENCODING_CONTENT_LENGTH || *encoding == SOUP_ENCODING_NONE) && - (req->request_body->length > 0 || - soup_message_headers_get_one (req->request_headers, "Content-Type")) && - !soup_message_headers_get_content_length (req->request_headers)) { + (msg->request_body->length > 0 || + soup_message_headers_get_one (msg->request_headers, "Content-Type")) && + !soup_message_headers_get_content_length (msg->request_headers)) { *encoding = SOUP_ENCODING_CONTENT_LENGTH; - soup_message_headers_set_content_length (req->request_headers, - req->request_body->length); + soup_message_headers_set_content_length (msg->request_headers, + msg->request_body->length); } - soup_message_headers_iter_init (&iter, req->request_headers); + soup_message_headers_iter_init (&iter, msg->request_headers); while (soup_message_headers_iter_next (&iter, &name, &value)) g_string_append_printf (header, "%s: %s\r\n", name, value); g_string_append (header, "\r\n"); @@ -142,7 +150,7 @@ soup_message_send_request (SoupMessageQueueItem *item, GMainContext *async_context; GIOStream *iostream; - if (SOUP_IS_SESSION_ASYNC (item->session)) { + if (!SOUP_IS_SESSION_SYNC (item->session)) { async_context = soup_session_get_async_context (item->session); if (!async_context) async_context = g_main_context_default (); @@ -150,7 +158,6 @@ soup_message_send_request (SoupMessageQueueItem *item, async_context = NULL; iostream = soup_socket_get_iostream (soup_connection_get_socket (item->conn)); - soup_message_cleanup_response (item->msg); soup_message_io_client (item, iostream, async_context, get_request_headers, parse_response_headers, diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c index b9bab238..9c704b49 100644 --- a/libsoup/soup-message-headers.c +++ b/libsoup/soup-message-headers.c @@ -9,6 +9,7 @@ #include "soup-message-headers.h" #include "soup.h" +#include "soup-misc-private.h" /** * SECTION:soup-message-headers @@ -300,7 +301,7 @@ soup_message_headers_remove (SoupMessageHeaders *hdrs, const char *name) * * Return value: the header's value or %NULL if not found. * - * Since: 2.26.1 + * Since: 2.28 **/ const char * soup_message_headers_get_one (SoupMessageHeaders *hdrs, const char *name) @@ -338,7 +339,7 @@ soup_message_headers_get_one (SoupMessageHeaders *hdrs, const char *name) * * Return value: the header's value or %NULL if not found. * - * Since: 2.26.1 + * Since: 2.28 **/ const char * soup_message_headers_get_list (SoupMessageHeaders *hdrs, const char *name) @@ -858,56 +859,40 @@ sort_ranges (gconstpointer a, gconstpointer b) return ra->start - rb->start; } -/** - * soup_message_headers_get_ranges: - * @hdrs: a #SoupMessageHeaders - * @total_length: the total_length of the response body - * @ranges: (out): return location for an array of #SoupRange - * @length: the length of the returned array - * - * Parses @hdrs's Range header and returns an array of the requested - * byte ranges. The returned array must be freed with - * soup_message_headers_free_ranges(). - * - * If @total_length is non-0, its value will be used to adjust the - * returned ranges to have explicit start and end values, and the - * returned ranges will be sorted and non-overlapping. If - * @total_length is 0, then some ranges may have an end value of -1, - * as described under #SoupRange, and some of the ranges may be - * redundant. - * - * Return value: %TRUE if @hdrs contained a "Range" header containing - * byte ranges which could be parsed, %FALSE otherwise (in which case - * @range and @length will not be set). - * - * Since: 2.26 - **/ -gboolean -soup_message_headers_get_ranges (SoupMessageHeaders *hdrs, - goffset total_length, - SoupRange **ranges, - int *length) +/* like soup_message_headers_get_ranges(), except it returns: + * SOUP_STATUS_OK if there is no Range or it should be ignored. + * SOUP_STATUS_PARTIAL_CONTENT if there is at least one satisfiable range. + * SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE if @check_satisfiable + * is %TRUE and the request is not satisfiable given @total_length. + */ +guint +soup_message_headers_get_ranges_internal (SoupMessageHeaders *hdrs, + goffset total_length, + gboolean check_satisfiable, + SoupRange **ranges, + int *length) { const char *range = soup_message_headers_get_one (hdrs, "Range"); GSList *range_list, *r; GArray *array; char *spec, *end; int i; + guint status = SOUP_STATUS_OK; if (!range || strncmp (range, "bytes", 5) != 0) - return FALSE; + return status; range += 5; while (g_ascii_isspace (*range)) range++; if (*range++ != '=') - return FALSE; + return status; while (g_ascii_isspace (*range)) range++; range_list = soup_header_parse_list (range); if (!range_list) - return FALSE; + return status; array = g_array_new (FALSE, FALSE, sizeof (SoupRange)); for (r = range_list; r; r = r->next) { @@ -921,22 +906,34 @@ soup_message_headers_get_ranges (SoupMessageHeaders *hdrs, cur.start = g_ascii_strtoull (spec, &end, 10); if (*end == '-') end++; - if (*end) + if (*end) { cur.end = g_ascii_strtoull (end, &end, 10); - else + if (cur.end < cur.start) { + status = SOUP_STATUS_OK; + break; + } + } else cur.end = total_length - 1; } if (*end) { - g_array_free (array, TRUE); - soup_header_free_list (range_list); - return FALSE; + status = SOUP_STATUS_OK; + break; + } else if (check_satisfiable && cur.start >= total_length) { + if (status == SOUP_STATUS_OK) + status = SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE; + continue; } g_array_append_val (array, cur); + status = SOUP_STATUS_PARTIAL_CONTENT; } - soup_header_free_list (range_list); + if (status != SOUP_STATUS_PARTIAL_CONTENT) { + g_array_free (array, TRUE); + return status; + } + if (total_length) { g_array_sort (array, sort_ranges); for (i = 1; i < array->len; i++) { @@ -954,7 +951,62 @@ soup_message_headers_get_ranges (SoupMessageHeaders *hdrs, *length = array->len; g_array_free (array, FALSE); - return TRUE; + return SOUP_STATUS_PARTIAL_CONTENT; +} + +/** + * soup_message_headers_get_ranges: + * @hdrs: a #SoupMessageHeaders + * @total_length: the total_length of the response body + * @ranges: (out): return location for an array of #SoupRange + * @length: the length of the returned array + * + * Parses @hdrs's Range header and returns an array of the requested + * byte ranges. The returned array must be freed with + * soup_message_headers_free_ranges(). + * + * If @total_length is non-0, its value will be used to adjust the + * returned ranges to have explicit start and end values, and the + * returned ranges will be sorted and non-overlapping. If + * @total_length is 0, then some ranges may have an end value of -1, + * as described under #SoupRange, and some of the ranges may be + * redundant. + * + * Beware that even if given a @total_length, this function does not + * check that the ranges are satisfiable. + * + * <note><para> + * #SoupServer has built-in handling for range requests. If your + * server handler returns a %SOUP_STATUS_OK response containing the + * complete response body (rather than pausing the message and + * returning some of the response body later), and there is a Range + * header in the request, then libsoup will automatically convert the + * response to a %SOUP_STATUS_PARTIAL_CONTENT response containing only + * the range(s) requested by the client. + * + * The only time you need to process the Range header yourself is if + * either you need to stream the response body rather than returning + * it all at once, or you do not already have the complete response + * body available, and only want to generate the parts that were + * actually requested by the client. + * </para></note> + * + * Return value: %TRUE if @hdrs contained a syntactically-valid + * "Range" header, %FALSE otherwise (in which case @range and @length + * will not be set). + * + * Since: 2.26 + **/ +gboolean +soup_message_headers_get_ranges (SoupMessageHeaders *hdrs, + goffset total_length, + SoupRange **ranges, + int *length) +{ + guint status; + + status = soup_message_headers_get_ranges_internal (hdrs, total_length, FALSE, ranges, length); + return status == SOUP_STATUS_PARTIAL_CONTENT; } /** @@ -1104,6 +1156,12 @@ soup_message_headers_get_content_range (SoupMessageHeaders *hdrs, * (Note that @total_length is the total length of the entire resource * that this is a range of, not simply @end - @start + 1.) * + * <note><para> + * #SoupServer has built-in handling for range requests, and you do + * not normally need to call this function youself. See + * soup_message_headers_get_ranges() for more details. + * </para></note> + * * Since: 2.26 **/ void @@ -1303,7 +1361,7 @@ soup_message_headers_get_content_disposition (SoupMessageHeaders *hdrs, char *filename = strrchr (orig_value, '/'); if (filename) - g_hash_table_insert (*params, orig_key, filename + 1); + g_hash_table_insert (*params, g_strdup (orig_key), filename + 1); } return TRUE; } diff --git a/libsoup/soup-message-headers.h b/libsoup/soup-message-headers.h index da66f7a9..02e752bc 100644 --- a/libsoup/soup-message-headers.h +++ b/libsoup/soup-message-headers.h @@ -8,6 +8,8 @@ #include <libsoup/soup-types.h> +G_BEGIN_DECLS + typedef struct SoupMessageHeaders SoupMessageHeaders; GType soup_message_headers_get_type (void); #define SOUP_TYPE_MESSAGE_HEADERS (soup_message_headers_get_type ()) @@ -33,15 +35,18 @@ void soup_message_headers_remove (SoupMessageHeaders *hdrs, const char *name); void soup_message_headers_clear (SoupMessageHeaders *hdrs); +SOUP_AVAILABLE_IN_2_36 void soup_message_headers_clean_connection_headers (SoupMessageHeaders *hdrs); -#ifndef LIBSOUP_DISABLE_DEPRECATED -G_DEPRECATED_FOR(soup_message_headers_get_one or soup_message_headers_get_list) +#ifndef SOUP_DISABLE_DEPRECATED +SOUP_DEPRECATED_IN_2_28_FOR ("soup_message_headers_get_one or soup_message_headers_get_list") const char *soup_message_headers_get (SoupMessageHeaders *hdrs, const char *name); #endif +SOUP_AVAILABLE_IN_2_28 const char *soup_message_headers_get_one (SoupMessageHeaders *hdrs, const char *name); +SOUP_AVAILABLE_IN_2_28 const char *soup_message_headers_get_list (SoupMessageHeaders *hdrs, const char *name); @@ -97,40 +102,52 @@ typedef struct { goffset end; } SoupRange; +SOUP_AVAILABLE_IN_2_26 gboolean soup_message_headers_get_ranges (SoupMessageHeaders *hdrs, goffset total_length, SoupRange **ranges, int *length); +SOUP_AVAILABLE_IN_2_26 void soup_message_headers_free_ranges (SoupMessageHeaders *hdrs, SoupRange *ranges); +SOUP_AVAILABLE_IN_2_26 void soup_message_headers_set_ranges (SoupMessageHeaders *hdrs, SoupRange *ranges, int length); +SOUP_AVAILABLE_IN_2_26 void soup_message_headers_set_range (SoupMessageHeaders *hdrs, goffset start, goffset end); +SOUP_AVAILABLE_IN_2_26 gboolean soup_message_headers_get_content_range (SoupMessageHeaders *hdrs, goffset *start, goffset *end, goffset *total_length); +SOUP_AVAILABLE_IN_2_26 void soup_message_headers_set_content_range (SoupMessageHeaders *hdrs, goffset start, goffset end, goffset total_length); +SOUP_AVAILABLE_IN_2_26 const char *soup_message_headers_get_content_type (SoupMessageHeaders *hdrs, GHashTable **params); +SOUP_AVAILABLE_IN_2_26 void soup_message_headers_set_content_type (SoupMessageHeaders *hdrs, const char *content_type, GHashTable *params); +SOUP_AVAILABLE_IN_2_26 gboolean soup_message_headers_get_content_disposition (SoupMessageHeaders *hdrs, char **disposition, GHashTable **params); +SOUP_AVAILABLE_IN_2_26 void soup_message_headers_set_content_disposition (SoupMessageHeaders *hdrs, const char *disposition, GHashTable *params); +G_END_DECLS + #endif /* SOUP_MESSAGE_HEADERS_H */ diff --git a/libsoup/soup-message-io.c b/libsoup/soup-message-io.c index eeb67553..be5cb2d2 100644 --- a/libsoup/soup-message-io.c +++ b/libsoup/soup-message-io.c @@ -16,8 +16,8 @@ #include "soup-body-output-stream.h" #include "soup-client-input-stream.h" #include "soup-connection.h" +#include "soup-content-processor.h" #include "soup-content-sniffer-stream.h" -#include "soup-converter-wrapper.h" #include "soup-filter-input-stream.h" #include "soup-message-private.h" #include "soup-message-queue.h" @@ -60,7 +60,6 @@ typedef struct { GOutputStream *ostream; GOutputStream *body_ostream; GMainContext *async_context; - gboolean blocking; SoupMessageIOState read_state; SoupEncoding read_encoding; @@ -156,8 +155,14 @@ soup_message_io_finished (SoupMessage *msg) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; - SoupMessageCompletionFn completion_cb = io->completion_cb; - gpointer completion_data = io->completion_data; + SoupMessageCompletionFn completion_cb; + gpointer completion_data; + + if (!io) + return; + + completion_cb = io->completion_cb; + completion_data = io->completion_data; g_object_ref (msg); soup_message_io_cleanup (msg); @@ -167,7 +172,8 @@ soup_message_io_finished (SoupMessage *msg) } static gboolean -read_headers (SoupMessage *msg, GCancellable *cancellable, GError **error) +read_headers (SoupMessage *msg, gboolean blocking, + GCancellable *cancellable, GError **error) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; @@ -180,7 +186,7 @@ read_headers (SoupMessage *msg, GCancellable *cancellable, GError **error) nread = soup_filter_input_stream_read_line (io->istream, io->read_header_buf->data + old_len, RESPONSE_BLOCK_SIZE, - io->blocking, + blocking, &got_lf, cancellable, error); io->read_header_buf->len = old_len + MAX (nread, 0); @@ -221,37 +227,53 @@ read_headers (SoupMessage *msg, GCancellable *cancellable, GError **error) return TRUE; } -static void -setup_body_istream (SoupMessage *msg) +static gint +processing_stage_cmp (gconstpointer a, + gconstpointer b) { - SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); - SoupMessageIOData *io = priv->io_data; - GConverter *decoder, *wrapper; - GInputStream *filter; - GSList *d; - - io->body_istream = - soup_body_input_stream_new (io->istream, - io->read_encoding, - io->read_length); - - for (d = priv->decoders; d; d = d->next) { - decoder = d->data; - wrapper = soup_converter_wrapper_new (decoder, msg); - filter = g_object_new (G_TYPE_CONVERTER_INPUT_STREAM, - "base-stream", io->body_istream, - "converter", wrapper, - NULL); - g_object_unref (io->body_istream); - io->body_istream = filter; - } + SoupProcessingStage stage_a = soup_content_processor_get_processing_stage (SOUP_CONTENT_PROCESSOR (a)); + SoupProcessingStage stage_b = soup_content_processor_get_processing_stage (SOUP_CONTENT_PROCESSOR (b)); + + if (stage_a > stage_b) + return 1; + if (stage_a == stage_b) + return 0; + return -1; +} - if (priv->sniffer) { - filter = soup_content_sniffer_stream_new (priv->sniffer, - msg, io->body_istream); - g_object_unref (io->body_istream); - io->body_istream = filter; +GInputStream * +soup_message_setup_body_istream (GInputStream *body_stream, + SoupMessage *msg, + SoupSession *session, + SoupProcessingStage start_at_stage) +{ + GInputStream *istream; + GSList *p, *processors; + + istream = g_object_ref (body_stream); + + processors = soup_session_get_features (session, SOUP_TYPE_CONTENT_PROCESSOR); + processors = g_slist_sort (processors, processing_stage_cmp); + + for (p = processors; p; p = p->next) { + GInputStream *wrapper; + SoupContentProcessor *processor; + + processor = SOUP_CONTENT_PROCESSOR (p->data); + if (soup_message_disables_feature (msg, p->data) || + soup_content_processor_get_processing_stage (processor) < start_at_stage) + continue; + + wrapper = soup_content_processor_wrap_input (processor, istream, msg, NULL); + if (wrapper) { + g_object_unref (istream); + istream = wrapper; + } } + + g_slist_free (processors); + + return istream; } /* @@ -287,7 +309,8 @@ setup_body_istream (SoupMessage *msg) * socket not writable, write is complete, etc). */ static gboolean -io_write (SoupMessage *msg, GCancellable *cancellable, GError **error) +io_write (SoupMessage *msg, gboolean blocking, + GCancellable *cancellable, GError **error) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; @@ -306,7 +329,7 @@ io_write (SoupMessage *msg, GCancellable *cancellable, GError **error) nwrote = g_pollable_stream_write (io->ostream, io->write_buf->str + io->written, io->write_buf->len - io->written, - io->blocking, + blocking, cancellable, error); if (nwrote == -1) return FALSE; @@ -398,7 +421,7 @@ io_write (SoupMessage *msg, GCancellable *cancellable, GError **error) nwrote = g_pollable_stream_write (io->body_ostream, io->write_chunk->data + io->written, io->write_chunk->length - io->written, - io->blocking, + blocking, cancellable, error); if (nwrote == -1) return FALSE; @@ -470,7 +493,8 @@ io_write (SoupMessage *msg, GCancellable *cancellable, GError **error) * socket not readable, read is complete, etc). */ static gboolean -io_read (SoupMessage *msg, GCancellable *cancellable, GError **error) +io_read (SoupMessage *msg, gboolean blocking, + GCancellable *cancellable, GError **error) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; @@ -481,13 +505,13 @@ io_read (SoupMessage *msg, GCancellable *cancellable, GError **error) switch (io->read_state) { case SOUP_MESSAGE_IO_STATE_HEADERS: - if (!read_headers (msg, cancellable, error)) + if (!read_headers (msg, blocking, cancellable, error)) return FALSE; status = io->parse_headers_cb (msg, (char *)io->read_header_buf->data, io->read_header_buf->len, &io->read_encoding, - io->header_data); + io->header_data, error); g_byte_array_set_size (io->read_header_buf, 0); if (status != SOUP_STATUS_OK) { @@ -569,15 +593,31 @@ io_read (SoupMessage *msg, GCancellable *cancellable, GError **error) case SOUP_MESSAGE_IO_STATE_BODY_START: - if (!io->body_istream) - setup_body_istream (msg); + if (!io->body_istream) { + GInputStream *body_istream = soup_body_input_stream_new (G_INPUT_STREAM (io->istream), + io->read_encoding, + io->read_length); + + /* TODO: server-side messages do not have a io->item. This means + * that we cannot use content processors for them right now. + */ + if (io->mode == SOUP_MESSAGE_IO_CLIENT) { + io->body_istream = soup_message_setup_body_istream (body_istream, msg, + io->item->session, + SOUP_STAGE_MESSAGE_BODY); + g_object_unref (body_istream); + } else { + io->body_istream = body_istream; + } + } if (priv->sniffer) { SoupContentSnifferStream *sniffer_stream = SOUP_CONTENT_SNIFFER_STREAM (io->body_istream); const char *content_type; GHashTable *params; - if (!soup_content_sniffer_stream_is_ready (sniffer_stream, io->blocking, cancellable, error)) + if (!soup_content_sniffer_stream_is_ready (sniffer_stream, blocking, + cancellable, error)) return FALSE; content_type = soup_content_sniffer_stream_sniff (sniffer_stream, ¶ms); @@ -607,7 +647,7 @@ io_read (SoupMessage *msg, GCancellable *cancellable, GError **error) nread = g_pollable_stream_read (io->body_istream, (guchar *)buffer->data, buffer->length, - io->blocking, + blocking, cancellable, error); if (nread > 0) { buffer->length = nread; @@ -628,8 +668,6 @@ io_read (SoupMessage *msg, GCancellable *cancellable, GError **error) case SOUP_MESSAGE_IO_STATE_BODY_DONE: io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; - if (io->item && io->item->conn) - soup_connection_set_reusable (io->item->conn); soup_message_got_body (msg); break; @@ -800,7 +838,7 @@ request_is_restartable (SoupMessage *msg, GError *error) } static gboolean -io_run_until (SoupMessage *msg, +io_run_until (SoupMessage *msg, gboolean blocking, SoupMessageIOState read_state, SoupMessageIOState write_state, GCancellable *cancellable, GError **error) { @@ -824,9 +862,9 @@ io_run_until (SoupMessage *msg, (io->read_state < read_state || io->write_state < write_state)) { if (SOUP_MESSAGE_IO_STATE_ACTIVE (io->read_state)) - progress = io_read (msg, cancellable, &my_error); + progress = io_read (msg, blocking, cancellable, &my_error); else if (SOUP_MESSAGE_IO_STATE_ACTIVE (io->write_state)) - progress = io_write (msg, cancellable, &my_error); + progress = io_write (msg, blocking, cancellable, &my_error); else progress = FALSE; } @@ -858,7 +896,7 @@ io_run_until (SoupMessage *msg, done = (io->read_state >= read_state && io->write_state >= write_state); - if (io->paused && !done) { + if (!blocking && !done) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, _("Operation would block")); @@ -870,8 +908,17 @@ io_run_until (SoupMessage *msg, return done; } +static void io_run (SoupMessage *msg, gboolean blocking); + static gboolean -io_run (SoupMessage *msg, gpointer user_data) +io_run_ready (SoupMessage *msg, gpointer user_data) +{ + io_run (msg, FALSE); + return FALSE; +} + +static void +io_run (SoupMessage *msg, gboolean blocking) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageIOData *io = priv->io_data; @@ -887,14 +934,14 @@ io_run (SoupMessage *msg, gpointer user_data) g_object_ref (msg); cancellable = io->cancellable ? g_object_ref (io->cancellable) : NULL; - if (io_run_until (msg, + if (io_run_until (msg, blocking, SOUP_MESSAGE_IO_STATE_DONE, SOUP_MESSAGE_IO_STATE_DONE, cancellable, &error)) { soup_message_io_finished (msg); } else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_clear_error (&error); - io->io_source = soup_message_io_get_source (msg, NULL, io_run, msg); + io->io_source = soup_message_io_get_source (msg, NULL, io_run_ready, msg); g_source_attach (io->io_source, io->async_context); } else if (error && priv->io_data == io) { if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) @@ -908,29 +955,28 @@ io_run (SoupMessage *msg, gpointer user_data) g_error_free (error); soup_message_io_finished (msg); - } + } else if (error) + g_error_free (error); g_object_unref (msg); g_clear_object (&cancellable); - - return FALSE; } gboolean -soup_message_io_run_until_write (SoupMessage *msg, +soup_message_io_run_until_write (SoupMessage *msg, gboolean blocking, GCancellable *cancellable, GError **error) { - return io_run_until (msg, + return io_run_until (msg, blocking, SOUP_MESSAGE_IO_STATE_ANY, SOUP_MESSAGE_IO_STATE_BODY, cancellable, error); } gboolean -soup_message_io_run_until_read (SoupMessage *msg, +soup_message_io_run_until_read (SoupMessage *msg, gboolean blocking, GCancellable *cancellable, GError **error) { - return io_run_until (msg, + return io_run_until (msg, blocking, SOUP_MESSAGE_IO_STATE_BODY, SOUP_MESSAGE_IO_STATE_ANY, cancellable, error); @@ -938,20 +984,30 @@ soup_message_io_run_until_read (SoupMessage *msg, gboolean soup_message_io_run_until_finish (SoupMessage *msg, + gboolean blocking, GCancellable *cancellable, GError **error) { + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + SoupMessageIOData *io = priv->io_data; + gboolean success; + g_object_ref (msg); - if (!io_run_until (msg, - SOUP_MESSAGE_IO_STATE_DONE, - SOUP_MESSAGE_IO_STATE_DONE, - cancellable, error)) - return FALSE; + if (io) { + g_return_val_if_fail (io->mode == SOUP_MESSAGE_IO_CLIENT, FALSE); + + if (io->read_state < SOUP_MESSAGE_IO_STATE_BODY_DONE) + io->read_state = SOUP_MESSAGE_IO_STATE_FINISHING; + } + + success = io_run_until (msg, blocking, + SOUP_MESSAGE_IO_STATE_DONE, + SOUP_MESSAGE_IO_STATE_DONE, + cancellable, error); - soup_message_io_finished (msg); g_object_unref (msg); - return TRUE; + return success; } static void @@ -1013,11 +1069,8 @@ new_iostate (SoupMessage *msg, GIOStream *iostream, io->istream = SOUP_FILTER_INPUT_STREAM (g_io_stream_get_input_stream (iostream)); io->ostream = g_io_stream_get_output_stream (iostream); - if (async_context) { + if (async_context) io->async_context = g_main_context_ref (async_context); - io->blocking = FALSE; - } else - io->blocking = TRUE; io->read_header_buf = g_byte_array_new (); io->write_buf = g_string_new (NULL); @@ -1056,8 +1109,13 @@ soup_message_io_client (SoupMessageQueueItem *item, io->write_body = item->msg->request_body; io->write_state = SOUP_MESSAGE_IO_STATE_HEADERS; - if (!item->new_api) - io_run (item->msg, NULL); + + if (!item->new_api) { + gboolean blocking = + SOUP_IS_SESSION_SYNC (item->session) || + (!SOUP_IS_SESSION_ASYNC (item->session) && !item->async); + io_run (item->msg, blocking); + } } void @@ -1080,7 +1138,7 @@ soup_message_io_server (SoupMessage *msg, io->write_body = msg->response_body; io->read_state = SOUP_MESSAGE_IO_STATE_HEADERS; - io_run (msg, NULL); + io_run (msg, FALSE); } void @@ -1102,6 +1160,7 @@ soup_message_io_pause (SoupMessage *msg) if (io->unpause_source) { g_source_destroy (io->unpause_source); + g_source_unref (io->unpause_source); io->unpause_source = NULL; } @@ -1115,13 +1174,14 @@ io_unpause_internal (gpointer msg) SoupMessageIOData *io = priv->io_data; g_return_val_if_fail (io != NULL, FALSE); - io->unpause_source = NULL; + + g_clear_pointer (&io->unpause_source, g_source_unref); io->paused = FALSE; if (io->io_source) return FALSE; - io_run (msg, NULL); + io_run (msg, FALSE); return FALSE; } @@ -1139,13 +1199,10 @@ soup_message_io_unpause (SoupMessage *msg) return; } - if (!io->blocking) { - if (!io->unpause_source) { - io->unpause_source = soup_add_completion ( - io->async_context, io_unpause_internal, msg); - } - } else - io_unpause_internal (msg); + if (!io->unpause_source) { + io->unpause_source = soup_add_completion_reffed (io->async_context, + io_unpause_internal, msg); + } } /** diff --git a/libsoup/soup-message-private.h b/libsoup/soup-message-private.h index 69490e94..35cc9887 100644 --- a/libsoup/soup-message-private.h +++ b/libsoup/soup-message-private.h @@ -8,7 +8,9 @@ #include "soup-message.h" #include "soup-auth.h" +#include "soup-content-processor.h" #include "soup-content-sniffer.h" +#include "soup-session.h" typedef struct { gpointer io_data; @@ -29,18 +31,22 @@ typedef struct { SoupAddress *addr; SoupAuth *auth, *proxy_auth; + SoupConnection *connection; GSList *disabled_features; - GSList *decoders; SoupURI *first_party; GTlsCertificate *tls_certificate; GTlsCertificateFlags tls_errors; + + SoupRequest *request; + + SoupMessagePriority priority; } SoupMessagePrivate; #define SOUP_MESSAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_MESSAGE, SoupMessagePrivate)) -void soup_message_cleanup_response (SoupMessage *req); +void soup_message_cleanup_response (SoupMessage *msg); typedef void (*SoupMessageGetHeadersFn) (SoupMessage *msg, @@ -51,7 +57,8 @@ typedef guint (*SoupMessageParseHeadersFn)(SoupMessage *msg, char *headers, guint header_len, SoupEncoding *encoding, - gpointer user_data); + gpointer user_data, + GError **error); typedef void (*SoupMessageCompletionFn) (SoupMessage *msg, gpointer user_data); @@ -59,7 +66,7 @@ typedef void (*SoupMessageCompletionFn) (SoupMessage *msg, void soup_message_send_request (SoupMessageQueueItem *item, SoupMessageCompletionFn completion_cb, gpointer user_data); -void soup_message_read_request (SoupMessage *req, +void soup_message_read_request (SoupMessage *msg, SoupSocket *sock, SoupMessageCompletionFn completion_cb, gpointer user_data); @@ -98,12 +105,15 @@ void soup_message_io_unpause (SoupMessage *msg); gboolean soup_message_io_in_progress (SoupMessage *msg); gboolean soup_message_io_run_until_write (SoupMessage *msg, + gboolean blocking, GCancellable *cancellable, GError **error); gboolean soup_message_io_run_until_read (SoupMessage *msg, + gboolean blocking, GCancellable *cancellable, GError **error); gboolean soup_message_io_run_until_finish (SoupMessage *msg, + gboolean blocking, GCancellable *cancellable, GError **error); @@ -126,4 +136,16 @@ void soup_message_network_event (SoupMessage *msg, GSocketClientEvent event, GIOStream *connection); +GInputStream *soup_message_setup_body_istream (GInputStream *body_stream, + SoupMessage *msg, + SoupSession *session, + SoupProcessingStage start_at_stage); + +void soup_message_set_soup_request (SoupMessage *msg, + SoupRequest *req); + +SoupConnection *soup_message_get_connection (SoupMessage *msg); +void soup_message_set_connection (SoupMessage *msg, + SoupConnection *conn); + #endif /* SOUP_MESSAGE_PRIVATE_H */ diff --git a/libsoup/soup-message-queue.c b/libsoup/soup-message-queue.c index 8ef129a9..4bc37267 100644 --- a/libsoup/soup-message-queue.c +++ b/libsoup/soup-message-queue.c @@ -58,28 +58,7 @@ queue_message_restarted (SoupMessage *msg, gpointer user_data) { SoupMessageQueueItem *item = user_data; - if (item->proxy_addr) { - g_object_unref (item->proxy_addr); - item->proxy_addr = NULL; - } - if (item->proxy_uri) { - soup_uri_free (item->proxy_uri); - item->proxy_uri = NULL; - } - - if (item->conn && - (!soup_message_is_keepalive (msg) || - SOUP_STATUS_IS_REDIRECTION (msg->status_code))) { - if (soup_connection_get_state (item->conn) == SOUP_CONNECTION_IN_USE) - soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE); - soup_message_queue_item_set_connection (item, NULL); - } - - soup_message_cleanup_response (msg); - g_cancellable_reset (item->cancellable); - - item->state = SOUP_MESSAGE_STARTING; } /** @@ -101,13 +80,16 @@ soup_message_queue_append (SoupMessageQueue *queue, SoupMessage *msg, SoupMessageQueueItem *item; item = g_slice_new0 (SoupMessageQueueItem); - item->session = queue->session; + item->session = g_object_ref (queue->session); item->async_context = soup_session_get_async_context (item->session); + if (item->async_context) + g_main_context_ref (item->async_context); item->queue = queue; item->msg = g_object_ref (msg); item->callback = callback; item->callback_data = user_data; item->cancellable = g_cancellable_new (); + item->priority = soup_message_get_priority (msg); g_signal_connect (msg, "restarted", G_CALLBACK (queue_message_restarted), item); @@ -120,9 +102,27 @@ soup_message_queue_append (SoupMessageQueue *queue, SoupMessage *msg, g_mutex_lock (&queue->mutex); if (queue->head) { - queue->tail->next = item; - item->prev = queue->tail; - queue->tail = item; + SoupMessageQueueItem *it = queue->head; + + while (it && it->priority >= item->priority) + it = it->next; + + if (!it) { + if (queue->tail) { + queue->tail->next = item; + item->prev = queue->tail; + } else + queue->head = item; + queue->tail = item; + } else { + if (it != queue->head) + it->prev->next = item; + else + queue->head = item; + item->prev = it->prev; + it->prev = item; + item->next = it; + } } else queue->head = queue->tail = item; @@ -163,6 +163,8 @@ soup_message_queue_item_unref (SoupMessageQueueItem *item) return; } + g_warn_if_fail (item->conn == NULL); + /* OK, @item is dead. Rewrite @queue around it */ if (item->prev) item->prev->next = item->next; @@ -178,45 +180,17 @@ soup_message_queue_item_unref (SoupMessageQueueItem *item) /* And free it */ g_signal_handlers_disconnect_by_func (item->msg, queue_message_restarted, item); + g_object_unref (item->session); g_object_unref (item->msg); g_object_unref (item->cancellable); - if (item->proxy_addr) - g_object_unref (item->proxy_addr); - if (item->proxy_uri) - soup_uri_free (item->proxy_uri); - if (item->result) - g_object_unref (item->result); - soup_message_queue_item_set_connection (item, NULL); - g_slice_free (SoupMessageQueueItem, item); -} - -static void -proxy_connection_event (SoupConnection *conn, - GSocketClientEvent event, - GIOStream *connection, - gpointer user_data) -{ - SoupMessageQueueItem *item = user_data; - - soup_message_network_event (item->msg, event, connection); -} - -void -soup_message_queue_item_set_connection (SoupMessageQueueItem *item, - SoupConnection *conn) -{ - if (item->conn) { - g_signal_handlers_disconnect_by_func (item->conn, proxy_connection_event, item); - g_object_unref (item->conn); - } - - item->conn = conn; - - if (item->conn) { - g_object_ref (item->conn); - g_signal_connect (item->conn, "event", - G_CALLBACK (proxy_connection_event), item); + g_clear_error (&item->error); + g_clear_object (&item->task); + g_clear_pointer (&item->async_context, g_main_context_unref); + if (item->io_source) { + g_source_destroy (item->io_source); + g_source_unref (item->io_source); } + g_slice_free (SoupMessageQueueItem, item); } /** diff --git a/libsoup/soup-message-queue.h b/libsoup/soup-message-queue.h index 54c2971f..f86a1296 100644 --- a/libsoup/soup-message-queue.h +++ b/libsoup/soup-message-queue.h @@ -15,7 +15,6 @@ G_BEGIN_DECLS typedef enum { SOUP_MESSAGE_STARTING, - SOUP_MESSAGE_AWAITING_CONNECTION, SOUP_MESSAGE_GOT_CONNECTION, SOUP_MESSAGE_CONNECTING, SOUP_MESSAGE_CONNECTED, @@ -23,6 +22,7 @@ typedef enum { SOUP_MESSAGE_TUNNELED, SOUP_MESSAGE_READY, SOUP_MESSAGE_RUNNING, + SOUP_MESSAGE_CACHED, SOUP_MESSAGE_RESTARTING, SOUP_MESSAGE_FINISHING, SOUP_MESSAGE_FINISHED @@ -38,21 +38,24 @@ struct _SoupMessageQueueItem { GMainContext *async_context; GCancellable *cancellable; - SoupAddress *proxy_addr; - SoupURI *proxy_uri; + GError *error; + SoupConnection *conn; - GSimpleAsyncResult *result; + GTask *task; + GSource *io_source; guint paused : 1; guint new_api : 1; guint io_started : 1; - guint redirection_count : 29; + guint async : 1; + guint resend_count : 28; SoupMessageQueueItemState state; /*< private >*/ guint removed : 1; - guint ref_count : 31; + guint priority : 3; + guint ref_count : 28; SoupMessageQueueItem *prev, *next; SoupMessageQueueItem *related; }; @@ -77,8 +80,6 @@ void soup_message_queue_destroy (SoupMessageQueue *queue void soup_message_queue_item_ref (SoupMessageQueueItem *item); void soup_message_queue_item_unref (SoupMessageQueueItem *item); -void soup_message_queue_item_set_connection (SoupMessageQueueItem *item, - SoupConnection *conn); G_END_DECLS diff --git a/libsoup/soup-message-server-io.c b/libsoup/soup-message-server-io.c index a53e5b7b..2647b811 100644 --- a/libsoup/soup-message-server-io.c +++ b/libsoup/soup-message-server-io.c @@ -11,13 +11,15 @@ #include <string.h> +#include <glib/gi18n-lib.h> + #include "soup.h" #include "soup-message-private.h" #include "soup-misc-private.h" static guint parse_request_headers (SoupMessage *msg, char *headers, guint headers_len, - SoupEncoding *encoding, gpointer sock) + SoupEncoding *encoding, gpointer sock, GError **error) { SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); char *req_method, *req_path, *url; @@ -31,8 +33,14 @@ parse_request_headers (SoupMessage *msg, char *headers, guint headers_len, &req_method, &req_path, &version); - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { + if (status == SOUP_STATUS_MALFORMED) { + g_set_error_literal (error, SOUP_REQUEST_ERROR, + SOUP_REQUEST_ERROR_PARSING, + _("Could not parse HTTP request")); + } return status; + } g_object_set (G_OBJECT (msg), SOUP_MESSAGE_METHOD, req_method, @@ -90,10 +98,7 @@ parse_request_headers (SoupMessage *msg, char *headers, guint headers_len, g_free (req_path); - if (!SOUP_URI_VALID_FOR_HTTP (uri)) { - /* certainly not "a valid host on the server" (RFC2616 5.2.3) - * SOUP_URI_VALID_FOR_HTTP also guards against uri == NULL - */ + if (!uri || !uri->host) { if (uri) soup_uri_free (uri); return SOUP_STATUS_BAD_REQUEST; @@ -111,6 +116,7 @@ handle_partial_get (SoupMessage *msg) SoupRange *ranges; int nranges; SoupBuffer *full_response; + guint status; /* Make sure the message is set up right for us to return a * partial response; it has to be a GET, the status must be @@ -129,9 +135,15 @@ handle_partial_get (SoupMessage *msg) /* Oh, and there has to have been a valid Range header on the * request, of course. */ - if (!soup_message_headers_get_ranges (msg->request_headers, - msg->response_body->length, - &ranges, &nranges)) + status = soup_message_headers_get_ranges_internal (msg->request_headers, + msg->response_body->length, + TRUE, + &ranges, &nranges); + if (status == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) { + soup_message_set_status (msg, status); + soup_message_body_truncate (msg->response_body); + return; + } else if (status != SOUP_STATUS_PARTIAL_CONTENT) return; full_response = soup_message_body_flatten (msg->response_body); diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c index 3e1112e1..23fd8b08 100644 --- a/libsoup/soup-message.c +++ b/libsoup/soup-message.c @@ -5,13 +5,11 @@ * Copyright (C) 2000-2003, Ximian, Inc. */ -//#include <stdlib.h> -//#include <string.h> +#include <string.h> #include "soup-message.h" #include "soup.h" #include "soup-connection.h" -#include "soup-marshal.h" #include "soup-message-private.h" /** @@ -22,9 +20,16 @@ * A #SoupMessage represents an HTTP message that is being sent or * received. * - * For client-side usage, you would create a #SoupMessage with + * For client-side usage, if you are using the traditional + * #SoupSession APIs (soup_session_queue_message() and + * soup_session_send_message()), you would create a #SoupMessage with * soup_message_new() or soup_message_new_from_uri(), set up its - * fields appropriate, and send it via a #SoupSession. + * fields appropriately, and send it. If you are using the newer + * #SoupRequest API, you would create a request with + * soup_session_request_http() or soup_session_request_http_uri(), and + * the returned #SoupRequestHTTP will already have an associated + * #SoupMessage that you can retrieve via + * soup_request_http_get_message(). * * For server-side usage, #SoupServer will create #SoupMessage<!-- * -->s automatically for incoming requests, which your application @@ -49,7 +54,7 @@ * * Represents an HTTP message being sent or received. * - * @status_code will normally be a #SoupKnownStatusCode, eg, + * @status_code will normally be a #SoupStatus value, eg, * %SOUP_STATUS_OK, though of course it might actually be an unknown * status code. @reason_phrase is the actual text returned from the * server, which may or may not correspond to the "standard" @@ -62,17 +67,22 @@ * * As described in the #SoupMessageBody documentation, the * @request_body and @response_body <literal>data</literal> 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 <literal>length</literal>), 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. + * will not necessarily be filled in at all times. When the body + * fields are filled in, they will be terminated with a '\0' byte + * (which is not included in the <literal>length</literal>), 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 + * <literal>data</literal> 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 + * <literal>data</literal> is filled in. If you are not using + * #SoupRequest to read the response, then @response_body's + * <literal>data</literal> will be filled in before + * #SoupMessage::finished is emitted. (If you are using #SoupRequest, + * then the message body is not accumulated by default, so + * @response_body's <literal>data</literal> will always be %NULL.) * * For a server-side #SoupMessage, @request_body's %data will be * filled in before #SoupMessage::got_body is emitted. @@ -121,11 +131,14 @@ enum { PROP_REASON_PHRASE, PROP_FIRST_PARTY, PROP_REQUEST_BODY, + PROP_REQUEST_BODY_DATA, PROP_REQUEST_HEADERS, PROP_RESPONSE_BODY, + PROP_RESPONSE_BODY_DATA, PROP_RESPONSE_HEADERS, PROP_TLS_CERTIFICATE, PROP_TLS_ERRORS, + PROP_PRIORITY, LAST_PROP }; @@ -136,6 +149,7 @@ soup_message_init (SoupMessage *msg) SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); priv->http_version = priv->orig_http_version = SOUP_HTTP_1_1; + priv->priority = SOUP_MESSAGE_PRIORITY_NORMAL; msg->request_body = soup_message_body_new (); msg->request_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST); @@ -162,8 +176,6 @@ soup_message_finalize (GObject *object) g_slist_free (priv->disabled_features); - g_slist_free_full (priv->decoders, g_object_unref); - g_clear_object (&priv->tls_certificate); soup_message_body_free (msg->request_body); @@ -229,6 +241,9 @@ soup_message_set_property (GObject *object, guint prop_id, else if (priv->tls_certificate) priv->msg_flags |= SOUP_MESSAGE_CERTIFICATE_TRUSTED; break; + case PROP_PRIORITY: + priv->priority = g_value_get_enum (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -241,6 +256,7 @@ soup_message_get_property (GObject *object, guint prop_id, { SoupMessage *msg = SOUP_MESSAGE (object); SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + SoupBuffer *buf; switch (prop_id) { case PROP_METHOD: @@ -270,12 +286,22 @@ soup_message_get_property (GObject *object, guint prop_id, case PROP_REQUEST_BODY: g_value_set_boxed (value, msg->request_body); break; + case PROP_REQUEST_BODY_DATA: + buf = soup_message_body_flatten (msg->request_body); + g_value_take_boxed (value, soup_buffer_get_as_bytes (buf)); + soup_buffer_free (buf); + break; case PROP_REQUEST_HEADERS: g_value_set_boxed (value, msg->request_headers); break; case PROP_RESPONSE_BODY: g_value_set_boxed (value, msg->response_body); break; + case PROP_RESPONSE_BODY_DATA: + buf = soup_message_body_flatten (msg->response_body); + g_value_take_boxed (value, soup_buffer_get_as_bytes (buf)); + soup_buffer_free (buf); + break; case PROP_RESPONSE_HEADERS: g_value_set_boxed (value, msg->response_headers); break; @@ -285,6 +311,9 @@ soup_message_get_property (GObject *object, guint prop_id, case PROP_TLS_ERRORS: g_value_set_flags (value, priv->tls_errors); break; + case PROP_PRIORITY: + g_value_set_enum (value, priv->priority); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -292,12 +321,12 @@ soup_message_get_property (GObject *object, guint prop_id, } static void -soup_message_real_got_body (SoupMessage *req) +soup_message_real_got_body (SoupMessage *msg) { - SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); SoupMessageBody *body; - body = priv->server_side ? req->request_body : req->response_body; + body = priv->server_side ? msg->request_body : msg->response_body; if (soup_message_body_get_accumulate (body)) { SoupBuffer *buffer; @@ -336,7 +365,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, wrote_informational), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -354,7 +383,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, wrote_headers), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -376,7 +405,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, wrote_chunk), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -391,7 +420,7 @@ soup_message_class_init (SoupMessageClass *message_class) * every successful write() call, not only after finishing a * complete "chunk". * - * Since: 2.4.1 + * Since: 2.24 **/ signals[WROTE_BODY_DATA] = g_signal_new ("wrote_body_data", @@ -399,7 +428,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, 0, /* FIXME after next ABI break */ NULL, NULL, - _soup_marshal_NONE__BOXED, + NULL, G_TYPE_NONE, 1, SOUP_TYPE_BUFFER); @@ -420,7 +449,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, wrote_body), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -443,7 +472,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, got_informational), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -475,7 +504,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, got_headers), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -498,7 +527,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, got_chunk), NULL, NULL, - _soup_marshal_NONE__BOXED, + NULL, G_TYPE_NONE, 1, /* Use %G_SIGNAL_TYPE_STATIC_SCOPE so that * the %SOUP_MEMORY_TEMPORARY buffers used @@ -527,7 +556,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, got_body), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -556,7 +585,7 @@ soup_message_class_init (SoupMessageClass *message_class) * that sniffing could be done is delivered on the first * emission of #SoupMessage::got-chunk. * - * Since: 2.27.3 + * Since: 2.28 **/ signals[CONTENT_SNIFFED] = g_signal_new ("content_sniffed", @@ -564,7 +593,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - _soup_marshal_NONE__STRING_BOXED, + NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_HASH_TABLE); @@ -584,7 +613,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, restarted), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -601,7 +630,7 @@ soup_message_class_init (SoupMessageClass *message_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupMessageClass, finished), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -768,6 +797,28 @@ soup_message_class_init (SoupMessageClass *message_class) SOUP_TYPE_MESSAGE_BODY, G_PARAM_READABLE)); /** + * SOUP_MESSAGE_REQUEST_BODY_DATA: + * + * Alias for the #SoupMessage:request-body-data property. (The + * message's HTTP request body, as a #GBytes.) + * + * Since: 2.46 + **/ + /** + * SoupMessage:request-body-data: + * + * The message's HTTP request body, as a #GBytes. + * + * Since: 2.46 + **/ + g_object_class_install_property ( + object_class, PROP_REQUEST_BODY_DATA, + g_param_spec_boxed (SOUP_MESSAGE_REQUEST_BODY_DATA, + "Request Body Data", + "The HTTP request body", + G_TYPE_BYTES, + G_PARAM_READABLE)); + /** * SOUP_MESSAGE_REQUEST_HEADERS: * * Alias for the #SoupMessage:request-headers property. (The @@ -794,6 +845,28 @@ soup_message_class_init (SoupMessageClass *message_class) SOUP_TYPE_MESSAGE_BODY, G_PARAM_READABLE)); /** + * SOUP_MESSAGE_RESPONSE_BODY_DATA: + * + * Alias for the #SoupMessage:response-body-data property. (The + * message's HTTP response body, as a #GBytes.) + * + * Since: 2.46 + **/ + /** + * SoupMessage:response-body-data: + * + * The message's HTTP response body, as a #GBytes. + * + * Since: 2.46 + **/ + g_object_class_install_property ( + object_class, PROP_RESPONSE_BODY_DATA, + g_param_spec_boxed (SOUP_MESSAGE_RESPONSE_BODY_DATA, + "Response Body Data", + "The HTTP response body", + G_TYPE_BYTES, + G_PARAM_READABLE)); + /** * SOUP_MESSAGE_RESPONSE_HEADERS: * * Alias for the #SoupMessage:response-headers property. (The @@ -850,6 +923,22 @@ soup_message_class_init (SoupMessageClass *message_class) "The verification errors on the message's TLS certificate", G_TYPE_TLS_CERTIFICATE_FLAGS, 0, G_PARAM_READWRITE)); + /** + * SOUP_MESSAGE_PRIORITY: + * + * Sets the priority of the #SoupMessage. See + * soup_message_set_priority() for further details. + * + * Since: 2.44 + **/ + g_object_class_install_property ( + object_class, PROP_PRIORITY, + g_param_spec_enum (SOUP_MESSAGE_PRIORITY, + "Priority", + "The priority of the message", + SOUP_TYPE_MESSAGE_PRIORITY, + SOUP_MESSAGE_PRIORITY_NORMAL, + G_PARAM_READWRITE)); } @@ -906,9 +995,10 @@ soup_message_new_from_uri (const char *method, SoupURI *uri) /** * soup_message_set_request: * @msg: the message - * @content_type: MIME Content-Type of the body + * @content_type: (allow-none): MIME Content-Type of the body * @req_use: a #SoupMemoryUse describing how to handle @req_body - * @req_body: a data buffer containing the body of the message request. + * @req_body: (allow-none) (array length=req_length) (element-type guint8): + * 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. If @@ -925,6 +1015,8 @@ soup_message_set_request (SoupMessage *msg, g_return_if_fail (content_type != NULL || req_length == 0); if (content_type) { + g_warn_if_fail (strchr (content_type, '/') != NULL); + soup_message_headers_replace (msg->request_headers, "Content-Type", content_type); soup_message_body_append (msg->request_body, req_use, @@ -941,8 +1033,8 @@ soup_message_set_request (SoupMessage *msg, * @msg: the message * @content_type: (allow-none): MIME Content-Type of the body * @resp_use: a #SoupMemoryUse describing how to handle @resp_body - * @resp_body: (array length=resp_length) (element-type guint8): a data buffer - * containing the body of the message response. + * @resp_body: (allow-none) (array length=resp_length) (element-type guint8): + * 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. If @@ -959,6 +1051,8 @@ soup_message_set_response (SoupMessage *msg, g_return_if_fail (content_type != NULL || resp_length == 0); if (content_type) { + g_warn_if_fail (strchr (content_type, '/') != NULL); + soup_message_headers_replace (msg->response_headers, "Content-Type", content_type); soup_message_body_append (msg->response_body, resp_use, @@ -970,150 +1064,66 @@ soup_message_set_response (SoupMessage *msg, } } -/** - * soup_message_wrote_informational: - * @msg: a #SoupMessage - * - * Emits the %wrote_informational signal, indicating that the IO layer - * finished writing an informational (1xx) response for @msg. - **/ void soup_message_wrote_informational (SoupMessage *msg) { g_signal_emit (msg, signals[WROTE_INFORMATIONAL], 0); } -/** - * soup_message_wrote_headers: - * @msg: a #SoupMessage - * - * Emits the %wrote_headers signal, indicating that the IO layer - * finished writing the (non-informational) headers for @msg. - **/ void soup_message_wrote_headers (SoupMessage *msg) { g_signal_emit (msg, signals[WROTE_HEADERS], 0); } -/** - * soup_message_wrote_chunk: - * @msg: a #SoupMessage - * - * Emits the %wrote_chunk signal, indicating that the IO layer - * finished writing a chunk of @msg's body. - **/ void soup_message_wrote_chunk (SoupMessage *msg) { g_signal_emit (msg, signals[WROTE_CHUNK], 0); } -/** - * soup_message_wrote_body_data: - * @msg: a #SoupMessage - * @chunk: the data written - * - * Emits the %wrote_body_data signal, indicating that the IO layer - * finished writing a portion of @msg's body. - **/ void soup_message_wrote_body_data (SoupMessage *msg, SoupBuffer *chunk) { g_signal_emit (msg, signals[WROTE_BODY_DATA], 0, chunk); } -/** - * soup_message_wrote_body: - * @msg: a #SoupMessage - * - * Emits the %wrote_body signal, indicating that the IO layer finished - * writing the body for @msg. - **/ void soup_message_wrote_body (SoupMessage *msg) { g_signal_emit (msg, signals[WROTE_BODY], 0); } -/** - * soup_message_got_informational: - * @msg: a #SoupMessage - * - * Emits the #SoupMessage::got_informational signal, indicating that - * the IO layer read a complete informational (1xx) response for @msg. - **/ void soup_message_got_informational (SoupMessage *msg) { g_signal_emit (msg, signals[GOT_INFORMATIONAL], 0); } -/** - * soup_message_got_headers: - * @msg: a #SoupMessage - * - * Emits the #SoupMessage::got_headers signal, indicating that the IO - * layer finished reading the (non-informational) headers for @msg. - **/ void soup_message_got_headers (SoupMessage *msg) { g_signal_emit (msg, signals[GOT_HEADERS], 0); } -/** - * soup_message_got_chunk: - * @msg: a #SoupMessage - * @chunk: the newly-read chunk - * - * Emits the #SoupMessage::got_chunk signal, indicating that the IO - * layer finished reading a chunk of @msg's body. - **/ void soup_message_got_chunk (SoupMessage *msg, SoupBuffer *chunk) { g_signal_emit (msg, signals[GOT_CHUNK], 0, chunk); } -/** - * soup_message_got_body: - * @msg: a #SoupMessage - * - * Emits the #SoupMessage::got_body signal, indicating that the IO - * layer finished reading the body for @msg. - **/ void soup_message_got_body (SoupMessage *msg) { g_signal_emit (msg, signals[GOT_BODY], 0); } -/** - * soup_message_content_sniffed: - * @msg: a #SoupMessage - * @content_type: a string with the sniffed content type - * @params: a #GHashTable with the parameters - * - * Emits the %content_sniffed signal, indicating that the IO layer - * finished sniffing the content type for @msg. If content sniffing - * will not be performed, due to the sniffer deciding to trust the - * Content-Type sent by the server, this signal is emitted immediately - * after #SoupMessage::got_headers, with %NULL as @content_type. - **/ void soup_message_content_sniffed (SoupMessage *msg, const char *content_type, GHashTable *params) { g_signal_emit (msg, signals[CONTENT_SNIFFED], 0, content_type, params); } -/** - * soup_message_restarted: - * @msg: a #SoupMessage - * - * Emits the %restarted signal, indicating that @msg should be - * requeued. - **/ void soup_message_restarted (SoupMessage *msg) { @@ -1125,13 +1135,6 @@ soup_message_restarted (SoupMessage *msg) g_signal_emit (msg, signals[RESTARTED], 0); } -/** - * soup_message_finished: - * @msg: a #SoupMessage - * - * Emits the %finished signal, indicating that @msg has been completely - * processed. - **/ void soup_message_finished (SoupMessage *msg) { @@ -1163,11 +1166,6 @@ header_handler_metamarshal (GClosure *closure, GValue *return_value, const char *header_name = marshal_data; SoupMessageHeaders *hdrs; -#ifdef FIXME - if (priv->io_status != SOUP_MESSAGE_IO_STATUS_RUNNING) - return; -#endif - hdrs = priv->server_side ? msg->request_headers : msg->response_headers; if (soup_message_headers_get_one (hdrs, header_name)) { closure->marshal (closure, return_value, n_param_values, @@ -1185,16 +1183,11 @@ header_handler_metamarshal (GClosure *closure, GValue *return_value, * @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 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. + * g_signal_connect(), but the @callback will only be run if @msg's + * incoming messages headers (that is, the + * <literal>request_headers</literal> for a client #SoupMessage, or + * the <literal>response_headers</literal> for a server #SoupMessage) + * contain a header named @header. * * Return value: the handler ID from g_signal_connect() **/ @@ -1232,11 +1225,6 @@ status_handler_metamarshal (GClosure *closure, GValue *return_value, SoupMessage *msg = g_value_get_object (¶m_values[0]); guint status = GPOINTER_TO_UINT (marshal_data); -#ifdef FIXME - if (priv->io_status != SOUP_MESSAGE_IO_STATUS_RUNNING) - return; -#endif - if (msg->status_code == status) { closure->marshal (closure, return_value, n_param_values, param_values, invocation_hint, @@ -1253,9 +1241,8 @@ status_handler_metamarshal (GClosure *closure, GValue *return_value, * @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. + * g_signal_connect(), but the @callback will only be run if @msg has + * the status @status_code. * * @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" @@ -1285,15 +1272,6 @@ soup_message_add_status_code_handler (SoupMessage *msg, } -/** - * soup_message_set_auth: - * @msg: a #SoupMessage - * @auth: a #SoupAuth, or %NULL - * - * Sets @msg to authenticate to its destination using @auth, which - * must have already been fully authenticated. If @auth is %NULL, @msg - * will not authenticate to its destination. - **/ void soup_message_set_auth (SoupMessage *msg, SoupAuth *auth) { @@ -1302,7 +1280,6 @@ soup_message_set_auth (SoupMessage *msg, SoupAuth *auth) g_return_if_fail (SOUP_IS_MESSAGE (msg)); g_return_if_fail (auth == NULL || SOUP_IS_AUTH (auth)); - g_return_if_fail (auth == NULL || soup_auth_is_authenticated (auth)); priv = SOUP_MESSAGE_GET_PRIVATE (msg); @@ -1317,20 +1294,13 @@ soup_message_set_auth (SoupMessage *msg, SoupAuth *auth) g_object_ref (priv->auth); token = soup_auth_get_authorization (auth, msg); - soup_message_headers_replace (msg->request_headers, - "Authorization", token); - g_free (token); + if (token) { + soup_message_headers_replace (msg->request_headers, + "Authorization", token); + g_free (token); + } } -/** - * soup_message_get_auth: - * @msg: a #SoupMessage - * - * Gets the #SoupAuth used by @msg for authentication. - * - * Return value: (transfer none): the #SoupAuth used by @msg for - * authentication, or %NULL if @msg is unauthenticated. - **/ SoupAuth * soup_message_get_auth (SoupMessage *msg) { @@ -1339,15 +1309,6 @@ soup_message_get_auth (SoupMessage *msg) return SOUP_MESSAGE_GET_PRIVATE (msg)->auth; } -/** - * soup_message_set_proxy_auth: - * @msg: a #SoupMessage - * @auth: a #SoupAuth, or %NULL - * - * Sets @msg to authenticate to its proxy using @auth, which must have - * already been fully authenticated. If @auth is %NULL, @msg will not - * authenticate to its proxy. - **/ void soup_message_set_proxy_auth (SoupMessage *msg, SoupAuth *auth) { @@ -1356,7 +1317,6 @@ soup_message_set_proxy_auth (SoupMessage *msg, SoupAuth *auth) g_return_if_fail (SOUP_IS_MESSAGE (msg)); g_return_if_fail (auth == NULL || SOUP_IS_AUTH (auth)); - g_return_if_fail (auth == NULL || soup_auth_is_authenticated (auth)); priv = SOUP_MESSAGE_GET_PRIVATE (msg); @@ -1376,15 +1336,6 @@ soup_message_set_proxy_auth (SoupMessage *msg, SoupAuth *auth) g_free (token); } -/** - * soup_message_get_proxy_auth: - * @msg: a #SoupMessage - * - * Gets the #SoupAuth used by @msg for authentication to its proxy.. - * - * Return value: the #SoupAuth used by @msg for authentication to its - * proxy, or %NULL if @msg isn't authenticated to its proxy. - **/ SoupAuth * soup_message_get_proxy_auth (SoupMessage *msg) { @@ -1393,43 +1344,54 @@ soup_message_get_proxy_auth (SoupMessage *msg) return SOUP_MESSAGE_GET_PRIVATE (msg)->proxy_auth; } +SoupConnection * +soup_message_get_connection (SoupMessage *msg) +{ + return SOUP_MESSAGE_GET_PRIVATE (msg)->connection; +} + +void +soup_message_set_connection (SoupMessage *msg, + SoupConnection *conn) +{ + SOUP_MESSAGE_GET_PRIVATE (msg)->connection = conn; +} + /** * soup_message_cleanup_response: - * @req: a #SoupMessage + * @msg: a #SoupMessage * - * Cleans up all response data on @req, so that the request can be sent + * Cleans up all response data on @msg, so that the request can be sent * again and receive a new response. (Eg, as a result of a redirect or * authorization request.) **/ void -soup_message_cleanup_response (SoupMessage *req) +soup_message_cleanup_response (SoupMessage *msg) { - SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (req); + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); - soup_message_body_truncate (req->response_body); - soup_message_headers_clear (req->response_headers); + soup_message_body_truncate (msg->response_body); + soup_message_headers_clear (msg->response_headers); if (priv->server_side) { - soup_message_headers_set_encoding (req->response_headers, + soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_CONTENT_LENGTH); } - g_slist_free_full (priv->decoders, g_object_unref); - priv->decoders = NULL; priv->msg_flags &= ~SOUP_MESSAGE_CONTENT_DECODED; - req->status_code = SOUP_STATUS_NONE; - if (req->reason_phrase) { - g_free (req->reason_phrase); - req->reason_phrase = NULL; + msg->status_code = SOUP_STATUS_NONE; + if (msg->reason_phrase) { + g_free (msg->reason_phrase); + msg->reason_phrase = NULL; } priv->http_version = priv->orig_http_version; - g_object_notify (G_OBJECT (req), SOUP_MESSAGE_STATUS_CODE); - g_object_notify (G_OBJECT (req), SOUP_MESSAGE_REASON_PHRASE); - g_object_notify (G_OBJECT (req), SOUP_MESSAGE_HTTP_VERSION); - g_object_notify (G_OBJECT (req), SOUP_MESSAGE_FLAGS); - g_object_notify (G_OBJECT (req), SOUP_MESSAGE_TLS_CERTIFICATE); - g_object_notify (G_OBJECT (req), SOUP_MESSAGE_TLS_ERRORS); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_STATUS_CODE); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_REASON_PHRASE); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_HTTP_VERSION); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_FLAGS); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_TLS_CERTIFICATE); + g_object_notify (G_OBJECT (msg), SOUP_MESSAGE_TLS_ERRORS); } /** @@ -1450,10 +1412,15 @@ soup_message_cleanup_response (SoupMessage *req) * @SOUP_MESSAGE_CERTIFICATE_TRUSTED: if set after an https response * has been received, indicates that the server's SSL certificate is * trusted according to the session's CA. - * @SOUP_MESSAGE_NEW_CONNECTION: The message should be sent on a - * newly-created connection, not reusing an existing persistent - * connection. Note that messages with non-idempotent - * #SoupMessage:method<!-- -->s behave this way by default. + * @SOUP_MESSAGE_NEW_CONNECTION: Requests that the message should be + * sent on a newly-created connection, not reusing an existing + * persistent connection. Note that messages with non-idempotent + * #SoupMessage:method<!-- -->s behave this way by default, unless + * #SOUP_MESSAGE_IDEMPOTENT is set. + * @SOUP_MESSAGE_IDEMPOTENT: The message is considered idempotent, + * regardless its #SoupMessage:method, and allows reuse of existing + * idle connections, instead of always requiring a new one, unless + * #SOUP_MESSAGE_NEW_CONNECTION is set. * * Various flags that can be set on a #SoupMessage to alter its * behavior. @@ -1739,6 +1706,9 @@ soup_message_set_status_full (SoupMessage *msg, * becomes possible to allocate a new buffer. * * Return value: the new buffer (or %NULL) + * + * Deprecated: Use #SoupRequest if you want to read into your + * own buffers. **/ /** @@ -1750,11 +1720,13 @@ soup_message_set_status_full (SoupMessage *msg, * destroyed * * Sets an alternate chunk-allocation function to use when reading - * @msg's body. Every time data is available to read, libsoup will - * call @allocator, which should return a #SoupBuffer. (See - * #SoupChunkAllocator for additional details.) Libsoup will then read - * data from the network into that buffer, and update the buffer's - * <literal>length</literal> to indicate how much data it read. + * @msg's body when using the traditional (ie, + * non-#SoupRequest<!-- -->-based) API. Every time data is available + * to read, libsoup will call @allocator, which should return a + * #SoupBuffer. (See #SoupChunkAllocator for additional details.) + * Libsoup will then read data from the network into that buffer, and + * update the buffer's <literal>length</literal> to indicate how much + * data it read. * * Generally, a custom chunk allocator would be used in conjunction * with soup_message_body_set_accumulate() %FALSE and @@ -1775,6 +1747,10 @@ soup_message_set_status_full (SoupMessage *msg, * you'll need to ref the #SoupBuffer (or its owner, in the * soup_buffer_new_with_owner() case) to ensure that the data remains * valid. + * + * Deprecated: #SoupRequest provides a much simpler API that lets you + * read the response directly into your own buffers without needing to + * mess with callbacks, pausing/unpausing, etc. **/ void soup_message_set_chunk_allocator (SoupMessage *msg, @@ -1804,10 +1780,8 @@ soup_message_set_chunk_allocator (SoupMessage *msg, * This disables the actions of #SoupSessionFeature<!-- -->s with the * given @feature_type (or a subclass of that type) on @msg, so that * @msg is processed as though the feature(s) hadn't been added to the - * session. Eg, passing #SOUP_TYPE_PROXY_URI_RESOLVER for @feature_type - * will disable proxy handling and cause @msg to be sent directly to - * the indicated origin server, regardless of system proxy - * configuration. + * session. Eg, passing #SOUP_TYPE_CONTENT_SNIFFER for @feature_type + * will disable Content-Type sniffing on the message. * * You must call this before queueing @msg on a session; calling it on * a message that has already been queued is undefined. In particular, @@ -1934,11 +1908,13 @@ soup_message_set_https_status (SoupMessage *msg, SoupConnection *conn) * @certificate: (out) (transfer none): @msg's TLS certificate * @errors: (out): the verification status of @certificate * - * If @msg is using https, this retrieves the #GTlsCertificate - * associated with its connection, and the #GTlsCertificateFlags showing - * what problems, if any, have been found with that certificate. + * If @msg is using https (or attempted to use https but got + * %SOUP_STATUS_SSL_FAILED), this retrieves the #GTlsCertificate + * associated with its connection, and the #GTlsCertificateFlags + * showing what problems, if any, have been found with that + * certificate. * - * Return value: %TRUE if @msg uses https, %FALSE if not + * Return value: %TRUE if @msg used/attempted https, %FALSE if not * * Since: 2.34 */ @@ -1994,3 +1970,102 @@ soup_message_set_redirect (SoupMessage *msg, guint status_code, g_free (location_str); soup_uri_free (location); } + +void +soup_message_set_soup_request (SoupMessage *msg, + SoupRequest *req) +{ + SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg); + + priv->request = req; +} + +/** + * soup_message_get_soup_request: + * @msg: a #SoupMessage + * + * If @msg is associated with a #SoupRequest, this returns that + * request. Otherwise it returns %NULL. + * + * Return value: (transfer none): @msg's associated #SoupRequest + * + * Since: 2.42 + */ +SoupRequest * +soup_message_get_soup_request (SoupMessage *msg) +{ + SoupMessagePrivate *priv; + + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL); + + priv = SOUP_MESSAGE_GET_PRIVATE (msg); + return priv->request; +} + +/** + * SoupMessagePriority: + * @SOUP_MESSAGE_PRIORITY_VERY_LOW: The lowest priority, the messages + * with this priority will be the last ones to be attended. + * @SOUP_MESSAGE_PRIORITY_LOW: Use this for low priority messages, a + * #SoupMessage with the default priority will be processed first. + * @SOUP_MESSAGE_PRIORITY_NORMAL: The default priotity, this is the + * priority assigned to the #SoupMessage by default. + * @SOUP_MESSAGE_PRIORITY_HIGH: High priority, a #SoupMessage with + * this priority will be processed before the ones with the default + * priority. + * @SOUP_MESSAGE_PRIORITY_VERY_HIGH: The highest priority, use this + * for very urgent #SoupMessage as they will be the first ones to be + * attended. + * + * Priorities that can be set on a #SoupMessage to instruct the + * message queue to process it before any other message with lower + * priority. + **/ + +/** + * soup_message_set_priority: + * @msg: a #SoupMessage + * @priority: the #SoupMessagePriority + * + * Sets the priority of a message. Note that this won't have any + * effect unless used before the message is added to the session's + * message processing queue. + * + * The message will be placed just before any other previously added + * message with lower priority (messages with the same priority are + * processed on a FIFO basis). + * + * Setting priorities does not currently work with #SoupSessionSync + * (or with synchronous messages on a plain #SoupSession) because in + * the synchronous/blocking case, priority ends up being determined + * semi-randomly by thread scheduling. + * + * Since: 2.44 + */ +void +soup_message_set_priority (SoupMessage *msg, + SoupMessagePriority priority) +{ + g_return_if_fail (SOUP_IS_MESSAGE (msg)); + + g_object_set (msg, SOUP_MESSAGE_PRIORITY, priority, NULL); +} + +/** + * soup_message_get_priority: + * @msg: a #SoupMessage + * + * Retrieves the #SoupMessagePriority. If not set this value defaults + * to #SOUP_MESSAGE_PRIORITY_NORMAL. + * + * Return value: the priority of the message. + * + * Since: 2.44 + */ +SoupMessagePriority +soup_message_get_priority (SoupMessage *msg) +{ + g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_MESSAGE_PRIORITY_NORMAL); + + return SOUP_MESSAGE_GET_PRIVATE (msg)->priority; +} diff --git a/libsoup/soup-message.h b/libsoup/soup-message.h index d3c7e3c5..b02d293b 100644 --- a/libsoup/soup-message.h +++ b/libsoup/soup-message.h @@ -60,20 +60,23 @@ typedef struct { GType soup_message_get_type (void); -#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_SERVER_SIDE "server-side" -#define SOUP_MESSAGE_STATUS_CODE "status-code" -#define SOUP_MESSAGE_REASON_PHRASE "reason-phrase" -#define SOUP_MESSAGE_FIRST_PARTY "first-party" -#define SOUP_MESSAGE_REQUEST_BODY "request-body" -#define SOUP_MESSAGE_REQUEST_HEADERS "request-headers" -#define SOUP_MESSAGE_RESPONSE_BODY "response-body" -#define SOUP_MESSAGE_RESPONSE_HEADERS "response-headers" -#define SOUP_MESSAGE_TLS_CERTIFICATE "tls-certificate" -#define SOUP_MESSAGE_TLS_ERRORS "tls-errors" +#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_SERVER_SIDE "server-side" +#define SOUP_MESSAGE_STATUS_CODE "status-code" +#define SOUP_MESSAGE_REASON_PHRASE "reason-phrase" +#define SOUP_MESSAGE_FIRST_PARTY "first-party" +#define SOUP_MESSAGE_REQUEST_BODY "request-body" +#define SOUP_MESSAGE_REQUEST_BODY_DATA "request-body-data" +#define SOUP_MESSAGE_REQUEST_HEADERS "request-headers" +#define SOUP_MESSAGE_RESPONSE_BODY "response-body" +#define SOUP_MESSAGE_RESPONSE_BODY_DATA "response-body-data" +#define SOUP_MESSAGE_RESPONSE_HEADERS "response-headers" +#define SOUP_MESSAGE_TLS_CERTIFICATE "tls-certificate" +#define SOUP_MESSAGE_TLS_ERRORS "tls-errors" +#define SOUP_MESSAGE_PRIORITY "priority" SoupMessage *soup_message_new (const char *method, const char *uri_string); @@ -105,21 +108,25 @@ gboolean soup_message_is_keepalive (SoupMessage *msg); SoupURI *soup_message_get_uri (SoupMessage *msg); void soup_message_set_uri (SoupMessage *msg, SoupURI *uri); +SOUP_AVAILABLE_IN_2_26 SoupAddress *soup_message_get_address (SoupMessage *msg); +SOUP_AVAILABLE_IN_2_30 SoupURI *soup_message_get_first_party (SoupMessage *msg); +SOUP_AVAILABLE_IN_2_30 void soup_message_set_first_party (SoupMessage *msg, SoupURI *first_party); typedef enum { SOUP_MESSAGE_NO_REDIRECT = (1 << 1), SOUP_MESSAGE_CAN_REBUILD = (1 << 2), -#ifndef LIBSOUP_DISABLE_DEPRECATED +#ifndef SOUP_DISABLE_DEPRECATED SOUP_MESSAGE_OVERWRITE_CHUNKS = (1 << 3), #endif SOUP_MESSAGE_CONTENT_DECODED = (1 << 4), SOUP_MESSAGE_CERTIFICATE_TRUSTED = (1 << 5), - SOUP_MESSAGE_NEW_CONNECTION = (1 << 6) + SOUP_MESSAGE_NEW_CONNECTION = (1 << 6), + SOUP_MESSAGE_IDEMPOTENT = (1 << 7) } SoupMessageFlags; void soup_message_set_flags (SoupMessage *msg, @@ -127,6 +134,7 @@ void soup_message_set_flags (SoupMessage *msg, SoupMessageFlags soup_message_get_flags (SoupMessage *msg); +SOUP_AVAILABLE_IN_2_34 gboolean soup_message_get_https_status (SoupMessage *msg, GTlsCertificate **certificate, GTlsCertificateFlags *errors); @@ -156,23 +164,48 @@ void soup_message_set_status_full (SoupMessage *msg, guint status_code, const char *reason_phrase); +SOUP_AVAILABLE_IN_2_38 void soup_message_set_redirect (SoupMessage *msg, guint status_code, const char *redirect_uri); /* I/O */ +#ifndef SOUP_DISABLE_DEPRECATED typedef SoupBuffer * (*SoupChunkAllocator) (SoupMessage *msg, gsize max_len, gpointer user_data); +SOUP_DEPRECATED_IN_2_42_FOR(SoupRequest) void soup_message_set_chunk_allocator (SoupMessage *msg, SoupChunkAllocator allocator, gpointer user_data, GDestroyNotify destroy_notify); +#endif +SOUP_AVAILABLE_IN_2_28 void soup_message_disable_feature (SoupMessage *msg, GType feature_type); +SOUP_AVAILABLE_IN_2_42 +SoupRequest *soup_message_get_soup_request (SoupMessage *msg); + + +typedef enum { + SOUP_MESSAGE_PRIORITY_VERY_LOW = 0, + SOUP_MESSAGE_PRIORITY_LOW, + SOUP_MESSAGE_PRIORITY_NORMAL, + SOUP_MESSAGE_PRIORITY_HIGH, + SOUP_MESSAGE_PRIORITY_VERY_HIGH +} SoupMessagePriority; + +SOUP_AVAILABLE_IN_2_44 +void soup_message_set_priority (SoupMessage *msg, + SoupMessagePriority priority); + + +SOUP_AVAILABLE_IN_2_44 +SoupMessagePriority soup_message_get_priority (SoupMessage *msg); + void soup_message_wrote_informational (SoupMessage *msg); void soup_message_wrote_headers (SoupMessage *msg); void soup_message_wrote_chunk (SoupMessage *msg); diff --git a/libsoup/soup-misc-private.h b/libsoup/soup-misc-private.h index 8276b8a5..50a3b988 100644 --- a/libsoup/soup-misc-private.h +++ b/libsoup/soup-misc-private.h @@ -4,26 +4,48 @@ * Copyright 2011 Red Hat, Inc. */ -#ifndef SOUP_URI_PRIVATE_H -#define SOUP_URI_PRIVATE_H 1 +#ifndef SOUP_MISC_PRIVATE_H +#define SOUP_MISC_PRIVATE_H 1 #include "soup-socket.h" +#include "soup-message-headers.h" -char *uri_decoded_copy (const char *str, int length); +char *soup_uri_decoded_copy (const char *str, int length, int *decoded_length); +char *soup_uri_to_string_internal (SoupURI *uri, gboolean just_path_and_query, + gboolean force_port); +gboolean soup_uri_is_http (SoupURI *uri, char **aliases); +gboolean soup_uri_is_https (SoupURI *uri, char **aliases); -guint soup_socket_handshake_sync (SoupSocket *sock, - GCancellable *cancellable); -void soup_socket_handshake_async (SoupSocket *sock, - GCancellable *cancellable, - SoupSocketCallback callback, - gpointer user_data); +gboolean soup_socket_connect_sync_internal (SoupSocket *sock, + GCancellable *cancellable, + GError **error); +void soup_socket_connect_async_internal (SoupSocket *sock, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean soup_socket_connect_finish_internal (SoupSocket *sock, + GAsyncResult *result, + GError **error); + +gboolean soup_socket_handshake_sync (SoupSocket *sock, + const char *host, + GCancellable *cancellable, + GError **error); +void soup_socket_handshake_async (SoupSocket *sock, + const char *host, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +gboolean soup_socket_handshake_finish (SoupSocket *sock, + GAsyncResult *result, + GError **error); GSocket *soup_socket_get_gsocket (SoupSocket *sock); GIOStream *soup_socket_get_connection (SoupSocket *sock); GIOStream *soup_socket_get_iostream (SoupSocket *sock); #define SOUP_SOCKET_CLEAN_DISPOSE "clean-dispose" -#define SOUP_SOCKET_USE_PROXY "use-proxy" +#define SOUP_SOCKET_PROXY_RESOLVER "proxy-resolver" SoupURI *soup_socket_get_http_proxy_uri (SoupSocket *sock); /* At some point it might be possible to mark additional methods @@ -41,4 +63,14 @@ SoupURI *soup_socket_get_http_proxy_uri (SoupSocket *sock); method == SOUP_METHOD_PUT || \ method == SOUP_METHOD_DELETE) -#endif /* SOUP_URI_PRIVATE_H */ +GSource *soup_add_completion_reffed (GMainContext *async_context, + GSourceFunc function, + gpointer data); + +guint soup_message_headers_get_ranges_internal (SoupMessageHeaders *hdrs, + goffset total_length, + gboolean check_satisfiable, + SoupRange **ranges, + int *length); + +#endif /* SOUP_MISC_PRIVATE_H */ diff --git a/libsoup/soup-misc.c b/libsoup/soup-misc.c index 83540457..325b5872 100644 --- a/libsoup/soup-misc.c +++ b/libsoup/soup-misc.c @@ -8,6 +8,7 @@ #include <string.h> #include "soup-misc.h" +#include "soup-misc-private.h" /** * SECTION:soup-misc @@ -112,6 +113,19 @@ soup_add_idle (GMainContext *async_context, return source; } +GSource * +soup_add_completion_reffed (GMainContext *async_context, + GSourceFunc function, + gpointer data) +{ + GSource *source = g_idle_source_new (); + + g_source_set_priority (source, G_PRIORITY_DEFAULT); + g_source_set_callback (source, function, data, NULL); + g_source_attach (source, async_context); + return source; +} + /** * soup_add_completion: (skip) * @async_context: (allow-none): the #GMainContext to dispatch the I/O @@ -132,10 +146,9 @@ GSource * soup_add_completion (GMainContext *async_context, GSourceFunc function, gpointer data) { - GSource *source = g_idle_source_new (); - g_source_set_priority (source, G_PRIORITY_DEFAULT); - g_source_set_callback (source, function, data, NULL); - g_source_attach (source, async_context); + GSource *source; + + source = soup_add_completion_reffed (async_context, function, data); g_source_unref (source); return source; } diff --git a/libsoup/soup-misc.h b/libsoup/soup-misc.h index 0807b5f7..534b59d6 100644 --- a/libsoup/soup-misc.h +++ b/libsoup/soup-misc.h @@ -6,7 +6,7 @@ #ifndef SOUP_MISC_H #define SOUP_MISC_H 1 -#include <glib-object.h> +#include <libsoup/soup-types.h> G_BEGIN_DECLS @@ -19,6 +19,7 @@ GSource *soup_add_io_watch (GMainContext *async_context, GSource *soup_add_idle (GMainContext *async_context, GSourceFunc function, gpointer data); +SOUP_AVAILABLE_IN_2_24 GSource *soup_add_completion (GMainContext *async_context, GSourceFunc function, gpointer data); diff --git a/libsoup/soup-multipart-input-stream.c b/libsoup/soup-multipart-input-stream.c new file mode 100644 index 00000000..dee4e59f --- /dev/null +++ b/libsoup/soup-multipart-input-stream.c @@ -0,0 +1,592 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-multipart-input-stream.c + * + * Copyright (C) 2012 Collabora Ltd. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "soup-body-input-stream.h" +#include "soup-filter-input-stream.h" +#include "soup-enum-types.h" +#include "soup-message.h" +#include "soup-message-private.h" +#include "soup-multipart-input-stream.h" + +#define RESPONSE_BLOCK_SIZE 8192 + +/** + * SECTION:soup-multipart-input-stream + * @short_description: Multipart input handling stream + * + * This adds support for the multipart responses. For handling the + * multiple parts the user needs to wrap the #GInputStream obtained by + * sending the request with a #SoupMultipartInputStream and use + * soup_multipart_input_stream_next_part() before reading. Responses + * which are not wrapped will be treated like non-multipart responses. + * + * Note that although #SoupMultipartInputStream is a #GInputStream, + * you should not read directly from it, and the results are undefined + * if you do. + * + * Since: 2.40 + **/ + +static void soup_multipart_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, gpointer interface_data); + +G_DEFINE_TYPE_WITH_CODE (SoupMultipartInputStream, soup_multipart_input_stream, G_TYPE_FILTER_INPUT_STREAM, + G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM, + soup_multipart_input_stream_pollable_init)) + +enum { + PROP_0, + + PROP_MESSAGE, +}; + +struct _SoupMultipartInputStreamPrivate { + SoupMessage *msg; + + gboolean done_with_part; + + GByteArray *meta_buf; + SoupMessageHeaders *current_headers; + + SoupFilterInputStream *base_stream; + + char *boundary; + gsize boundary_size; + + goffset remaining_bytes; +}; + +static void +soup_multipart_input_stream_dispose (GObject *object) +{ + SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object); + + g_clear_object (&multipart->priv->msg); + g_clear_object (&multipart->priv->base_stream); + + G_OBJECT_CLASS (soup_multipart_input_stream_parent_class)->dispose (object); +} + +static void +soup_multipart_input_stream_finalize (GObject *object) +{ + SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object); + + g_free (multipart->priv->boundary); + + if (multipart->priv->meta_buf) + g_clear_pointer (&multipart->priv->meta_buf, g_byte_array_unref); + + G_OBJECT_CLASS (soup_multipart_input_stream_parent_class)->finalize (object); +} + +static void +soup_multipart_input_stream_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object); + + switch (prop_id) { + case PROP_MESSAGE: + multipart->priv->msg = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +soup_multipart_input_stream_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object); + + switch (prop_id) { + case PROP_MESSAGE: + g_value_set_object (value, multipart->priv->msg); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gssize +soup_multipart_input_stream_read_real (GInputStream *stream, + void *buffer, + gsize count, + gboolean blocking, + GCancellable *cancellable, + GError **error) +{ + SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (stream); + SoupMultipartInputStreamPrivate *priv = multipart->priv; + gboolean got_boundary = FALSE; + gssize nread = 0; + guint8 *buf; + + g_return_val_if_fail (priv->boundary != NULL, -1); + + /* If we have received a Content-Length, and are not yet close to the end of + * the part, let's not look for the boundary for now. This optimization is + * necessary for keeping CPU usage civil. + */ + if (priv->remaining_bytes > priv->boundary_size) { + goffset bytes_to_read = MIN (count, priv->remaining_bytes - priv->boundary_size); + + nread = g_pollable_stream_read (G_INPUT_STREAM (priv->base_stream), + buffer, bytes_to_read, blocking, + cancellable, error); + + if (nread > 0) + priv->remaining_bytes -= nread; + + return nread; + } + + if (priv->done_with_part) + return 0; + + nread = soup_filter_input_stream_read_until (priv->base_stream, buffer, count, + priv->boundary, priv->boundary_size, + blocking, FALSE, &got_boundary, + cancellable, error); + + if (nread <= 0) + return nread; + + if (!got_boundary) + return nread; + + priv->done_with_part = TRUE; + + /* Ignore the newline that preceded the boundary. */ + if (nread == 1) { + buf = ((guint8*)buffer); + if (!memcmp (buf, "\n", 1)) + nread -= 1; + } else { + buf = ((guint8*)buffer) + nread - 2; + if (!memcmp (buf, "\r\n", 2)) + nread -= 2; + else if (!memcmp (buf, "\n", 1)) + nread -= 1; + } + + return nread; +} + +static gssize +soup_multipart_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + return soup_multipart_input_stream_read_real (stream, buffer, count, + TRUE, cancellable, error); +} + +static void +soup_multipart_input_stream_init (SoupMultipartInputStream *multipart) +{ + SoupMultipartInputStreamPrivate *priv; + priv = multipart->priv = G_TYPE_INSTANCE_GET_PRIVATE (multipart, + SOUP_TYPE_MULTIPART_INPUT_STREAM, + SoupMultipartInputStreamPrivate); + + priv->meta_buf = g_byte_array_sized_new (RESPONSE_BLOCK_SIZE); + priv->done_with_part = FALSE; +} + +static void +soup_multipart_input_stream_constructed (GObject *object) +{ + SoupMultipartInputStream *multipart; + SoupMultipartInputStreamPrivate *priv; + GInputStream *base_stream; + const char* boundary; + GHashTable *params = NULL; + + multipart = SOUP_MULTIPART_INPUT_STREAM (object); + priv = multipart->priv; + + base_stream = G_FILTER_INPUT_STREAM (multipart)->base_stream; + priv->base_stream = SOUP_FILTER_INPUT_STREAM (soup_filter_input_stream_new (base_stream)); + + soup_message_headers_get_content_type (priv->msg->response_headers, + ¶ms); + + boundary = g_hash_table_lookup (params, "boundary"); + if (boundary) { + if (g_str_has_prefix (boundary, "--")) + priv->boundary = g_strdup (boundary); + else + priv->boundary = g_strdup_printf ("--%s", boundary); + + priv->boundary_size = strlen (priv->boundary); + } else { + g_warning ("No boundary found in message tagged as multipart."); + } + + g_hash_table_destroy (params); + + if (G_OBJECT_CLASS (soup_multipart_input_stream_parent_class)->constructed) + G_OBJECT_CLASS (soup_multipart_input_stream_parent_class)->constructed (object); +} + +static gboolean +soup_multipart_input_stream_is_readable (GPollableInputStream *stream) +{ + SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (stream); + SoupMultipartInputStreamPrivate *priv = multipart->priv; + + return g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (priv->base_stream)); +} + +static gssize +soup_multipart_input_stream_read_nonblocking (GPollableInputStream *stream, + void *buffer, + gsize count, + GError **error) +{ + SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (stream); + + return soup_multipart_input_stream_read_real (G_INPUT_STREAM (multipart), + buffer, count, + FALSE, NULL, error); +} + +static GSource * +soup_multipart_input_stream_create_source (GPollableInputStream *stream, + GCancellable *cancellable) +{ + SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (stream); + SoupMultipartInputStreamPrivate *priv = multipart->priv; + GSource *base_source, *pollable_source; + + base_source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (priv->base_stream), cancellable); + + pollable_source = g_pollable_source_new_full (stream, base_source, cancellable); + g_source_unref (base_source); + + return pollable_source; +} + +static void +soup_multipart_input_stream_class_init (SoupMultipartInputStreamClass *multipart_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (multipart_class); + GInputStreamClass *input_stream_class = + G_INPUT_STREAM_CLASS (multipart_class); + + g_type_class_add_private (multipart_class, sizeof (SoupMultipartInputStreamPrivate)); + + object_class->dispose = soup_multipart_input_stream_dispose; + object_class->finalize = soup_multipart_input_stream_finalize; + object_class->constructed = soup_multipart_input_stream_constructed; + object_class->set_property = soup_multipart_input_stream_set_property; + object_class->get_property = soup_multipart_input_stream_get_property; + + input_stream_class->read_fn = soup_multipart_input_stream_read; + + g_object_class_install_property ( + object_class, PROP_MESSAGE, + g_param_spec_object ("message", + "Message", + "The SoupMessage", + SOUP_TYPE_MESSAGE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + +} + +static void +soup_multipart_input_stream_pollable_init (GPollableInputStreamInterface *pollable_interface, + gpointer interface_data) +{ + pollable_interface->is_readable = soup_multipart_input_stream_is_readable; + pollable_interface->read_nonblocking = soup_multipart_input_stream_read_nonblocking; + pollable_interface->create_source = soup_multipart_input_stream_create_source; +} + +static void +soup_multipart_input_stream_parse_headers (SoupMultipartInputStream *multipart) +{ + SoupMultipartInputStreamPrivate *priv = multipart->priv; + gboolean success; + + priv->current_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART); + + /* The part lacks headers, but is there. */ + if (!priv->meta_buf->len) + return; + + success = soup_headers_parse ((const char*) priv->meta_buf->data, + (int) priv->meta_buf->len, + priv->current_headers); + + if (success) + priv->remaining_bytes = soup_message_headers_get_content_length (priv->current_headers); + else + g_clear_pointer (&priv->current_headers, soup_message_headers_free); + + g_byte_array_remove_range (priv->meta_buf, 0, priv->meta_buf->len); +} + +static gboolean +soup_multipart_input_stream_read_headers (SoupMultipartInputStream *multipart, + GCancellable *cancellable, + GError **error) +{ + SoupMultipartInputStreamPrivate *priv = multipart->priv; + guchar read_buf[RESPONSE_BLOCK_SIZE]; + guchar *buf; + gboolean got_boundary = FALSE; + gboolean got_lf = FALSE; + gssize nread = 0; + + g_return_val_if_fail (priv->boundary != NULL, TRUE); + + g_clear_pointer (&priv->current_headers, soup_message_headers_free); + + while (1) { + nread = soup_filter_input_stream_read_line (priv->base_stream, read_buf, sizeof (read_buf), + /* blocking */ TRUE, &got_lf, cancellable, error); + + if (nread <= 0) + break; + + g_byte_array_append (priv->meta_buf, read_buf, nread); + + /* Need to do this boundary check before checking for the line feed, since we + * may get the multipart end indicator without getting a new line. + */ + if (!got_boundary && + !strncmp ((char *)priv->meta_buf->data, + priv->boundary, + priv->boundary_size)) { + got_boundary = TRUE; + + /* Now check for possible multipart termination. */ + buf = &read_buf[nread - 4]; + if ((nread >= 4 && !memcmp (buf, "--\r\n", 4)) || + (nread >= 3 && !memcmp (buf + 1, "--\n", 3)) || + (nread >= 3 && !memcmp (buf + 2, "--", 2))) { + g_byte_array_set_size (priv->meta_buf, 0); + return FALSE; + } + } + + g_return_val_if_fail (got_lf, FALSE); + + /* Discard pre-boundary lines. */ + if (!got_boundary) { + g_byte_array_set_size (priv->meta_buf, 0); + continue; + } + + if (nread == 1 && + priv->meta_buf->len >= 2 && + !strncmp ((char *)priv->meta_buf->data + + priv->meta_buf->len - 2, + "\n\n", 2)) + break; + else if (nread == 2 && + priv->meta_buf->len >= 3 && + !strncmp ((char *)priv->meta_buf->data + + priv->meta_buf->len - 3, + "\n\r\n", 3)) + break; + } + + return TRUE; +} + +/* Public APIs */ + +/** + * soup_multipart_input_stream_new: + * @msg: the #SoupMessage the response is related to. + * @base_stream: the #GInputStream returned by sending the request. + * + * Creates a new #SoupMultipartInputStream that wraps the + * #GInputStream obtained by sending the #SoupRequest. Reads should + * not be done directly through this object, use the input streams + * returned by soup_multipart_input_stream_next_part() or its async + * counterpart instead. + * + * Returns: a new #SoupMultipartInputStream + * + * Since: 2.40 + **/ +SoupMultipartInputStream * +soup_multipart_input_stream_new (SoupMessage *msg, + GInputStream *base_stream) +{ + return g_object_new (SOUP_TYPE_MULTIPART_INPUT_STREAM, + "message", msg, + "base-stream", base_stream, + NULL); +} + +/** + * soup_multipart_input_stream_next_part: + * @multipart: the #SoupMultipartInputStream + * @cancellable: a #GCancellable + * @error: a #GError + * + * Obtains an input stream for the next part. When dealing with a + * multipart response the input stream needs to be wrapped in a + * #SoupMultipartInputStream and this function or its async + * counterpart need to be called to obtain the first part for + * reading. + * + * After calling this function, + * soup_multipart_input_stream_get_headers() can be used to obtain the + * headers for the first part. A read of 0 bytes indicates the end of + * the part; a new call to this function should be done at that point, + * to obtain the next part. + * + * Return value: (transfer full): a new #GInputStream, or %NULL if + * there are no more parts + * + * Since: 2.40 + */ +GInputStream * +soup_multipart_input_stream_next_part (SoupMultipartInputStream *multipart, + GCancellable *cancellable, + GError **error) +{ + if (!soup_multipart_input_stream_read_headers (multipart, cancellable, error)) + return NULL; + + soup_multipart_input_stream_parse_headers (multipart); + + multipart->priv->done_with_part = FALSE; + + return G_INPUT_STREAM (g_object_new (SOUP_TYPE_BODY_INPUT_STREAM, + "base-stream", G_INPUT_STREAM (multipart), + "close-base-stream", FALSE, + "encoding", SOUP_ENCODING_EOF, + NULL)); + +} + +static void +soup_multipart_input_stream_next_part_thread (GTask *task, + gpointer object, + gpointer task_data, + GCancellable *cancellable) +{ + SoupMultipartInputStream *multipart = SOUP_MULTIPART_INPUT_STREAM (object); + GError *error = NULL; + GInputStream *new_stream; + + new_stream = soup_multipart_input_stream_next_part (multipart, cancellable, &error); + + g_input_stream_clear_pending (G_INPUT_STREAM (multipart)); + + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, new_stream, g_object_unref); +} + +/** + * soup_multipart_input_stream_next_part_async: + * @multipart: the #SoupMultipartInputStream. + * @io_priority: the I/O priority for the request. + * @cancellable: a #GCancellable. + * @callback: callback to call when request is satisfied. + * @data: data for @callback + * + * Obtains a #GInputStream for the next request. See + * soup_multipart_input_stream_next_part() for details on the + * workflow. + * + * Since: 2.40 + */ +void +soup_multipart_input_stream_next_part_async (SoupMultipartInputStream *multipart, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + GInputStream *stream = G_INPUT_STREAM (multipart); + GTask *task; + GError *error = NULL; + + g_return_if_fail (SOUP_IS_MULTIPART_INPUT_STREAM (multipart)); + + task = g_task_new (multipart, cancellable, callback, data); + g_task_set_priority (task, io_priority); + + if (!g_input_stream_set_pending (stream, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + g_task_run_in_thread (task, soup_multipart_input_stream_next_part_thread); + g_object_unref (task); +} + +/** + * soup_multipart_input_stream_next_part_finish: + * @multipart: a #SoupMultipartInputStream. + * @result: a #GAsyncResult. + * @error: a #GError location to store any error, or NULL to ignore. + * + * Finishes an asynchronous request for the next part. + * + * Return value: (transfer full): a newly created #GInputStream for + * reading the next part or %NULL if there are no more parts. + * + * Since: 2.40 + */ +GInputStream * +soup_multipart_input_stream_next_part_finish (SoupMultipartInputStream *multipart, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, multipart), FALSE); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +/** + * soup_multipart_input_stream_get_headers: + * @multipart: a #SoupMultipartInputStream. + * + * Obtains the headers for the part currently being processed. Note + * that the #SoupMessageHeaders that are returned are owned by the + * #SoupMultipartInputStream and will be replaced when a call is made + * to soup_multipart_input_stream_next_part() or its async + * counterpart, so if keeping the headers is required, a copy must be + * made. + * + * Note that if a part had no headers at all an empty #SoupMessageHeaders + * will be returned. + * + * Return value: (transfer none): a #SoupMessageHeaders containing the headers + * for the part currently being processed or %NULL if the headers failed to + * parse. + * + * Since: 2.40 + */ +SoupMessageHeaders * +soup_multipart_input_stream_get_headers (SoupMultipartInputStream *multipart) +{ + return multipart->priv->current_headers; +} diff --git a/libsoup/soup-multipart-input-stream.h b/libsoup/soup-multipart-input-stream.h new file mode 100644 index 00000000..8b735063 --- /dev/null +++ b/libsoup/soup-multipart-input-stream.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012 Collabora Ltd. + */ + +#ifndef SOUP_MULTIPART_INPUT_STREAM_H +#define SOUP_MULTIPART_INPUT_STREAM_H 1 + +#include <libsoup/soup-types.h> +#include <libsoup/soup-message-headers.h> + +G_BEGIN_DECLS + +#define SOUP_TYPE_MULTIPART_INPUT_STREAM (soup_multipart_input_stream_get_type ()) +#define SOUP_MULTIPART_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), SOUP_TYPE_MULTIPART_INPUT_STREAM, SoupMultipartInputStream)) +#define SOUP_MULTIPART_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), SOUP_TYPE_MULTIPART_INPUT_STREAM, SoupMultipartInputStreamClass)) +#define SOUP_IS_MULTIPART_INPUT_STREAM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), SOUP_TYPE_MULTIPART_INPUT_STREAM)) +#define SOUP_IS_MULTIPART_INPUT_STREAM_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), SOUP_TYPE_MULTIPART_INPUT_STREAM)) +#define SOUP_MULTIPART_INPUT_STREAM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), SOUP_TYPE_MULTIPART_INPUT_STREAM, SoupMultipartInputStreamClass)) + +typedef struct _SoupMultipartInputStream SoupMultipartInputStream; +typedef struct _SoupMultipartInputStreamPrivate SoupMultipartInputStreamPrivate; +typedef struct _SoupMultipartInputStreamClass SoupMultipartInputStreamClass; + +struct _SoupMultipartInputStream { + GFilterInputStream parent_instance; + + /*< private >*/ + SoupMultipartInputStreamPrivate *priv; +}; + +struct _SoupMultipartInputStreamClass { + GFilterInputStreamClass parent_class; +}; + +SOUP_AVAILABLE_IN_2_40 +GType soup_multipart_input_stream_get_type (void) G_GNUC_CONST; + +SOUP_AVAILABLE_IN_2_40 +SoupMultipartInputStream *soup_multipart_input_stream_new (SoupMessage *msg, + GInputStream *base_stream); + +SOUP_AVAILABLE_IN_2_40 +GInputStream *soup_multipart_input_stream_next_part (SoupMultipartInputStream *multipart, + GCancellable *cancellable, + GError **error); + +SOUP_AVAILABLE_IN_2_40 +void soup_multipart_input_stream_next_part_async (SoupMultipartInputStream *multipart, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); + +SOUP_AVAILABLE_IN_2_40 +GInputStream *soup_multipart_input_stream_next_part_finish (SoupMultipartInputStream *multipart, + GAsyncResult *result, + GError **error); + +SOUP_AVAILABLE_IN_2_40 +SoupMessageHeaders *soup_multipart_input_stream_get_headers (SoupMultipartInputStream *multipart); + + +G_END_DECLS + +#endif /* SOUP_MULTIPART_INPUT_STREAM_H */ diff --git a/libsoup/soup-multipart.h b/libsoup/soup-multipart.h index cc01eec6..ef2e36c7 100644 --- a/libsoup/soup-multipart.h +++ b/libsoup/soup-multipart.h @@ -17,33 +17,42 @@ typedef struct SoupMultipart SoupMultipart; GType soup_multipart_get_type (void); #define SOUP_TYPE_MULTIPART (soup_multipart_get_type ()) +SOUP_AVAILABLE_IN_2_26 SoupMultipart *soup_multipart_new (const char *mime_type); +SOUP_AVAILABLE_IN_2_26 SoupMultipart *soup_multipart_new_from_message (SoupMessageHeaders *headers, SoupMessageBody *body); +SOUP_AVAILABLE_IN_2_26 int soup_multipart_get_length (SoupMultipart *multipart); +SOUP_AVAILABLE_IN_2_26 gboolean soup_multipart_get_part (SoupMultipart *multipart, int part, SoupMessageHeaders **headers, SoupBuffer **body); +SOUP_AVAILABLE_IN_2_26 void soup_multipart_append_part (SoupMultipart *multipart, SoupMessageHeaders *headers, SoupBuffer *body); +SOUP_AVAILABLE_IN_2_26 void soup_multipart_append_form_string (SoupMultipart *multipart, const char *control_name, const char *data); +SOUP_AVAILABLE_IN_2_26 void soup_multipart_append_form_file (SoupMultipart *multipart, const char *control_name, const char *filename, const char *content_type, SoupBuffer *body); +SOUP_AVAILABLE_IN_2_26 void soup_multipart_to_message (SoupMultipart *multipart, SoupMessageHeaders *dest_headers, SoupMessageBody *dest_body); +SOUP_AVAILABLE_IN_2_26 void soup_multipart_free (SoupMultipart *multipart); G_END_DECLS diff --git a/libsoup/soup-password-manager-gnome.c b/libsoup/soup-password-manager-gnome.c index 475cb594..8101683d 100644 --- a/libsoup/soup-password-manager-gnome.c +++ b/libsoup/soup-password-manager-gnome.c @@ -5,216 +5,24 @@ * Copyright (C) 2008 Red Hat, Inc. */ +/* This is just a stub now; eventually it will go away completely. */ + #ifdef HAVE_CONFIG_H #include <config.h> #endif -#include <gnome-keyring.h> - -#define LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY - #include "soup-password-manager-gnome.h" #include "soup.h" -static void soup_password_manager_gnome_interface_init (SoupPasswordManagerInterface *password_manager_interface); - G_DEFINE_TYPE_EXTENDED (SoupPasswordManagerGNOME, soup_password_manager_gnome, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, NULL) - G_IMPLEMENT_INTERFACE (SOUP_TYPE_PASSWORD_MANAGER, soup_password_manager_gnome_interface_init)) + G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, NULL)) static void soup_password_manager_gnome_init (SoupPasswordManagerGNOME *manager_gnome) { } - -static void -save_password_callback (GnomeKeyringResult result, guint32 val, gpointer data) -{ -} - -static void -async_save_password (SoupAuth *auth, const char *username, - const char *password, gpointer user_data) -{ - SoupURI *uri = user_data; - - gnome_keyring_set_network_password ( - NULL, /* use default keyring */ - username, - soup_auth_get_realm (auth), - uri->host, - NULL, - uri->scheme, - soup_auth_get_scheme_name (auth), - uri->port, - password, - save_password_callback, NULL, NULL); -} - -static void -sync_save_password (SoupAuth *auth, const char *username, - const char *password, gpointer user_data) -{ - SoupURI *uri = user_data; - guint32 item_id; - - gnome_keyring_set_network_password_sync ( - NULL, /* use default keyring */ - username, - soup_auth_get_realm (auth), - uri->host, - NULL, - uri->scheme, - soup_auth_get_scheme_name (auth), - uri->port, - password, - &item_id); -} - -static void -update_auth_for_passwords (SoupAuth *auth, SoupMessage *msg, - GList *passwords, gboolean async) -{ - GnomeKeyringNetworkPasswordData *pdata; - SoupURI *uri; - - while (passwords) { - pdata = passwords->data; - soup_auth_has_saved_password (auth, pdata->user, - pdata->password); - passwords = passwords->next; - } - - uri = g_object_get_data (G_OBJECT (auth), - "SoupPasswordManagerGNOME-save_password-uri"); - if (uri) { - g_signal_handlers_disconnect_by_func (auth, async_save_password, uri); - g_signal_handlers_disconnect_by_func (auth, sync_save_password, uri); - } - - uri = soup_uri_copy (soup_message_get_uri (msg)); - g_signal_connect (auth, "save_password", - G_CALLBACK (async ? async_save_password : sync_save_password), - uri); - g_object_set_data_full (G_OBJECT (auth), - "SoupPasswordManagerGNOME-save_password-uri", - uri, (GDestroyNotify)soup_uri_free); -} - -typedef struct { - SoupPasswordManager *password_manager; - SoupMessage *msg; - SoupAuth *auth; - gboolean retrying; - - SoupPasswordManagerCallback callback; - gpointer user_data; - - gpointer request; -} SoupPasswordManagerGNOMEAuthData; - -static void -find_password_callback (GnomeKeyringResult result, GList *list, - gpointer user_data) -{ - SoupPasswordManagerGNOMEAuthData *auth_data = user_data; - - /* FIXME: check result? */ - - update_auth_for_passwords (auth_data->auth, auth_data->msg, list, TRUE); - auth_data->callback (auth_data->password_manager, - auth_data->msg, auth_data->auth, - auth_data->retrying, auth_data->user_data); - - /* gnome-keyring will call free_auth_data to clean up for us. */ -} - -static void -free_auth_data (gpointer data) -{ - SoupPasswordManagerGNOMEAuthData *auth_data = data; - - g_object_unref (auth_data->auth); - g_object_unref (auth_data->msg); - g_slice_free (SoupPasswordManagerGNOMEAuthData, auth_data); -} - -static void -soup_password_manager_gnome_get_passwords_async (SoupPasswordManager *password_manager, - SoupMessage *msg, - SoupAuth *auth, - gboolean retrying, - GMainContext *async_context, - GCancellable *cancellable, - SoupPasswordManagerCallback callback, - gpointer user_data) -{ - SoupPasswordManagerGNOMEAuthData *auth_data; - SoupURI *uri = soup_message_get_uri (msg); - - auth_data = g_slice_new (SoupPasswordManagerGNOMEAuthData); - auth_data->password_manager = password_manager; - auth_data->msg = g_object_ref (msg); - auth_data->auth = g_object_ref (auth); - auth_data->retrying = retrying; - - /* FIXME: async_context, cancellable */ - - auth_data->callback = callback; - auth_data->user_data = user_data; - - /* FIXME: should we be specifying protocol and port here, or - * leaving them NULL/0 and filtering results in the callback? - * We don't want to send https passwords to http, but the - * reverse might be OK (if that's how other clients tend to - * behave). - */ - auth_data->request = gnome_keyring_find_network_password ( - NULL, /* user -- accept any */ - soup_auth_get_realm (auth), /* domain */ - uri->host, /* server */ - NULL, /* object -- unused */ - uri->scheme, /* protocol */ - soup_auth_get_scheme_name (auth), /* authtype */ - uri->port, /* port */ - find_password_callback, auth_data, free_auth_data); -} - -static void -soup_password_manager_gnome_get_passwords_sync (SoupPasswordManager *password_manager, - SoupMessage *msg, - SoupAuth *auth, - GCancellable *cancellable) -{ - SoupURI *uri = soup_message_get_uri (msg); - GList *results = NULL; - - /* FIXME: cancellable */ - - gnome_keyring_find_network_password_sync ( - NULL, /* user -- accept any */ - soup_auth_get_realm (auth), /* domain */ - uri->host, /* server */ - NULL, /* object -- unused */ - uri->scheme, /* protocol */ - soup_auth_get_scheme_name (auth), /* authtype */ - uri->port, /* port */ - &results); - - update_auth_for_passwords (auth, msg, results, FALSE); -} - static void soup_password_manager_gnome_class_init (SoupPasswordManagerGNOMEClass *gnome_class) { } - -static void -soup_password_manager_gnome_interface_init (SoupPasswordManagerInterface *password_manager_interface) -{ - password_manager_interface->get_passwords_async = - soup_password_manager_gnome_get_passwords_async; - password_manager_interface->get_passwords_sync = - soup_password_manager_gnome_get_passwords_sync; -} diff --git a/libsoup/soup-password-manager.c b/libsoup/soup-password-manager.c index 3914179f..532ff7eb 100644 --- a/libsoup/soup-password-manager.c +++ b/libsoup/soup-password-manager.c @@ -9,8 +9,6 @@ #include <config.h> #endif -#define LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY - #include "soup-password-manager.h" #include "soup.h" @@ -25,27 +23,8 @@ soup_password_manager_default_init (SoupPasswordManagerInterface *iface) /** * soup_password_manager_get_passwords_async: - * @password_manager: the #SoupPasswordManager - * @msg: the #SoupMessage being authenticated - * @auth: the #SoupAuth being authenticated - * @retrying: whether or not this is a re-attempt to authenticate - * @async_context: (allow-none): the #GMainContext to invoke @callback in - * @cancellable: a #GCancellable, or %NULL - * @callback: callback to invoke after fetching passwords - * @user_data: data for @callback - * - * Asynchronously attempts to look up saved passwords for @auth/@msg - * and then calls @callback after updating @auth with the information. - * Also registers @auth with @password_manager so that if the caller - * calls soup_auth_save_password() on it, the password will be saved. - * - * #SoupPasswordManager does not actually use the @retrying flag itself; - * it just passes its value on to @callback. - * - * If @cancellable is cancelled, @callback will still be invoked. - * - * Since: 2.28 - **/ + * @callback: (scope async) + */ void soup_password_manager_get_passwords_async (SoupPasswordManager *password_manager, SoupMessage *msg, @@ -56,32 +35,14 @@ soup_password_manager_get_passwords_async (SoupPasswordManager *password_manage SoupPasswordManagerCallback callback, gpointer user_data) { - SOUP_PASSWORD_MANAGER_GET_CLASS (password_manager)-> - get_passwords_async (password_manager, msg, auth, retrying, - async_context, cancellable, - callback, user_data); + g_warn_if_reached (); } -/** - * soup_password_manager_get_passwords_sync: - * @password_manager: the #SoupPasswordManager - * @msg: the #SoupMessage being authenticated - * @auth: the #SoupAuth being authenticated - * @cancellable: a #GCancellable, or %NULL - * - * Synchronously attempts to look up saved passwords for @auth/@msg - * and updates @auth with the information. Also registers @auth with - * @password_manager so that if the caller calls - * soup_auth_save_password() on it, the password will be saved. - * - * Since: 2.28 - **/ void soup_password_manager_get_passwords_sync (SoupPasswordManager *password_manager, SoupMessage *msg, SoupAuth *auth, GCancellable *cancellable) { - SOUP_PASSWORD_MANAGER_GET_CLASS (password_manager)-> - get_passwords_sync (password_manager, msg, auth, cancellable); + g_warn_if_reached (); } diff --git a/libsoup/soup-password-manager.h b/libsoup/soup-password-manager.h index 775f84c5..aa5faf55 100644 --- a/libsoup/soup-password-manager.h +++ b/libsoup/soup-password-manager.h @@ -6,8 +6,6 @@ #ifndef SOUP_PASSWORD_MANAGER_H #define SOUP_PASSWORD_MANAGER_H 1 -#ifdef LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY - #include <libsoup/soup-types.h> #define SOUP_TYPE_PASSWORD_MANAGER (soup_password_manager_get_type ()) @@ -19,8 +17,8 @@ typedef struct _SoupPasswordManager SoupPasswordManager; -typedef void (*SoupPasswordManagerCallback) (SoupPasswordManager *, - SoupMessage *, SoupAuth *, +typedef void (*SoupPasswordManagerCallback) (SoupPasswordManager *password_manager, + SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data); @@ -28,17 +26,25 @@ typedef struct { GTypeInterface base; /* virtual methods */ - void (*get_passwords_async) (SoupPasswordManager *, SoupMessage *, - SoupAuth *, gboolean, - GMainContext *, GCancellable *, - SoupPasswordManagerCallback, gpointer); - void (*get_passwords_sync) (SoupPasswordManager *, SoupMessage *, - SoupAuth *, GCancellable *); + void (*get_passwords_async) (SoupPasswordManager *password_manager, + SoupMessage *msg, SoupAuth *auth, + gboolean retrying, + GMainContext *async_context, + GCancellable *cancellable, + SoupPasswordManagerCallback callback, + gpointer user_data); + void (*get_passwords_sync) (SoupPasswordManager *password_manager, + SoupMessage *msg, SoupAuth *auth, + GCancellable *cancellable); } SoupPasswordManagerInterface; +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_28 GType soup_password_manager_get_type (void); +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_28 void soup_password_manager_get_passwords_async (SoupPasswordManager *password_manager, SoupMessage *msg, SoupAuth *auth, @@ -48,11 +54,11 @@ void soup_password_manager_get_passwords_async (SoupPasswordManager *password_ SoupPasswordManagerCallback callback, gpointer user_data); +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_28 void soup_password_manager_get_passwords_sync (SoupPasswordManager *password_manager, SoupMessage *msg, SoupAuth *auth, GCancellable *cancellable); -#endif /* LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY */ - #endif /* SOUP_PASSWORD_MANAGER_H */ diff --git a/libsoup/soup-proxy-resolver-default.c b/libsoup/soup-proxy-resolver-default.c index 76e9ff6d..7fd09349 100644 --- a/libsoup/soup-proxy-resolver-default.c +++ b/libsoup/soup-proxy-resolver-default.c @@ -9,16 +9,29 @@ #include <config.h> #endif +#undef SOUP_VERSION_MIN_REQUIRED +#define SOUP_VERSION_MIN_REQUIRED SOUP_VERSION_2_42 + #include "soup-proxy-resolver-default.h" #include "soup.h" /** - * SoupProxyResolverDefault: + * SECTION:soup-proxy-resolver-default + * @short_description: System proxy configuration integration + * + * #SoupProxyResolverDefault is a <type>SoupProxyURIResolver</type> + * implementation that uses the default gio #GProxyResolver to resolve + * proxies. * - * A #SoupProxyURIResolver implementation that uses the default gio - * #GProxyResolver to resolve proxies. + * In libsoup 2.44 and later, you can set the session's + * #SoupSession:proxy-resolver property to the resolver returned by + * g_proxy_resolver_get_default() to get the same effect. Note that + * for "plain" #SoupSessions (ie, not #SoupSessionAsync or + * #SoupSessionSync), this is done for you automatically. * * Since: 2.34 + * + * Deprecated: Use #SoupSession:proxy-resolver */ static void soup_proxy_resolver_default_interface_init (SoupProxyURIResolverInterface *proxy_resolver_interface); diff --git a/libsoup/soup-proxy-resolver-gnome.c b/libsoup/soup-proxy-resolver-gnome.c index bc423370..16a34680 100644 --- a/libsoup/soup-proxy-resolver-gnome.c +++ b/libsoup/soup-proxy-resolver-gnome.c @@ -9,6 +9,9 @@ #include <config.h> #endif +#undef SOUP_VERSION_MIN_REQUIRED +#define SOUP_VERSION_MIN_REQUIRED SOUP_VERSION_2_40 + #include <string.h> #include "soup-proxy-resolver-gnome.h" diff --git a/libsoup/soup-proxy-resolver-static.c b/libsoup/soup-proxy-resolver-static.c deleted file mode 100644 index 94327671..00000000 --- a/libsoup/soup-proxy-resolver-static.c +++ /dev/null @@ -1,177 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * soup-proxy-resolver-static.c: Static proxy "resolution" - * - * Copyright (C) 2008 Red Hat, Inc. - */ - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include "soup-proxy-resolver-static.h" -#include "soup.h" - -typedef struct { - SoupURI *proxy_uri; - -} SoupProxyResolverStaticPrivate; -#define SOUP_PROXY_RESOLVER_STATIC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_PROXY_RESOLVER_STATIC, SoupProxyResolverStaticPrivate)) - -static void soup_proxy_resolver_static_interface_init (SoupProxyURIResolverInterface *proxy_resolver_interface); - -G_DEFINE_TYPE_EXTENDED (SoupProxyResolverStatic, soup_proxy_resolver_static, G_TYPE_OBJECT, 0, - G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE, NULL) - G_IMPLEMENT_INTERFACE (SOUP_TYPE_PROXY_URI_RESOLVER, soup_proxy_resolver_static_interface_init)) - -enum { - PROP_0, - - PROP_PROXY_URI, - - LAST_PROP -}; - -static void -soup_proxy_resolver_static_init (SoupProxyResolverStatic *resolver_static) -{ -} - -static void -soup_proxy_resolver_static_finalize (GObject *object) -{ - SoupProxyResolverStaticPrivate *priv = - SOUP_PROXY_RESOLVER_STATIC_GET_PRIVATE (object); - - g_clear_pointer (&priv->proxy_uri, soup_uri_free); - - G_OBJECT_CLASS (soup_proxy_resolver_static_parent_class)->finalize (object); -} - -static void -soup_proxy_resolver_static_set_property (GObject *object, guint prop_id, - const GValue *value, GParamSpec *pspec) -{ - SoupProxyResolverStaticPrivate *priv = - SOUP_PROXY_RESOLVER_STATIC_GET_PRIVATE (object); - SoupURI *uri; - - switch (prop_id) { - case PROP_PROXY_URI: - uri = g_value_get_boxed (value); - if (priv->proxy_uri) - soup_uri_free (priv->proxy_uri); - - priv->proxy_uri = uri ? soup_uri_copy (uri) : NULL; - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -soup_proxy_resolver_static_get_property (GObject *object, guint prop_id, - GValue *value, GParamSpec *pspec) -{ - SoupProxyResolverStaticPrivate *priv = - SOUP_PROXY_RESOLVER_STATIC_GET_PRIVATE (object); - - switch (prop_id) { - case PROP_PROXY_URI: - g_value_set_boxed (value, priv->proxy_uri); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -typedef struct { - SoupProxyURIResolver *proxy_resolver; - SoupProxyURIResolverCallback callback; - gpointer user_data; -} SoupStaticAsyncData; - -static gboolean -idle_return_proxy_uri (gpointer data) -{ - SoupStaticAsyncData *ssad = data; - SoupProxyResolverStaticPrivate *priv = - SOUP_PROXY_RESOLVER_STATIC_GET_PRIVATE (ssad->proxy_resolver); - - ssad->callback (ssad->proxy_resolver, - SOUP_STATUS_OK, priv->proxy_uri, - ssad->user_data); - g_object_unref (ssad->proxy_resolver); - g_slice_free (SoupStaticAsyncData, ssad); - - return FALSE; -} - -static void -soup_proxy_resolver_static_get_proxy_uri_async (SoupProxyURIResolver *proxy_resolver, - SoupURI *uri, - GMainContext *async_context, - GCancellable *cancellable, - SoupProxyURIResolverCallback callback, - gpointer user_data) -{ - SoupStaticAsyncData *ssad; - - ssad = g_slice_new0 (SoupStaticAsyncData); - ssad->proxy_resolver = g_object_ref (proxy_resolver); - ssad->callback = callback; - ssad->user_data = user_data; - soup_add_completion (async_context, idle_return_proxy_uri, ssad); -} - -static guint -soup_proxy_resolver_static_get_proxy_uri_sync (SoupProxyURIResolver *proxy_resolver, - SoupURI *uri, - GCancellable *cancellable, - SoupURI **proxy_uri) -{ - SoupProxyResolverStaticPrivate *priv = - SOUP_PROXY_RESOLVER_STATIC_GET_PRIVATE (proxy_resolver); - - *proxy_uri = soup_uri_copy (priv->proxy_uri); - return SOUP_STATUS_OK; -} - -static void -soup_proxy_resolver_static_class_init (SoupProxyResolverStaticClass *static_class) -{ - GObjectClass *object_class = G_OBJECT_CLASS (static_class); - - g_type_class_add_private (static_class, sizeof (SoupProxyResolverStaticPrivate)); - - object_class->set_property = soup_proxy_resolver_static_set_property; - object_class->get_property = soup_proxy_resolver_static_get_property; - object_class->finalize = soup_proxy_resolver_static_finalize; - - g_object_class_install_property ( - object_class, PROP_PROXY_URI, - g_param_spec_boxed (SOUP_PROXY_RESOLVER_STATIC_PROXY_URI, - "Proxy URI", - "The HTTP Proxy to use", - SOUP_TYPE_URI, - G_PARAM_READWRITE)); -} - -static void -soup_proxy_resolver_static_interface_init (SoupProxyURIResolverInterface *proxy_uri_resolver_interface) -{ - proxy_uri_resolver_interface->get_proxy_uri_async = - soup_proxy_resolver_static_get_proxy_uri_async; - proxy_uri_resolver_interface->get_proxy_uri_sync = - soup_proxy_resolver_static_get_proxy_uri_sync; -} - -SoupProxyURIResolver * -soup_proxy_resolver_static_new (SoupURI *proxy_uri) -{ - return g_object_new (SOUP_TYPE_PROXY_RESOLVER_STATIC, - SOUP_PROXY_RESOLVER_STATIC_PROXY_URI, proxy_uri, - NULL); -} diff --git a/libsoup/soup-proxy-resolver-static.h b/libsoup/soup-proxy-resolver-static.h deleted file mode 100644 index 090b84b9..00000000 --- a/libsoup/soup-proxy-resolver-static.h +++ /dev/null @@ -1,35 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2008 Red Hat, Inc. - */ - -#ifndef SOUP_PROXY_RESOLVER_STATIC_H -#define SOUP_PROXY_RESOLVER_STATIC_H 1 - -#include "soup-proxy-uri-resolver.h" -#include "soup-uri.h" - -#define SOUP_TYPE_PROXY_RESOLVER_STATIC (soup_proxy_resolver_static_get_type ()) -#define SOUP_PROXY_RESOLVER_STATIC(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_PROXY_RESOLVER_STATIC, SoupProxyResolverStatic)) -#define SOUP_PROXY_RESOLVER_STATIC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_PROXY_RESOLVER_STATIC, SoupProxyResolverStaticClass)) -#define SOUP_IS_PROXY_RESOLVER_STATIC(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_PROXY_RESOLVER_STATIC)) -#define SOUP_IS_PROXY_RESOLVER_STATIC_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_PROXY_RESOLVER_STATIC)) -#define SOUP_PROXY_RESOLVER_STATIC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_PROXY_RESOLVER_STATIC, SoupProxyResolverStaticClass)) - -typedef struct { - GObject parent; - -} SoupProxyResolverStatic; - -typedef struct { - GObjectClass parent_class; - -} SoupProxyResolverStaticClass; - -GType soup_proxy_resolver_static_get_type (void); - -#define SOUP_PROXY_RESOLVER_STATIC_PROXY_URI "proxy-uri" - -SoupProxyURIResolver *soup_proxy_resolver_static_new (SoupURI *proxy_uri); - -#endif /* SOUP_PROXY_RESOLVER_STATIC_H */ diff --git a/libsoup/soup-proxy-resolver-wrapper.c b/libsoup/soup-proxy-resolver-wrapper.c new file mode 100644 index 00000000..e07664ca --- /dev/null +++ b/libsoup/soup-proxy-resolver-wrapper.c @@ -0,0 +1,167 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-proxy-resolver-wrapper.c: SoupProxyURIResolver -> GProxyResolver wrapper + * + * Copyright 2013 Red Hat, Inc. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "soup-proxy-resolver-wrapper.h" +#include "soup.h" + +static void soup_proxy_resolver_wrapper_interface_init (GProxyResolverInterface *proxy_resolver_interface); + +G_DEFINE_TYPE_WITH_CODE (SoupProxyResolverWrapper, soup_proxy_resolver_wrapper, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_PROXY_RESOLVER, soup_proxy_resolver_wrapper_interface_init); + ) + +static void +soup_proxy_resolver_wrapper_init (SoupProxyResolverWrapper *resolver_wrapper) +{ +} + +static void +soup_proxy_resolver_wrapper_finalize (GObject *object) +{ + SoupProxyResolverWrapper *wrapper = + SOUP_PROXY_RESOLVER_WRAPPER (object); + + g_clear_object (&wrapper->soup_resolver); + + G_OBJECT_CLASS (soup_proxy_resolver_wrapper_parent_class)->finalize (object); +} + +static char ** +convert_response (SoupURI *source_uri, guint status, + SoupURI *proxy_uri, GError **error) +{ + char **proxies = NULL; + + if (status == SOUP_STATUS_CANT_RESOLVE_PROXY) { + g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND, + "%s (%s)", soup_status_get_phrase (status), + source_uri->host); + } else if (status == SOUP_STATUS_CANT_CONNECT_PROXY) { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED, + "%s (%s)", soup_status_get_phrase (status), + source_uri->host); + } else { + g_return_val_if_fail (status == SOUP_STATUS_OK, NULL); + + proxies = g_new (char *, 2); + proxies[0] = soup_uri_to_string (proxy_uri, FALSE); + proxies[1] = NULL; + + soup_uri_free (proxy_uri); + } + + return proxies; +} + +static void +wrapper_lookup_async_complete (SoupProxyURIResolver *resolver, + guint status, SoupURI *proxy_uri, + gpointer user_data) +{ + GTask *task = user_data; + SoupURI *source_uri = g_task_get_task_data (task); + char **proxies; + GError *error = NULL; + + proxies = convert_response (source_uri, status, proxy_uri, &error); + if (error) + g_task_return_error (task, error); + else + g_task_return_pointer (task, proxies, (GDestroyNotify) g_strfreev); + g_object_unref (task); +} + +static void +soup_proxy_resolver_wrapper_lookup_async (GProxyResolver *resolver, + const gchar *uri, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SoupProxyResolverWrapper *wrapper = + SOUP_PROXY_RESOLVER_WRAPPER (resolver); + GTask *task; + SoupURI *source_uri; + + task = g_task_new (resolver, cancellable, callback, user_data); + source_uri = soup_uri_new (uri); + g_task_set_task_data (task, source_uri, (GDestroyNotify) soup_uri_free); + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + soup_proxy_uri_resolver_get_proxy_uri_async (wrapper->soup_resolver, + source_uri, + g_main_context_get_thread_default (), + cancellable, + wrapper_lookup_async_complete, + task); + G_GNUC_END_IGNORE_DEPRECATIONS; +} + +static char ** +soup_proxy_resolver_wrapper_lookup_finish (GProxyResolver *resolver, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_pointer (G_TASK (result), error); +} + +static gchar ** +soup_proxy_resolver_wrapper_lookup (GProxyResolver *resolver, + const gchar *uri, + GCancellable *cancellable, + GError **error) +{ + SoupProxyResolverWrapper *wrapper = + SOUP_PROXY_RESOLVER_WRAPPER (resolver); + SoupURI *source_uri, *proxy_uri; + guint status; + gchar **proxies; + + source_uri = soup_uri_new (uri); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + status = soup_proxy_uri_resolver_get_proxy_uri_sync (wrapper->soup_resolver, + source_uri, + cancellable, + &proxy_uri); + G_GNUC_END_IGNORE_DEPRECATIONS; + proxies = convert_response (source_uri, status, proxy_uri, error); + soup_uri_free (source_uri); + return proxies; +} + +static void +soup_proxy_resolver_wrapper_class_init (SoupProxyResolverWrapperClass *static_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (static_class); + + object_class->finalize = soup_proxy_resolver_wrapper_finalize; +} + +static void +soup_proxy_resolver_wrapper_interface_init (GProxyResolverInterface *proxy_resolver_interface) +{ + proxy_resolver_interface->lookup = + soup_proxy_resolver_wrapper_lookup; + proxy_resolver_interface->lookup_async = + soup_proxy_resolver_wrapper_lookup_async; + proxy_resolver_interface->lookup_finish = + soup_proxy_resolver_wrapper_lookup_finish; +} + +GProxyResolver * +soup_proxy_resolver_wrapper_new (SoupProxyURIResolver *soup_resolver) +{ + SoupProxyResolverWrapper *wrapper; + + wrapper = g_object_new (SOUP_TYPE_PROXY_RESOLVER_WRAPPER, NULL); + wrapper->soup_resolver = g_object_ref (soup_resolver); + return (GProxyResolver *)wrapper; +} diff --git a/libsoup/soup-proxy-resolver-wrapper.h b/libsoup/soup-proxy-resolver-wrapper.h new file mode 100644 index 00000000..d16b57c9 --- /dev/null +++ b/libsoup/soup-proxy-resolver-wrapper.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Red Hat, Inc. + */ + +#ifndef SOUP_PROXY_RESOLVER_WRAPPER_H +#define SOUP_PROXY_RESOLVER_WRAPPER_H 1 + +#include "soup-proxy-uri-resolver.h" +#include "soup-uri.h" + +#define SOUP_TYPE_PROXY_RESOLVER_WRAPPER (soup_proxy_resolver_wrapper_get_type ()) +#define SOUP_PROXY_RESOLVER_WRAPPER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_PROXY_RESOLVER_WRAPPER, SoupProxyResolverWrapper)) +#define SOUP_PROXY_RESOLVER_WRAPPER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SOUP_TYPE_PROXY_RESOLVER_WRAPPER, SoupProxyResolverWrapperClass)) +#define SOUP_IS_PROXY_RESOLVER_WRAPPER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SOUP_TYPE_PROXY_RESOLVER_WRAPPER)) +#define SOUP_IS_PROXY_RESOLVER_WRAPPER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_PROXY_RESOLVER_WRAPPER)) +#define SOUP_PROXY_RESOLVER_WRAPPER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_PROXY_RESOLVER_WRAPPER, SoupProxyResolverWrapperClass)) + +typedef struct { + GObject parent; + + SoupProxyURIResolver *soup_resolver; +} SoupProxyResolverWrapper; + +typedef struct { + GObjectClass parent_class; + +} SoupProxyResolverWrapperClass; + +GType soup_proxy_resolver_wrapper_get_type (void); + +GProxyResolver *soup_proxy_resolver_wrapper_new (SoupProxyURIResolver *soup_resolver); + +#endif /* SOUP_PROXY_RESOLVER_WRAPPER_H */ diff --git a/libsoup/soup-proxy-resolver.c b/libsoup/soup-proxy-resolver.c index 8092175f..a84c27ab 100644 --- a/libsoup/soup-proxy-resolver.c +++ b/libsoup/soup-proxy-resolver.c @@ -13,46 +13,14 @@ #include "soup.h" static void soup_proxy_resolver_default_init (SoupProxyResolverInterface *iface); -static void soup_proxy_resolver_uri_resolver_interface_init (SoupProxyURIResolverInterface *uri_resolver_interface); G_DEFINE_INTERFACE_WITH_CODE (SoupProxyResolver, soup_proxy_resolver, G_TYPE_OBJECT, g_type_interface_add_prerequisite (g_define_type_id, SOUP_TYPE_SESSION_FEATURE); ) static void -proxy_resolver_interface_check (gpointer func_data, gpointer g_iface) -{ - GTypeInterface *iface = g_iface; - - if (iface->g_type != SOUP_TYPE_PROXY_RESOLVER) - return; - - /* If the class hasn't already declared that it implements - * SoupProxyURIResolver, add our own compat implementation. - */ - if (!g_type_is_a (iface->g_instance_type, SOUP_TYPE_PROXY_URI_RESOLVER)) { - const GInterfaceInfo uri_resolver_interface_info = { - (GInterfaceInitFunc) soup_proxy_resolver_uri_resolver_interface_init, NULL, NULL - }; - g_type_add_interface_static (iface->g_instance_type, - SOUP_TYPE_PROXY_URI_RESOLVER, - &uri_resolver_interface_info); - } -} - - -static void soup_proxy_resolver_default_init (SoupProxyResolverInterface *iface) { - /* Add an interface_check where we can kludgily add the - * SoupProxyURIResolver interface to all SoupProxyResolvers. - * (SoupProxyResolver can't just implement - * SoupProxyURIResolver itself because interface types can't - * implement other interfaces.) This is an ugly hack, but it - * only gets used if someone actually creates a - * SoupProxyResolver... - */ - g_type_add_interface_check (NULL, proxy_resolver_interface_check); } void @@ -63,10 +31,16 @@ soup_proxy_resolver_get_proxy_async (SoupProxyResolver *proxy_resolver, SoupProxyResolverCallback callback, gpointer user_data) { +#ifdef G_GNUC_BEGIN_IGNORE_DEPRECATIONS +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +#endif SOUP_PROXY_RESOLVER_GET_CLASS (proxy_resolver)-> get_proxy_async (proxy_resolver, msg, async_context, cancellable, callback, user_data); +#ifdef G_GNUC_END_IGNORE_DEPRECATIONS +G_GNUC_END_IGNORE_DEPRECATIONS +#endif } guint @@ -75,94 +49,12 @@ soup_proxy_resolver_get_proxy_sync (SoupProxyResolver *proxy_resolver, GCancellable *cancellable, SoupAddress **addr) { +#ifdef G_GNUC_BEGIN_IGNORE_DEPRECATIONS +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +#endif return SOUP_PROXY_RESOLVER_GET_CLASS (proxy_resolver)-> get_proxy_sync (proxy_resolver, msg, cancellable, addr); -} - -/* SoupProxyURIResolver implementation */ - -static SoupURI * -uri_from_address (SoupAddress *addr) -{ - SoupURI *proxy_uri; - - proxy_uri = soup_uri_new (NULL); - soup_uri_set_scheme (proxy_uri, SOUP_URI_SCHEME_HTTP); - soup_uri_set_host (proxy_uri, soup_address_get_name (addr)); - soup_uri_set_port (proxy_uri, soup_address_get_port (addr)); - soup_uri_set_path (proxy_uri, ""); - return proxy_uri; -} - -typedef struct { - SoupProxyURIResolverCallback callback; - gpointer user_data; -} ProxyURIResolverAsyncData; - -static void -compat_got_proxy (SoupProxyResolver *proxy_resolver, - SoupMessage *msg, guint status, SoupAddress *proxy_addr, - gpointer user_data) -{ - ProxyURIResolverAsyncData *purad = user_data; - SoupURI *proxy_uri; - - proxy_uri = proxy_addr ? uri_from_address (proxy_addr) : NULL; - purad->callback (SOUP_PROXY_URI_RESOLVER (proxy_resolver), - status, proxy_uri, purad->user_data); - g_object_unref (msg); - if (proxy_uri) - soup_uri_free (proxy_uri); - g_slice_free (ProxyURIResolverAsyncData, purad); -} - -static void -compat_get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_resolver, - SoupURI *uri, GMainContext *async_context, - GCancellable *cancellable, - SoupProxyURIResolverCallback callback, - gpointer user_data) -{ - SoupMessage *dummy_msg; - ProxyURIResolverAsyncData *purad; - - dummy_msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); - - purad = g_slice_new (ProxyURIResolverAsyncData); - purad->callback = callback; - purad->user_data = user_data; - - soup_proxy_resolver_get_proxy_async ( - SOUP_PROXY_RESOLVER (proxy_uri_resolver), dummy_msg, - async_context, cancellable, - compat_got_proxy, purad); -} - -static guint -compat_get_proxy_uri_sync (SoupProxyURIResolver *proxy_uri_resolver, - SoupURI *uri, GCancellable *cancellable, - SoupURI **proxy_uri) -{ - SoupMessage *dummy_msg; - SoupAddress *proxy_addr = NULL; - guint status; - - dummy_msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); - status = soup_proxy_resolver_get_proxy_sync ( - SOUP_PROXY_RESOLVER (proxy_uri_resolver), dummy_msg, - cancellable, &proxy_addr); - g_object_unref (dummy_msg); - if (!proxy_addr) - return status; - - *proxy_uri = uri_from_address (proxy_addr); - g_object_unref (proxy_addr); - return status; -} - -static void -soup_proxy_resolver_uri_resolver_interface_init (SoupProxyURIResolverInterface *uri_resolver_interface) -{ - uri_resolver_interface->get_proxy_uri_async = compat_get_proxy_uri_async; - uri_resolver_interface->get_proxy_uri_sync = compat_get_proxy_uri_sync; +#ifdef G_GNUC_END_IGNORE_DEPRECATIONS +G_GNUC_END_IGNORE_DEPRECATIONS +#endif } diff --git a/libsoup/soup-proxy-resolver.h b/libsoup/soup-proxy-resolver.h index dd787143..72e48852 100644 --- a/libsoup/soup-proxy-resolver.h +++ b/libsoup/soup-proxy-resolver.h @@ -10,7 +10,7 @@ G_BEGIN_DECLS -#ifndef LIBSOUP_DISABLE_DEPRECATED +#ifndef SOUP_DISABLE_DEPRECATED #define SOUP_TYPE_PROXY_RESOLVER (soup_proxy_resolver_get_type ()) #define SOUP_PROXY_RESOLVER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SOUP_TYPE_PROXY_RESOLVER, SoupProxyResolver)) @@ -39,14 +39,17 @@ typedef struct { } SoupProxyResolverInterface; +SOUP_DEPRECATED_IN_2_28 GType soup_proxy_resolver_get_type (void); +SOUP_DEPRECATED_IN_2_28 void soup_proxy_resolver_get_proxy_async (SoupProxyResolver *proxy_resolver, SoupMessage *msg, GMainContext *async_context, GCancellable *cancellable, SoupProxyResolverCallback callback, gpointer user_data); +SOUP_DEPRECATED_IN_2_28 guint soup_proxy_resolver_get_proxy_sync (SoupProxyResolver *proxy_resolver, SoupMessage *msg, GCancellable *cancellable, diff --git a/libsoup/soup-proxy-uri-resolver.c b/libsoup/soup-proxy-uri-resolver.c index 8aecec47..7971fcb7 100644 --- a/libsoup/soup-proxy-uri-resolver.c +++ b/libsoup/soup-proxy-uri-resolver.c @@ -9,27 +9,48 @@ #include <config.h> #endif +#undef SOUP_VERSION_MIN_REQUIRED +#define SOUP_VERSION_MIN_REQUIRED SOUP_VERSION_2_42 + #include "soup-proxy-uri-resolver.h" #include "soup.h" -G_DEFINE_INTERFACE (SoupProxyURIResolver, soup_proxy_uri_resolver, G_TYPE_OBJECT) +/* + * SECTION:soup-proxy-uri-resolver + * @short_description: Interface for locating HTTP proxies + * + * #SoupProxyURIResolver is an interface for finding appropriate HTTP + * proxies to use. + * + * Deprecated: #SoupSession now has a #SoupSession:proxy-resolver + * property that takes a #GProxyResolver (which is semantically + * identical to #SoupProxyURIResolver). + * + * Even in older releases of libsoup, you are not likely to have to + * implement this interface on your own; instead, you should usually + * just be able to use #SoupProxyResolverDefault. + */ + +G_DEFINE_INTERFACE_WITH_CODE (SoupProxyURIResolver, soup_proxy_uri_resolver, G_TYPE_OBJECT, + g_type_interface_add_prerequisite (g_define_type_id, SOUP_TYPE_SESSION_FEATURE); + ) static void soup_proxy_uri_resolver_default_init (SoupProxyURIResolverInterface *iface) { } -/** +/* * SoupProxyURIResolverCallback: * @resolver: the #SoupProxyURIResolver - * @status: a #SoupKnownStatusCode + * @status: a #SoupStatus * @proxy_uri: the resolved proxy URI, or %NULL * @user_data: data passed to soup_proxy_uri_resolver_get_proxy_uri_async() * * Callback for soup_proxy_uri_resolver_get_proxy_uri_async() - **/ + */ -/** +/* * soup_proxy_uri_resolver_get_proxy_uri_async: * @proxy_uri_resolver: the #SoupProxyURIResolver * @uri: the #SoupURI you want a proxy for @@ -42,7 +63,10 @@ soup_proxy_uri_resolver_default_init (SoupProxyURIResolverInterface *iface) * @callback. * * Since: 2.26.3 - **/ + * + * Deprecated: #SoupProxyURIResolver is deprecated in favor of + * #GProxyResolver + */ void soup_proxy_uri_resolver_get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_resolver, SoupURI *uri, @@ -57,7 +81,7 @@ soup_proxy_uri_resolver_get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_re callback, user_data); } -/** +/* * soup_proxy_uri_resolver_get_proxy_uri_sync: * @proxy_uri_resolver: the #SoupProxyURIResolver * @uri: the #SoupURI you want a proxy for @@ -72,7 +96,10 @@ soup_proxy_uri_resolver_get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_re * error. * * Since: 2.26.3 - **/ + * + * Deprecated: #SoupProxyURIResolver is deprecated in favor of + * #GProxyResolver + */ guint soup_proxy_uri_resolver_get_proxy_uri_sync (SoupProxyURIResolver *proxy_uri_resolver, SoupURI *uri, diff --git a/libsoup/soup-proxy-uri-resolver.h b/libsoup/soup-proxy-uri-resolver.h index 1d15b8de..e203125b 100644 --- a/libsoup/soup-proxy-uri-resolver.h +++ b/libsoup/soup-proxy-uri-resolver.h @@ -43,14 +43,20 @@ typedef struct { void (*_libsoup_reserved4) (void); } SoupProxyURIResolverInterface; +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_44 GType soup_proxy_uri_resolver_get_type (void); +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_44 void soup_proxy_uri_resolver_get_proxy_uri_async (SoupProxyURIResolver *proxy_uri_resolver, SoupURI *uri, GMainContext *async_context, GCancellable *cancellable, SoupProxyURIResolverCallback callback, gpointer user_data); +SOUP_AVAILABLE_IN_2_28 +SOUP_DEPRECATED_IN_2_44 guint soup_proxy_uri_resolver_get_proxy_uri_sync (SoupProxyURIResolver *proxy_uri_resolver, SoupURI *uri, GCancellable *cancellable, diff --git a/libsoup/soup-request-data.c b/libsoup/soup-request-data.c index 933d7522..678e84da 100644 --- a/libsoup/soup-request-data.c +++ b/libsoup/soup-request-data.c @@ -25,14 +25,19 @@ #include <config.h> #endif -#include <glib/gi18n-lib.h> - -#define LIBSOUP_USE_UNSTABLE_REQUEST_API +#include <string.h> #include "soup-request-data.h" #include "soup.h" #include "soup-misc-private.h" +/** + * SECTION:soup-request-data + * @short_description: SoupRequest support for "data" URIs + * + * #SoupRequestData implements #SoupRequest for "data" URIs. + */ + G_DEFINE_TYPE (SoupRequestData, soup_request_data, SOUP_TYPE_REQUEST) struct _SoupRequestDataPrivate { @@ -91,7 +96,7 @@ soup_request_data_send (SoupRequest *request, end = comma; if (end != start) - data->priv->content_type = uri_decoded_copy (start, end - start); + data->priv->content_type = soup_uri_decoded_copy (start, end - start, NULL); } memstream = g_memory_input_stream_new (); @@ -100,12 +105,14 @@ soup_request_data_send (SoupRequest *request, start = comma + 1; if (*start) { - guchar *buf = (guchar *) soup_uri_decode (start); + int decoded_length = 0; + guchar *buf = (guchar *) soup_uri_decoded_copy (start, strlen (start), + &decoded_length); if (base64) buf = g_base64_decode_inplace ((gchar*) buf, &data->priv->content_length); else - data->priv->content_length = strlen ((const char *) buf); + data->priv->content_length = decoded_length; g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (memstream), buf, data->priv->content_length, diff --git a/libsoup/soup-request-data.h b/libsoup/soup-request-data.h index aeb95995..929c5908 100644 --- a/libsoup/soup-request-data.h +++ b/libsoup/soup-request-data.h @@ -22,8 +22,6 @@ #ifndef SOUP_REQUEST_DATA_H #define SOUP_REQUEST_DATA_H 1 -#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API - #include "soup-request.h" G_BEGIN_DECLS @@ -47,10 +45,9 @@ typedef struct { SoupRequestClass parent; } SoupRequestDataClass; +SOUP_AVAILABLE_IN_2_34 GType soup_request_data_get_type (void); G_END_DECLS -#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */ - #endif /* SOUP_REQUEST_DATA_H */ diff --git a/libsoup/soup-request-file.c b/libsoup/soup-request-file.c index 0b5638d2..5a56b28b 100644 --- a/libsoup/soup-request-file.c +++ b/libsoup/soup-request-file.c @@ -25,15 +25,21 @@ #include <config.h> #endif -#include <glib/gi18n-lib.h> - -#define LIBSOUP_USE_UNSTABLE_REQUEST_API +#include <string.h> #include "soup-request-file.h" #include "soup.h" #include "soup-directory-input-stream.h" #include "soup-requester.h" +/** + * SECTION:soup-request-file + * @short_description: SoupRequest support for "file" and "resource" URIs + * + * #SoupRequestFile implements #SoupRequest for "file" and "resource" + * URIs. + */ + G_DEFINE_TYPE (SoupRequestFile, soup_request_file, SOUP_TYPE_REQUEST) struct _SoupRequestFilePrivate { @@ -128,7 +134,15 @@ soup_request_file_ensure_file (SoupRequestFile *file, windowsify_file_uri_path (decoded_path); #endif - file->priv->gfile = g_file_new_for_path (decoded_path); + if (uri->scheme == SOUP_URI_SCHEME_RESOURCE) { + char *uri_str; + + uri_str = g_strdup_printf ("resource://%s", decoded_path); + file->priv->gfile = g_file_new_for_uri (uri_str); + g_free (uri_str); + } else + file->priv->gfile = g_file_new_for_path (decoded_path); + g_free (decoded_path); return TRUE; } @@ -184,36 +198,33 @@ soup_request_file_send (SoupRequest *request, } static void -soup_request_file_send_async_thread (GSimpleAsyncResult *res, - GObject *object, - GCancellable *cancellable) +soup_request_file_send_async_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) { + SoupRequest *request = source_object; GInputStream *stream; - SoupRequest *request; GError *error = NULL; - request = SOUP_REQUEST (object); - stream = soup_request_file_send (request, cancellable, &error); - if (stream == NULL) - g_simple_async_result_take_error (res, error); + g_task_return_error (task, error); else - g_simple_async_result_set_op_res_gpointer (res, stream, g_object_unref); + g_task_return_pointer (task, stream, g_object_unref); } static void soup_request_file_send_async (SoupRequest *request, GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) + GAsyncReadyCallback callback, + gpointer user_data) { - GSimpleAsyncResult *res; - - res = g_simple_async_result_new (G_OBJECT (request), callback, user_data, soup_request_file_send_async); + GTask *task; - g_simple_async_result_run_in_thread (res, soup_request_file_send_async_thread, G_PRIORITY_DEFAULT, cancellable); - g_object_unref (res); + task = g_task_new (request, cancellable, callback, user_data); + g_task_run_in_thread (task, soup_request_file_send_async_thread); + g_object_unref (task); } static GInputStream * @@ -221,14 +232,9 @@ soup_request_file_send_finish (SoupRequest *request, GAsyncResult *result, GError **error) { - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); - - g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == soup_request_file_send_async); - - if (g_simple_async_result_propagate_error (simple, error)) - return NULL; + g_return_val_if_fail (g_task_is_valid (result, request), NULL); - return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple)); + return g_task_propagate_pointer (G_TASK (result), error); } static goffset @@ -250,7 +256,7 @@ soup_request_file_get_content_type (SoupRequest *request) return file->priv->mime_type; } -static const char *file_schemes[] = { "file", NULL }; +static const char *file_schemes[] = { "file", "resource", NULL }; static void soup_request_file_class_init (SoupRequestFileClass *request_file_class) @@ -281,7 +287,7 @@ soup_request_file_class_init (SoupRequestFileClass *request_file_class) * * Return value: (transfer full): a #GFile corresponding to @file * - * Since: 2.34 + * Since: 2.40 */ GFile * soup_request_file_get_file (SoupRequestFile *file) diff --git a/libsoup/soup-request-file.h b/libsoup/soup-request-file.h index acb1a085..df2cbab1 100644 --- a/libsoup/soup-request-file.h +++ b/libsoup/soup-request-file.h @@ -22,8 +22,6 @@ #ifndef SOUP_REQUEST_FILE_H #define SOUP_REQUEST_FILE_H 1 -#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API - #include "soup-request.h" G_BEGIN_DECLS @@ -47,12 +45,12 @@ typedef struct { SoupRequestClass parent; } SoupRequestFileClass; +SOUP_AVAILABLE_IN_2_34 GType soup_request_file_get_type (void); +SOUP_AVAILABLE_IN_2_34 GFile *soup_request_file_get_file (SoupRequestFile *file); G_END_DECLS -#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */ - #endif /* SOUP_REQUEST_FILE_H */ diff --git a/libsoup/soup-request-http.c b/libsoup/soup-request-http.c index 44a1f7d5..9ae2c2a7 100644 --- a/libsoup/soup-request-http.c +++ b/libsoup/soup-request-http.c @@ -27,13 +27,23 @@ #include <glib/gi18n-lib.h> -#define LIBSOUP_USE_UNSTABLE_REQUEST_API - #include "soup-request-http.h" #include "soup.h" -#include "soup-cache-private.h" +#include "soup-message-private.h" #include "soup-session-private.h" +/** + * SECTION:soup-request-http + * @short_description: SoupRequest support for "http" and "https" URIs + * + * #SoupRequestHTTP implements #SoupRequest for "http" and "https" + * URIs. + * + * To do more complicated HTTP operations using the #SoupRequest APIs, + * call soup_request_http_get_message() to get the request's + * #SoupMessage. + */ + G_DEFINE_TYPE (SoupRequestHTTP, soup_request_http, SOUP_TYPE_REQUEST) struct _SoupRequestHTTPPrivate { @@ -63,6 +73,8 @@ soup_request_http_check_uri (SoupRequest *request, return FALSE; http->priv->msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); + soup_message_set_soup_request (http->priv->msg, request); + g_signal_connect (http->priv->msg, "content-sniffed", G_CALLBACK (content_sniffed), http); return TRUE; @@ -91,109 +103,28 @@ soup_request_http_send (SoupRequest *request, GError **error) { SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request); + SoupSession *session = soup_request_get_session (request); - return soup_session_send_request (soup_request_get_session (request), - http->priv->msg, - cancellable, error); -} - - -typedef struct { - SoupRequestHTTP *http; - GCancellable *cancellable; - GSimpleAsyncResult *simple; - - SoupMessage *original; - GInputStream *stream; -} SendAsyncData; - -static void -free_send_async_data (SendAsyncData *sadata) -{ - g_object_unref (sadata->http); - g_object_unref (sadata->simple); - - if (sadata->cancellable) - g_object_unref (sadata->cancellable); - if (sadata->stream) - g_object_unref (sadata->stream); - if (sadata->original) - g_object_unref (sadata->original); + g_return_val_if_fail (!SOUP_IS_SESSION_ASYNC (session), NULL); - g_slice_free (SendAsyncData, sadata); + return soup_session_send (session, http->priv->msg, + cancellable, error); } + static void http_input_stream_ready_cb (GObject *source, GAsyncResult *result, gpointer user_data) { - SendAsyncData *sadata = user_data; + GTask *task = user_data; GError *error = NULL; GInputStream *stream; - stream = soup_session_send_request_finish (SOUP_SESSION (source), result, &error); - if (stream) { - g_simple_async_result_set_op_res_gpointer (sadata->simple, stream, g_object_unref); - } else { - g_simple_async_result_take_error (sadata->simple, error); - } - g_simple_async_result_complete (sadata->simple); - free_send_async_data (sadata); -} - - -static void -conditional_get_ready_cb (SoupSession *session, SoupMessage *msg, gpointer user_data) -{ - SendAsyncData *sadata = user_data; - GInputStream *stream; - - if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) { - SoupCache *cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE); - - stream = soup_cache_send_response (cache, sadata->original); - if (stream) { - g_simple_async_result_set_op_res_gpointer (sadata->simple, stream, g_object_unref); - - soup_message_got_headers (sadata->original); - - sadata->http->priv->content_type = g_strdup (soup_message_headers_get_content_type (msg->response_headers, NULL)); - - g_simple_async_result_complete (sadata->simple); - - soup_message_finished (sadata->original); - free_send_async_data (sadata); - return; - } - } - - /* The resource was modified or the server returned a 200 - * OK. Either way we reload it. This is far from optimal as - * we're donwloading the resource twice, but we will change it - * once the cache is integrated in the streams stack. - */ - soup_session_send_request_async (session, sadata->original, sadata->cancellable, - http_input_stream_ready_cb, sadata); -} - -static gboolean -idle_return_from_cache_cb (gpointer data) -{ - SendAsyncData *sadata = data; - - g_simple_async_result_set_op_res_gpointer (sadata->simple, - g_object_ref (sadata->stream), g_object_unref); - - /* Issue signals */ - soup_message_got_headers (sadata->http->priv->msg); - - sadata->http->priv->content_type = g_strdup (soup_message_headers_get_content_type (sadata->http->priv->msg->response_headers, NULL)); - - g_simple_async_result_complete (sadata->simple); - - soup_message_finished (sadata->http->priv->msg); - - free_send_async_data (sadata); - return FALSE; + stream = soup_session_send_finish (SOUP_SESSION (source), result, &error); + if (stream) + g_task_return_pointer (task, stream, g_object_unref); + else + g_task_return_error (task, error); + g_object_unref (task); } static void @@ -203,57 +134,14 @@ soup_request_http_send_async (SoupRequest *request, gpointer user_data) { SoupRequestHTTP *http = SOUP_REQUEST_HTTP (request); - SendAsyncData *sadata; - GInputStream *stream; - SoupSession *session; - SoupCache *cache; - - sadata = g_slice_new0 (SendAsyncData); - sadata->http = g_object_ref (http); - sadata->cancellable = cancellable ? g_object_ref (cancellable) : NULL; - sadata->simple = g_simple_async_result_new (G_OBJECT (request), callback, user_data, - soup_request_http_send_async); - - session = soup_request_get_session (request); - cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE); - - if (cache) { - SoupCacheResponse response; - - response = soup_cache_has_response (cache, http->priv->msg); - if (response == SOUP_CACHE_RESPONSE_FRESH) { - stream = soup_cache_send_response (cache, http->priv->msg); - - /* Cached resource file could have been deleted outside */ - if (stream) { - /* Do return the stream asynchronously as in - * the other cases. It's not enough to use - * g_simple_async_result_complete_in_idle as - * the signals must be also emitted - * asynchronously - */ - sadata->stream = stream; - soup_add_completion (soup_session_get_async_context (session), - idle_return_from_cache_cb, sadata); - return; - } - } else if (response == SOUP_CACHE_RESPONSE_NEEDS_VALIDATION) { - SoupMessage *conditional_msg; - - conditional_msg = soup_cache_generate_conditional_request (cache, http->priv->msg); - - if (conditional_msg) { - sadata->original = g_object_ref (http->priv->msg); - soup_session_queue_message (session, conditional_msg, - conditional_get_ready_cb, - sadata); - return; - } - } - } + SoupSession *session = soup_request_get_session (request); + GTask *task; + + g_return_if_fail (!SOUP_IS_SESSION_SYNC (session)); - soup_session_send_request_async (session, http->priv->msg, cancellable, - http_input_stream_ready_cb, sadata); + task = g_task_new (request, cancellable, callback, user_data); + soup_session_send_async (session, http->priv->msg, cancellable, + http_input_stream_ready_cb, task); } static GInputStream * @@ -261,14 +149,9 @@ soup_request_http_send_finish (SoupRequest *request, GAsyncResult *result, GError **error) { - GSimpleAsyncResult *simple; - - g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (request), soup_request_http_send_async), NULL); + g_return_val_if_fail (g_task_is_valid (result, request), NULL); - simple = G_SIMPLE_ASYNC_RESULT (result); - if (g_simple_async_result_propagate_error (simple, error)) - return NULL; - return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple)); + return g_task_propagate_pointer (G_TASK (result), error); } static goffset @@ -342,7 +225,7 @@ soup_request_http_class_init (SoupRequestHTTPClass *request_http_class) * * Returns: (transfer full): a new reference to the #SoupMessage * - * Since: 2.34 + * Since: 2.40 */ SoupMessage * soup_request_http_get_message (SoupRequestHTTP *http) diff --git a/libsoup/soup-request-http.h b/libsoup/soup-request-http.h index 6402646c..271a8052 100644 --- a/libsoup/soup-request-http.h +++ b/libsoup/soup-request-http.h @@ -22,8 +22,6 @@ #ifndef SOUP_REQUEST_HTTP_H #define SOUP_REQUEST_HTTP_H 1 -#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API - #include "soup-request.h" G_BEGIN_DECLS @@ -37,22 +35,22 @@ G_BEGIN_DECLS typedef struct _SoupRequestHTTPPrivate SoupRequestHTTPPrivate; -typedef struct { +struct _SoupRequestHTTP { SoupRequest parent; SoupRequestHTTPPrivate *priv; -} SoupRequestHTTP; +}; typedef struct { SoupRequestClass parent; } SoupRequestHTTPClass; +SOUP_AVAILABLE_IN_2_34 GType soup_request_http_get_type (void); +SOUP_AVAILABLE_IN_2_34 SoupMessage *soup_request_http_get_message (SoupRequestHTTP *http); G_END_DECLS -#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */ - #endif /* SOUP_REQUEST_HTTP_H */ diff --git a/libsoup/soup-request.c b/libsoup/soup-request.c index e7471c1c..0dfe3eff 100644 --- a/libsoup/soup-request.c +++ b/libsoup/soup-request.c @@ -27,8 +27,6 @@ #include <glib/gi18n-lib.h> -#define LIBSOUP_USE_UNSTABLE_REQUEST_API - #include "soup-request.h" #include "soup.h" #include "soup-requester.h" @@ -37,8 +35,8 @@ * SECTION:soup-request * @short_description: Protocol-independent streaming request interface * - * A #SoupRequest is created by #SoupRequester, and represents a - * request to retrieve a particular URI. + * A #SoupRequest is created by #SoupSession, and represents a request + * to retrieve a particular URI. */ /** @@ -46,7 +44,7 @@ * * A request to retrieve a particular URI. * - * Since: 2.34 + * Since: 2.42 */ static void soup_request_initable_interface_init (GInitableIface *initable_interface); @@ -138,7 +136,7 @@ soup_request_initable_init (GInitable *initable, gboolean ok; if (!request->priv->uri) { - g_set_error (error, SOUP_REQUESTER_ERROR, SOUP_REQUESTER_ERROR_BAD_URI, + g_set_error (error, SOUP_REQUEST_ERROR, SOUP_REQUEST_ERROR_BAD_URI, _("No URI provided")); return FALSE; } @@ -148,7 +146,7 @@ soup_request_initable_init (GInitable *initable, if (!ok && error && !*error) { char *uri_string = soup_uri_to_string (request->priv->uri, FALSE); - g_set_error (error, SOUP_REQUESTER_ERROR, SOUP_REQUESTER_ERROR_BAD_URI, + g_set_error (error, SOUP_REQUEST_ERROR, SOUP_REQUEST_ERROR_BAD_URI, _("Invalid '%s' URI: %s"), request->priv->uri->scheme, uri_string); @@ -173,13 +171,18 @@ soup_request_default_send_async (SoupRequest *request, GAsyncReadyCallback callback, gpointer user_data) { - GSimpleAsyncResult *simple; - - simple = g_simple_async_result_new (G_OBJECT (request), - callback, user_data, - soup_request_default_send_async); - g_simple_async_result_complete_in_idle (simple); - g_object_unref (simple); + GTask *task; + GInputStream *stream; + GError *error = NULL; + + task = g_task_new (request, cancellable, callback, user_data); + + stream = soup_request_send (request, cancellable, &error); + if (stream) + g_task_return_pointer (task, stream, g_object_unref); + else + g_task_return_error (task, error); + g_object_unref (task); } static GInputStream * @@ -187,9 +190,7 @@ soup_request_default_send_finish (SoupRequest *request, GAsyncResult *result, GError **error) { - g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (request), soup_request_default_send_async), NULL); - - return soup_request_send (request, NULL, error); + return g_task_propagate_pointer (G_TASK (result), error); } /** @@ -201,10 +202,13 @@ soup_request_default_send_finish (SoupRequest *request, * Synchronously requests the URI pointed to by @request, and returns * a #GInputStream that can be used to read its contents. * + * Note that you cannot use this method with #SoupRequests attached to + * a #SoupSessionAsync. + * * Return value: (transfer full): a #GInputStream that can be used to * read from the URI pointed to by @request. * - * Since: 2.34 + * Since: 2.42 */ GInputStream * soup_request_send (SoupRequest *request, @@ -225,7 +229,10 @@ soup_request_send (SoupRequest *request, * Begins an asynchronously request for the URI pointed to by * @request. * - * Since: 2.34 + * Note that you cannot use this method with #SoupRequests attached to + * a #SoupSessionSync. + * + * Since: 2.42 */ void soup_request_send_async (SoupRequest *request, @@ -248,7 +255,7 @@ soup_request_send_async (SoupRequest *request, * Return value: (transfer full): a #GInputStream that can be used to * read from the URI pointed to by @request. * - * Since: 2.34 + * Since: 2.42 */ GInputStream * soup_request_send_finish (SoupRequest *request, @@ -274,6 +281,20 @@ soup_request_class_init (SoupRequestClass *request_class) object_class->set_property = soup_request_set_property; object_class->get_property = soup_request_get_property; + /** + * SOUP_REQUEST_URI: + * + * Alias for the #SoupRequest:uri property, qv. + * + * Since: 2.42 + */ + /** + * SoupRequest:uri: + * + * The request URI. + * + * Since: 2.42 + */ g_object_class_install_property ( object_class, PROP_URI, g_param_spec_boxed (SOUP_REQUEST_URI, @@ -281,6 +302,20 @@ soup_request_class_init (SoupRequestClass *request_class) "The request URI", SOUP_TYPE_URI, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + /** + * SOUP_REQUEST_SESSION: + * + * Alias for the #SoupRequest:session property, qv. + * + * Since: 2.42 + */ + /** + * SoupRequest:session: + * + * The request's #SoupSession. + * + * Since: 2.42 + */ g_object_class_install_property ( object_class, PROP_SESSION, g_param_spec_object (SOUP_REQUEST_SESSION, @@ -304,7 +339,7 @@ soup_request_initable_interface_init (GInitableIface *initable_interface) * * Return value: (transfer none): @request's URI * - * Since: 2.34 + * Since: 2.42 */ SoupURI * soup_request_get_uri (SoupRequest *request) @@ -320,7 +355,7 @@ soup_request_get_uri (SoupRequest *request) * * Return value: (transfer none): @request's #SoupSession * - * Since: 2.34 + * Since: 2.42 */ SoupSession * soup_request_get_session (SoupRequest *request) @@ -332,12 +367,14 @@ soup_request_get_session (SoupRequest *request) * soup_request_get_content_length: * @request: a #SoupRequest * - * Gets the length of the data represented by @request. + * Gets the length of the data represented by @request. For most + * request types, this will not be known until after you call + * soup_request_send() or soup_request_send_finish(). * * Return value: the length of the data represented by @request, * or -1 if not known. * - * Since: 2.34 + * Since: 2.42 */ goffset soup_request_get_content_length (SoupRequest *request) @@ -349,14 +386,17 @@ soup_request_get_content_length (SoupRequest *request) * soup_request_get_content_type: * @request: a #SoupRequest * - * Gets the type of the data represented by @request. As in the - * HTTP Content-Type header, this may include parameters after - * the MIME type. + * Gets the type of the data represented by @request. For most request + * types, this will not be known until after you call + * soup_request_send() or soup_request_send_finish(). + * + * As in the HTTP Content-Type header, this may include parameters + * after the MIME type. * * Return value: the type of the data represented by @request, * or %NULL if not known. * - * Since: 2.34 + * Since: 2.42 */ const char * soup_request_get_content_type (SoupRequest *request) diff --git a/libsoup/soup-request.h b/libsoup/soup-request.h index a48e9174..84ad6d6b 100644 --- a/libsoup/soup-request.h +++ b/libsoup/soup-request.h @@ -22,8 +22,6 @@ #ifndef SOUP_REQUEST_H #define SOUP_REQUEST_H 1 -#ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API - #include <gio/gio.h> #include <libsoup/soup-types.h> @@ -37,7 +35,6 @@ G_BEGIN_DECLS #define SOUP_IS_REQUEST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SOUP_TYPE_REQUEST)) #define SOUP_REQUEST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SOUP_TYPE_REQUEST, SoupRequestClass)) -typedef struct _SoupRequest SoupRequest; typedef struct _SoupRequestPrivate SoupRequestPrivate; typedef struct _SoupRequestClass SoupRequestClass; @@ -71,30 +68,36 @@ struct _SoupRequestClass { const char * (*get_content_type) (SoupRequest *request); }; +SOUP_AVAILABLE_IN_2_34 GType soup_request_get_type (void); #define SOUP_REQUEST_URI "uri" #define SOUP_REQUEST_SESSION "session" +SOUP_AVAILABLE_IN_2_34 GInputStream *soup_request_send (SoupRequest *request, GCancellable *cancellable, GError **error); +SOUP_AVAILABLE_IN_2_34 void soup_request_send_async (SoupRequest *request, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); +SOUP_AVAILABLE_IN_2_34 GInputStream *soup_request_send_finish (SoupRequest *request, GAsyncResult *result, GError **error); +SOUP_AVAILABLE_IN_2_34 SoupURI *soup_request_get_uri (SoupRequest *request); +SOUP_AVAILABLE_IN_2_34 SoupSession *soup_request_get_session (SoupRequest *request); +SOUP_AVAILABLE_IN_2_34 goffset soup_request_get_content_length (SoupRequest *request); +SOUP_AVAILABLE_IN_2_34 const char *soup_request_get_content_type (SoupRequest *request); G_END_DECLS -#endif /* LIBSOUP_USE_UNSTABLE_REQUEST_API */ - #endif /* SOUP_REQUEST_H */ diff --git a/libsoup/soup-requester.c b/libsoup/soup-requester.c index 281ebb17..c67bfe31 100644 --- a/libsoup/soup-requester.c +++ b/libsoup/soup-requester.c @@ -22,22 +22,18 @@ #include "config.h" -#include <glib/gi18n-lib.h> - #define LIBSOUP_USE_UNSTABLE_REQUEST_API #include "soup-requester.h" #include "soup.h" -#include "soup-request-data.h" -#include "soup-request-file.h" -#include "soup-request-http.h" + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS static SoupSessionFeatureInterface *soup_requester_default_feature_interface; static void soup_requester_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data); struct _SoupRequesterPrivate { SoupSession *session; - GHashTable *request_types; }; G_DEFINE_TYPE_WITH_CODE (SoupRequester, soup_requester, G_TYPE_OBJECT, @@ -47,44 +43,19 @@ G_DEFINE_TYPE_WITH_CODE (SoupRequester, soup_requester, G_TYPE_OBJECT, static void soup_requester_init (SoupRequester *requester) { - SoupSessionFeature *feature; - requester->priv = G_TYPE_INSTANCE_GET_PRIVATE (requester, SOUP_TYPE_REQUESTER, SoupRequesterPrivate); - - requester->priv->request_types = g_hash_table_new (soup_str_case_hash, - soup_str_case_equal); - - feature = SOUP_SESSION_FEATURE (requester); - soup_session_feature_add_feature (feature, SOUP_TYPE_REQUEST_HTTP); - soup_session_feature_add_feature (feature, SOUP_TYPE_REQUEST_FILE); - soup_session_feature_add_feature (feature, SOUP_TYPE_REQUEST_DATA); -} - -static void -soup_requester_finalize (GObject *object) -{ - SoupRequester *requester = SOUP_REQUESTER (object); - - g_hash_table_destroy (requester->priv->request_types); - - G_OBJECT_CLASS (soup_requester_parent_class)->finalize (object); } static void soup_requester_class_init (SoupRequesterClass *requester_class) { - GObjectClass *object_class = G_OBJECT_CLASS (requester_class); - g_type_class_add_private (requester_class, sizeof (SoupRequesterPrivate)); - - /* virtual method override */ - object_class->finalize = soup_requester_finalize; } static void -attach (SoupSessionFeature *feature, SoupSession *session) +soup_requester_attach (SoupSessionFeature *feature, SoupSession *session) { SoupRequester *requester = SOUP_REQUESTER (feature); @@ -94,7 +65,7 @@ attach (SoupSessionFeature *feature, SoupSession *session) } static void -detach (SoupSessionFeature *feature, SoupSession *session) +soup_requester_detach (SoupSessionFeature *feature, SoupSession *session) { SoupRequester *requester = SOUP_REQUESTER (feature); @@ -104,63 +75,38 @@ detach (SoupSessionFeature *feature, SoupSession *session) } static gboolean -add_feature (SoupSessionFeature *feature, GType type) +soup_requester_add_feature (SoupSessionFeature *feature, GType type) { SoupRequester *requester = SOUP_REQUESTER (feature); - SoupRequestClass *request_class; - int i; if (!g_type_is_a (type, SOUP_TYPE_REQUEST)) return FALSE; - request_class = g_type_class_ref (type); - for (i = 0; request_class->schemes[i]; i++) { - g_hash_table_insert (requester->priv->request_types, - (char *)request_class->schemes[i], - GSIZE_TO_POINTER (type)); - } + soup_session_add_feature_by_type (requester->priv->session, type); return TRUE; } static gboolean -remove_feature (SoupSessionFeature *feature, GType type) +soup_requester_remove_feature (SoupSessionFeature *feature, GType type) { SoupRequester *requester = SOUP_REQUESTER (feature); - SoupRequestClass *request_class; - int i, orig_size; if (!g_type_is_a (type, SOUP_TYPE_REQUEST)) return FALSE; - request_class = g_type_class_peek (type); - if (!request_class) - return FALSE; - - orig_size = g_hash_table_size (requester->priv->request_types); - for (i = 0; request_class->schemes[i]; i++) { - g_hash_table_remove (requester->priv->request_types, - request_class->schemes[i]); - } - - return g_hash_table_size (requester->priv->request_types) != orig_size; + soup_session_remove_feature_by_type (requester->priv->session, type); + return TRUE; } static gboolean -has_feature (SoupSessionFeature *feature, GType type) +soup_requester_has_feature (SoupSessionFeature *feature, GType type) { SoupRequester *requester = SOUP_REQUESTER (feature); - GHashTableIter iter; - gpointer key, value; if (!g_type_is_a (type, SOUP_TYPE_REQUEST)) return FALSE; - g_hash_table_iter_init (&iter, requester->priv->request_types); - while (g_hash_table_iter_next (&iter, &key, &value)) { - if (value == GSIZE_TO_POINTER (type)) - return TRUE; - } - return FALSE; + return soup_session_has_feature (requester->priv->session, type); } static void @@ -170,114 +116,77 @@ soup_requester_session_feature_init (SoupSessionFeatureInterface *feature_interf soup_requester_default_feature_interface = g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE); - feature_interface->attach = attach; - feature_interface->detach = detach; - feature_interface->add_feature = add_feature; - feature_interface->remove_feature = remove_feature; - feature_interface->has_feature = has_feature; + feature_interface->attach = soup_requester_attach; + feature_interface->detach = soup_requester_detach; + feature_interface->add_feature = soup_requester_add_feature; + feature_interface->remove_feature = soup_requester_remove_feature; + feature_interface->has_feature = soup_requester_has_feature; } -/** - * soup_requester_new: - * - * Creates a new #SoupRequester object, which can be added to - * a #SoupSession with soup_session_add_feature(). - * - * Return value: the new #SoupRequester - * - * Since: 2.34 - */ SoupRequester * soup_requester_new (void) { return g_object_new (SOUP_TYPE_REQUESTER, NULL); } +static void +translate_error (GError *error) +{ + if (error->domain != SOUP_REQUEST_ERROR) + return; + + error->domain = SOUP_REQUESTER_ERROR; + if (error->code == SOUP_REQUEST_ERROR_BAD_URI) + error->code = SOUP_REQUESTER_ERROR_BAD_URI; + else if (error->code == SOUP_REQUEST_ERROR_UNSUPPORTED_URI_SCHEME) + error->code = SOUP_REQUESTER_ERROR_UNSUPPORTED_URI_SCHEME; + else + g_warn_if_reached (); +} + /** * soup_requester_request: - * @requester: a #SoupRequester - * @uri_string: a URI, in string form - * @error: return location for a #GError, or %NULL - * - * Creates a #SoupRequest for retrieving @uri_string. - * - * Return value: (transfer full): a new #SoupRequest, or - * %NULL on error. * - * Since: 2.34 + * Return value: (transfer full): */ SoupRequest * soup_requester_request (SoupRequester *requester, const char *uri_string, GError **error) { - SoupURI *uri; SoupRequest *req; - uri = soup_uri_new (uri_string); - if (!uri) { - g_set_error (error, SOUP_REQUESTER_ERROR, SOUP_REQUESTER_ERROR_BAD_URI, - _("Could not parse URI '%s'"), uri_string); - return NULL; - } + g_return_val_if_fail (SOUP_IS_REQUESTER (requester), NULL); + + req = soup_session_request (requester->priv->session, + uri_string, error); + if (req || !error) + return req; - req = soup_requester_request_uri (requester, uri, error); - soup_uri_free (uri); - return req; + translate_error (*error); + return NULL; } /** * soup_requester_request_uri: - * @requester: a #SoupRequester - * @uri: a #SoupURI representing the URI to retrieve - * @error: return location for a #GError, or %NULL - * - * Creates a #SoupRequest for retrieving @uri. * - * Return value: (transfer full): a new #SoupRequest, or - * %NULL on error. - * - * Since: 2.34 + * Return value: (transfer full): */ SoupRequest * soup_requester_request_uri (SoupRequester *requester, SoupURI *uri, GError **error) { - GType request_type; + SoupRequest *req; g_return_val_if_fail (SOUP_IS_REQUESTER (requester), NULL); - request_type = (GType)GPOINTER_TO_SIZE (g_hash_table_lookup (requester->priv->request_types, uri->scheme)); - if (!request_type) { - g_set_error (error, SOUP_REQUESTER_ERROR, - SOUP_REQUESTER_ERROR_UNSUPPORTED_URI_SCHEME, - _("Unsupported URI scheme '%s'"), uri->scheme); - return NULL; - } - - return g_initable_new (request_type, NULL, error, - "uri", uri, - "session", requester->priv->session, - NULL); -} + req = soup_session_request_uri (requester->priv->session, + uri, error); + if (req || !error) + return req; -/** - * SOUP_REQUESTER_ERROR: - * - * A #GError domain for #SoupRequester errors. Used with - * #SoupRequesterError. - * - * Since: 2.34 - */ -/** - * SoupRequesterError: - * @SOUP_REQUESTER_ERROR_BAD_URI: the URI could not be parsed - * @SOUP_REQUESTER_ERROR_UNSUPPORTED_URI_SCHEME: the URI scheme is not - * supported by this #SoupRequester - * - * A #SoupRequester error. - * - * Since: 2.34 - */ + translate_error (*error); + return NULL; +} GQuark soup_requester_error_quark (void) @@ -287,3 +196,5 @@ soup_requester_error_quark (void) error = g_quark_from_static_string ("soup_requester_error_quark"); return error; } + +G_GNUC_END_IGNORE_DEPRECATIONS diff --git a/libsoup/soup-requester.h b/libsoup/soup-requester.h index a10f1e55..06355131 100644 --- a/libsoup/soup-requester.h +++ b/libsoup/soup-requester.h @@ -24,7 +24,6 @@ #ifdef LIBSOUP_USE_UNSTABLE_REQUEST_API #include <libsoup/soup-types.h> -#include <libsoup/soup-request.h> G_BEGIN_DECLS @@ -48,18 +47,28 @@ typedef struct { GObjectClass parent_class; } SoupRequesterClass; +SOUP_AVAILABLE_IN_2_34 +SOUP_DEPRECATED_IN_2_42 GType soup_requester_get_type (void); +SOUP_AVAILABLE_IN_2_34 +SOUP_DEPRECATED_IN_2_42 SoupRequester *soup_requester_new (void); +SOUP_AVAILABLE_IN_2_34 +SOUP_DEPRECATED_IN_2_42_FOR(soup_session_request) SoupRequest *soup_requester_request (SoupRequester *requester, const char *uri_string, GError **error); +SOUP_AVAILABLE_IN_2_34 +SOUP_DEPRECATED_IN_2_42_FOR(soup_session_request_uri) SoupRequest *soup_requester_request_uri (SoupRequester *requester, SoupURI *uri, GError **error); +SOUP_AVAILABLE_IN_2_34 +SOUP_DEPRECATED_IN_2_42_FOR(SOUP_REQUEST_ERROR) GQuark soup_requester_error_quark (void); #define SOUP_REQUESTER_ERROR soup_requester_error_quark () diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c index 1e462ea2..07d801d1 100644 --- a/libsoup/soup-server.c +++ b/libsoup/soup-server.c @@ -14,7 +14,7 @@ #include "soup-server.h" #include "soup.h" #include "soup-message-private.h" -#include "soup-marshal.h" +#include "soup-misc-private.h" #include "soup-path-map.h" /** @@ -106,6 +106,8 @@ typedef struct { GSList *auth_domains; GMainContext *async_context; + + char **http_aliases, **https_aliases; } SoupServerPrivate; #define SOUP_SERVER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SERVER, SoupServerPrivate)) @@ -122,6 +124,8 @@ enum { PROP_ASYNC_CONTEXT, PROP_RAW_PATHS, PROP_SERVER_HEADER, + PROP_HTTP_ALIASES, + PROP_HTTPS_ALIASES, LAST_PROP }; @@ -142,6 +146,10 @@ soup_server_init (SoupServer *server) SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); priv->handlers = soup_path_map_new ((GDestroyNotify)free_handler); + + priv->http_aliases = g_new (char *, 2); + priv->http_aliases[0] = (char *)g_intern_string ("*"); + priv->http_aliases[1] = NULL; } static void @@ -192,6 +200,9 @@ soup_server_finalize (GObject *object) g_clear_pointer (&priv->loop, g_main_loop_unref); g_clear_pointer (&priv->async_context, g_main_context_unref); + g_free (priv->http_aliases); + g_free (priv->https_aliases); + G_OBJECT_CLASS (soup_server_parent_class)->finalize (object); } @@ -251,6 +262,29 @@ soup_server_constructor (GType type, return server; } +/* priv->http_aliases and priv->https_aliases are stored as arrays of + * *interned* strings, so we can't just use g_strdupv() to set them. + */ +static void +set_aliases (char ***variable, char **value) +{ + int len, i; + + if (*variable) + g_free (*variable); + + if (!value) { + *variable = NULL; + return; + } + + len = g_strv_length (value); + *variable = g_new (char *, len + 1); + for (i = 0; i < len; i++) + (*variable)[i] = (char *)g_intern_string (value[i]); + (*variable)[i] = NULL; +} + static void soup_server_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) @@ -305,6 +339,12 @@ soup_server_set_property (GObject *object, guint prop_id, } else priv->server_header = g_strdup (header); break; + case PROP_HTTP_ALIASES: + set_aliases (&priv->http_aliases, g_value_get_boxed (value)); + break; + case PROP_HTTPS_ALIASES: + set_aliases (&priv->https_aliases, g_value_get_boxed (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -342,6 +382,12 @@ soup_server_get_property (GObject *object, guint prop_id, case PROP_SERVER_HEADER: g_value_set_string (value, priv->server_header); break; + case PROP_HTTP_ALIASES: + g_value_set_boxed (value, priv->http_aliases); + break; + case PROP_HTTPS_ALIASES: + g_value_set_boxed (value, priv->https_aliases); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -387,7 +433,7 @@ soup_server_class_init (SoupServerClass *server_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupServerClass, request_started), NULL, NULL, - _soup_marshal_NONE__OBJECT_POINTER, + NULL, G_TYPE_NONE, 2, SOUP_TYPE_MESSAGE, SOUP_TYPE_CLIENT_CONTEXT); @@ -412,7 +458,7 @@ soup_server_class_init (SoupServerClass *server_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupServerClass, request_read), NULL, NULL, - _soup_marshal_NONE__OBJECT_POINTER, + NULL, G_TYPE_NONE, 2, SOUP_TYPE_MESSAGE, SOUP_TYPE_CLIENT_CONTEXT); @@ -432,7 +478,7 @@ soup_server_class_init (SoupServerClass *server_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupServerClass, request_finished), NULL, NULL, - _soup_marshal_NONE__OBJECT_POINTER, + NULL, G_TYPE_NONE, 2, SOUP_TYPE_MESSAGE, SOUP_TYPE_CLIENT_CONTEXT); @@ -461,7 +507,7 @@ soup_server_class_init (SoupServerClass *server_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupServerClass, request_aborted), NULL, NULL, - _soup_marshal_NONE__OBJECT_POINTER, + NULL, G_TYPE_NONE, 2, SOUP_TYPE_MESSAGE, SOUP_TYPE_CLIENT_CONTEXT); @@ -629,6 +675,68 @@ soup_server_class_init (SoupServerClass *server_class) "Server header", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** + * SoupServer:http-aliases: + * + * A %NULL-terminated array of URI schemes that should be + * considered to be aliases for "http". Eg, if this included + * <literal>"dav"</literal>, than a URI of + * <literal>dav://example.com/path</literal> would be treated + * identically to <literal>http://example.com/path</literal>. + * In particular, this is needed in cases where a client + * sends requests with absolute URIs, where those URIs do + * not use "http:". + * + * The default value is an array containing the single element + * <literal>"*"</literal>, a special value which means that + * any scheme except "https" is considered to be an alias for + * "http". + * + * See also #SoupServer:https-aliases. + * + * Since: 2.44 + */ + /** + * SOUP_SERVERI_HTTP_ALIASES: + * + * Alias for the #SoupServer:http-aliases property, qv. + * + * Since: 2.44 + */ + g_object_class_install_property ( + object_class, PROP_HTTP_ALIASES, + g_param_spec_boxed (SOUP_SERVER_HTTP_ALIASES, + "http aliases", + "URI schemes that are considered aliases for 'http'", + G_TYPE_STRV, + G_PARAM_READWRITE)); + /** + * SoupServer:https-aliases: + * + * A comma-delimited list of URI schemes that should be + * considered to be aliases for "https". See + * #SoupServer:http-aliases for more information. + * + * The default value is %NULL, meaning that no URI schemes + * are considered aliases for "https". + * + * Since: 2.44 + */ + /** + * SOUP_SERVER_HTTPS_ALIASES: + * + * Alias for the #SoupServer:https-aliases property, qv. + * + * Since: 2.44 + **/ + g_object_class_install_property ( + object_class, PROP_HTTPS_ALIASES, + g_param_spec_boxed (SOUP_SERVER_HTTPS_ALIASES, + "https aliases", + "URI schemes that are considered aliases for 'https'", + G_TYPE_STRV, + G_PARAM_READWRITE)); } /** @@ -680,7 +788,8 @@ soup_server_get_port (SoupServer *server) * * 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. + * or %SOUP_SERVER_TLS_CERTIFICATE property to provide it with an SSL + * certificate to use. * * Return value: %TRUE if @server is serving https. **/ @@ -692,7 +801,7 @@ soup_server_is_https (SoupServer *server) g_return_val_if_fail (SOUP_IS_SERVER (server), 0); priv = SOUP_SERVER_GET_PRIVATE (server); - return (priv->ssl_cert_file && priv->ssl_key_file); + return priv->ssl_cert != NULL; } /** @@ -805,7 +914,7 @@ soup_server_get_handler (SoupServer *server, const char *path) } static void -got_headers (SoupMessage *req, SoupClientContext *client) +got_headers (SoupMessage *msg, SoupClientContext *client) { SoupServer *server = client->server; SoupServerPrivate *priv = SOUP_SERVER_GET_PRIVATE (server); @@ -817,17 +926,23 @@ got_headers (SoupMessage *req, SoupClientContext *client) gboolean rejected = FALSE; char *auth_user; + uri = soup_message_get_uri (msg); + if ((soup_server_is_https (server) && !soup_uri_is_https (uri, priv->https_aliases)) || + (!soup_server_is_https (server) && !soup_uri_is_http (uri, priv->http_aliases))) { + soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST); + return; + } + if (!priv->raw_paths) { char *decoded_path; - uri = soup_message_get_uri (req); decoded_path = soup_uri_decode (uri->path); if (strstr (decoded_path, "/../") || g_str_has_suffix (decoded_path, "/..")) { /* Introducing new ".." segments is not allowed */ g_free (decoded_path); - soup_message_set_status (req, SOUP_STATUS_BAD_REQUEST); + soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST); return; } @@ -838,7 +953,7 @@ got_headers (SoupMessage *req, SoupClientContext *client) /* 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", + soup_message_headers_replace (msg->response_headers, "Date", date_string); g_free (date_string); soup_date_free (date); @@ -851,8 +966,8 @@ got_headers (SoupMessage *req, SoupClientContext *client) 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 (soup_auth_domain_covers (domain, msg)) { + auth_user = soup_auth_domain_accepts (domain, msg); if (auth_user) { client->auth_domain = g_object_ref (domain); client->auth_user = auth_user; @@ -870,27 +985,27 @@ got_headers (SoupMessage *req, SoupClientContext *client) for (iter = priv->auth_domains; iter; iter = iter->next) { domain = iter->data; - if (soup_auth_domain_covers (domain, req)) - soup_auth_domain_challenge (domain, req); + if (soup_auth_domain_covers (domain, msg)) + soup_auth_domain_challenge (domain, msg); } } static void -call_handler (SoupMessage *req, SoupClientContext *client) +call_handler (SoupMessage *msg, SoupClientContext *client) { SoupServer *server = client->server; SoupServerHandler *hand; SoupURI *uri; - g_signal_emit (server, signals[REQUEST_READ], 0, req, client); + g_signal_emit (server, signals[REQUEST_READ], 0, msg, client); - if (req->status_code != 0) + if (msg->status_code != 0) return; - uri = soup_message_get_uri (req); + uri = soup_message_get_uri (msg); hand = soup_server_get_handler (server, uri->path); if (!hand) { - soup_message_set_status (req, SOUP_STATUS_NOT_FOUND); + soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); return; } @@ -903,12 +1018,12 @@ call_handler (SoupMessage *req, SoupClientContext *client) form_data_set = NULL; /* Call method handler */ - (*hand->callback) (server, req, + (*hand->callback) (server, msg, uri->path, form_data_set, client, hand->user_data); if (form_data_set) - g_hash_table_destroy (form_data_set); + g_hash_table_unref (form_data_set); } } @@ -1436,6 +1551,10 @@ soup_server_remove_auth_domain (SoupServer *server, SoupAuthDomain *auth_domain) * 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. + * + * This must only be called on #SoupMessages which were created by the + * #SoupServer and are currently doing I/O, such as those passed into a + * #SoupServerCallback or emitted in a #SoupServer::request-read signal. **/ void soup_server_pause_message (SoupServer *server, @@ -1457,6 +1576,10 @@ soup_server_pause_message (SoupServer *server, * chunked response. * * I/O won't actually resume until you return to the main loop. + * + * This must only be called on #SoupMessages which were created by the + * #SoupServer and are currently doing I/O, such as those passed into a + * #SoupServerCallback or emitted in a #SoupServer::request-read signal. **/ void soup_server_unpause_message (SoupServer *server, diff --git a/libsoup/soup-server.h b/libsoup/soup-server.h index e1c9bbfb..0d09322e 100644 --- a/libsoup/soup-server.h +++ b/libsoup/soup-server.h @@ -64,6 +64,8 @@ typedef void (*SoupServerCallback) (SoupServer *server, #define SOUP_SERVER_ASYNC_CONTEXT "async-context" #define SOUP_SERVER_RAW_PATHS "raw-paths" #define SOUP_SERVER_SERVER_HEADER "server-header" +#define SOUP_SERVER_HTTP_ALIASES "http-aliases" +#define SOUP_SERVER_HTTPS_ALIASES "https-aliases" SoupServer *soup_server_new (const char *optname1, ...) G_GNUC_NULL_TERMINATED; diff --git a/libsoup/soup-session-async.c b/libsoup/soup-session-async.c index 46900552..b9353480 100644 --- a/libsoup/soup-session-async.c +++ b/libsoup/soup-session-async.c @@ -9,64 +9,44 @@ #include <config.h> #endif -#define LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY - #include "soup-session-async.h" #include "soup.h" #include "soup-session-private.h" #include "soup-message-private.h" #include "soup-message-queue.h" +#include "soup-misc-private.h" /** * SECTION:soup-session-async - * @short_description: Soup session for asynchronous (main-loop-based) I/O. + * @short_description: (Deprecated) SoupSession 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. + * non-blocking I/O via the glib main loop for all I/O. + * + * As of libsoup 2.42, this is deprecated in favor of the plain + * #SoupSession class (which uses both asynchronous and synchronous + * I/O, depending on the API used). See the <link + * linkend="libsoup-session-porting">porting guide</link>. **/ -static void run_queue (SoupSessionAsync *sa); -static void do_idle_run_queue (SoupSession *session); - -static void send_request_running (SoupSession *session, SoupMessageQueueItem *item); -static void send_request_restarted (SoupSession *session, SoupMessageQueueItem *item); -static void send_request_finished (SoupSession *session, SoupMessageQueueItem *item); - G_DEFINE_TYPE (SoupSessionAsync, soup_session_async, SOUP_TYPE_SESSION) -typedef struct { - SoupSessionAsync *sa; - gboolean disposed; - -} SoupSessionAsyncPrivate; -#define SOUP_SESSION_ASYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_ASYNC, SoupSessionAsyncPrivate)) - static void soup_session_async_init (SoupSessionAsync *sa) { - SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (sa); - - priv->sa = sa; -} - -static void -soup_session_async_dispose (GObject *object) -{ - SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (object); - - priv->disposed = TRUE; - - G_OBJECT_CLASS (soup_session_async_parent_class)->dispose (object); } - /** * soup_session_async_new: * * Creates an asynchronous #SoupSession with the default options. * * Return value: the new session. + * + * Deprecated: #SoupSessionAsync is deprecated; use a plain + * #SoupSession, created with soup_session_new(). See the <link + * linkend="libsoup-session-porting">porting guide</link>. **/ SoupSession * soup_session_async_new (void) @@ -82,6 +62,10 @@ soup_session_async_new (void) * Creates an asynchronous #SoupSession with the specified options. * * Return value: the new session. + * + * Deprecated: #SoupSessionAsync is deprecated; use a plain + * #SoupSession, created with soup_session_new_with_options(). See the + * <link linkend="libsoup-session-porting">porting guide</link>. **/ SoupSession * soup_session_async_new_with_options (const char *optname1, ...) @@ -97,331 +81,23 @@ soup_session_async_new_with_options (const char *optname1, ...) return session; } -static void -connection_closed (SoupConnection *conn, gpointer session) -{ - /* Run the queue in case anyone was waiting for a connection - * to be closed. - */ - do_idle_run_queue (session); -} - -static void -message_completed (SoupMessage *msg, gpointer user_data) -{ - SoupMessageQueueItem *item = user_data; - - do_idle_run_queue (item->session); - - if (item->state != SOUP_MESSAGE_RESTARTING) - item->state = SOUP_MESSAGE_FINISHING; -} - -static void -tunnel_complete (SoupMessageQueueItem *item) -{ - SoupSession *session = item->session; - - soup_message_finished (item->msg); - if (item->related->msg->status_code) - item->related->state = SOUP_MESSAGE_FINISHING; - else - soup_message_set_https_status (item->related->msg, item->conn); - - do_idle_run_queue (session); - soup_message_queue_item_unref (item->related); - soup_session_unqueue_item (session, item); - soup_message_queue_item_unref (item); - g_object_unref (session); -} - -static void -ssl_tunnel_completed (SoupConnection *conn, guint status, gpointer user_data) -{ - SoupMessageQueueItem *item = user_data; - - if (SOUP_STATUS_IS_SUCCESSFUL (status)) { - g_signal_connect (item->conn, "disconnected", - G_CALLBACK (connection_closed), item->session); - soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE); - soup_connection_set_state (item->conn, SOUP_CONNECTION_IN_USE); - - item->related->state = SOUP_MESSAGE_READY; - } else { - if (item->conn) - soup_connection_disconnect (item->conn); - soup_message_set_status (item->related->msg, SOUP_STATUS_SSL_FAILED); - } - - tunnel_complete (item); -} - -static void -tunnel_message_completed (SoupMessage *msg, gpointer user_data) -{ - SoupMessageQueueItem *item = user_data; - SoupSession *session = item->session; - - if (item->state == SOUP_MESSAGE_RESTARTING) { - soup_message_restarted (msg); - if (item->conn) { - item->state = SOUP_MESSAGE_RUNNING; - soup_session_send_queue_item (session, item, tunnel_message_completed); - return; - } - - soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN); - } - - item->state = SOUP_MESSAGE_FINISHED; - - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - if (item->conn) - soup_connection_disconnect (item->conn); - if (msg->status_code == SOUP_STATUS_TRY_AGAIN) { - item->related->state = SOUP_MESSAGE_AWAITING_CONNECTION; - soup_message_queue_item_set_connection (item->related, NULL); - } else - soup_message_set_status (item->related->msg, msg->status_code); - - tunnel_complete (item); - return; - } - - soup_connection_start_ssl_async (item->conn, item->cancellable, - ssl_tunnel_completed, item); -} - -static void -got_connection (SoupConnection *conn, guint status, gpointer user_data) -{ - SoupMessageQueueItem *item = user_data; - SoupSession *session = item->session; - - if (item->state != SOUP_MESSAGE_CONNECTING) { - soup_connection_disconnect (conn); - do_idle_run_queue (session); - soup_message_queue_item_unref (item); - g_object_unref (session); - return; - } - - soup_message_set_https_status (item->msg, conn); - - if (status != SOUP_STATUS_OK) { - soup_connection_disconnect (conn); - - if (status == SOUP_STATUS_TRY_AGAIN) { - soup_message_queue_item_set_connection (item, NULL); - item->state = SOUP_MESSAGE_AWAITING_CONNECTION; - } else { - soup_session_set_item_status (session, item, status); - item->state = SOUP_MESSAGE_FINISHING; - } - - do_idle_run_queue (session); - soup_message_queue_item_unref (item); - g_object_unref (session); - return; - } - - if (soup_connection_is_tunnelled (conn)) { - SoupMessageQueueItem *tunnel_item; - - item->state = SOUP_MESSAGE_TUNNELING; - - tunnel_item = soup_session_make_connect_message (session, conn); - tunnel_item->related = item; - soup_session_send_queue_item (session, tunnel_item, tunnel_message_completed); - return; - } - - item->state = SOUP_MESSAGE_READY; - g_signal_connect (conn, "disconnected", - G_CALLBACK (connection_closed), session); - run_queue ((SoupSessionAsync *)session); - soup_message_queue_item_unref (item); - g_object_unref (session); -} - -static void -process_queue_item (SoupMessageQueueItem *item, - gboolean *should_prune, - gboolean loop) -{ - SoupSession *session = item->session; - - if (item->async_context != soup_session_get_async_context (session)) - return; - - do { - if (item->paused) - return; - - switch (item->state) { - case SOUP_MESSAGE_STARTING: - item->state = SOUP_MESSAGE_AWAITING_CONNECTION; - break; - - case SOUP_MESSAGE_AWAITING_CONNECTION: - if (!soup_session_get_connection (session, item, should_prune)) - return; - - if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) { - item->state = SOUP_MESSAGE_READY; - break; - } - - item->state = SOUP_MESSAGE_CONNECTING; - soup_message_queue_item_ref (item); - g_object_ref (session); - soup_connection_connect_async (item->conn, item->cancellable, - got_connection, item); - return; - - case SOUP_MESSAGE_READY: - item->state = SOUP_MESSAGE_RUNNING; - soup_session_send_queue_item (session, item, message_completed); - if (item->new_api) - send_request_running (session, item); - break; - - case SOUP_MESSAGE_RESTARTING: - item->state = SOUP_MESSAGE_STARTING; - soup_message_restarted (item->msg); - if (item->new_api) - send_request_restarted (session, item); - break; - - case SOUP_MESSAGE_FINISHING: - item->state = SOUP_MESSAGE_FINISHED; - soup_message_finished (item->msg); - if (item->state != SOUP_MESSAGE_FINISHED) - break; - - g_object_ref (session); - soup_session_unqueue_item (session, item); - if (item->callback) - item->callback (session, item->msg, item->callback_data); - else if (item->new_api) - send_request_finished (session, item); - do_idle_run_queue (session); - g_object_unref (session); - return; - - default: - /* Nothing to do with this message in any - * other state. - */ - return; - } - } while (loop && item->state != SOUP_MESSAGE_FINISHED); -} - -static void -run_queue (SoupSessionAsync *sa) -{ - SoupSession *session = SOUP_SESSION (sa); - SoupMessageQueue *queue = soup_session_get_queue (session); - SoupMessageQueueItem *item; - SoupMessage *msg; - gboolean try_pruning = TRUE, should_prune = FALSE; - - g_object_ref (session); - soup_session_cleanup_connections (session, FALSE); - - try_again: - for (item = soup_message_queue_first (queue); - item; - item = soup_message_queue_next (queue, item)) { - msg = item->msg; - - /* CONNECT messages are handled specially */ - if (msg->method != SOUP_METHOD_CONNECT) - process_queue_item (item, &should_prune, TRUE); - } - - if (try_pruning && should_prune) { - /* There is at least one message in the queue that - * could be sent if we pruned an idle connection from - * some other server. - */ - if (soup_session_cleanup_connections (session, TRUE)) { - try_pruning = should_prune = FALSE; - goto try_again; - } - } - - g_object_unref (session); -} - -static gboolean -idle_run_queue (gpointer user_data) -{ - SoupSessionAsyncPrivate *priv = user_data; - - if (priv->disposed) - return FALSE; - - /* Ensure that the source is destroyed before running the queue */ - g_source_destroy (g_main_current_source ()); - - run_queue (priv->sa); - return FALSE; -} - -static void -do_idle_run_queue (SoupSession *session) -{ - SoupSessionAsyncPrivate *priv = SOUP_SESSION_ASYNC_GET_PRIVATE (session); - GMainContext *async_context = soup_session_get_async_context (session); - GSource *source; - - if (priv->disposed) - return; - - /* We use priv rather than session as the source data, because - * other parts of libsoup (or the calling app) may have sources - * using the session as the source data. - */ - - source = g_main_context_find_source_by_user_data (async_context, priv); - if (source) - return; - - source = soup_add_completion (async_context, idle_run_queue, priv); -} - -static void -soup_session_async_queue_message (SoupSession *session, SoupMessage *req, - SoupSessionCallback callback, gpointer user_data) -{ - SoupMessageQueueItem *item; - - item = soup_session_append_queue_item (session, req, callback, user_data); - soup_message_queue_item_unref (item); - - do_idle_run_queue (session); -} - static guint -soup_session_async_send_message (SoupSession *session, SoupMessage *req) +soup_session_async_send_message (SoupSession *session, SoupMessage *msg) { SoupMessageQueueItem *item; GMainContext *async_context = soup_session_get_async_context (session); - soup_session_async_queue_message (session, req, NULL, NULL); - - item = soup_message_queue_lookup (soup_session_get_queue (session), req); - g_return_val_if_fail (item != NULL, SOUP_STATUS_MALFORMED); + item = soup_session_append_queue_item (session, msg, TRUE, FALSE, + NULL, NULL); + soup_session_kick_queue (session); while (item->state != SOUP_MESSAGE_FINISHED) g_main_context_iteration (async_context, TRUE); soup_message_queue_item_unref (item); - return req->status_code; + return msg->status_code; } static void @@ -430,7 +106,6 @@ soup_session_async_cancel_message (SoupSession *session, SoupMessage *msg, { SoupMessageQueue *queue; SoupMessageQueueItem *item; - gboolean dummy; SOUP_SESSION_CLASS (soup_session_async_parent_class)-> cancel_message (session, msg, status_code); @@ -452,304 +127,17 @@ soup_session_async_cancel_message (SoupSession *session, SoupMessage *msg, item->state = SOUP_MESSAGE_FINISHING; if (item->state != SOUP_MESSAGE_FINISHED) - process_queue_item (item, &dummy, FALSE); - - soup_message_queue_item_unref (item); -} - -static void -got_passwords (SoupPasswordManager *password_manager, SoupMessage *msg, - SoupAuth *auth, gboolean retrying, gpointer session) -{ - soup_session_unpause_message (session, msg); - SOUP_SESSION_CLASS (soup_session_async_parent_class)-> - auth_required (session, msg, auth, retrying); - g_object_unref (auth); -} - -static void -soup_session_async_auth_required (SoupSession *session, SoupMessage *msg, - SoupAuth *auth, gboolean retrying) -{ - SoupSessionFeature *password_manager; - - password_manager = soup_session_get_feature_for_message ( - session, SOUP_TYPE_PASSWORD_MANAGER, msg); - if (password_manager) { - soup_session_pause_message (session, msg); - g_object_ref (auth); - soup_password_manager_get_passwords_async ( - SOUP_PASSWORD_MANAGER (password_manager), - msg, auth, retrying, - soup_session_get_async_context (session), - NULL, /* FIXME cancellable */ - got_passwords, session); - } else { - SOUP_SESSION_CLASS (soup_session_async_parent_class)-> - auth_required (session, msg, auth, retrying); - } -} - -static void -soup_session_async_kick (SoupSession *session) -{ - do_idle_run_queue (session); -} - - -static void -send_request_return_result (SoupMessageQueueItem *item, - gpointer stream, GError *error) -{ - GSimpleAsyncResult *simple; - - simple = item->result; - item->result = NULL; - - if (error) - g_simple_async_result_take_error (simple, error); - else if (SOUP_STATUS_IS_TRANSPORT_ERROR (item->msg->status_code)) { - if (stream) - g_object_unref (stream); - g_simple_async_result_set_error (simple, - SOUP_HTTP_ERROR, - item->msg->status_code, - "%s", - item->msg->reason_phrase); - } else - g_simple_async_result_set_op_res_gpointer (simple, stream, g_object_unref); - - g_simple_async_result_complete (simple); - g_object_unref (simple); -} - -static void -send_request_restarted (SoupSession *session, SoupMessageQueueItem *item) -{ - /* We won't be needing this, then. */ - g_object_set_data (G_OBJECT (item->msg), "SoupSessionAsync:ostream", NULL); - item->io_started = FALSE; -} - -static void -send_request_finished (SoupSession *session, SoupMessageQueueItem *item) -{ - GMemoryOutputStream *mostream; - GInputStream *istream = NULL; - GError *error = NULL; - - if (!item->result) { - /* Something else already took care of it. */ - return; - } - - mostream = g_object_get_data (G_OBJECT (item->msg), "SoupSessionAsync:ostream"); - if (mostream) { - gpointer data; - gssize size; - - /* We thought it would be requeued, but it wasn't, so - * return the original body. - */ - size = g_memory_output_stream_get_data_size (mostream); - data = size ? g_memory_output_stream_steal_data (mostream) : g_strdup (""); - istream = g_memory_input_stream_new_from_data (data, size, g_free); - } else if (item->io_started) { - /* The message finished before becoming readable. This - * will happen, eg, if it's cancelled from got-headers. - * Do nothing; the op will complete via read_ready_cb() - * after we return; - */ - return; - } else { - /* The message finished before even being started; - * probably a tunnel connect failure. - */ - istream = g_memory_input_stream_new (); - } - - send_request_return_result (item, istream, error); -} - -static void -send_async_spliced (GObject *source, GAsyncResult *result, gpointer user_data) -{ - SoupMessageQueueItem *item = user_data; - GInputStream *istream = g_object_get_data (source, "istream"); - - GError *error = NULL; - - /* If the message was cancelled, it will be completed via other means */ - if (g_cancellable_is_cancelled (item->cancellable) || - !item->result) { - soup_message_queue_item_unref (item); - return; - } - - if (g_output_stream_splice_finish (G_OUTPUT_STREAM (source), - result, &error) == -1) { - send_request_return_result (item, NULL, error); - return; - } + soup_session_process_queue_item (session, item, NULL, FALSE); - /* Otherwise either restarted or finished will eventually be called. - * It should be safe to call the sync close() method here since - * the message body has already been written. - */ - g_input_stream_close (istream, NULL, NULL); - do_idle_run_queue (item->session); soup_message_queue_item_unref (item); } static void -send_async_maybe_complete (SoupMessageQueueItem *item, - GInputStream *stream) -{ - if (item->msg->status_code == SOUP_STATUS_UNAUTHORIZED || - item->msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED || - soup_session_would_redirect (item->session, item->msg)) { - GOutputStream *ostream; - - /* Message may be requeued, so gather the current message body... */ - ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); - g_object_set_data_full (G_OBJECT (item->msg), "SoupSessionAsync:ostream", - ostream, g_object_unref); - - g_object_set_data_full (G_OBJECT (ostream), "istream", - stream, g_object_unref); - - /* Give the splice op its own ref on item */ - soup_message_queue_item_ref (item); - g_output_stream_splice_async (ostream, stream, - /* We can't use CLOSE_SOURCE because it - * might get closed in the wrong thread. - */ - G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, - G_PRIORITY_DEFAULT, - item->cancellable, - send_async_spliced, item); - return; - } - - send_request_return_result (item, stream, NULL); -} - -static void try_run_until_read (SoupMessageQueueItem *item); - -static gboolean -read_ready_cb (SoupMessage *msg, gpointer user_data) -{ - SoupMessageQueueItem *item = user_data; - - try_run_until_read (item); - return FALSE; -} - -static void -try_run_until_read (SoupMessageQueueItem *item) -{ - GError *error = NULL; - GInputStream *stream = NULL; - GSource *source; - - if (soup_message_io_run_until_read (item->msg, item->cancellable, &error)) - stream = soup_message_io_get_response_istream (item->msg, &error); - if (stream) { - send_async_maybe_complete (item, stream); - return; - } - - if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) { - item->state = SOUP_MESSAGE_RESTARTING; - soup_message_io_finished (item->msg); - g_error_free (error); - return; - } - - if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { - send_request_return_result (item, NULL, error); - return; - } - - g_clear_error (&error); - source = soup_message_io_get_source (item->msg, item->cancellable, - read_ready_cb, item); - g_source_attach (source, soup_session_get_async_context (item->session)); - g_source_unref (source); -} - -static void -send_request_running (SoupSession *session, SoupMessageQueueItem *item) -{ - item->io_started = TRUE; - try_run_until_read (item); -} - -void -soup_session_send_request_async (SoupSession *session, - SoupMessage *msg, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) -{ - SoupMessageQueueItem *item; - gboolean use_thread_context; - - g_return_if_fail (SOUP_IS_SESSION_ASYNC (session)); - - g_object_get (G_OBJECT (session), - SOUP_SESSION_USE_THREAD_CONTEXT, &use_thread_context, - NULL); - g_return_if_fail (use_thread_context); - - soup_session_async_queue_message (session, msg, NULL, NULL); - - item = soup_message_queue_lookup (soup_session_get_queue (session), msg); - g_return_if_fail (item != NULL); - - item->new_api = TRUE; - item->result = g_simple_async_result_new (G_OBJECT (session), - callback, user_data, - soup_session_send_request_async); - g_simple_async_result_set_op_res_gpointer (item->result, item, (GDestroyNotify) soup_message_queue_item_unref); - - if (cancellable) { - g_object_unref (item->cancellable); - item->cancellable = g_object_ref (cancellable); - } -} - -GInputStream * -soup_session_send_request_finish (SoupSession *session, - GAsyncResult *result, - GError **error) -{ - GSimpleAsyncResult *simple; - - g_return_val_if_fail (SOUP_IS_SESSION_ASYNC (session), NULL); - g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (session), soup_session_send_request_async), NULL); - - simple = G_SIMPLE_ASYNC_RESULT (result); - if (g_simple_async_result_propagate_error (simple, error)) - return NULL; - return g_object_ref (g_simple_async_result_get_op_res_gpointer (simple)); -} - -static void soup_session_async_class_init (SoupSessionAsyncClass *soup_session_async_class) { SoupSessionClass *session_class = SOUP_SESSION_CLASS (soup_session_async_class); - GObjectClass *object_class = G_OBJECT_CLASS (session_class); - - g_type_class_add_private (soup_session_async_class, - sizeof (SoupSessionAsyncPrivate)); /* virtual method override */ - session_class->queue_message = soup_session_async_queue_message; session_class->send_message = soup_session_async_send_message; session_class->cancel_message = soup_session_async_cancel_message; - session_class->auth_required = soup_session_async_auth_required; - session_class->kick = soup_session_async_kick; - - object_class->dispose = soup_session_async_dispose; } diff --git a/libsoup/soup-session-async.h b/libsoup/soup-session-async.h index 9fb9cfe0..96217638 100644 --- a/libsoup/soup-session-async.h +++ b/libsoup/soup-session-async.h @@ -35,9 +35,11 @@ typedef struct { GType soup_session_async_get_type (void); +#ifndef SOUP_DISABLE_DEPRECATED SoupSession *soup_session_async_new (void); SoupSession *soup_session_async_new_with_options (const char *optname1, ...) G_GNUC_NULL_TERMINATED; +#endif G_END_DECLS diff --git a/libsoup/soup-session-feature.c b/libsoup/soup-session-feature.c index 9f9b235a..560bb8f7 100644 --- a/libsoup/soup-session-feature.c +++ b/libsoup/soup-session-feature.c @@ -52,7 +52,7 @@ * Since: 2.24 **/ -static void soup_session_feature_default_init (SoupSessionFeatureInterface *interface); +static void soup_session_feature_default_init (SoupSessionFeatureInterface *iface); G_DEFINE_INTERFACE (SoupSessionFeature, soup_session_feature, G_TYPE_OBJECT) @@ -142,10 +142,10 @@ soup_session_feature_detach (SoupSessionFeature *feature, } static void -soup_session_feature_default_init (SoupSessionFeatureInterface *interface) +soup_session_feature_default_init (SoupSessionFeatureInterface *iface) { - interface->attach = soup_session_feature_real_attach; - interface->detach = soup_session_feature_real_detach; + iface->attach = soup_session_feature_real_attach; + iface->detach = soup_session_feature_real_detach; } /** diff --git a/libsoup/soup-session-feature.h b/libsoup/soup-session-feature.h index aa64aa0f..962c8e11 100644 --- a/libsoup/soup-session-feature.h +++ b/libsoup/soup-session-feature.h @@ -46,17 +46,23 @@ typedef struct { } SoupSessionFeatureInterface; +SOUP_AVAILABLE_IN_2_24 GType soup_session_feature_get_type (void); +SOUP_AVAILABLE_IN_2_24 void soup_session_feature_attach (SoupSessionFeature *feature, SoupSession *session); +SOUP_AVAILABLE_IN_2_24 void soup_session_feature_detach (SoupSessionFeature *feature, SoupSession *session); +SOUP_AVAILABLE_IN_2_34 gboolean soup_session_feature_add_feature (SoupSessionFeature *feature, GType type); +SOUP_AVAILABLE_IN_2_34 gboolean soup_session_feature_remove_feature (SoupSessionFeature *feature, GType type); +SOUP_AVAILABLE_IN_2_34 gboolean soup_session_feature_has_feature (SoupSessionFeature *feature, GType type); diff --git a/libsoup/soup-session-private.h b/libsoup/soup-session-private.h index b5e24e33..dc4d300b 100644 --- a/libsoup/soup-session-private.h +++ b/libsoup/soup-session-private.h @@ -17,23 +17,12 @@ SoupMessageQueue *soup_session_get_queue (SoupSession *s SoupMessageQueueItem *soup_session_append_queue_item (SoupSession *session, SoupMessage *msg, + gboolean async, + gboolean new_api, SoupSessionCallback callback, gpointer user_data); -SoupMessageQueueItem *soup_session_make_connect_message (SoupSession *session, - SoupConnection *conn); -gboolean soup_session_get_connection (SoupSession *session, - SoupMessageQueueItem *item, - gboolean *try_pruning); -gboolean soup_session_cleanup_connections (SoupSession *session, - gboolean prune_idle); -void soup_session_send_queue_item (SoupSession *session, - SoupMessageQueueItem *item, - SoupMessageCompletionFn completion_cb); -void soup_session_unqueue_item (SoupSession *session, - SoupMessageQueueItem *item); -void soup_session_set_item_status (SoupSession *session, - SoupMessageQueueItem *item, - guint status_code); + +void soup_session_kick_queue (SoupSession *session); GInputStream *soup_session_send_request (SoupSession *session, SoupMessage *msg, @@ -49,6 +38,11 @@ GInputStream *soup_session_send_request_finish (SoupSession *s GAsyncResult *result, GError **error); +void soup_session_process_queue_item (SoupSession *session, + SoupMessageQueueItem *item, + gboolean *should_prune, + gboolean loop); + G_END_DECLS #endif /* SOUP_SESSION_PRIVATE_H */ diff --git a/libsoup/soup-session-sync.c b/libsoup/soup-session-sync.c index 96ab9165..374f22bd 100644 --- a/libsoup/soup-session-sync.c +++ b/libsoup/soup-session-sync.c @@ -9,8 +9,6 @@ #include <config.h> #endif -#define LIBSOUP_I_HAVE_READ_BUG_594377_AND_KNOW_SOUP_PASSWORD_MANAGER_MIGHT_GO_AWAY - #include "soup-session-sync.h" #include "soup.h" #include "soup-session-private.h" @@ -19,64 +17,35 @@ /** * SECTION:soup-session-sync - * @short_description: Soup session for blocking I/O in multithreaded - * programs. + * @short_description: (Deprecated) SoupSession 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. * - * You can use #SoupSessionSync from multiple threads concurrently. - * Eg, you can send a #SoupMessage in one thread, and then while - * waiting for the response, send another #SoupMessage from another - * thread. You can also send a message from one thread and then call - * soup_session_cancel_message() on it from any other thread (although - * you need to be careful to avoid race conditions, where the message - * finishes and is then unreffed by the sending thread just before you - * cancel it). - * - * However, the majority of other types and methods in libsoup are not - * MT-safe. In particular, you <emphasis>cannot</emphasis> modify or - * examine a #SoupMessage while it is being transmitted by - * #SoupSessionSync in another thread. Once a message has been handed - * off to #SoupSessionSync, it can only be manipulated from its signal - * handler callbacks, until I/O is complete. + * As of libsoup 2.42, this is deprecated in favor of the plain + * #SoupSession class (which uses both asynchronous and synchronous + * I/O, depending on the API used). See the <link + * linkend="libsoup-session-porting">porting guide</link>. **/ -typedef struct { - GMutex lock; - GCond cond; -} SoupSessionSyncPrivate; -#define SOUP_SESSION_SYNC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION_SYNC, SoupSessionSyncPrivate)) - G_DEFINE_TYPE (SoupSessionSync, soup_session_sync, SOUP_TYPE_SESSION) static void soup_session_sync_init (SoupSessionSync *ss) { - SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (ss); - - g_mutex_init (&priv->lock); - g_cond_init (&priv->cond); -} - -static void -soup_session_sync_finalize (GObject *object) -{ - SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (object); - - g_mutex_clear (&priv->lock); - g_cond_clear (&priv->cond); - - G_OBJECT_CLASS (soup_session_sync_parent_class)->finalize (object); } - /** * soup_session_sync_new: * * Creates an synchronous #SoupSession with the default options. * * Return value: the new session. + * + * Deprecated: #SoupSessionSync is deprecated; use a plain + * #SoupSession, created with soup_session_new(). See the <link + * linkend="libsoup-session-porting">porting guide</link>. **/ SoupSession * soup_session_sync_new (void) @@ -92,6 +61,10 @@ soup_session_sync_new (void) * Creates an synchronous #SoupSession with the specified options. * * Return value: the new session. + * + * Deprecated: #SoupSessionSync is deprecated; use a plain + * #SoupSession, created with soup_session_new_with_options(). See the + * <link linkend="libsoup-session-porting">porting guide</link>. **/ SoupSession * soup_session_sync_new_with_options (const char *optname1, ...) @@ -107,197 +80,12 @@ soup_session_sync_new_with_options (const char *optname1, ...) return session; } -static guint -tunnel_connect (SoupSession *session, SoupMessageQueueItem *related) -{ - SoupConnection *conn = related->conn; - SoupMessageQueueItem *item; - guint status; - - g_object_ref (conn); - - item = soup_session_make_connect_message (session, conn); - do { - soup_session_send_queue_item (session, item, NULL); - status = item->msg->status_code; - if (item->state == SOUP_MESSAGE_RESTARTING && - soup_message_io_in_progress (item->msg)) { - soup_message_restarted (item->msg); - item->state = SOUP_MESSAGE_RUNNING; - } else { - if (item->state == SOUP_MESSAGE_RESTARTING) - status = SOUP_STATUS_TRY_AGAIN; - item->state = SOUP_MESSAGE_FINISHED; - soup_message_finished (item->msg); - } - } while (item->state == SOUP_MESSAGE_STARTING); - soup_session_unqueue_item (session, item); - soup_message_queue_item_unref (item); - - if (SOUP_STATUS_IS_SUCCESSFUL (status)) { - if (!soup_connection_start_ssl_sync (conn, related->cancellable)) - status = SOUP_STATUS_SSL_FAILED; - soup_message_set_https_status (related->msg, conn); - } - - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) - soup_connection_disconnect (conn); - - g_object_unref (conn); - return status; -} - -static void -get_connection (SoupMessageQueueItem *item) -{ - SoupSession *session = item->session; - SoupMessage *msg = item->msg; - gboolean try_pruning = FALSE; - guint status; - -try_again: - soup_session_cleanup_connections (session, FALSE); - - if (!soup_session_get_connection (session, item, &try_pruning)) { - if (!try_pruning) - return; - soup_session_cleanup_connections (session, TRUE); - if (!soup_session_get_connection (session, item, &try_pruning)) - return; - try_pruning = FALSE; - } - - if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) { - item->state = SOUP_MESSAGE_READY; - return; - } - - status = soup_connection_connect_sync (item->conn, item->cancellable); - if (status == SOUP_STATUS_TRY_AGAIN) { - soup_connection_disconnect (item->conn); - soup_message_queue_item_set_connection (item, NULL); - goto try_again; - } - - soup_message_set_https_status (msg, item->conn); - - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - if (!msg->status_code) - soup_session_set_item_status (session, item, status); - item->state = SOUP_MESSAGE_FINISHING; - soup_connection_disconnect (item->conn); - soup_message_queue_item_set_connection (item, NULL); - return; - } - - if (soup_connection_is_tunnelled (item->conn)) { - status = tunnel_connect (session, item); - if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { - soup_connection_disconnect (item->conn); - soup_message_queue_item_set_connection (item, NULL); - if (status == SOUP_STATUS_TRY_AGAIN) - goto try_again; - soup_session_set_item_status (session, item, status); - item->state = SOUP_MESSAGE_FINISHING; - return; - } - } - - item->state = SOUP_MESSAGE_READY; -} - -static void process_queue_item (SoupMessageQueueItem *item); - -static void -new_api_message_completed (SoupMessage *msg, gpointer user_data) -{ - SoupMessageQueueItem *item = user_data; - - if (item->state != SOUP_MESSAGE_RESTARTING) { - item->state = SOUP_MESSAGE_FINISHING; - process_queue_item (item); - } -} - -static void -process_queue_item (SoupMessageQueueItem *item) -{ - SoupSession *session = item->session; - SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session); - - soup_message_queue_item_ref (item); - - do { - if (item->paused) { - g_mutex_lock (&priv->lock); - while (item->paused) - g_cond_wait (&priv->cond, &priv->lock); - g_mutex_unlock (&priv->lock); - } - - switch (item->state) { - case SOUP_MESSAGE_STARTING: - item->state = SOUP_MESSAGE_AWAITING_CONNECTION; - break; - - case SOUP_MESSAGE_AWAITING_CONNECTION: - g_mutex_lock (&priv->lock); - do { - get_connection (item); - if (item->state == SOUP_MESSAGE_AWAITING_CONNECTION) - g_cond_wait (&priv->cond, &priv->lock); - } while (item->state == SOUP_MESSAGE_AWAITING_CONNECTION); - g_mutex_unlock (&priv->lock); - break; - - case SOUP_MESSAGE_READY: - item->state = SOUP_MESSAGE_RUNNING; - - if (item->new_api) { - soup_session_send_queue_item (item->session, item, new_api_message_completed); - goto out; - } - - soup_session_send_queue_item (item->session, item, NULL); - if (item->state != SOUP_MESSAGE_RESTARTING) - item->state = SOUP_MESSAGE_FINISHING; - break; - - case SOUP_MESSAGE_RUNNING: - g_warn_if_fail (item->new_api); - item->state = SOUP_MESSAGE_FINISHING; - break; - - case SOUP_MESSAGE_RESTARTING: - item->state = SOUP_MESSAGE_STARTING; - soup_message_restarted (item->msg); - break; - - case SOUP_MESSAGE_FINISHING: - item->state = SOUP_MESSAGE_FINISHED; - soup_message_finished (item->msg); - soup_session_unqueue_item (session, item); - g_cond_broadcast (&priv->cond); - break; - - default: - g_warn_if_reached (); - item->state = SOUP_MESSAGE_FINISHING; - break; - } - } while (item->state != SOUP_MESSAGE_FINISHED); - - out: - soup_message_queue_item_unref (item); -} - static gboolean queue_message_callback (gpointer data) { SoupMessageQueueItem *item = data; item->callback (item->session, item->msg, item->callback_data); - g_object_unref (item->session); soup_message_queue_item_unref (item); return FALSE; } @@ -307,14 +95,12 @@ queue_message_thread (gpointer data) { SoupMessageQueueItem *item = data; - process_queue_item (item); + soup_session_process_queue_item (item->session, item, NULL, TRUE); if (item->callback) { soup_add_completion (soup_session_get_async_context (item->session), queue_message_callback, item); - } else { - g_object_unref (item->session); + } else soup_message_queue_item_unref (item); - } return NULL; } @@ -326,234 +112,18 @@ soup_session_sync_queue_message (SoupSession *session, SoupMessage *msg, SoupMessageQueueItem *item; GThread *thread; - g_object_ref (session); - item = soup_session_append_queue_item (session, msg, callback, user_data); + item = soup_session_append_queue_item (session, msg, FALSE, FALSE, + callback, user_data); thread = g_thread_new ("SoupSessionSync:queue_message", queue_message_thread, item); g_thread_unref (thread); } -static guint -soup_session_sync_send_message (SoupSession *session, SoupMessage *msg) -{ - SoupMessageQueueItem *item; - guint status; - - item = soup_session_append_queue_item (session, msg, NULL, NULL); - process_queue_item (item); - status = msg->status_code; - soup_message_queue_item_unref (item); - return status; -} - -static void -soup_session_sync_cancel_message (SoupSession *session, SoupMessage *msg, guint status_code) -{ - SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session); - - g_mutex_lock (&priv->lock); - SOUP_SESSION_CLASS (soup_session_sync_parent_class)->cancel_message (session, msg, status_code); - g_cond_broadcast (&priv->cond); - g_mutex_unlock (&priv->lock); -} - -static void -soup_session_sync_auth_required (SoupSession *session, SoupMessage *msg, - SoupAuth *auth, gboolean retrying) -{ - SoupSessionFeature *password_manager; - - password_manager = soup_session_get_feature_for_message ( - session, SOUP_TYPE_PASSWORD_MANAGER, msg); - if (password_manager) { - soup_password_manager_get_passwords_sync ( - SOUP_PASSWORD_MANAGER (password_manager), - msg, auth, NULL); /* FIXME cancellable */ - } - - SOUP_SESSION_CLASS (soup_session_sync_parent_class)-> - auth_required (session, msg, auth, retrying); -} - -static void -soup_session_sync_flush_queue (SoupSession *session) -{ - SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session); - SoupMessageQueue *queue; - SoupMessageQueueItem *item; - GHashTable *current; - gboolean done = FALSE; - - /* Record the current contents of the queue */ - current = g_hash_table_new (NULL, NULL); - queue = soup_session_get_queue (session); - for (item = soup_message_queue_first (queue); - item; - item = soup_message_queue_next (queue, item)) - g_hash_table_insert (current, item, item); - - /* Cancel everything */ - SOUP_SESSION_CLASS (soup_session_sync_parent_class)->flush_queue (session); - - /* Wait until all of the items in @current have been removed - * from the queue. (This is not the same as "wait for the - * queue to be empty", because the app may queue new requests - * in response to the cancellation of the old ones. We don't - * try to cancel those requests as well, since we'd likely - * just end up looping forever.) - */ - g_mutex_lock (&priv->lock); - do { - done = TRUE; - for (item = soup_message_queue_first (queue); - item; - item = soup_message_queue_next (queue, item)) { - if (g_hash_table_lookup (current, item)) - done = FALSE; - } - - if (!done) - g_cond_wait (&priv->cond, &priv->lock); - } while (!done); - g_mutex_unlock (&priv->lock); - - g_hash_table_destroy (current); -} - -static void -soup_session_sync_kick (SoupSession *session) -{ - SoupSessionSyncPrivate *priv = SOUP_SESSION_SYNC_GET_PRIVATE (session); - - g_mutex_lock (&priv->lock); - g_cond_broadcast (&priv->cond); - g_mutex_unlock (&priv->lock); -} - static void soup_session_sync_class_init (SoupSessionSyncClass *session_sync_class) { - GObjectClass *object_class = G_OBJECT_CLASS (session_sync_class); SoupSessionClass *session_class = SOUP_SESSION_CLASS (session_sync_class); - g_type_class_add_private (session_sync_class, sizeof (SoupSessionSyncPrivate)); - /* virtual method override */ session_class->queue_message = soup_session_sync_queue_message; - session_class->send_message = soup_session_sync_send_message; - session_class->cancel_message = soup_session_sync_cancel_message; - session_class->auth_required = soup_session_sync_auth_required; - session_class->flush_queue = soup_session_sync_flush_queue; - session_class->kick = soup_session_sync_kick; - - object_class->finalize = soup_session_sync_finalize; -} - - -GInputStream * -soup_session_send_request (SoupSession *session, - SoupMessage *msg, - GCancellable *cancellable, - GError **error) -{ - SoupMessageQueueItem *item; - GInputStream *stream = NULL; - GOutputStream *ostream; - GMemoryOutputStream *mostream; - gssize size; - GError *my_error = NULL; - - g_return_val_if_fail (SOUP_IS_SESSION_SYNC (session), NULL); - - item = soup_session_append_queue_item (session, msg, NULL, NULL); - - item->new_api = TRUE; - if (cancellable) { - g_object_unref (item->cancellable); - item->cancellable = g_object_ref (cancellable); - } - - while (!stream) { - /* Get a connection, etc */ - process_queue_item (item); - if (item->state != SOUP_MESSAGE_RUNNING) - break; - - /* Send request, read headers */ - if (!soup_message_io_run_until_read (msg, item->cancellable, &my_error)) { - if (g_error_matches (my_error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) { - item->state = SOUP_MESSAGE_RESTARTING; - soup_message_io_finished (item->msg); - g_clear_error (&my_error); - continue; - } else - break; - } - - stream = soup_message_io_get_response_istream (msg, &my_error); - if (!stream) - break; - - /* Break if the message doesn't look likely-to-be-requeued */ - if (msg->status_code != SOUP_STATUS_UNAUTHORIZED && - msg->status_code != SOUP_STATUS_PROXY_UNAUTHORIZED && - !soup_session_would_redirect (session, msg)) - break; - - /* Gather the current message body... */ - ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); - if (g_output_stream_splice (ostream, stream, - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | - G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, - item->cancellable, &my_error) == -1) { - g_object_unref (stream); - g_object_unref (ostream); - stream = NULL; - break; - } - g_object_unref (stream); - stream = NULL; - - /* If the message was requeued, loop */ - if (item->state == SOUP_MESSAGE_RESTARTING) { - g_object_unref (ostream); - continue; - } - - /* Not requeued, so return the original body */ - mostream = G_MEMORY_OUTPUT_STREAM (ostream); - size = g_memory_output_stream_get_data_size (mostream); - stream = g_memory_input_stream_new (); - if (size) { - g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (stream), - g_memory_output_stream_steal_data (mostream), - size, g_free); - } - g_object_unref (ostream); - } - - if (my_error) - g_propagate_error (error, my_error); - else if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) { - if (stream) { - g_object_unref (stream); - stream = NULL; - } - g_set_error_literal (error, SOUP_HTTP_ERROR, msg->status_code, - msg->reason_phrase); - } else if (!stream) - stream = g_memory_input_stream_new (); - - if (!stream) { - if (soup_message_io_in_progress (msg)) - soup_message_io_finished (msg); - else if (item->state != SOUP_MESSAGE_FINISHED) - item->state = SOUP_MESSAGE_FINISHING; - - if (item->state != SOUP_MESSAGE_FINISHED) - process_queue_item (item); - } - - soup_message_queue_item_unref (item); - return stream; } diff --git a/libsoup/soup-session-sync.h b/libsoup/soup-session-sync.h index 845e01c3..cf81f6b0 100644 --- a/libsoup/soup-session-sync.h +++ b/libsoup/soup-session-sync.h @@ -35,9 +35,11 @@ typedef struct { GType soup_session_sync_get_type (void); +#ifndef SOUP_DISABLE_DEPRECATED SoupSession *soup_session_sync_new (void); SoupSession *soup_session_sync_new_with_options (const char *optname1, ...) G_GNUC_NULL_TERMINATED; +#endif G_END_DECLS diff --git a/libsoup/soup-session.c b/libsoup/soup-session.c index 7b68d427..132554d6 100644 --- a/libsoup/soup-session.c +++ b/libsoup/soup-session.c @@ -13,13 +13,13 @@ #include "soup-session.h" #include "soup.h" -#include "soup-auth-manager-ntlm.h" +#include "soup-auth-manager.h" +#include "soup-cache-private.h" #include "soup-connection.h" -#include "soup-marshal.h" #include "soup-message-private.h" #include "soup-misc-private.h" #include "soup-message-queue.h" -#include "soup-proxy-resolver-static.h" +#include "soup-proxy-resolver-wrapper.h" #include "soup-session-private.h" #define HOST_KEEP_ALIVE 5 * 60 * 1000 /* 5 min in msecs */ @@ -31,7 +31,8 @@ * #SoupSession is the object that controls client-side HTTP. A * #SoupSession encapsulates all of the state that libsoup is keeping * on behalf of your program; cached HTTP connections, authentication - * information, etc. + * information, etc. It also keeps track of various global options + * and features that you are using. * * Most applications will only need a single #SoupSession; the primary * reason you might need multiple sessions is if you need to have @@ -42,12 +43,16 @@ * one session for the first user, and a second session for the other * user.) * - * #SoupSession itself is an abstract class, with two subclasses. If - * you are using the glib main loop, you will generally want to use - * #SoupSessionAsync, which uses non-blocking I/O and callbacks. On - * the other hand, if your application is threaded and you want to do - * synchronous I/O in a separate thread from the UI, use - * #SoupSessionSync. + * In the past, #SoupSession was an abstract class, and users needed + * to choose between #SoupSessionAsync (which always uses + * #GMainLoop<!-- -->-based I/O), or #SoupSessionSync (which always uses + * blocking I/O and can be used from multiple threads simultaneously). + * This is no longer necessary; you can (and should) use a plain + * #SoupSession, which supports both synchronous and asynchronous use. + * (When using a plain #SoupSession, soup_session_queue_message() + * behaves like it traditionally did on a #SoupSessionAsync, and + * soup_session_send_message() behaves like it traditionally did on a + * #SoupSessionSync.) **/ static void @@ -74,12 +79,16 @@ typedef struct { SoupSession *session; } SoupSessionHost; static guint soup_host_uri_hash (gconstpointer key); -gboolean soup_host_uri_equal (gconstpointer v1, gconstpointer v2); +static gboolean soup_host_uri_equal (gconstpointer v1, gconstpointer v2); typedef struct { + SoupSession *session; + gboolean disposed; + GTlsDatabase *tlsdb; char *ssl_ca_file; gboolean ssl_strict; + gboolean tlsdb_use_default; SoupMessageQueue *queue; @@ -96,37 +105,60 @@ typedef struct { guint max_conns, max_conns_per_host; guint io_timeout, idle_timeout; - /* Must hold the conn_lock before potentially creating a - * new SoupSessionHost, or adding/removing a connection. - * Must not emit signals or destroy objects while holding it. + SoupAddress *local_addr; + + /* Must hold the conn_lock before potentially creating a new + * SoupSessionHost, adding/removing a connection, + * disconnecting a connection, or moving a connection from + * IDLE to IN_USE. Must not emit signals or destroy objects + * while holding it. conn_cond is signaled when it may be + * possible for a previously-blocked message to continue. */ GMutex conn_lock; + GCond conn_cond; GMainContext *async_context; gboolean use_thread_context; + GSList *run_queue_sources; GResolver *resolver; + GProxyResolver *proxy_resolver; + gboolean proxy_use_default; + SoupURI *proxy_uri; char **http_aliases, **https_aliases; + + GHashTable *request_types; } SoupSessionPrivate; #define SOUP_SESSION_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_SESSION, SoupSessionPrivate)) +#define SOUP_IS_PLAIN_SESSION(o) (G_TYPE_FROM_INSTANCE (o) == SOUP_TYPE_SESSION) + static void free_host (SoupSessionHost *host); +static void connection_state_changed (GObject *object, GParamSpec *param, + gpointer user_data); +static void connection_disconnected (SoupConnection *conn, gpointer user_data); +static void drop_connection (SoupSession *session, SoupSessionHost *host, + SoupConnection *conn); static void auth_manager_authenticate (SoupAuthManager *manager, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user_data); +static void async_run_queue (SoupSession *session); + +static void async_send_request_running (SoupSession *session, SoupMessageQueueItem *item); + #define SOUP_SESSION_MAX_CONNS_DEFAULT 10 #define SOUP_SESSION_MAX_CONNS_PER_HOST_DEFAULT 2 -#define SOUP_SESSION_MAX_REDIRECTION_COUNT 20 +#define SOUP_SESSION_MAX_RESEND_COUNT 20 #define SOUP_SESSION_USER_AGENT_BASE "libsoup/" PACKAGE_VERSION -G_DEFINE_ABSTRACT_TYPE_WITH_CODE (SoupSession, soup_session, G_TYPE_OBJECT, - soup_init (); - ) +G_DEFINE_TYPE_WITH_CODE (SoupSession, soup_session, G_TYPE_OBJECT, + soup_init (); + ) enum { REQUEST_QUEUED, @@ -144,6 +176,7 @@ enum { PROP_0, PROP_PROXY_URI, + PROP_PROXY_RESOLVER, PROP_MAX_CONNS, PROP_MAX_CONNS_PER_HOST, PROP_USE_NTLM, @@ -163,6 +196,7 @@ enum { PROP_REMOVE_FEATURE_BY_TYPE, PROP_HTTP_ALIASES, PROP_HTTPS_ALIASES, + PROP_LOCAL_ADDRESS, LAST_PROP }; @@ -173,9 +207,12 @@ soup_session_init (SoupSession *session) SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupAuthManager *auth_manager; + priv->session = session; + priv->queue = soup_message_queue_new (session); g_mutex_init (&priv->conn_lock); + g_cond_init (&priv->conn_cond); priv->http_hosts = g_hash_table_new_full (soup_host_uri_hash, soup_host_uri_equal, NULL, (GDestroyNotify)free_host); @@ -189,7 +226,7 @@ soup_session_init (SoupSession *session) priv->features_cache = g_hash_table_new (NULL, NULL); - auth_manager = g_object_new (SOUP_TYPE_AUTH_MANAGER_NTLM, NULL); + auth_manager = g_object_new (SOUP_TYPE_AUTH_MANAGER, NULL); g_signal_connect (auth_manager, "authenticate", G_CALLBACK (auth_manager_authenticate), session); soup_session_feature_add_feature (SOUP_SESSION_FEATURE (auth_manager), @@ -209,6 +246,49 @@ soup_session_init (SoupSession *session) priv->http_aliases = g_new (char *, 2); priv->http_aliases[0] = (char *)g_intern_string ("*"); priv->http_aliases[1] = NULL; + + priv->request_types = g_hash_table_new (soup_str_case_hash, + soup_str_case_equal); + soup_session_add_feature_by_type (session, SOUP_TYPE_REQUEST_HTTP); + soup_session_add_feature_by_type (session, SOUP_TYPE_REQUEST_FILE); + soup_session_add_feature_by_type (session, SOUP_TYPE_REQUEST_DATA); +} + +static GObject * +soup_session_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_params) +{ + GObject *object; + + object = G_OBJECT_CLASS (soup_session_parent_class)->constructor (type, n_construct_properties, construct_params); + + /* If this is a "plain" SoupSession, fix up the default + * properties values, etc. + */ + if (type == SOUP_TYPE_SESSION) { + SoupSession *session = SOUP_SESSION (object); + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + + g_clear_pointer (&priv->async_context, g_main_context_unref); + priv->async_context = g_main_context_ref_thread_default (); + priv->use_thread_context = TRUE; + + priv->io_timeout = priv->idle_timeout = 60; + + priv->http_aliases[0] = NULL; + + /* If the user overrides the proxy or tlsdb during construction, + * we don't want to needlessly resolve the extension point. So + * we just set flags saying to do it later. + */ + priv->proxy_use_default = TRUE; + priv->tlsdb_use_default = TRUE; + + soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER); + } + + return object; } static void @@ -216,8 +296,19 @@ soup_session_dispose (GObject *object) { SoupSession *session = SOUP_SESSION (object); SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + GSList *iter; + priv->disposed = TRUE; + + for (iter = priv->run_queue_sources; iter; iter = iter->next) { + g_source_destroy (iter->data); + g_source_unref (iter->data); + } + g_clear_pointer (&priv->run_queue_sources, g_slist_free); + + priv->disposed = TRUE; soup_session_abort (session); + g_warn_if_fail (g_hash_table_size (priv->conns) == 0); while (priv->features) soup_session_remove_feature (session, priv->features->data); @@ -234,6 +325,7 @@ soup_session_finalize (GObject *object) soup_message_queue_destroy (priv->queue); g_mutex_clear (&priv->conn_lock); + g_cond_clear (&priv->conn_cond); g_hash_table_destroy (priv->http_hosts); g_hash_table_destroy (priv->https_hosts); g_hash_table_destroy (priv->conns); @@ -245,14 +337,19 @@ soup_session_finalize (GObject *object) g_free (priv->ssl_ca_file); g_clear_pointer (&priv->async_context, g_main_context_unref); + g_clear_object (&priv->local_addr); g_hash_table_destroy (priv->features_cache); g_object_unref (priv->resolver); + g_clear_object (&priv->proxy_resolver); + g_clear_pointer (&priv->proxy_uri, soup_uri_free); g_free (priv->http_aliases); g_free (priv->https_aliases); + g_hash_table_destroy (priv->request_types); + G_OBJECT_CLASS (soup_session_parent_class)->finalize (object); } @@ -342,16 +439,19 @@ set_tlsdb (SoupSession *session, GTlsDatabase *tlsdb) SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); GTlsDatabase *system_default; + priv->tlsdb_use_default = FALSE; if (tlsdb == priv->tlsdb) return; g_object_freeze_notify (G_OBJECT (session)); system_default = g_tls_backend_get_default_database (g_tls_backend_get_default ()); - if (priv->tlsdb == system_default || tlsdb == system_default) { - g_object_notify (G_OBJECT (session), "ssl-use-system-ca-file"); + if (system_default) { + if (priv->tlsdb == system_default || tlsdb == system_default) { + g_object_notify (G_OBJECT (session), "ssl-use-system-ca-file"); + } + g_object_unref (system_default); } - g_object_unref (system_default); if (priv->ssl_ca_file) { g_free (priv->ssl_ca_file); @@ -375,6 +475,8 @@ set_use_system_ca_file (SoupSession *session, gboolean use_system_ca_file) SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); GTlsDatabase *system_default; + priv->tlsdb_use_default = FALSE; + system_default = g_tls_backend_get_default_database (g_tls_backend_get_default ()); if (use_system_ca_file) @@ -382,7 +484,7 @@ set_use_system_ca_file (SoupSession *session, gboolean use_system_ca_file) else if (priv->tlsdb == system_default) set_tlsdb (session, NULL); - g_object_unref (system_default); + g_clear_object (&system_default); } static void @@ -392,6 +494,7 @@ set_ssl_ca_file (SoupSession *session, const char *ssl_ca_file) GTlsDatabase *tlsdb; GError *error = NULL; + priv->tlsdb_use_default = FALSE; if (!g_strcmp0 (priv->ssl_ca_file, ssl_ca_file)) return; @@ -406,6 +509,7 @@ set_ssl_ca_file (SoupSession *session, const char *ssl_ca_file) path = g_build_filename (cwd, ssl_ca_file, NULL); tlsdb = g_tls_file_database_new (path, &error); g_free (path); + g_free (cwd); } if (error) { @@ -419,10 +523,15 @@ set_ssl_ca_file (SoupSession *session, const char *ssl_ca_file) } set_tlsdb (session, tlsdb); - g_object_unref (tlsdb); + if (tlsdb) { + g_object_unref (tlsdb); - priv->ssl_ca_file = g_strdup (ssl_ca_file); - g_object_notify (G_OBJECT (session), "ssl-ca-file"); + priv->ssl_ca_file = g_strdup (ssl_ca_file); + g_object_notify (G_OBJECT (session), "ssl-ca-file"); + } else if (priv->ssl_ca_file) { + g_clear_pointer (&priv->ssl_ca_file, g_free); + g_object_notify (G_OBJECT (session), "ssl-ca-file"); + } g_object_thaw_notify (G_OBJECT (session)); } @@ -451,29 +560,60 @@ set_aliases (char ***variable, char **value) } static void +set_proxy_resolver (SoupSession *session, SoupURI *uri, + SoupProxyURIResolver *soup_resolver, + GProxyResolver *g_resolver) +{ + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_URI_RESOLVER); + G_GNUC_END_IGNORE_DEPRECATIONS; + g_clear_object (&priv->proxy_resolver); + g_clear_pointer (&priv->proxy_uri, soup_uri_free); + priv->proxy_use_default = FALSE; + + if (uri) { + char *uri_string; + + priv->proxy_uri = soup_uri_copy (uri); + uri_string = soup_uri_to_string_internal (uri, FALSE, TRUE); + priv->proxy_resolver = g_simple_proxy_resolver_new (uri_string, NULL); + g_free (uri_string); + } else if (soup_resolver) { + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + if (SOUP_IS_PROXY_RESOLVER_DEFAULT (soup_resolver)) + priv->proxy_resolver = g_object_ref (g_proxy_resolver_get_default ()); + else + priv->proxy_resolver = soup_proxy_resolver_wrapper_new (soup_resolver); + G_GNUC_END_IGNORE_DEPRECATIONS; + } else if (g_resolver) + priv->proxy_resolver = g_object_ref (g_resolver); +} + +static void soup_session_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { SoupSession *session = SOUP_SESSION (object); SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - SoupURI *uri; const char *user_agent; SoupSessionFeature *feature; + GMainContext *async_context; switch (prop_id) { + case PROP_LOCAL_ADDRESS: + priv->local_addr = g_value_dup_object (value); + break; case PROP_PROXY_URI: - uri = g_value_get_boxed (value); - - if (uri) { - soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER); - feature = SOUP_SESSION_FEATURE (soup_proxy_resolver_static_new (uri)); - soup_session_add_feature (session, feature); - g_object_unref (feature); - } else - soup_session_remove_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER_STATIC); - + set_proxy_resolver (session, g_value_get_boxed (value), + NULL, NULL); soup_session_abort (session); break; + case PROP_PROXY_RESOLVER: + set_proxy_resolver (session, NULL, NULL, + g_value_get_object (value)); + break; case PROP_MAX_CONNS: priv->max_conns = g_value_get_int (value); break; @@ -481,7 +621,8 @@ soup_session_set_property (GObject *object, guint prop_id, priv->max_conns_per_host = g_value_get_int (value); break; case PROP_USE_NTLM: - feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM); + g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session)); + feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER); if (feature) { if (g_value_get_boolean (value)) soup_session_feature_add_feature (feature, SOUP_TYPE_AUTH_NTLM); @@ -503,11 +644,16 @@ soup_session_set_property (GObject *object, guint prop_id, priv->ssl_strict = g_value_get_boolean (value); break; case PROP_ASYNC_CONTEXT: - priv->async_context = g_value_get_pointer (value); + async_context = g_value_get_pointer (value); + if (async_context && async_context != g_main_context_get_thread_default ()) + g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session)); + priv->async_context = async_context; if (priv->async_context) g_main_context_ref (priv->async_context); break; case PROP_USE_THREAD_CONTEXT: + if (!g_value_get_boolean (value)) + g_return_if_fail (!SOUP_IS_PLAIN_SESSION (session)); priv->use_thread_context = g_value_get_boolean (value); if (priv->use_thread_context) { if (priv->async_context) @@ -575,6 +721,30 @@ soup_session_set_property (GObject *object, guint prop_id, } } +static GProxyResolver * +get_proxy_resolver (SoupSession *session) +{ + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + + if (priv->proxy_use_default) { + priv->proxy_resolver = g_object_ref (g_proxy_resolver_get_default ()); + priv->proxy_use_default = FALSE; + } + return priv->proxy_resolver; +} + +static GTlsDatabase * +get_tls_database (SoupSession *session) +{ + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + + if (priv->tlsdb_use_default) { + priv->tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ()); + priv->tlsdb_use_default = FALSE; + } + return priv->tlsdb; +} + static void soup_session_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) @@ -585,14 +755,14 @@ soup_session_get_property (GObject *object, guint prop_id, GTlsDatabase *tlsdb; switch (prop_id) { + case PROP_LOCAL_ADDRESS: + g_value_set_object (value, priv->local_addr); + break; case PROP_PROXY_URI: - feature = soup_session_get_feature (session, SOUP_TYPE_PROXY_RESOLVER_STATIC); - if (feature) { - g_object_get_property (G_OBJECT (feature), - SOUP_PROXY_RESOLVER_STATIC_PROXY_URI, - value); - } else - g_value_set_boxed (value, NULL); + g_value_set_boxed (value, priv->proxy_uri); + break; + case PROP_PROXY_RESOLVER: + g_value_set_object (value, get_proxy_resolver (session)); break; case PROP_MAX_CONNS: g_value_set_int (value, priv->max_conns); @@ -601,7 +771,7 @@ soup_session_get_property (GObject *object, guint prop_id, g_value_set_int (value, priv->max_conns_per_host); break; case PROP_USE_NTLM: - feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER_NTLM); + feature = soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER); if (feature) g_value_set_boolean (value, soup_session_feature_has_feature (feature, SOUP_TYPE_AUTH_NTLM)); else @@ -612,11 +782,11 @@ soup_session_get_property (GObject *object, guint prop_id, break; case PROP_SSL_USE_SYSTEM_CA_FILE: tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ()); - g_value_set_boolean (value, priv->tlsdb == tlsdb); - g_object_unref (tlsdb); + g_value_set_boolean (value, get_tls_database (session) == tlsdb); + g_clear_object (&tlsdb); break; case PROP_TLS_DATABASE: - g_value_set_object (value, priv->tlsdb); + g_value_set_object (value, get_tls_database (session)); break; case PROP_SSL_STRICT: g_value_set_boolean (value, priv->ssl_strict); @@ -654,59 +824,57 @@ soup_session_get_property (GObject *object, guint prop_id, } } -static gboolean -uri_is_http (SoupSessionPrivate *priv, SoupURI *uri) +/** + * soup_session_new: + * + * Creates a #SoupSession with the default options. + * + * Return value: the new session. + * + * Since: 2.42 + */ +SoupSession * +soup_session_new (void) { - int i; - - if (uri->scheme == SOUP_URI_SCHEME_HTTP) - return TRUE; - else if (uri->scheme == SOUP_URI_SCHEME_HTTPS) - return FALSE; - else if (!priv->http_aliases) - return FALSE; - - for (i = 0; priv->http_aliases[i]; i++) { - if (uri->scheme == priv->http_aliases[i]) - return TRUE; - } - - if (!priv->http_aliases[1] && !strcmp (priv->http_aliases[0], "*")) - return TRUE; - else - return FALSE; + return g_object_new (SOUP_TYPE_SESSION, NULL); } -static gboolean -uri_is_https (SoupSessionPrivate *priv, SoupURI *uri) +/** + * soup_session_new_with_options: + * @optname1: name of first property to set + * @...: value of @optname1, followed by additional property/value pairs + * + * Creates a #SoupSession with the specified options. + * + * Return value: the new session. + * + * Since: 2.42 + */ +SoupSession * +soup_session_new_with_options (const char *optname1, + ...) { - int i; - - if (uri->scheme == SOUP_URI_SCHEME_HTTPS) - return TRUE; - else if (uri->scheme == SOUP_URI_SCHEME_HTTP) - return FALSE; - else if (!priv->https_aliases) - return FALSE; + SoupSession *session; + va_list ap; - for (i = 0; priv->https_aliases[i]; i++) { - if (uri->scheme == priv->https_aliases[i]) - return TRUE; - } + va_start (ap, optname1); + session = (SoupSession *)g_object_new_valist (SOUP_TYPE_SESSION, + optname1, ap); + va_end (ap); - return FALSE; + return session; } /** * soup_session_get_async_context: * @session: a #SoupSession * - * Gets @session's async_context. This does not add a ref to the - * context, so you will need to ref it yourself if you want it to - * outlive its session. + * Gets @session's #SoupSession:async-context. This does not add a ref + * to the context, so you will need to ref it yourself if you want it + * to outlive its session. * - * If #SoupSession:use-thread-context is true, this will return the - * current thread-default main context. + * For a modern #SoupSession, this will always just return the + * thread-default #GMainContext, and so is not especially useful. * * Return value: (transfer none): @session's #GMainContext, which may * be %NULL @@ -727,6 +895,10 @@ soup_session_get_async_context (SoupSession *session) /* Hosts */ +/* Note that we can't use soup_uri_host_hash() and soup_uri_host_equal() + * because we want to ignore the protocol; http://example.com and + * webcal://example.com are the same host. + */ static guint soup_host_uri_hash (gconstpointer key) { @@ -737,7 +909,7 @@ soup_host_uri_hash (gconstpointer key) return uri->port + soup_str_case_hash (uri->host); } -gboolean +static gboolean soup_host_uri_equal (gconstpointer v1, gconstpointer v2) { const SoupURI *one = v1; @@ -764,7 +936,7 @@ soup_session_host_new (SoupSession *session, SoupURI *uri) host->uri->scheme != SOUP_URI_SCHEME_HTTPS) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - if (uri_is_https (priv, host->uri)) + if (soup_uri_is_https (host->uri, priv->https_aliases)) host->uri->scheme = SOUP_URI_SCHEME_HTTPS; else host->uri->scheme = SOUP_URI_SCHEME_HTTP; @@ -788,7 +960,7 @@ get_host_for_uri (SoupSession *session, SoupURI *uri) SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupSessionHost *host; - if (uri_is_https (priv, uri)) + if (soup_uri_is_https (uri, priv->https_aliases)) host = g_hash_table_lookup (priv->https_hosts, uri); else host = g_hash_table_lookup (priv->http_hosts, uri); @@ -797,7 +969,7 @@ get_host_for_uri (SoupSession *session, SoupURI *uri) host = soup_session_host_new (session, uri); - if (uri_is_https (priv, uri)) + if (soup_uri_is_https (uri, priv->https_aliases)) g_hash_table_insert (priv->https_hosts, host->uri, host); else g_hash_table_insert (priv->http_hosts, host->uri, host); @@ -805,10 +977,7 @@ get_host_for_uri (SoupSession *session, SoupURI *uri) return host; } -/* Note: get_host_for_message doesn't lock the conn_lock. The caller - * must do it itself if there's a chance the host doesn't already - * exist. - */ +/* Requires conn_lock to be locked */ static SoupSessionHost * get_host_for_message (SoupSession *session, SoupMessage *msg) { @@ -818,8 +987,7 @@ get_host_for_message (SoupSession *session, SoupMessage *msg) static void free_host (SoupSessionHost *host) { - g_slist_free_full (host->connections, - (GDestroyNotify) soup_connection_disconnect); + g_warn_if_fail (host->connections == NULL); if (host->keep_alive_src) { g_source_destroy (host->keep_alive_src); @@ -832,19 +1000,11 @@ free_host (SoupSessionHost *host) } static void -soup_session_real_auth_required (SoupSession *session, SoupMessage *msg, - SoupAuth *auth, gboolean retrying) -{ - g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying); -} - -static void auth_manager_authenticate (SoupAuthManager *manager, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer session) { - SOUP_SESSION_GET_CLASS (session)->auth_required ( - session, msg, auth, retrying); + g_signal_emit (session, signals[AUTHENTICATE], 0, msg, auth, retrying); } #define SOUP_SESSION_WOULD_REDIRECT_AS_GET(session, msg) \ @@ -911,7 +1071,8 @@ soup_session_would_redirect (SoupSession *session, SoupMessage *msg) if (!new_uri) return FALSE; if (!new_uri->host || !*new_uri->host || - (!uri_is_http (priv, new_uri) && !uri_is_https (priv, new_uri))) { + (!soup_uri_is_http (new_uri, priv->http_aliases) && + !soup_uri_is_https (new_uri, priv->https_aliases))) { soup_uri_free (new_uri); return FALSE; } @@ -947,27 +1108,12 @@ soup_session_would_redirect (SoupSession *session, SoupMessage *msg) gboolean soup_session_redirect_message (SoupSession *session, SoupMessage *msg) { - SoupMessageQueueItem *item; SoupURI *new_uri; new_uri = redirection_uri (msg); if (!new_uri) return FALSE; - item = soup_message_queue_lookup (soup_session_get_queue (session), msg); - if (!item) { - soup_uri_free (new_uri); - return FALSE; - } - if (item->redirection_count >= SOUP_SESSION_MAX_REDIRECTION_COUNT) { - soup_uri_free (new_uri); - soup_session_cancel_message (session, msg, SOUP_STATUS_TOO_MANY_REDIRECTS); - soup_message_queue_item_unref (item); - return FALSE; - } - item->redirection_count++; - soup_message_queue_item_unref (item); - if (SOUP_SESSION_WOULD_REDIRECT_AS_GET (session, msg)) { if (msg->method != SOUP_METHOD_HEAD) { g_object_set (msg, @@ -1010,15 +1156,67 @@ redirect_handler (SoupMessage *msg, gpointer user_data) soup_session_redirect_message (session, msg); } +static void +re_emit_connection_event (SoupConnection *conn, + GSocketClientEvent event, + GIOStream *connection, + gpointer user_data) +{ + SoupMessageQueueItem *item = user_data; + + soup_message_network_event (item->msg, event, connection); +} + +static void +soup_session_set_item_connection (SoupSession *session, + SoupMessageQueueItem *item, + SoupConnection *conn) +{ + if (item->conn) { + g_signal_handlers_disconnect_by_func (item->conn, re_emit_connection_event, item); + g_object_unref (item->conn); + } + + item->conn = conn; + soup_message_set_connection (item->msg, conn); + + if (item->conn) { + g_object_ref (item->conn); + g_signal_connect (item->conn, "event", + G_CALLBACK (re_emit_connection_event), item); + } +} + +static void +message_restarted (SoupMessage *msg, gpointer user_data) +{ + SoupMessageQueueItem *item = user_data; + + if (item->conn && + (!soup_message_is_keepalive (msg) || + SOUP_STATUS_IS_REDIRECTION (msg->status_code))) { + if (soup_connection_get_state (item->conn) == SOUP_CONNECTION_IN_USE) + soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE); + soup_session_set_item_connection (item->session, item, NULL); + } + + soup_message_cleanup_response (msg); +} + SoupMessageQueueItem * soup_session_append_queue_item (SoupSession *session, SoupMessage *msg, + gboolean async, gboolean new_api, SoupSessionCallback callback, gpointer user_data) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupMessageQueueItem *item; SoupSessionHost *host; + soup_message_cleanup_response (msg); + item = soup_message_queue_append (priv->queue, msg, callback, user_data); + item->async = async; + item->new_api = new_api; g_mutex_lock (&priv->conn_lock); host = get_host_for_message (session, item->msg); @@ -1030,6 +1228,8 @@ soup_session_append_queue_item (SoupSession *session, SoupMessage *msg, msg, "got_body", "Location", G_CALLBACK (redirect_handler), item); } + g_signal_connect (msg, "restarted", + G_CALLBACK (message_restarted), item); g_signal_emit (session, signals[REQUEST_QUEUED], 0, msg); @@ -1037,7 +1237,7 @@ soup_session_append_queue_item (SoupSession *session, SoupMessage *msg, return item; } -void +static void soup_session_send_queue_item (SoupSession *session, SoupMessageQueueItem *item, SoupMessageCompletionFn completion_cb) @@ -1076,9 +1276,9 @@ soup_session_send_queue_item (SoupSession *session, soup_connection_send_request (item->conn, item, completion_cb, item); } -gboolean +static gboolean soup_session_cleanup_connections (SoupSession *session, - gboolean prune_idle) + gboolean cleanup_idle) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); GSList *conns = NULL, *c; @@ -1091,8 +1291,11 @@ soup_session_cleanup_connections (SoupSession *session, while (g_hash_table_iter_next (&iter, &conn, &host)) { state = soup_connection_get_state (conn); if (state == SOUP_CONNECTION_REMOTE_DISCONNECTED || - (prune_idle && state == SOUP_CONNECTION_IDLE)) + (cleanup_idle && state == SOUP_CONNECTION_IDLE)) { conns = g_slist_prepend (conns, g_object_ref (conn)); + g_hash_table_iter_remove (&iter); + drop_connection (session, host, conn); + } } g_mutex_unlock (&priv->conn_lock); @@ -1116,6 +1319,15 @@ free_unused_host (gpointer user_data) SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (host->session); g_mutex_lock (&priv->conn_lock); + + /* In a multithreaded session, a connection might have been + * added while we were waiting for conn_lock. + */ + if (host->connections) { + g_mutex_unlock (&priv->conn_lock); + return FALSE; + } + /* This will free the host in addition to removing it from the * hash table */ @@ -1129,17 +1341,15 @@ free_unused_host (gpointer user_data) } static void -connection_disconnected (SoupConnection *conn, gpointer user_data) +drop_connection (SoupSession *session, SoupSessionHost *host, SoupConnection *conn) { - SoupSession *session = user_data; SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - SoupSessionHost *host; - g_mutex_lock (&priv->conn_lock); + /* Note: caller must hold conn_lock, and must remove @conn + * from priv->conns itself. + */ - host = g_hash_table_lookup (priv->conns, conn); if (host) { - g_hash_table_remove (priv->conns, conn); host->connections = g_slist_remove (host->connections, conn); host->num_conns--; @@ -1161,106 +1371,430 @@ connection_disconnected (SoupConnection *conn, gpointer user_data) } g_signal_handlers_disconnect_by_func (conn, connection_disconnected, session); + g_signal_handlers_disconnect_by_func (conn, connection_state_changed, session); priv->num_conns--; - g_mutex_unlock (&priv->conn_lock); g_object_unref (conn); } -SoupMessageQueueItem * -soup_session_make_connect_message (SoupSession *session, - SoupConnection *conn) +static void +connection_disconnected (SoupConnection *conn, gpointer user_data) +{ + SoupSession *session = user_data; + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + SoupSessionHost *host; + + g_mutex_lock (&priv->conn_lock); + + host = g_hash_table_lookup (priv->conns, conn); + if (host) + g_hash_table_remove (priv->conns, conn); + drop_connection (session, host, conn); + + g_mutex_unlock (&priv->conn_lock); + + soup_session_kick_queue (session); +} + +static void +connection_state_changed (GObject *object, GParamSpec *param, gpointer user_data) +{ + SoupSession *session = user_data; + SoupConnection *conn = SOUP_CONNECTION (object); + + if (soup_connection_get_state (conn) == SOUP_CONNECTION_IDLE) + soup_session_kick_queue (session); +} + +SoupMessageQueue * +soup_session_get_queue (SoupSession *session) { + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + + return priv->queue; +} + +static void +soup_session_unqueue_item (SoupSession *session, + SoupMessageQueueItem *item) +{ + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + SoupSessionHost *host; + + if (item->conn) { + if (item->msg->method != SOUP_METHOD_CONNECT || + !SOUP_STATUS_IS_SUCCESSFUL (item->msg->status_code)) + soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE); + soup_session_set_item_connection (session, item, NULL); + } + + if (item->state != SOUP_MESSAGE_FINISHED) { + g_warning ("finished an item with state %d", item->state); + return; + } + + soup_message_queue_remove (priv->queue, item); + + g_mutex_lock (&priv->conn_lock); + host = get_host_for_message (session, item->msg); + host->num_messages--; + g_cond_broadcast (&priv->conn_cond); + g_mutex_unlock (&priv->conn_lock); + + /* g_signal_handlers_disconnect_by_func doesn't work if you + * have a metamarshal, meaning it doesn't work with + * soup_message_add_header_handler() + */ + g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, item); + g_signal_emit (session, signals[REQUEST_UNQUEUED], 0, item->msg); + soup_message_queue_item_unref (item); +} + +static void +soup_session_set_item_status (SoupSession *session, + SoupMessageQueueItem *item, + guint status_code, + GError *error) +{ + SoupURI *uri = NULL; + + switch (status_code) { + case SOUP_STATUS_CANT_RESOLVE: + case SOUP_STATUS_CANT_CONNECT: + uri = soup_message_get_uri (item->msg); + break; + + case SOUP_STATUS_CANT_RESOLVE_PROXY: + case SOUP_STATUS_CANT_CONNECT_PROXY: + if (item->conn) + uri = soup_connection_get_proxy_uri (item->conn); + break; + + case SOUP_STATUS_SSL_FAILED: + if (!g_tls_backend_supports_tls (g_tls_backend_get_default ())) { + soup_message_set_status_full (item->msg, status_code, + "TLS/SSL support not available; install glib-networking"); + return; + } + break; + + default: + break; + } + + if (error) + soup_message_set_status_full (item->msg, status_code, error->message); + else if (uri && uri->host) { + char *msg = g_strdup_printf ("%s (%s)", + soup_status_get_phrase (status_code), + uri->host); + soup_message_set_status_full (item->msg, status_code, msg); + g_free (msg); + } else + soup_message_set_status (item->msg, status_code); +} + + +static void +message_completed (SoupMessage *msg, gpointer user_data) +{ + SoupMessageQueueItem *item = user_data; + + if (item->async) + soup_session_kick_queue (item->session); + + if (item->state != SOUP_MESSAGE_RESTARTING) { + item->state = SOUP_MESSAGE_FINISHING; + + if (item->new_api && !item->async) + soup_session_process_queue_item (item->session, item, NULL, TRUE); + } +} + +static guint +status_from_connect_error (SoupMessageQueueItem *item, GError *error) +{ + guint status; + + if (!error) + return SOUP_STATUS_OK; + + if (g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS)) { + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (item->session); + SoupSessionHost *host; + + g_mutex_lock (&priv->conn_lock); + host = get_host_for_message (item->session, item->msg); + if (!host->ssl_fallback) { + host->ssl_fallback = TRUE; + status = SOUP_STATUS_TRY_AGAIN; + } else + status = SOUP_STATUS_SSL_FAILED; + g_mutex_unlock (&priv->conn_lock); + } else if (error->domain == G_TLS_ERROR) + status = SOUP_STATUS_SSL_FAILED; + else if (error->domain == G_RESOLVER_ERROR) + status = SOUP_STATUS_CANT_RESOLVE; + else if (error->domain == G_IO_ERROR) { + if (error->code == G_IO_ERROR_CANCELLED) + status = SOUP_STATUS_CANCELLED; + else if (error->code == G_IO_ERROR_HOST_UNREACHABLE || + error->code == G_IO_ERROR_NETWORK_UNREACHABLE || + error->code == G_IO_ERROR_CONNECTION_REFUSED) + status = SOUP_STATUS_CANT_CONNECT; + else if (error->code == G_IO_ERROR_PROXY_FAILED || + error->code == G_IO_ERROR_PROXY_AUTH_FAILED || + error->code == G_IO_ERROR_PROXY_NEED_AUTH || + error->code == G_IO_ERROR_PROXY_NOT_ALLOWED) + status = SOUP_STATUS_CANT_CONNECT_PROXY; + else + status = SOUP_STATUS_IO_ERROR; + } else + status = SOUP_STATUS_IO_ERROR; + + if (item->conn && soup_connection_is_via_proxy (item->conn)) + return soup_status_proxify (status); + else + return status; +} + +static void +tunnel_complete (SoupMessageQueueItem *tunnel_item, + guint status, GError *error) +{ + SoupMessageQueueItem *item = tunnel_item->related; + SoupSession *session = tunnel_item->session; + + soup_message_finished (tunnel_item->msg); + soup_message_queue_item_unref (tunnel_item); + + if (item->msg->status_code) + item->state = SOUP_MESSAGE_FINISHING; + soup_message_set_https_status (item->msg, item->conn); + + item->error = error; + if (!status) + status = status_from_connect_error (item, error); + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { + soup_connection_disconnect (item->conn); + soup_session_set_item_connection (session, item, NULL); + if (!item->new_api || item->msg->status_code == 0) + soup_session_set_item_status (session, item, status, error); + } + + item->state = SOUP_MESSAGE_READY; + if (item->async) + soup_session_kick_queue (session); + soup_message_queue_item_unref (item); +} + +static void +tunnel_handshake_complete (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SoupConnection *conn = SOUP_CONNECTION (object); + SoupMessageQueueItem *tunnel_item = user_data; + GError *error = NULL; + + soup_connection_start_ssl_finish (conn, result, &error); + tunnel_complete (tunnel_item, 0, error); +} + +static void +tunnel_message_completed (SoupMessage *msg, gpointer user_data) +{ + SoupMessageQueueItem *tunnel_item = user_data; + SoupMessageQueueItem *item = tunnel_item->related; + SoupSession *session = tunnel_item->session; + guint status; + + if (tunnel_item->state == SOUP_MESSAGE_RESTARTING) { + soup_message_restarted (msg); + if (tunnel_item->conn) { + tunnel_item->state = SOUP_MESSAGE_RUNNING; + soup_session_send_queue_item (session, tunnel_item, + tunnel_message_completed); + return; + } + + soup_message_set_status (msg, SOUP_STATUS_TRY_AGAIN); + } + + tunnel_item->state = SOUP_MESSAGE_FINISHED; + soup_session_unqueue_item (session, tunnel_item); + + status = tunnel_item->msg->status_code; + if (!SOUP_STATUS_IS_SUCCESSFUL (status)) { + tunnel_complete (tunnel_item, status, NULL); + return; + } + + if (tunnel_item->async) { + soup_connection_start_ssl_async (item->conn, item->cancellable, + tunnel_handshake_complete, + tunnel_item); + } else { + GError *error = NULL; + + soup_connection_start_ssl_sync (item->conn, item->cancellable, &error); + tunnel_complete (tunnel_item, 0, error); + } +} + +static void +tunnel_connect (SoupMessageQueueItem *item) +{ + SoupSession *session = item->session; + SoupMessageQueueItem *tunnel_item; SoupURI *uri; SoupMessage *msg; - SoupMessageQueueItem *item; - uri = soup_connection_get_remote_uri (conn); + item->state = SOUP_MESSAGE_TUNNELING; + + uri = soup_connection_get_remote_uri (item->conn); msg = soup_message_new_from_uri (SOUP_METHOD_CONNECT, uri); soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT); - item = soup_session_append_queue_item (session, msg, NULL, NULL); - soup_message_queue_item_set_connection (item, conn); + tunnel_item = soup_session_append_queue_item (session, msg, + item->async, FALSE, + NULL, NULL); g_object_unref (msg); - item->state = SOUP_MESSAGE_RUNNING; + tunnel_item->related = item; + soup_message_queue_item_ref (item); + soup_session_set_item_connection (session, tunnel_item, item->conn); + tunnel_item->state = SOUP_MESSAGE_RUNNING; - g_signal_emit (session, signals[TUNNELING], 0, conn); - return item; + g_signal_emit (session, signals[TUNNELING], 0, tunnel_item->conn); + + soup_session_send_queue_item (session, tunnel_item, + tunnel_message_completed); } -gboolean -soup_session_get_connection (SoupSession *session, - SoupMessageQueueItem *item, - gboolean *try_pruning) +static void +connect_complete (SoupMessageQueueItem *item, SoupConnection *conn, GError *error) +{ + SoupSession *session = item->session; + guint status; + + soup_message_set_https_status (item->msg, item->conn); + + if (!error) { + item->state = SOUP_MESSAGE_CONNECTED; + return; + } + + item->error = error; + status = status_from_connect_error (item, error); + soup_connection_disconnect (conn); + if (item->state == SOUP_MESSAGE_CONNECTING) { + if (!item->new_api || item->msg->status_code == 0) + soup_session_set_item_status (session, item, status, error); + soup_session_set_item_connection (session, item, NULL); + item->state = SOUP_MESSAGE_READY; + } +} + +static void +connect_async_complete (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + SoupConnection *conn = SOUP_CONNECTION (object); + SoupMessageQueueItem *item = user_data; + GError *error = NULL; + + soup_connection_connect_finish (conn, result, &error); + connect_complete (item, conn, error); + + if (item->state == SOUP_MESSAGE_CONNECTED || + item->state == SOUP_MESSAGE_READY) + async_run_queue (item->session); + else + soup_session_kick_queue (item->session); + + soup_message_queue_item_unref (item); +} + +/* requires conn_lock */ +static SoupConnection * +get_connection_for_host (SoupSession *session, + SoupMessageQueueItem *item, + SoupSessionHost *host, + gboolean need_new_connection, + gboolean *try_cleanup) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupConnection *conn; - SoupSessionHost *host; GSList *conns; int num_pending = 0; - gboolean need_new_connection; + GProxyResolver *proxy_resolver; + GTlsDatabase *tlsdb; + + if (priv->disposed) + return FALSE; if (item->conn) { g_return_val_if_fail (soup_connection_get_state (item->conn) != SOUP_CONNECTION_DISCONNECTED, FALSE); - return TRUE; + return item->conn; } - need_new_connection = - (soup_message_get_flags (item->msg) & SOUP_MESSAGE_NEW_CONNECTION) || - !SOUP_METHOD_IS_IDEMPOTENT (item->msg->method); - - g_mutex_lock (&priv->conn_lock); - - host = get_host_for_message (session, item->msg); for (conns = host->connections; conns; conns = conns->next) { - if (!need_new_connection && soup_connection_get_state (conns->data) == SOUP_CONNECTION_IDLE) { - soup_connection_set_state (conns->data, SOUP_CONNECTION_IN_USE); - g_mutex_unlock (&priv->conn_lock); - soup_message_queue_item_set_connection (item, conns->data); - soup_message_set_https_status (item->msg, item->conn); - return TRUE; - } else if (soup_connection_get_state (conns->data) == SOUP_CONNECTION_CONNECTING) + conn = conns->data; + + if (!need_new_connection && soup_connection_get_state (conn) == SOUP_CONNECTION_IDLE) { + soup_connection_set_state (conn, SOUP_CONNECTION_IN_USE); + return conn; + } else if (soup_connection_get_state (conn) == SOUP_CONNECTION_CONNECTING) num_pending++; } /* Limit the number of pending connections; num_messages / 2 * is somewhat arbitrary... */ - if (num_pending > host->num_messages / 2) { - g_mutex_unlock (&priv->conn_lock); - return FALSE; - } + if (num_pending > host->num_messages / 2) + return NULL; if (host->num_conns >= priv->max_conns_per_host) { if (need_new_connection) - *try_pruning = TRUE; - g_mutex_unlock (&priv->conn_lock); - return FALSE; + *try_cleanup = TRUE; + return NULL; } if (priv->num_conns >= priv->max_conns) { - *try_pruning = TRUE; - g_mutex_unlock (&priv->conn_lock); - return FALSE; + *try_cleanup = TRUE; + return NULL; } + proxy_resolver = get_proxy_resolver (session); + tlsdb = get_tls_database (session); + conn = g_object_new ( SOUP_TYPE_CONNECTION, SOUP_CONNECTION_REMOTE_URI, host->uri, - SOUP_CONNECTION_PROXY_RESOLVER, soup_session_get_feature (session, SOUP_TYPE_PROXY_URI_RESOLVER), - SOUP_CONNECTION_SSL, uri_is_https (priv, soup_message_get_uri (item->msg)), - SOUP_CONNECTION_SSL_CREDENTIALS, priv->tlsdb, - SOUP_CONNECTION_SSL_STRICT, (priv->tlsdb != NULL) && priv->ssl_strict, + SOUP_CONNECTION_PROXY_RESOLVER, proxy_resolver, + SOUP_CONNECTION_SSL, soup_uri_is_https (soup_message_get_uri (item->msg), priv->https_aliases), + SOUP_CONNECTION_SSL_CREDENTIALS, tlsdb, + SOUP_CONNECTION_SSL_STRICT, priv->ssl_strict && (tlsdb != NULL || SOUP_IS_PLAIN_SESSION (session)), SOUP_CONNECTION_ASYNC_CONTEXT, priv->async_context, SOUP_CONNECTION_USE_THREAD_CONTEXT, priv->use_thread_context, SOUP_CONNECTION_TIMEOUT, priv->io_timeout, SOUP_CONNECTION_IDLE_TIMEOUT, priv->idle_timeout, SOUP_CONNECTION_SSL_FALLBACK, host->ssl_fallback, + SOUP_CONNECTION_LOCAL_ADDRESS, priv->local_addr, NULL); g_signal_connect (conn, "disconnected", G_CALLBACK (connection_disconnected), session); + g_signal_connect (conn, "notify::state", + G_CALLBACK (connection_state_changed), + session); + /* This is a debugging-related signal, and so can ignore the + * usual rule about not emitting signals while holding + * conn_lock. + */ g_signal_emit (session, signals[CONNECTION_CREATED], 0, conn); g_hash_table_insert (priv->conns, conn, host); @@ -1275,98 +1809,227 @@ soup_session_get_connection (SoupSession *session, host->keep_alive_src = NULL; } - g_mutex_unlock (&priv->conn_lock); - soup_message_queue_item_set_connection (item, conn); - return TRUE; + return conn; } -SoupMessageQueue * -soup_session_get_queue (SoupSession *session) +static gboolean +get_connection (SoupMessageQueueItem *item, gboolean *should_cleanup) { + SoupSession *session = item->session; SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + SoupSessionHost *host; + SoupConnection *conn = NULL; + gboolean my_should_cleanup = FALSE; + gboolean need_new_connection; - return priv->queue; -} + soup_session_cleanup_connections (session, FALSE); -void -soup_session_unqueue_item (SoupSession *session, - SoupMessageQueueItem *item) -{ - SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); - SoupSessionHost *host; + need_new_connection = + (soup_message_get_flags (item->msg) & SOUP_MESSAGE_NEW_CONNECTION) || + (!(soup_message_get_flags (item->msg) & SOUP_MESSAGE_IDEMPOTENT) && + !SOUP_METHOD_IS_IDEMPOTENT (item->msg->method)); - if (item->conn) { - if (soup_connection_get_state (item->conn) == SOUP_CONNECTION_IN_USE) - soup_connection_set_state (item->conn, SOUP_CONNECTION_IDLE); - soup_message_queue_item_set_connection (item, NULL); + g_mutex_lock (&priv->conn_lock); + host = get_host_for_message (session, item->msg); + while (TRUE) { + conn = get_connection_for_host (session, item, host, + need_new_connection, + &my_should_cleanup); + if (conn || item->async) + break; + + if (my_should_cleanup) { + g_mutex_unlock (&priv->conn_lock); + soup_session_cleanup_connections (session, TRUE); + g_mutex_lock (&priv->conn_lock); + + my_should_cleanup = FALSE; + continue; + } + + g_cond_wait (&priv->conn_cond, &priv->conn_lock); } + g_mutex_unlock (&priv->conn_lock); - if (item->state != SOUP_MESSAGE_FINISHED) { - g_warning ("finished an item with state %d", item->state); - return; + if (!conn) { + if (should_cleanup) + *should_cleanup = my_should_cleanup; + return FALSE; } - soup_message_queue_remove (priv->queue, item); + soup_session_set_item_connection (session, item, conn); - g_mutex_lock (&priv->conn_lock); - host = get_host_for_message (session, item->msg); - host->num_messages--; - g_mutex_unlock (&priv->conn_lock); + if (soup_connection_get_state (item->conn) != SOUP_CONNECTION_NEW) { + item->state = SOUP_MESSAGE_READY; + soup_message_set_https_status (item->msg, item->conn); + return TRUE; + } - /* g_signal_handlers_disconnect_by_func doesn't work if you - * have a metamarshal, meaning it doesn't work with - * soup_message_add_header_handler() - */ - g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA, - 0, 0, NULL, NULL, item); - g_signal_emit (session, signals[REQUEST_UNQUEUED], 0, item->msg); - soup_message_queue_item_unref (item); + item->state = SOUP_MESSAGE_CONNECTING; + + if (item->async) { + soup_message_queue_item_ref (item); + soup_connection_connect_async (item->conn, item->cancellable, + connect_async_complete, item); + return FALSE; + } else { + GError *error = NULL; + + soup_connection_connect_sync (item->conn, item->cancellable, &error); + connect_complete (item, conn, error); + + return TRUE; + } } void -soup_session_set_item_status (SoupSession *session, - SoupMessageQueueItem *item, - guint status_code) +soup_session_process_queue_item (SoupSession *session, + SoupMessageQueueItem *item, + gboolean *should_cleanup, + gboolean loop) { - SoupURI *uri; - char *msg; + g_assert (item->session == session); - switch (status_code) { - case SOUP_STATUS_CANT_RESOLVE: - case SOUP_STATUS_CANT_CONNECT: - uri = soup_message_get_uri (item->msg); - msg = g_strdup_printf ("%s (%s)", - soup_status_get_phrase (status_code), - uri->host); - soup_message_set_status_full (item->msg, status_code, msg); - g_free (msg); - break; + do { + if (item->paused) + return; - case SOUP_STATUS_CANT_RESOLVE_PROXY: - case SOUP_STATUS_CANT_CONNECT_PROXY: - if (item->proxy_uri && item->proxy_uri->host) { - msg = g_strdup_printf ("%s (%s)", - soup_status_get_phrase (status_code), - item->proxy_uri->host); - soup_message_set_status_full (item->msg, status_code, msg); - g_free (msg); + switch (item->state) { + case SOUP_MESSAGE_STARTING: + if (!get_connection (item, should_cleanup)) + return; break; + + case SOUP_MESSAGE_CONNECTED: + if (soup_connection_is_tunnelled (item->conn)) + tunnel_connect (item); + else + item->state = SOUP_MESSAGE_READY; + break; + + case SOUP_MESSAGE_READY: + if (item->msg->status_code) { + if (item->msg->status_code == SOUP_STATUS_TRY_AGAIN) { + soup_message_cleanup_response (item->msg); + item->state = SOUP_MESSAGE_STARTING; + } else + item->state = SOUP_MESSAGE_FINISHING; + break; + } + + item->state = SOUP_MESSAGE_RUNNING; + + soup_session_send_queue_item (session, item, message_completed); + + if (item->new_api) { + if (item->async) + async_send_request_running (session, item); + return; + } + break; + + case SOUP_MESSAGE_RUNNING: + if (item->async) + return; + + g_warn_if_fail (item->new_api); + item->state = SOUP_MESSAGE_FINISHING; + break; + + case SOUP_MESSAGE_CACHED: + /* Will be handled elsewhere */ + return; + + case SOUP_MESSAGE_RESTARTING: + item->state = SOUP_MESSAGE_STARTING; + soup_message_restarted (item->msg); + break; + + case SOUP_MESSAGE_FINISHING: + item->state = SOUP_MESSAGE_FINISHED; + soup_message_finished (item->msg); + if (item->state != SOUP_MESSAGE_FINISHED) { + g_return_if_fail (!item->new_api); + break; + } + + soup_message_queue_item_ref (item); + soup_session_unqueue_item (session, item); + if (item->async && item->callback) + item->callback (session, item->msg, item->callback_data); + soup_message_queue_item_unref (item); + return; + + default: + /* Nothing to do with this message in any + * other state. + */ + g_warn_if_fail (item->async); + return; } - soup_message_set_status (item->msg, status_code); - break; + } while (loop && item->state != SOUP_MESSAGE_FINISHED); +} - case SOUP_STATUS_SSL_FAILED: - if (!g_tls_backend_supports_tls (g_tls_backend_get_default ())) { - soup_message_set_status_full (item->msg, status_code, - "TLS/SSL support not available; install glib-networking"); - } else - soup_message_set_status (item->msg, status_code); - break; +static void +async_run_queue (SoupSession *session) +{ + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + SoupMessageQueueItem *item; + SoupMessage *msg; + gboolean try_cleanup = TRUE, should_cleanup = FALSE; - default: - soup_message_set_status (item->msg, status_code); - break; + g_object_ref (session); + soup_session_cleanup_connections (session, FALSE); + + try_again: + for (item = soup_message_queue_first (priv->queue); + item; + item = soup_message_queue_next (priv->queue, item)) { + msg = item->msg; + + /* CONNECT messages are handled specially */ + if (msg->method == SOUP_METHOD_CONNECT) + continue; + + if (item->async_context != soup_session_get_async_context (session)) + continue; + + soup_session_process_queue_item (session, item, &should_cleanup, TRUE); } + + if (try_cleanup && should_cleanup) { + /* There is at least one message in the queue that + * could be sent if we cleanupd an idle connection from + * some other server. + */ + if (soup_session_cleanup_connections (session, TRUE)) { + try_cleanup = should_cleanup = FALSE; + goto try_again; + } + } + + g_object_unref (session); +} + +static gboolean +idle_run_queue (gpointer user_data) +{ + SoupSessionPrivate *priv = user_data; + GSource *source; + + if (priv->disposed) + return FALSE; + + source = g_main_current_source (); + priv->run_queue_sources = g_slist_remove (priv->run_queue_sources, source); + + /* Ensure that the source is destroyed before running the queue */ + g_source_destroy (source); + g_source_unref (source); + + g_assert (priv->session); + async_run_queue (priv->session); + return FALSE; } /** @@ -1379,6 +2042,18 @@ soup_session_set_item_status (SoupSession *session, * qv. **/ +static void +soup_session_real_queue_message (SoupSession *session, SoupMessage *msg, + SoupSessionCallback callback, gpointer user_data) +{ + SoupMessageQueueItem *item; + + item = soup_session_append_queue_item (session, msg, TRUE, FALSE, + callback, user_data); + soup_session_kick_queue (session); + soup_message_queue_item_unref (item); +} + /** * soup_session_queue_message: * @session: a #SoupSession @@ -1387,14 +2062,26 @@ soup_session_set_item_status (SoupSession *session, * be called after the message completes or when an unrecoverable error occurs. * @user_data: (allow-none): a pointer passed to @callback. * - * Queues the message @msg for sending. All messages are processed - * while the glib main loop runs. If @msg has been processed before, - * any resources related to the time it was last sent are freed. + * Queues the message @msg for asynchronously sending the request and + * receiving a response in the current thread-default #GMainContext. + * If @msg has been processed before, any resources related to the + * time it was last sent are freed. * * Upon message completion, the callback specified in @callback will - * be invoked (in the thread associated with @session's async - * context). If after returning from this callback the message has not + * be invoked. If after returning from this callback the message has not * been requeued, @msg will be unreffed. + * + * (The behavior above applies to a plain #SoupSession; if you are + * using #SoupSessionAsync or #SoupSessionSync, then the #GMainContext + * that is used depends on the settings of #SoupSession:async-context + * and #SoupSession:use-thread-context, and for #SoupSessionSync, the + * message will actually be sent and processed in another thread, with + * only the final callback occurring in the indicated #GMainContext.) + * + * Contrast this method with soup_session_send_async(), which also + * asynchronously sends a message, but returns before reading the + * response body, and allows you to read the response via a + * #GInputStream. */ void soup_session_queue_message (SoupSession *session, SoupMessage *msg, @@ -1419,7 +2106,17 @@ soup_session_real_requeue_message (SoupSession *session, SoupMessage *msg) item = soup_message_queue_lookup (priv->queue, msg); g_return_if_fail (item != NULL); - item->state = SOUP_MESSAGE_RESTARTING; + + if (item->resend_count >= SOUP_SESSION_MAX_RESEND_COUNT) { + if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) + soup_message_set_status (msg, SOUP_STATUS_TOO_MANY_REDIRECTS); + else + g_warning ("SoupMessage %p stuck in infinite loop?", msg); + } else { + item->resend_count++; + item->state = SOUP_MESSAGE_RESTARTING; + } + soup_message_queue_item_unref (item); } @@ -1440,6 +2137,19 @@ soup_session_requeue_message (SoupSession *session, SoupMessage *msg) SOUP_SESSION_GET_CLASS (session)->requeue_message (session, msg); } +static guint +soup_session_real_send_message (SoupSession *session, SoupMessage *msg) +{ + SoupMessageQueueItem *item; + guint status; + + item = soup_session_append_queue_item (session, msg, FALSE, FALSE, + NULL, NULL); + soup_session_process_queue_item (session, item, NULL, TRUE); + status = msg->status_code; + soup_message_queue_item_unref (item); + return status; +} /** * soup_session_send_message: @@ -1450,7 +2160,18 @@ soup_session_requeue_message (SoupSession *session, SoupMessage *msg) * transfer is finished successfully or there is an unrecoverable * error. * - * @msg is not freed upon return. + * Unlike with soup_session_queue_message(), @msg is not freed upon + * return. + * + * (Note that if you call this method on a #SoupSessionAsync, it will + * still use asynchronous I/O internally, running the glib main loop + * to process the message, which may also cause other events to be + * processed.) + * + * Contrast this method with soup_session_send(), which also + * synchronously sends a message, but returns before reading the + * response body, and allows you to read the response via a + * #GInputStream. * * Return value: the HTTP status code of the response */ @@ -1471,6 +2192,9 @@ soup_session_send_message (SoupSession *session, SoupMessage *msg) * * Pauses HTTP I/O on @msg. Call soup_session_unpause_message() to * resume I/O. + * + * This may only be called for asynchronous messages (those sent on a + * #SoupSessionAsync or using soup_session_queue_message()). **/ void soup_session_pause_message (SoupSession *session, @@ -1485,6 +2209,7 @@ soup_session_pause_message (SoupSession *session, priv = SOUP_SESSION_GET_PRIVATE (session); item = soup_message_queue_lookup (priv->queue, msg); g_return_if_fail (item != NULL); + g_return_if_fail (item->async); item->paused = TRUE; if (item->state == SOUP_MESSAGE_RUNNING) @@ -1492,6 +2217,51 @@ soup_session_pause_message (SoupSession *session, soup_message_queue_item_unref (item); } +static void +soup_session_real_kick_queue (SoupSession *session) +{ + SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); + SoupMessageQueueItem *item; + gboolean have_sync_items = FALSE; + + if (priv->disposed) + return; + + for (item = soup_message_queue_first (priv->queue); + item; + item = soup_message_queue_next (priv->queue, item)) { + if (item->async) { + GSource *source; + + /* We use priv rather than session as the + * source data, because other parts of libsoup + * (or the calling app) may have sources using + * the session as the source data. + */ + source = g_main_context_find_source_by_user_data (item->async_context, priv); + if (!source) { + source = soup_add_completion_reffed (item->async_context, + idle_run_queue, priv); + priv->run_queue_sources = g_slist_prepend (priv->run_queue_sources, + source); + } + } else + have_sync_items = TRUE; + } + + if (have_sync_items) { + g_mutex_lock (&priv->conn_lock); + g_cond_broadcast (&priv->conn_cond); + g_mutex_unlock (&priv->conn_lock); + } +} + +void +soup_session_kick_queue (SoupSession *session) +{ + SOUP_SESSION_GET_CLASS (session)->kick (session); +} + /** * soup_session_unpause_message: * @session: a #SoupSession @@ -1503,6 +2273,9 @@ soup_session_pause_message (SoupSession *session, * 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. + * + * This may only be called for asynchronous messages (those sent on a + * #SoupSessionAsync or using soup_session_queue_message()). **/ void soup_session_unpause_message (SoupSession *session, @@ -1517,13 +2290,14 @@ soup_session_unpause_message (SoupSession *session, priv = SOUP_SESSION_GET_PRIVATE (session); item = soup_message_queue_lookup (priv->queue, msg); g_return_if_fail (item != NULL); + g_return_if_fail (item->async); item->paused = FALSE; if (item->state == SOUP_MESSAGE_RUNNING) soup_message_io_unpause (msg); soup_message_queue_item_unref (item); - SOUP_SESSION_GET_CLASS (session)->kick (session); + soup_session_kick_queue (session); } @@ -1540,6 +2314,7 @@ soup_session_real_cancel_message (SoupSession *session, SoupMessage *msg, guint soup_message_set_status (msg, status_code); g_cancellable_cancel (item->cancellable); + soup_session_kick_queue (item->session); soup_message_queue_item_unref (item); } @@ -1555,7 +2330,7 @@ soup_session_real_cancel_message (SoupSession *session, SoupMessage *msg, guint * may call this at any time after handing @msg off to @session; if * @session has started sending the request but has not yet received * the complete response, then it will close the request's connection. - * Note that with non-idempotent requests (eg, + * Note that with requests that have side effects (eg, * <literal>POST</literal>, <literal>PUT</literal>, * <literal>DELETE</literal>) it is possible that you might cancel the * request after the server acts on it, but before it returns a @@ -1566,9 +2341,12 @@ soup_session_real_cancel_message (SoupSession *session, SoupMessage *msg, guint * The response headers, on the other hand, will always be either * empty or complete. * - * For messages queued with soup_session_queue_message() (and - * cancelled from the same thread), the callback will be invoked - * before soup_session_cancel_message() returns. + * Beware that with the deprecated #SoupSessionAsync, messages queued + * with soup_session_queue_message() will have their callbacks invoked + * before soup_session_cancel_message() returns. The plain + * #SoupSession does not have this behavior; cancelling an + * asynchronous message will merely queue its callback to be run after + * returning to the main loop. **/ void soup_session_cancel_message (SoupSession *session, SoupMessage *msg, @@ -1599,20 +2377,66 @@ soup_session_real_flush_queue (SoupSession *session) { SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); SoupMessageQueueItem *item; + GHashTable *current = NULL; + gboolean done = FALSE; + + if (SOUP_IS_SESSION_SYNC (session)) { + /* Record the current contents of the queue */ + current = g_hash_table_new (NULL, NULL); + for (item = soup_message_queue_first (priv->queue); + item; + item = soup_message_queue_next (priv->queue, item)) + g_hash_table_insert (current, item, item); + } + /* Cancel everything */ for (item = soup_message_queue_first (priv->queue); item; item = soup_message_queue_next (priv->queue, item)) { soup_session_cancel_message (session, item->msg, SOUP_STATUS_CANCELLED); } + + if (SOUP_IS_SESSION_SYNC (session)) { + /* Wait until all of the items in @current have been + * removed from the queue. (This is not the same as + * "wait for the queue to be empty", because the app + * may queue new requests in response to the + * cancellation of the old ones. We don't try to + * cancel those requests as well, since we'd likely + * just end up looping forever.) + */ + g_mutex_lock (&priv->conn_lock); + do { + done = TRUE; + for (item = soup_message_queue_first (priv->queue); + item; + item = soup_message_queue_next (priv->queue, item)) { + if (g_hash_table_lookup (current, item)) + done = FALSE; + } + + if (!done) + g_cond_wait (&priv->conn_cond, &priv->conn_lock); + } while (!done); + g_mutex_unlock (&priv->conn_lock); + + g_hash_table_destroy (current); + } } /** * soup_session_abort: * @session: the session * - * Cancels all pending requests in @session. + * Cancels all pending requests in @session and closes all idle + * persistent connections. + * + * The message cancellation has the same semantics as with + * soup_session_cancel_message(); asynchronous requests on a + * #SoupSessionAsync will have their callback called before + * soup_session_abort() returns. Requests on a plain #SoupSession will + * not. **/ void soup_session_abort (SoupSession *session) @@ -1631,8 +2455,11 @@ soup_session_abort (SoupSession *session) g_mutex_lock (&priv->conn_lock); conns = NULL; g_hash_table_iter_init (&iter, priv->conns); - while (g_hash_table_iter_next (&iter, &conn, &host)) + while (g_hash_table_iter_next (&iter, &conn, &host)) { conns = g_slist_prepend (conns, g_object_ref (conn)); + g_hash_table_iter_remove (&iter); + drop_connection (session, host, conn); + } g_mutex_unlock (&priv->conn_lock); for (c = conns; c; c = c->next) { @@ -1666,23 +2493,19 @@ prefetch_uri (SoupSession *session, SoupURI *uri, } /** -* soup_session_prepare_for_uri: -* @session: a #SoupSession -* @uri: a #SoupURI which may be required -* -* Tells @session that @uri may be requested shortly, and so the -* session can try to prepare (resolving the domain name, obtaining -* proxy address, etc.) in order to work more quickly once the URI is -* actually requested. -* -* This method acts asynchronously, in @session's -* #SoupSession:async_context. If you are using #SoupSessionSync and do -* not have a main loop running, then you can't use this method. -* -* Since: 2.30 -* -* Deprecated: 2.38: use soup_session_prefetch_dns() instead -**/ + * soup_session_prepare_for_uri: + * @session: a #SoupSession + * @uri: a #SoupURI which may be required + * + * Tells @session that @uri may be requested shortly, and so the + * session can try to prepare (resolving the domain name, obtaining + * proxy address, etc.) in order to work more quickly once the URI is + * actually requested. + * + * Since: 2.30 + * + * Deprecated: 2.38: use soup_session_prefetch_dns() instead + **/ void soup_session_prepare_for_uri (SoupSession *session, SoupURI *uri) { @@ -1713,10 +2536,6 @@ soup_session_prepare_for_uri (SoupSession *session, SoupURI *uri) * resolution. @callback will still be invoked in this case, with a * status of %SOUP_STATUS_CANCELLED. * -* This method acts asynchronously, in @session's -* #SoupSession:async_context. If you are using #SoupSessionSync and do -* not have a main loop running, then you can't use this method. -* * Since: 2.38 **/ void @@ -1748,6 +2567,9 @@ soup_session_prefetch_dns (SoupSession *session, const char *hostname, * feature to the session at construct time by using the * %SOUP_SESSION_ADD_FEATURE property. * + * Note that a #SoupContentDecoder is added to the session by default + * (unless you are using one of the deprecated session subclasses). + * * Since: 2.24 **/ void @@ -1759,6 +2581,15 @@ soup_session_add_feature (SoupSession *session, SoupSessionFeature *feature) g_return_if_fail (SOUP_IS_SESSION_FEATURE (feature)); priv = SOUP_SESSION_GET_PRIVATE (session); + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + if (SOUP_IS_PROXY_URI_RESOLVER (feature)) { + set_proxy_resolver (session, NULL, + SOUP_PROXY_URI_RESOLVER (feature), + NULL); + } + G_GNUC_END_IGNORE_DEPRECATIONS; + priv->features = g_slist_prepend (priv->features, g_object_ref (feature)); g_hash_table_remove_all (priv->features_cache); soup_session_feature_attach (feature, session); @@ -1774,29 +2605,45 @@ soup_session_add_feature (SoupSession *session, SoupSessionFeature *feature) * adds it to @session as with soup_session_add_feature(). You can use * this when you don't need to customize the new feature in any way. * - * If @feature_type is not a #SoupSessionFeature type, this gives - * each existing feature on @session the chance to accept @feature_type - * as a "subfeature". This can be used to add new #SoupAuth types, - * for instance. + * If @feature_type is not a #SoupSessionFeature type, this gives each + * existing feature on @session the chance to accept @feature_type as + * a "subfeature". This can be used to add new #SoupAuth or + * #SoupRequest types, for instance. * * You can also add a feature to the session at construct time by * using the %SOUP_SESSION_ADD_FEATURE_BY_TYPE property. * + * Note that a #SoupContentDecoder is added to the session by default + * (unless you are using one of the deprecated session subclasses). + * * Since: 2.24 **/ void soup_session_add_feature_by_type (SoupSession *session, GType feature_type) { + SoupSessionPrivate *priv; + g_return_if_fail (SOUP_IS_SESSION (session)); + priv = SOUP_SESSION_GET_PRIVATE (session); + if (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE)) { SoupSessionFeature *feature; feature = g_object_new (feature_type, NULL); soup_session_add_feature (session, feature); g_object_unref (feature); + } else if (g_type_is_a (feature_type, SOUP_TYPE_REQUEST)) { + SoupRequestClass *request_class; + int i; + + request_class = g_type_class_ref (feature_type); + for (i = 0; request_class->schemes[i]; i++) { + g_hash_table_insert (priv->request_types, + (char *)request_class->schemes[i], + GSIZE_TO_POINTER (feature_type)); + } } else { - SoupSessionPrivate *priv = SOUP_SESSION_GET_PRIVATE (session); GSList *f; for (f = priv->features; f; f = f->next) { @@ -1828,6 +2675,15 @@ soup_session_remove_feature (SoupSession *session, SoupSessionFeature *feature) priv->features = g_slist_remove (priv->features, feature); g_hash_table_remove_all (priv->features_cache); soup_session_feature_detach (feature, session); + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + if (SOUP_IS_PROXY_URI_RESOLVER (feature)) { + if (SOUP_IS_PROXY_RESOLVER_WRAPPER (priv->proxy_resolver) && + SOUP_PROXY_RESOLVER_WRAPPER (priv->proxy_resolver)->soup_resolver == SOUP_PROXY_URI_RESOLVER (feature)) + g_clear_object (&priv->proxy_resolver); + } + G_GNUC_END_IGNORE_DEPRECATIONS; + g_object_unref (feature); } } @@ -1862,6 +2718,21 @@ soup_session_remove_feature_by_type (SoupSession *session, GType feature_type) goto restart; } } + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + if (g_type_is_a (feature_type, SOUP_TYPE_PROXY_URI_RESOLVER)) + priv->proxy_use_default = FALSE; + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + } else if (g_type_is_a (feature_type, SOUP_TYPE_REQUEST)) { + SoupRequestClass *request_class; + int i; + + request_class = g_type_class_peek (feature_type); + if (!request_class) + return; + for (i = 0; request_class->schemes[i]; i++) { + g_hash_table_remove (priv->request_types, + request_class->schemes[i]); + } } else { for (f = priv->features; f; f = f->next) { if (soup_session_feature_remove_feature (f->data, feature_type)) @@ -1872,6 +2743,49 @@ soup_session_remove_feature_by_type (SoupSession *session, GType feature_type) } /** + * soup_session_has_feature: + * @session: a #SoupSession + * @feature_type: the #GType of the class of features to check for + * + * Tests if @session has at a feature of type @feature_type (which can + * be the type of either a #SoupSessionFeature, or else a subtype of + * some class managed by another feature, such as #SoupAuth or + * #SoupRequest). + * + * Return value: %TRUE or %FALSE + * + * Since: 2.42 + **/ +gboolean +soup_session_has_feature (SoupSession *session, + GType feature_type) +{ + SoupSessionPrivate *priv; + GSList *f; + + g_return_val_if_fail (SOUP_IS_SESSION (session), FALSE); + + priv = SOUP_SESSION_GET_PRIVATE (session); + + if (g_type_is_a (feature_type, SOUP_TYPE_SESSION_FEATURE)) { + for (f = priv->features; f; f = f->next) { + if (G_TYPE_CHECK_INSTANCE_TYPE (f->data, feature_type)) + return TRUE; + } + } else if (g_type_is_a (feature_type, SOUP_TYPE_REQUEST)) { + return g_hash_table_lookup (priv->request_types, + GSIZE_TO_POINTER (feature_type)) != NULL; + } else { + for (f = priv->features; f; f = f->next) { + if (soup_session_feature_has_feature (f->data, feature_type)) + return TRUE; + } + } + + return FALSE; +} + +/** * soup_session_get_features: * @session: a #SoupSession * @feature_type: the #GType of the class of features to get @@ -1982,12 +2896,15 @@ soup_session_class_init (SoupSessionClass *session_class) g_type_class_add_private (session_class, sizeof (SoupSessionPrivate)); /* virtual method definition */ + session_class->queue_message = soup_session_real_queue_message; + session_class->send_message = soup_session_real_send_message; session_class->requeue_message = soup_session_real_requeue_message; session_class->cancel_message = soup_session_real_cancel_message; - session_class->auth_required = soup_session_real_auth_required; session_class->flush_queue = soup_session_real_flush_queue; + session_class->kick = soup_session_real_kick_queue; /* virtual method override */ + object_class->constructor = soup_session_constructor; object_class->dispose = soup_session_dispose; object_class->finalize = soup_session_finalize; object_class->set_property = soup_session_set_property; @@ -2036,7 +2953,7 @@ soup_session_class_init (SoupSessionClass *session_class) * #SoupMessage::finished (and all of the other #SoupMessage * signals) may be invoked multiple times for a given message. * - * Since: 2.4.1 + * Since: 2.24 **/ signals[REQUEST_QUEUED] = g_signal_new ("request-queued", @@ -2044,7 +2961,7 @@ soup_session_class_init (SoupSessionClass *session_class) G_SIGNAL_RUN_FIRST, 0, /* FIXME? */ NULL, NULL, - _soup_marshal_NONE__OBJECT, + NULL, G_TYPE_NONE, 1, SOUP_TYPE_MESSAGE); @@ -2064,7 +2981,7 @@ soup_session_class_init (SoupSessionClass *session_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupSessionClass, request_started), NULL, NULL, - _soup_marshal_NONE__OBJECT_OBJECT, + NULL, G_TYPE_NONE, 2, SOUP_TYPE_MESSAGE, SOUP_TYPE_SOCKET); @@ -2079,7 +2996,7 @@ soup_session_class_init (SoupSessionClass *session_class) * #SoupSession::request_queued for a detailed description of the * message lifecycle within a session. * - * Since: 2.4.1 + * Since: 2.24 **/ signals[REQUEST_UNQUEUED] = g_signal_new ("request-unqueued", @@ -2087,7 +3004,7 @@ soup_session_class_init (SoupSessionClass *session_class) G_SIGNAL_RUN_FIRST, 0, /* FIXME? */ NULL, NULL, - _soup_marshal_NONE__OBJECT, + NULL, G_TYPE_NONE, 1, SOUP_TYPE_MESSAGE); @@ -2120,7 +3037,7 @@ soup_session_class_init (SoupSessionClass *session_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupSessionClass, authenticate), NULL, NULL, - _soup_marshal_NONE__OBJECT_OBJECT_BOOLEAN, + NULL, G_TYPE_NONE, 3, SOUP_TYPE_MESSAGE, SOUP_TYPE_AUTH, @@ -2143,7 +3060,7 @@ soup_session_class_init (SoupSessionClass *session_class) G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - _soup_marshal_NONE__OBJECT, + NULL, G_TYPE_NONE, 1, /* SoupConnection is private, so we can't use * SOUP_TYPE_CONNECTION here. @@ -2167,7 +3084,7 @@ soup_session_class_init (SoupSessionClass *session_class) G_SIGNAL_RUN_FIRST, 0, NULL, NULL, - _soup_marshal_NONE__OBJECT, + NULL, G_TYPE_NONE, 1, /* SoupConnection is private, so we can't use * SOUP_TYPE_CONNECTION here. @@ -2177,10 +3094,30 @@ soup_session_class_init (SoupSessionClass *session_class) /* properties */ /** + * SoupSession:proxy-uri: + * + * A proxy to use for all http and https requests in this + * session. Setting this will clear the + * #SoupSession:proxy-resolver property, and remove any + * <type>SoupProxyURIResolver</type> features that have been + * added to the session. Setting this property will also + * cancel all currently pending messages. + * + * Note that #SoupSession will normally handle looking up the + * user's proxy settings for you; you should only use + * #SoupSession:proxy-uri if you need to override the user's + * normal proxy settings. + * + * Also note that this proxy will be used for + * <emphasis>all</emphasis> requests; even requests to + * <literal>localhost</literal>. If you need more control over + * proxies, you can create a #GSimpleProxyResolver and set the + * #SoupSession:proxy-resolver property. + */ + /** * SOUP_SESSION_PROXY_URI: * - * Alias for the #SoupSession:proxy-uri property. (The HTTP - * proxy to use for this session.) + * Alias for the #SoupSession:proxy-uri property, qv. **/ g_object_class_install_property ( object_class, PROP_PROXY_URI, @@ -2190,10 +3127,37 @@ soup_session_class_init (SoupSessionClass *session_class) SOUP_TYPE_URI, G_PARAM_READWRITE)); /** + * SoupSession:proxy-resolver: + * + * A #GProxyResolver to use with this session. Setting this + * will clear the #SoupSession:proxy-uri property, and remove + * any <type>SoupProxyURIResolver</type> features that have + * been added to the session. + * + * By default, in a plain #SoupSession, this is set to the + * default #GProxyResolver, but you can set it to %NULL if you + * don't want to use proxies, or set it to your own + * #GProxyResolver if you want to control what proxies get + * used. + * + * Since: 2.42 + */ + /** + * SOUP_SESSION_PROXY_RESOLVER: + * + * Alias for the #SoupSession:proxy-resolver property, qv. + **/ + g_object_class_install_property ( + object_class, PROP_PROXY_RESOLVER, + g_param_spec_object (SOUP_SESSION_PROXY_RESOLVER, + "Proxy Resolver", + "The GProxyResolver to use for this session", + G_TYPE_PROXY_RESOLVER, + G_PARAM_READWRITE)); + /** * SOUP_SESSION_MAX_CONNS: * - * Alias for the #SoupSession:max-conns property. (The maximum - * number of connections that the session can open at once.) + * Alias for the #SoupSession:max-conns property, qv. **/ g_object_class_install_property ( object_class, PROP_MAX_CONNS, @@ -2207,9 +3171,7 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SOUP_SESSION_MAX_CONNS_PER_HOST: * - * Alias for the #SoupSession:max-conns-per-host property. - * (The maximum number of connections that the session can - * open at once to a given host.) + * Alias for the #SoupSession:max-conns-per-host property, qv. **/ g_object_class_install_property ( object_class, PROP_MAX_CONNS_PER_HOST, @@ -2223,24 +3185,35 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SoupSession:idle-timeout: * - * Connection lifetime when idle + * Connection lifetime (in seconds) when idle. Any connection + * left idle longer than this will be closed. * - * Since: 2.4.1 + * Although you can change this property at any time, it will + * only affect newly-created connections, not currently-open + * ones. You can call soup_session_abort() after setting this + * if you want to ensure that all future connections will have + * this timeout value. + * + * Note that the default value of 60 seconds only applies to + * plain #SoupSessions. If you are using #SoupSessionAsync or + * #SoupSessionSync, the default value is 0 (meaning idle + * connections will never time out). + * + * Since: 2.24 **/ /** * SOUP_SESSION_IDLE_TIMEOUT: * - * Alias for the #SoupSession:idle-timeout property. (The idle - * connection lifetime.) + * Alias for the #SoupSession:idle-timeout property, qv. * - * Since: 2.4.1 + * Since: 2.24 **/ g_object_class_install_property ( object_class, PROP_IDLE_TIMEOUT, g_param_spec_uint (SOUP_SESSION_IDLE_TIMEOUT, "Idle Timeout", "Connection lifetime when idle", - 0, G_MAXUINT, 0, + 0, G_MAXUINT, 60, G_PARAM_READWRITE)); /** * SoupSession:use-ntlm: @@ -2253,8 +3226,7 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SOUP_SESSION_USE_NTLM: * - * Alias for the #SoupSession:use-ntlm property. (Whether or - * not to use NTLM authentication.) + * Alias for the #SoupSession:use-ntlm property, qv. **/ g_object_class_install_property ( object_class, PROP_USE_NTLM, @@ -2262,23 +3234,25 @@ soup_session_class_init (SoupSessionClass *session_class) "Use NTLM", "Whether or not to use NTLM authentication", FALSE, - G_PARAM_READWRITE)); + G_PARAM_READWRITE | G_PARAM_DEPRECATED)); /** * SoupSession:ssl-ca-file: * * File containing SSL CA certificates. * - * Deprecated: use #SoupSession:ssl-use-system-ca-file or - * #SoupSession:tls-database instead + * If the specified file does not exist or cannot be read, + * then libsoup will print a warning, and then behave as + * though it had read in a empty CA file, meaning that all SSL + * certificates will be considered invalid. + * + * Deprecated: use #SoupSession:ssl-use-system-ca-file, or + * else #SoupSession:tls-database with a #GTlsFileDatabase + * (which allows you to do explicit error handling). **/ /** * SOUP_SESSION_SSL_CA_FILE: * - * Alias for the #SoupSession:ssl-ca-file property. (File - * containing SSL CA certificates.). - * - * Deprecated: use #SoupSession:ssl-use-system-ca-file or - * #SoupSession:tls-database instead + * Alias for the #SoupSession:ssl-ca-file property, qv. **/ g_object_class_install_property ( object_class, PROP_SSL_CA_FILE, @@ -2286,9 +3260,9 @@ soup_session_class_init (SoupSessionClass *session_class) "SSL CA file", "File containing SSL CA certificates", NULL, - G_PARAM_READWRITE)); + G_PARAM_READWRITE | G_PARAM_DEPRECATED)); /** - * SOUP_SESSION_USE_SYSTEM_CA_FILE: + * SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE: * * Alias for the #SoupSession:ssl-use-system-ca-file property, * qv. @@ -2310,6 +3284,11 @@ soup_session_class_init (SoupSessionClass *session_class) * See #SoupSession:ssl-strict for more information on how * https certificate validation is handled. * + * Note that the default value of %TRUE only applies to plain + * #SoupSessions. If you are using #SoupSessionAsync or + * #SoupSessionSync, the default value is %FALSE, for backward + * compatibility. + * * Since: 2.38 **/ g_object_class_install_property ( @@ -2317,7 +3296,7 @@ soup_session_class_init (SoupSessionClass *session_class) g_param_spec_boolean (SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, "Use system CA file", "Use the system certificate database", - FALSE, + TRUE, G_PARAM_READWRITE)); /** * SOUP_SESSION_TLS_DATABASE: @@ -2340,6 +3319,12 @@ soup_session_class_init (SoupSessionClass *session_class) * See #SoupSession:ssl-strict for more information on how * https certificate validation is handled. * + * If you are using a plain #SoupSession then + * #SoupSession:ssl-use-system-ca-file will be %TRUE by + * default, and so this property will be a copy of the system + * CA database. If you are using #SoupSessionAsync or + * #SoupSessionSync, this property will be %NULL by default. + * * Since: 2.38 **/ g_object_class_install_property ( @@ -2359,8 +3344,8 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SoupSession:ssl-strict: * - * Normally, if #SoupSession:tlsdb is set (including if it was - * set via #SoupSession:ssl-use-system-ca-file or + * Normally, if #SoupSession:tls-database is set (including if + * it was set via #SoupSession:ssl-use-system-ca-file or * #SoupSession:ssl-ca-file), then libsoup will reject any * certificate that is invalid (ie, expired) or that is not * signed by one of the given CA certificates, and the @@ -2374,9 +3359,13 @@ soup_session_class_init (SoupSessionClass *session_class) * accept invalid certificates after giving some sort of * warning.) * - * If the session has no CA file or TLS database, then all - * certificates are always accepted, and this property has no - * effect. + * For a plain #SoupSession, if the session has no CA file or + * TLS database, and this property is %TRUE, then all + * certificates will be rejected. However, beware that the + * deprecated #SoupSession subclasses (#SoupSessionAsync and + * #SoupSessionSync) have the opposite behavior: if there is + * no CA file or TLS database, then all certificates are always + * accepted, and this property has no effect. * * Since: 2.30 */ @@ -2388,10 +3377,26 @@ soup_session_class_init (SoupSessionClass *session_class) TRUE, G_PARAM_READWRITE)); /** + * SoupSession:async-context: + * + * The #GMainContext that miscellaneous session-related + * asynchronous callbacks are invoked on. (Eg, setting + * #SoupSession:idle-timeout will add a timeout source on this + * context.) + * + * For a plain #SoupSession, this property is always set to + * the #GMainContext that is the thread-default at the time + * the session was created, and cannot be overridden. For the + * deprecated #SoupSession subclasses, the default value is + * %NULL, meaning to use the global default #GMainContext. + * + * If #SoupSession:use-thread-context is %FALSE, this context + * will also be used for asynchronous HTTP I/O. + */ + /** * SOUP_SESSION_ASYNC_CONTEXT: * - * Alias for the #SoupSession:async-context property. (The - * session's #GMainContext.) + * Alias for the #SoupSession:async-context property, qv. */ g_object_class_install_property ( object_class, PROP_ASYNC_CONTEXT, @@ -2409,13 +3414,11 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SoupSession:use-thread-context: * - * If set, asynchronous operations in this session will run in + * If %TRUE (which it always is on a plain #SoupSession), + * asynchronous HTTP requests in this session will run in * whatever the thread-default #GMainContext is at the time - * they are started, rather than always occurring in a context - * fixed at the session's construction time. "Bookkeeping" - * tasks (like expiring idle connections) will happen in the - * context that was thread-default at the time the session was - * created. + * they are started, rather than always occurring in + * #SoupSession:async-context. * * Since: 2.38 */ @@ -2427,10 +3430,31 @@ soup_session_class_init (SoupSessionClass *session_class) FALSE, G_PARAM_READWRITE)); /** + * SoupSession:timeout: + * + * The timeout (in seconds) for socket I/O operations + * (including connecting to a server, and waiting for a reply + * to an HTTP request). + * + * Although you can change this property at any time, it will + * only affect newly-created connections, not currently-open + * ones. You can call soup_session_abort() after setting this + * if you want to ensure that all future connections will have + * this timeout value. + * + * Note that the default value of 60 seconds only applies to + * plain #SoupSessions. If you are using #SoupSessionAsync or + * #SoupSessionSync, the default value is 0 (meaning socket I/O + * will not time out). + * + * Not to be confused with #SoupSession:idle-timeout (which is + * the length of time that idle persistent connections will be + * kept open). + */ + /** * SOUP_SESSION_TIMEOUT: * - * Alias for the #SoupSession:timeout property. (The timeout - * in seconds for blocking socket I/O operations.) + * Alias for the #SoupSession:timeout property, qv. **/ g_object_class_install_property ( object_class, PROP_TIMEOUT, @@ -2544,8 +3568,7 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SOUP_SESSION_ADD_FEATURE: (skip) * - * Alias for the #SoupSession:add-feature property. (Shortcut - * for calling soup_session_add_feature(). + * Alias for the #SoupSession:add-feature property, qv. * * Since: 2.24 **/ @@ -2567,8 +3590,7 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SOUP_SESSION_ADD_FEATURE_BY_TYPE: (skip) * - * Alias for the #SoupSession:add-feature-by-type property. - * (Shortcut for calling soup_session_add_feature_by_type(). + * Alias for the #SoupSession:add-feature-by-type property, qv. * * Since: 2.24 **/ @@ -2577,7 +3599,7 @@ soup_session_class_init (SoupSessionClass *session_class) g_param_spec_gtype (SOUP_SESSION_ADD_FEATURE_BY_TYPE, "Add Feature By Type", "Add a feature object of the given type to the session", - SOUP_TYPE_SESSION_FEATURE, + G_TYPE_OBJECT, G_PARAM_READWRITE)); /** * SoupSession:remove-feature-by-type: (skip) @@ -2590,9 +3612,8 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SOUP_SESSION_REMOVE_FEATURE_BY_TYPE: (skip) * - * Alias for the #SoupSession:remove-feature-by-type - * property. (Shortcut for calling - * soup_session_remove_feature_by_type(). + * Alias for the #SoupSession:remove-feature-by-type property, + * qv. * * Since: 2.24 **/ @@ -2601,7 +3622,7 @@ soup_session_class_init (SoupSessionClass *session_class) g_param_spec_gtype (SOUP_SESSION_REMOVE_FEATURE_BY_TYPE, "Remove Feature By Type", "Remove features of the given type from the session", - SOUP_TYPE_SESSION_FEATURE, + G_TYPE_OBJECT, G_PARAM_READWRITE)); /** * SoupSession:http-aliases: @@ -2611,14 +3632,14 @@ soup_session_class_init (SoupSessionClass *session_class) * <literal>"dav"</literal>, than a URI of * <literal>dav://example.com/path</literal> would be treated * identically to <literal>http://example.com/path</literal>. - * If the value is %NULL, then only "http" is recognized as - * meaning "http". * - * For backward-compatibility reasons, the default value for - * this property is an array containing the single element - * <literal>"*"</literal>, a special value which means that - * any scheme except "https" is considered to be an alias for - * "http". + * In a plain #SoupSession, the default value is %NULL, + * meaning that only "http" is recognized as meaning "http". + * In #SoupSessionAsync and #SoupSessionSync, for backward + * compatibility, the default value is an array containing the + * single element <literal>"*"</literal>, a special value + * which means that any scheme except "https" is considered to + * be an alias for "http". * * See also #SoupSession:https-aliases. * @@ -2627,8 +3648,7 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SOUP_SESSION_HTTP_ALIASES: * - * Alias for the #SoupSession:http-aliases property. (URI - * schemes that will be considered aliases for "http".) + * Alias for the #SoupSession:http-aliases property, qv. * * Since: 2.38 */ @@ -2654,8 +3674,7 @@ soup_session_class_init (SoupSessionClass *session_class) /** * SOUP_SESSION_HTTPS_ALIASES: * - * Alias for the #SoupSession:https-aliases property. (URI - * schemes that will be considered aliases for "https".) + * Alias for the #SoupSession:https-aliases property, qv. * * Since: 2.38 **/ @@ -2666,4 +3685,874 @@ soup_session_class_init (SoupSessionClass *session_class) "URI schemes that are considered aliases for 'https'", G_TYPE_STRV, G_PARAM_READWRITE)); + + /** + * SOUP_SESSION_LOCAL_ADDRESS: + * + * Alias for the #SoupSession:local-address property, qv. + * + * Since: 2.42 + **/ + /** + * SoupSession:local-address: + * + * Sets the #SoupAddress to use for the client side of + * the connection. + * + * Use this property if you want for instance to bind the + * local socket to a specific IP address. + * + * Since: 2.42 + **/ + g_object_class_install_property ( + object_class, PROP_LOCAL_ADDRESS, + g_param_spec_object (SOUP_SESSION_LOCAL_ADDRESS, + "Local address", + "Address of local end of socket", + SOUP_TYPE_ADDRESS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + + +static gboolean +expected_to_be_requeued (SoupSession *session, SoupMessage *msg) +{ + if (msg->status_code == SOUP_STATUS_UNAUTHORIZED || + msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) { + SoupSessionFeature *feature = + soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER); + return !feature || !soup_message_disables_feature (msg, feature); + } + + if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_NO_REDIRECT)) + return soup_session_would_redirect (session, msg); + + return FALSE; +} + +/* send_request_async */ + +static void +async_send_request_return_result (SoupMessageQueueItem *item, + gpointer stream, GError *error) +{ + GTask *task; + + g_return_if_fail (item->task != NULL); + + g_signal_handlers_disconnect_matched (item->msg, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, item); + + task = item->task; + item->task = NULL; + + if (item->io_source) { + g_source_destroy (item->io_source); + g_clear_pointer (&item->io_source, g_source_unref); + } + + if (error) + g_task_return_error (task, error); + else if (item->error) { + if (stream) + g_object_unref (stream); + g_task_return_error (task, g_error_copy (item->error)); + } else if (SOUP_STATUS_IS_TRANSPORT_ERROR (item->msg->status_code)) { + if (stream) + g_object_unref (stream); + g_task_return_new_error (task, SOUP_HTTP_ERROR, + item->msg->status_code, + "%s", + item->msg->reason_phrase); + } else + g_task_return_pointer (task, stream, g_object_unref); + g_object_unref (task); +} + +static void +async_send_request_restarted (SoupMessage *msg, gpointer user_data) +{ + SoupMessageQueueItem *item = user_data; + + /* We won't be needing this, then. */ + g_object_set_data (G_OBJECT (item->msg), "SoupSession:ostream", NULL); + item->io_started = FALSE; +} + +static void +async_send_request_finished (SoupMessage *msg, gpointer user_data) +{ + SoupMessageQueueItem *item = user_data; + GMemoryOutputStream *mostream; + GInputStream *istream = NULL; + GError *error = NULL; + + if (!item->task) { + /* Something else already took care of it. */ + return; + } + + mostream = g_object_get_data (G_OBJECT (item->task), "SoupSession:ostream"); + if (mostream) { + gpointer data; + gssize size; + + /* We thought it would be requeued, but it wasn't, so + * return the original body. + */ + size = g_memory_output_stream_get_data_size (mostream); + data = size ? g_memory_output_stream_steal_data (mostream) : g_strdup (""); + istream = g_memory_input_stream_new_from_data (data, size, g_free); + } else if (item->io_started) { + /* The message finished before becoming readable. This + * will happen, eg, if it's cancelled from got-headers. + * Do nothing; the op will complete via read_ready_cb() + * after we return; + */ + return; + } else { + /* The message finished before even being started; + * probably a tunnel connect failure. + */ + istream = g_memory_input_stream_new (); + } + + async_send_request_return_result (item, istream, error); +} + +static void +send_async_spliced (GObject *source, GAsyncResult *result, gpointer user_data) +{ + SoupMessageQueueItem *item = user_data; + GInputStream *istream = g_object_get_data (source, "istream"); + GError *error = NULL; + + /* It should be safe to call the sync close() method here since + * the message body has already been written. + */ + g_input_stream_close (istream, NULL, NULL); + g_object_unref (istream); + + /* If the message was cancelled, it will be completed via other means */ + if (g_cancellable_is_cancelled (item->cancellable) || + !item->task) { + soup_message_queue_item_unref (item); + return; + } + + if (g_output_stream_splice_finish (G_OUTPUT_STREAM (source), + result, &error) == -1) { + async_send_request_return_result (item, NULL, error); + soup_message_queue_item_unref (item); + return; + } + + /* Otherwise either restarted or finished will eventually be called. */ + soup_session_kick_queue (item->session); + soup_message_queue_item_unref (item); +} + +static void +send_async_maybe_complete (SoupMessageQueueItem *item, + GInputStream *stream) +{ + if (expected_to_be_requeued (item->session, item->msg)) { + GOutputStream *ostream; + + /* Gather the current message body... */ + ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); + g_object_set_data_full (G_OBJECT (item->task), "SoupSession:ostream", + ostream, g_object_unref); + + g_object_set_data (G_OBJECT (ostream), "istream", stream); + + /* Give the splice op its own ref on item */ + soup_message_queue_item_ref (item); + /* We don't use CLOSE_SOURCE because we need to control when the + * side effects of closing the SoupClientInputStream happen. + */ + g_output_stream_splice_async (ostream, stream, + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + G_PRIORITY_DEFAULT, + item->cancellable, + send_async_spliced, item); + return; + } + + async_send_request_return_result (item, stream, NULL); +} + +static void try_run_until_read (SoupMessageQueueItem *item); + +static gboolean +read_ready_cb (SoupMessage *msg, gpointer user_data) +{ + SoupMessageQueueItem *item = user_data; + + g_clear_pointer (&item->io_source, g_source_unref); + try_run_until_read (item); + return FALSE; +} + +static void +try_run_until_read (SoupMessageQueueItem *item) +{ + GError *error = NULL; + GInputStream *stream = NULL; + + if (soup_message_io_run_until_read (item->msg, FALSE, item->cancellable, &error)) + stream = soup_message_io_get_response_istream (item->msg, &error); + if (stream) { + send_async_maybe_complete (item, stream); + return; + } + + if (g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) { + item->state = SOUP_MESSAGE_RESTARTING; + soup_message_io_finished (item->msg); + g_error_free (error); + return; + } + + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + if (item->state != SOUP_MESSAGE_FINISHED) { + if (soup_message_io_in_progress (item->msg)) + soup_message_io_finished (item->msg); + item->state = SOUP_MESSAGE_FINISHING; + soup_session_process_queue_item (item->session, item, NULL, FALSE); + } + async_send_request_return_result (item, NULL, error); + return; + } + + g_clear_error (&error); + item->io_source = soup_message_io_get_source (item->msg, item->cancellable, + read_ready_cb, item); + g_source_attach (item->io_source, soup_session_get_async_context (item->session)); +} + +static void +async_send_request_running (SoupSession *session, SoupMessageQueueItem *item) +{ + item->io_started = TRUE; + try_run_until_read (item); +} + +static void +async_return_from_cache (SoupMessageQueueItem *item, + GInputStream *stream) +{ + const char *content_type; + GHashTable *params = NULL; + + soup_message_got_headers (item->msg); + + content_type = soup_message_headers_get_content_type (item->msg->response_headers, ¶ms); + if (content_type) { + soup_message_content_sniffed (item->msg, content_type, params); + g_hash_table_unref (params); + } + + item->state = SOUP_MESSAGE_FINISHING; + async_send_request_return_result (item, g_object_ref (stream), NULL); +} + +typedef struct { + SoupCache *cache; + SoupMessage *conditional_msg; +} AsyncCacheCancelData; + + +static void +free_async_cache_cancel_data (AsyncCacheCancelData *data) +{ + g_object_unref (data->conditional_msg); + g_object_unref (data->cache); + g_slice_free (AsyncCacheCancelData, data); +} + +static void +cancel_cache_response (SoupMessageQueueItem *item) +{ + item->paused = FALSE; + item->state = SOUP_MESSAGE_FINISHING; + soup_message_set_status (item->msg, SOUP_STATUS_CANCELLED); + soup_session_kick_queue (item->session); +} + +static void +conditional_request_cancelled_cb (GCancellable *cancellable, AsyncCacheCancelData *data) +{ + soup_cache_cancel_conditional_request (data->cache, data->conditional_msg); +} + +static void +conditional_get_ready_cb (SoupSession *session, SoupMessage *msg, gpointer user_data) +{ + SoupMessageQueueItem *item = user_data; + GInputStream *stream; + SoupCache *cache; + + if (g_cancellable_is_cancelled (item->cancellable)) { + cancel_cache_response (item); + return; + } else { + gulong handler_id = GPOINTER_TO_SIZE (g_object_get_data (G_OBJECT (msg), "SoupSession:handler-id")); + g_cancellable_disconnect (item->cancellable, handler_id); + } + + cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE); + soup_cache_update_from_conditional_request (cache, msg); + + if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) { + stream = soup_cache_send_response (cache, item->msg); + if (stream) { + async_return_from_cache (item, stream); + g_object_unref (stream); + return; + } + } + + /* The resource was modified or the server returned a 200 + * OK. Either way we reload it. FIXME. + */ + item->state = SOUP_MESSAGE_STARTING; + soup_session_kick_queue (session); +} + +static gboolean +idle_return_from_cache_cb (gpointer data) +{ + GTask *task = data; + SoupMessageQueueItem *item = g_task_get_task_data (task); + GInputStream *istream; + + if (item->state == SOUP_MESSAGE_FINISHED) { + /* The original request was cancelled using + * soup_session_cancel_message () so it has been + * already handled by the cancellation code path. + */ + return FALSE; + } else if (g_cancellable_is_cancelled (item->cancellable)) { + /* Cancel original msg after g_cancellable_cancel(). */ + cancel_cache_response (item); + return FALSE; + } + + istream = g_object_get_data (G_OBJECT (task), "SoupSession:istream"); + async_return_from_cache (item, istream); + + return FALSE; +} + + +static gboolean +async_respond_from_cache (SoupSession *session, + SoupMessageQueueItem *item) +{ + SoupCache *cache; + SoupCacheResponse response; + + cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE); + if (!cache) + return FALSE; + + response = soup_cache_has_response (cache, item->msg); + if (response == SOUP_CACHE_RESPONSE_FRESH) { + GInputStream *stream; + GSource *source; + + stream = soup_cache_send_response (cache, item->msg); + if (!stream) { + /* Cached file was deleted? */ + return FALSE; + } + g_object_set_data_full (G_OBJECT (item->task), "SoupSession:istream", + stream, g_object_unref); + + source = g_timeout_source_new (0); + g_task_attach_source (item->task, source, + (GSourceFunc) idle_return_from_cache_cb); + g_source_unref (source); + return TRUE; + } else if (response == SOUP_CACHE_RESPONSE_NEEDS_VALIDATION) { + SoupMessage *conditional_msg; + AsyncCacheCancelData *data; + gulong handler_id; + + conditional_msg = soup_cache_generate_conditional_request (cache, item->msg); + if (!conditional_msg) + return FALSE; + + /* Detect any quick cancellation before the cache is able to return data. */ + data = g_slice_new0 (AsyncCacheCancelData); + data->cache = g_object_ref (cache); + data->conditional_msg = g_object_ref (conditional_msg); + handler_id = g_cancellable_connect (item->cancellable, G_CALLBACK (conditional_request_cancelled_cb), + data, (GDestroyNotify) free_async_cache_cancel_data); + + g_object_set_data (G_OBJECT (conditional_msg), "SoupSession:handler-id", + GSIZE_TO_POINTER (handler_id)); + soup_session_queue_message (session, conditional_msg, + conditional_get_ready_cb, + item); + + + return TRUE; + } else + return FALSE; +} + +/** + * soup_session_send_async: + * @session: a #SoupSession + * @msg: a #SoupMessage + * @cancellable: a #GCancellable + * @callback: the callback to invoke + * @user_data: data for @callback + * + * Asynchronously sends @msg and waits for the beginning of a + * response. When @callback is called, then either @msg has been sent, + * and its response headers received, or else an error has occurred. + * Call soup_session_send_finish() to get a #GInputStream for reading + * the response body. + * + * See soup_session_send() for more details on the general semantics. + * + * Contrast this method with soup_session_queue_message(), which also + * asynchronously sends a #SoupMessage, but doesn't invoke its + * callback until the response has been completely read. + * + * (Note that this method cannot be called on the deprecated + * #SoupSessionSync subclass, and can only be called on + * #SoupSessionAsync if you have set the + * #SoupSession:use-thread-context property.) + * + * Since: 2.42 + */ +void +soup_session_send_async (SoupSession *session, + SoupMessage *msg, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SoupMessageQueueItem *item; + gboolean use_thread_context; + + g_return_if_fail (SOUP_IS_SESSION (session)); + g_return_if_fail (!SOUP_IS_SESSION_SYNC (session)); + + g_object_get (G_OBJECT (session), + SOUP_SESSION_USE_THREAD_CONTEXT, &use_thread_context, + NULL); + g_return_if_fail (use_thread_context); + + item = soup_session_append_queue_item (session, msg, TRUE, TRUE, + NULL, NULL); + g_signal_connect (msg, "restarted", + G_CALLBACK (async_send_request_restarted), item); + g_signal_connect (msg, "finished", + G_CALLBACK (async_send_request_finished), item); + + if (cancellable) { + g_object_unref (item->cancellable); + item->cancellable = g_object_ref (cancellable); + } + + item->new_api = TRUE; + item->task = g_task_new (session, item->cancellable, callback, user_data); + g_task_set_task_data (item->task, item, (GDestroyNotify) soup_message_queue_item_unref); + + /* Do not check for cancellations as we do not want to + * overwrite custom error messages set during cancellations + * (for example SOUP_HTTP_ERROR is set for cancelled messages + * in async_send_request_return_result() (status_code==1 + * means CANCEL and is considered a TRANSPORT_ERROR)). + */ + g_task_set_check_cancellable (item->task, FALSE); + + if (async_respond_from_cache (session, item)) + item->state = SOUP_MESSAGE_CACHED; + else + soup_session_kick_queue (session); +} + +/** + * soup_session_send_finish: + * @session: a #SoupSession + * @result: the #GAsyncResult passed to your callback + * @error: return location for a #GError, or %NULL + * + * Gets the response to a soup_session_send_async() call and (if + * successful), returns a #GInputStream that can be used to read the + * response body. + * + * Return value: (transfer full): a #GInputStream for reading the + * response body, or %NULL on error. + * + * Since: 2.42 + */ +GInputStream * +soup_session_send_finish (SoupSession *session, + GAsyncResult *result, + GError **error) +{ + GTask *task; + + g_return_val_if_fail (SOUP_IS_SESSION (session), NULL); + g_return_val_if_fail (!SOUP_IS_SESSION_SYNC (session), NULL); + g_return_val_if_fail (g_task_is_valid (result, session), NULL); + + task = G_TASK (result); + if (g_task_had_error (task)) { + SoupMessageQueueItem *item = g_task_get_task_data (task); + + if (soup_message_io_in_progress (item->msg)) + soup_message_io_finished (item->msg); + else if (item->state != SOUP_MESSAGE_FINISHED) + item->state = SOUP_MESSAGE_FINISHING; + + if (item->state != SOUP_MESSAGE_FINISHED) + soup_session_process_queue_item (session, item, NULL, FALSE); + } + + return g_task_propagate_pointer (task, error); +} + +/** + * soup_session_send: + * @session: a #SoupSession + * @msg: a #SoupMessage + * @cancellable: a #GCancellable + * @error: return location for a #GError, or %NULL + * + * Synchronously sends @msg and waits for the beginning of a response. + * On success, a #GInputStream will be returned which you can use to + * read the response body. ("Success" here means only that an HTTP + * response was received and understood; it does not necessarily mean + * that a 2xx class status code was received.) + * + * If non-%NULL, @cancellable can be used to cancel the request; + * soup_session_send() will return a %G_IO_ERROR_CANCELLED error. Note + * that with requests that have side effects (eg, + * <literal>POST</literal>, <literal>PUT</literal>, + * <literal>DELETE</literal>) it is possible that you might cancel the + * request after the server acts on it, but before it returns a + * response, leaving the remote resource in an unknown state. + * + * If @msg is requeued due to a redirect or authentication, the + * initial (3xx/401/407) response body will be suppressed, and + * soup_session_send() will only return once a final response has been + * received. + * + * Contrast this method with soup_session_send_message(), which also + * synchronously sends a #SoupMessage, but doesn't return until the + * response has been completely read. + * + * (Note that this method cannot be called on the deprecated + * #SoupSessionAsync subclass.) + * + * Return value: (transfer full): a #GInputStream for reading the + * response body, or %NULL on error. + * + * Since: 2.42 + */ +GInputStream * +soup_session_send (SoupSession *session, + SoupMessage *msg, + GCancellable *cancellable, + GError **error) +{ + SoupMessageQueueItem *item; + GInputStream *stream = NULL; + GOutputStream *ostream; + GMemoryOutputStream *mostream; + gssize size; + GError *my_error = NULL; + + g_return_val_if_fail (SOUP_IS_SESSION (session), NULL); + g_return_val_if_fail (!SOUP_IS_SESSION_ASYNC (session), NULL); + + item = soup_session_append_queue_item (session, msg, FALSE, TRUE, + NULL, NULL); + + item->new_api = TRUE; + if (cancellable) { + g_object_unref (item->cancellable); + item->cancellable = g_object_ref (cancellable); + } + + while (!stream) { + /* Get a connection, etc */ + soup_session_process_queue_item (session, item, NULL, TRUE); + if (item->state != SOUP_MESSAGE_RUNNING) + break; + + /* Send request, read headers */ + if (!soup_message_io_run_until_read (msg, TRUE, item->cancellable, &my_error)) { + if (g_error_matches (my_error, SOUP_HTTP_ERROR, SOUP_STATUS_TRY_AGAIN)) { + item->state = SOUP_MESSAGE_RESTARTING; + soup_message_io_finished (item->msg); + g_clear_error (&my_error); + continue; + } else + break; + } + + stream = soup_message_io_get_response_istream (msg, &my_error); + if (!stream) + break; + + if (!expected_to_be_requeued (session, msg)) + break; + + /* Gather the current message body... */ + ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); + if (g_output_stream_splice (ostream, stream, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + item->cancellable, &my_error) == -1) { + g_object_unref (stream); + g_object_unref (ostream); + stream = NULL; + break; + } + g_object_unref (stream); + stream = NULL; + + /* If the message was requeued, loop */ + if (item->state == SOUP_MESSAGE_RESTARTING) { + g_object_unref (ostream); + continue; + } + + /* Not requeued, so return the original body */ + mostream = G_MEMORY_OUTPUT_STREAM (ostream); + size = g_memory_output_stream_get_data_size (mostream); + stream = g_memory_input_stream_new (); + if (size) { + g_memory_input_stream_add_data (G_MEMORY_INPUT_STREAM (stream), + g_memory_output_stream_steal_data (mostream), + size, g_free); + } + g_object_unref (ostream); + } + + if (my_error) + g_propagate_error (error, my_error); + else if (item->error) { + g_clear_object (&stream); + if (error) + *error = g_error_copy (item->error); + } else if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) { + g_clear_object (&stream); + g_set_error_literal (error, SOUP_HTTP_ERROR, msg->status_code, + msg->reason_phrase); + } else if (!stream) + stream = g_memory_input_stream_new (); + + if (!stream) { + if (soup_message_io_in_progress (msg)) + soup_message_io_finished (msg); + else if (item->state != SOUP_MESSAGE_FINISHED) + item->state = SOUP_MESSAGE_FINISHING; + + if (item->state != SOUP_MESSAGE_FINISHED) + soup_session_process_queue_item (session, item, NULL, TRUE); + } + + soup_message_queue_item_unref (item); + return stream; +} + +/** + * soup_session_request: + * @session: a #SoupSession + * @uri_string: a URI, in string form + * @error: return location for a #GError, or %NULL + * + * Creates a #SoupRequest for retrieving @uri_string. + * + * Return value: (transfer full): a new #SoupRequest, or + * %NULL on error. + * + * Since: 2.42 + */ +SoupRequest * +soup_session_request (SoupSession *session, const char *uri_string, + GError **error) +{ + SoupURI *uri; + SoupRequest *req; + + uri = soup_uri_new (uri_string); + if (!uri) { + g_set_error (error, SOUP_REQUEST_ERROR, + SOUP_REQUEST_ERROR_BAD_URI, + _("Could not parse URI '%s'"), uri_string); + return NULL; + } + + req = soup_session_request_uri (session, uri, error); + soup_uri_free (uri); + return req; +} + +/** + * soup_session_request_uri: + * @session: a #SoupSession + * @uri: a #SoupURI representing the URI to retrieve + * @error: return location for a #GError, or %NULL + * + * Creates a #SoupRequest for retrieving @uri. + * + * Return value: (transfer full): a new #SoupRequest, or + * %NULL on error. + * + * Since: 2.42 + */ +SoupRequest * +soup_session_request_uri (SoupSession *session, SoupURI *uri, + GError **error) +{ + SoupSessionPrivate *priv; + GType request_type; + + g_return_val_if_fail (SOUP_IS_SESSION (session), NULL); + + priv = SOUP_SESSION_GET_PRIVATE (session); + + request_type = (GType)GPOINTER_TO_SIZE (g_hash_table_lookup (priv->request_types, uri->scheme)); + if (!request_type) { + g_set_error (error, SOUP_REQUEST_ERROR, + SOUP_REQUEST_ERROR_UNSUPPORTED_URI_SCHEME, + _("Unsupported URI scheme '%s'"), uri->scheme); + return NULL; + } + + return g_initable_new (request_type, NULL, error, + "uri", uri, + "session", session, + NULL); +} + +static SoupRequestHTTP * +initialize_http_request (SoupRequest *req, + const char *method, + GError **error) +{ + SoupRequestHTTP *http; + SoupMessage *msg; + + if (!SOUP_IS_REQUEST_HTTP (req)) { + g_object_unref (req); + g_set_error (error, SOUP_REQUEST_ERROR, + SOUP_REQUEST_ERROR_BAD_URI, + _("Not an HTTP URI")); + return NULL; + } + + http = SOUP_REQUEST_HTTP (req); + msg = soup_request_http_get_message (http); + g_object_set (G_OBJECT (msg), + SOUP_MESSAGE_METHOD, method, + NULL); + g_object_unref (msg); + + return http; +} + +/** + * soup_session_request_http: + * @session: a #SoupSession + * @method: an HTTP method + * @uri_string: a URI, in string form + * @error: return location for a #GError, or %NULL + * + * Creates a #SoupRequest for retrieving @uri_string, which must be an + * "http" or "https" URI (or another protocol listed in @session's + * #SoupSession:http-aliases or #SoupSession:https-aliases). + * + * Return value: (transfer full): a new #SoupRequestHTTP, or + * %NULL on error. + * + * Since: 2.42 + */ +SoupRequestHTTP * +soup_session_request_http (SoupSession *session, + const char *method, + const char *uri_string, + GError **error) +{ + SoupRequest *req; + + req = soup_session_request (session, uri_string, error); + if (!req) + return NULL; + + return initialize_http_request (req, method, error); +} + +/** + * soup_session_request_http_uri: + * @session: a #SoupSession + * @method: an HTTP method + * @uri: a #SoupURI representing the URI to retrieve + * @error: return location for a #GError, or %NULL + * + * Creates a #SoupRequest for retrieving @uri, which must be an + * "http" or "https" URI (or another protocol listed in @session's + * #SoupSession:http-aliases or #SoupSession:https-aliases). + * + * Return value: (transfer full): a new #SoupRequestHTTP, or + * %NULL on error. + * + * Since: 2.42 + */ +SoupRequestHTTP * +soup_session_request_http_uri (SoupSession *session, + const char *method, + SoupURI *uri, + GError **error) +{ + SoupRequest *req; + + req = soup_session_request_uri (session, uri, error); + if (!req) + return NULL; + + return initialize_http_request (req, method, error); +} + +/** + * SOUP_REQUEST_ERROR: + * + * A #GError domain for #SoupRequest<!-- -->-related errors. Used with + * #SoupRequestError. + * + * Since: 2.42 + */ +/** + * SoupRequestError: + * @SOUP_REQUEST_ERROR_BAD_URI: the URI could not be parsed + * @SOUP_REQUEST_ERROR_UNSUPPORTED_URI_SCHEME: the URI scheme is not + * supported by this #SoupSession + * @SOUP_REQUEST_ERROR_PARSING: the server's response could not + * be parsed + * @SOUP_REQUEST_ERROR_ENCODING: the server's response was in an + * unsupported format + * + * A #SoupRequest error. + * + * Since: 2.42 + */ + +GQuark +soup_request_error_quark (void) +{ + static GQuark error; + if (!error) + error = g_quark_from_static_string ("soup_request_error_quark"); + return error; } diff --git a/libsoup/soup-session.h b/libsoup/soup-session.h index 8748a764..67a59eaa 100644 --- a/libsoup/soup-session.h +++ b/libsoup/soup-session.h @@ -60,7 +60,9 @@ typedef struct { GType soup_session_get_type (void); +#define SOUP_SESSION_LOCAL_ADDRESS "local-address" #define SOUP_SESSION_PROXY_URI "proxy-uri" +#define SOUP_SESSION_PROXY_RESOLVER "proxy-resolver" #define SOUP_SESSION_MAX_CONNS "max-conns" #define SOUP_SESSION_MAX_CONNS_PER_HOST "max-conns-per-host" #define SOUP_SESSION_USE_NTLM "use-ntlm" @@ -81,7 +83,12 @@ GType soup_session_get_type (void); #define SOUP_SESSION_HTTP_ALIASES "http-aliases" #define SOUP_SESSION_HTTPS_ALIASES "https-aliases" -GMainContext *soup_session_get_async_context(SoupSession *session); +SOUP_AVAILABLE_IN_2_42 +SoupSession *soup_session_new (void); + +SOUP_AVAILABLE_IN_2_42 +SoupSession *soup_session_new_with_options (const char *optname1, + ...) G_GNUC_NULL_TERMINATED; void soup_session_queue_message (SoupSession *session, SoupMessage *msg, @@ -103,36 +110,101 @@ void soup_session_cancel_message (SoupSession *session, guint status_code); void soup_session_abort (SoupSession *session); +GMainContext *soup_session_get_async_context(SoupSession *session); + +SOUP_AVAILABLE_IN_2_42 +void soup_session_send_async (SoupSession *session, + SoupMessage *msg, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +SOUP_AVAILABLE_IN_2_42 +GInputStream *soup_session_send_finish (SoupSession *session, + GAsyncResult *result, + GError **error); +SOUP_AVAILABLE_IN_2_42 +GInputStream *soup_session_send (SoupSession *session, + SoupMessage *msg, + GCancellable *cancellable, + GError **error); + +#ifndef SOUP_DISABLE_DEPRECATED +/* SOUP_AVAILABLE_IN_2_30 -- this trips up gtkdoc-scan */ +SOUP_DEPRECATED_IN_2_38_FOR (soup_session_prefetch_dns) void soup_session_prepare_for_uri (SoupSession *session, SoupURI *uri); +#endif +SOUP_AVAILABLE_IN_2_38 void soup_session_prefetch_dns (SoupSession *session, const char *hostname, GCancellable *cancellable, SoupAddressCallback callback, gpointer user_data); +SOUP_AVAILABLE_IN_2_38 gboolean soup_session_would_redirect (SoupSession *session, SoupMessage *msg); +SOUP_AVAILABLE_IN_2_38 gboolean soup_session_redirect_message (SoupSession *session, SoupMessage *msg); +SOUP_AVAILABLE_IN_2_24 void soup_session_add_feature (SoupSession *session, SoupSessionFeature *feature); +SOUP_AVAILABLE_IN_2_24 void soup_session_add_feature_by_type (SoupSession *session, GType feature_type); +SOUP_AVAILABLE_IN_2_24 void soup_session_remove_feature (SoupSession *session, SoupSessionFeature *feature); +SOUP_AVAILABLE_IN_2_24 void soup_session_remove_feature_by_type (SoupSession *session, GType feature_type); +SOUP_AVAILABLE_IN_2_42 +gboolean soup_session_has_feature (SoupSession *session, + GType feature_type); +SOUP_AVAILABLE_IN_2_26 GSList *soup_session_get_features (SoupSession *session, GType feature_type); +SOUP_AVAILABLE_IN_2_26 SoupSessionFeature *soup_session_get_feature (SoupSession *session, GType feature_type); +SOUP_AVAILABLE_IN_2_28 SoupSessionFeature *soup_session_get_feature_for_message(SoupSession *session, GType feature_type, SoupMessage *msg); +SOUP_AVAILABLE_IN_2_42 +SoupRequest *soup_session_request (SoupSession *session, + const char *uri_string, + GError **error); +SOUP_AVAILABLE_IN_2_42 +SoupRequest *soup_session_request_uri (SoupSession *session, + SoupURI *uri, + GError **error); +SOUP_AVAILABLE_IN_2_42 +SoupRequestHTTP *soup_session_request_http (SoupSession *session, + const char *method, + const char *uri_string, + GError **error); +SOUP_AVAILABLE_IN_2_42 +SoupRequestHTTP *soup_session_request_http_uri (SoupSession *session, + const char *method, + SoupURI *uri, + GError **error); + +SOUP_AVAILABLE_IN_2_42 +GQuark soup_request_error_quark (void); +#define SOUP_REQUEST_ERROR soup_request_error_quark () + +typedef enum { + SOUP_REQUEST_ERROR_BAD_URI, + SOUP_REQUEST_ERROR_UNSUPPORTED_URI_SCHEME, + SOUP_REQUEST_ERROR_PARSING, + SOUP_REQUEST_ERROR_ENCODING +} SoupRequestError; + G_END_DECLS #endif /* SOUP_SESSION_H */ diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index 2ac359de..b9f1dfca 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -11,11 +11,12 @@ #include <string.h> +#include <gio/gnetworking.h> + #include "soup-socket.h" #include "soup.h" #include "soup-filter-input-stream.h" #include "soup-io-stream.h" -#include "soup-marshal.h" #include "soup-misc-private.h" /** @@ -58,7 +59,7 @@ enum { PROP_CLEAN_DISPOSE, PROP_TLS_CERTIFICATE, PROP_TLS_ERRORS, - PROP_USE_PROXY, + PROP_PROXY_RESOLVER, LAST_PROP }; @@ -70,6 +71,7 @@ typedef struct { GInputStream *istream; GOutputStream *ostream; GTlsCertificateFlags tls_errors; + GProxyResolver *proxy_resolver; guint non_blocking:1; guint is_server:1; @@ -78,7 +80,6 @@ typedef struct { guint ssl_fallback:1; guint clean_dispose:1; guint use_thread_context:1; - guint use_proxy:1; gpointer ssl_creds; GMainContext *async_context; @@ -149,6 +150,8 @@ soup_socket_finalize (GObject *object) g_clear_object (&priv->local_addr); g_clear_object (&priv->remote_addr); + g_clear_object (&priv->proxy_resolver); + if (priv->watch_src) { if (priv->clean_dispose && !priv->is_server) g_warning ("Disposing socket %p during async op", object); @@ -179,6 +182,7 @@ finish_socket_setup (SoupSocketPrivate *priv) priv->ostream = g_object_ref (g_io_stream_get_output_stream (priv->iostream)); g_socket_set_timeout (priv->gsock, priv->timeout); + g_socket_set_option (priv->gsock, IPPROTO_TCP, TCP_NODELAY, TRUE, NULL); } static void @@ -219,8 +223,8 @@ soup_socket_set_property (GObject *object, guint prop_id, if (priv->conn) g_socket_set_timeout (priv->gsock, priv->timeout); break; - case PROP_USE_PROXY: - priv->use_proxy = g_value_get_boolean (value); + case PROP_PROXY_RESOLVER: + priv->proxy_resolver = g_value_dup_object (value); break; case PROP_CLEAN_DISPOSE: priv->clean_dispose = g_value_get_boolean (value); @@ -280,8 +284,8 @@ soup_socket_get_property (GObject *object, guint prop_id, case PROP_TLS_ERRORS: g_value_set_flags (value, priv->tls_errors); break; - case PROP_USE_PROXY: - g_value_set_boolean (value, priv->use_proxy); + case PROP_PROXY_RESOLVER: + g_value_set_object (value, priv->proxy_resolver); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -317,7 +321,7 @@ soup_socket_class_init (SoupSocketClass *socket_class) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (SoupSocketClass, readable), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -333,7 +337,7 @@ soup_socket_class_init (SoupSocketClass *socket_class) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (SoupSocketClass, writable), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -349,7 +353,7 @@ soup_socket_class_init (SoupSocketClass *socket_class) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (SoupSocketClass, disconnected), NULL, NULL, - _soup_marshal_NONE__NONE, + NULL, G_TYPE_NONE, 0); /** @@ -369,7 +373,7 @@ soup_socket_class_init (SoupSocketClass *socket_class) G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SoupSocketClass, new_connection), NULL, NULL, - _soup_marshal_NONE__OBJECT, + NULL, G_TYPE_NONE, 1, SOUP_TYPE_SOCKET); /** @@ -544,14 +548,14 @@ soup_socket_class_init (SoupSocketClass *socket_class) * Alias for the #SoupSocket:use-thread-context property. (Use * g_main_context_get_thread_default()) * - * Since: 2.36.1 + * Since: 2.38 */ /** * SoupSocket:use-thread-context: * * Use g_main_context_get_thread_default(). * - * Since: 2.36.1 + * Since: 2.38 */ g_object_class_install_property ( object_class, PROP_USE_THREAD_CONTEXT, @@ -618,12 +622,12 @@ soup_socket_class_init (SoupSocketClass *socket_class) G_PARAM_READABLE)); g_object_class_install_property ( - object_class, PROP_USE_PROXY, - g_param_spec_boolean (SOUP_SOCKET_USE_PROXY, - "Use proxy", - "Use #GProxyResolver", - FALSE, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + object_class, PROP_PROXY_RESOLVER, + g_param_spec_object (SOUP_SOCKET_PROXY_RESOLVER, + "Proxy resolver", + "GProxyResolver to use", + G_TYPE_PROXY_RESOLVER, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } @@ -651,11 +655,11 @@ soup_socket_new (const char *optname1, ...) } static void -proxy_socket_client_event (GSocketClient *client, - GSocketClientEvent event, - GSocketConnectable *connectable, - GIOStream *connection, - gpointer user_data) +re_emit_socket_client_event (GSocketClient *client, + GSocketClientEvent event, + GSocketConnectable *connectable, + GIOStream *connection, + gpointer user_data) { SoupSocket *sock = user_data; @@ -663,35 +667,115 @@ proxy_socket_client_event (GSocketClient *client, event, connection); } +static gboolean +socket_connect_finish (SoupSocket *sock, GSocketConnection *conn) +{ + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + + g_clear_object (&priv->connect_cancel); + + if (conn) { + priv->conn = (GIOStream *)conn; + priv->gsock = g_object_ref (g_socket_connection_get_socket (conn)); + finish_socket_setup (priv); + return TRUE; + } else + return FALSE; +} + static guint -socket_connected (SoupSocket *sock, GSocketConnection *conn, GError *error) +socket_legacy_error (SoupSocket *sock, GError *error) +{ + guint status; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + status = SOUP_STATUS_CANCELLED; + else if (error->domain == G_RESOLVER_ERROR) + status = SOUP_STATUS_CANT_RESOLVE; + else + status = SOUP_STATUS_CANT_CONNECT; + + g_error_free (error); + return status; +} + +static GSocketClient * +new_socket_client (SoupSocket *sock) { SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + GSocketClient *client = g_socket_client_new (); - if (priv->connect_cancel) { - GCancellable *cancellable = priv->connect_cancel; + g_signal_connect (client, "event", + G_CALLBACK (re_emit_socket_client_event), sock); + if (priv->proxy_resolver) { + g_socket_client_set_proxy_resolver (client, priv->proxy_resolver); + g_socket_client_add_application_proxy (client, "http"); + } else + g_socket_client_set_enable_proxy (client, FALSE); + if (priv->timeout) + g_socket_client_set_timeout (client, priv->timeout); - g_object_unref (priv->connect_cancel); - priv->connect_cancel = NULL; - if (g_cancellable_is_cancelled (cancellable)) - return SOUP_STATUS_CANCELLED; - } + if (priv->local_addr) { + GSocketAddress *addr; - if (error) { - if (error->domain == G_RESOLVER_ERROR) { - g_error_free (error); - return SOUP_STATUS_CANT_RESOLVE; - } else { - g_error_free (error); - return SOUP_STATUS_CANT_CONNECT; - } + addr = soup_address_get_gsockaddr (priv->local_addr); + g_socket_client_set_local_address (client, addr); + g_object_unref (addr); } - priv->conn = (GIOStream *)conn; - priv->gsock = g_object_ref (g_socket_connection_get_socket (conn)); - finish_socket_setup (priv); + return client; +} - return SOUP_STATUS_OK; +static void +async_connected (GObject *client, GAsyncResult *result, gpointer data) +{ + GTask *task = data; + SoupSocket *sock = g_task_get_source_object (task); + GSocketConnection *conn; + GError *error = NULL; + + conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client), + result, &error); + if (socket_connect_finish (sock, conn)) + g_task_return_boolean (task, TRUE); + else + g_task_return_error (task, error); + g_object_unref (task); +} + +gboolean +soup_socket_connect_finish_internal (SoupSocket *sock, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); +} + +void +soup_socket_connect_async_internal (SoupSocket *sock, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SoupSocketPrivate *priv; + GSocketClient *client; + GTask *task; + + g_return_if_fail (SOUP_IS_SOCKET (sock)); + priv = SOUP_SOCKET_GET_PRIVATE (sock); + g_return_if_fail (!priv->is_server); + g_return_if_fail (priv->gsock == NULL); + g_return_if_fail (priv->remote_addr != NULL); + + priv->connect_cancel = cancellable ? g_object_ref (cancellable) : g_cancellable_new (); + task = g_task_new (sock, priv->connect_cancel, callback, user_data); + + client = new_socket_client (sock); + g_socket_client_connect_async (client, + G_SOCKET_CONNECTABLE (priv->remote_addr), + priv->connect_cancel, + async_connected, task); + g_object_unref (client); } /** @@ -710,22 +794,25 @@ typedef struct { } SoupSocketAsyncConnectData; static void -async_connected (GObject *client, GAsyncResult *result, gpointer data) +legacy_connect_async_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) { - SoupSocketAsyncConnectData *sacd = data; - SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sacd->sock); + SoupSocket *sock = SOUP_SOCKET (object); + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + SoupSocketAsyncConnectData *sacd = user_data; GError *error = NULL; - GSocketConnection *conn; guint status; if (priv->async_context && !priv->use_thread_context) g_main_context_pop_thread_default (priv->async_context); - conn = g_socket_client_connect_finish (G_SOCKET_CLIENT (client), - result, &error); - status = socket_connected (sacd->sock, conn, error); + if (soup_socket_connect_finish_internal (sock, result, &error)) + status = SOUP_STATUS_OK; + else + status = socket_legacy_error (sock, error); - sacd->callback (sacd->sock, status, sacd->user_data); + sacd->callback (sock, status, sacd->user_data); g_object_unref (sacd->sock); g_slice_free (SoupSocketAsyncConnectData, sacd); } @@ -751,10 +838,11 @@ soup_socket_connect_async (SoupSocket *sock, GCancellable *cancellable, { SoupSocketPrivate *priv; SoupSocketAsyncConnectData *sacd; - GSocketClient *client; g_return_if_fail (SOUP_IS_SOCKET (sock)); priv = SOUP_SOCKET_GET_PRIVATE (sock); + g_return_if_fail (!priv->is_server); + g_return_if_fail (priv->gsock == NULL); g_return_if_fail (priv->remote_addr != NULL); sacd = g_slice_new0 (SoupSocketAsyncConnectData); @@ -762,25 +850,38 @@ soup_socket_connect_async (SoupSocket *sock, GCancellable *cancellable, sacd->callback = callback; sacd->user_data = user_data; - priv->connect_cancel = cancellable ? g_object_ref (cancellable) : g_cancellable_new (); - if (priv->async_context && !priv->use_thread_context) g_main_context_push_thread_default (priv->async_context); - client = g_socket_client_new (); - g_signal_connect (client, "event", - G_CALLBACK (proxy_socket_client_event), sock); - if (priv->use_proxy) - g_socket_client_add_application_proxy (client, "http"); - else - g_socket_client_set_enable_proxy (client, FALSE); - if (priv->timeout) - g_socket_client_set_timeout (client, priv->timeout); - g_socket_client_connect_async (client, - G_SOCKET_CONNECTABLE (priv->remote_addr), - priv->connect_cancel, - async_connected, sacd); + soup_socket_connect_async_internal (sock, cancellable, + legacy_connect_async_cb, + sacd); +} + +gboolean +soup_socket_connect_sync_internal (SoupSocket *sock, + GCancellable *cancellable, + GError **error) +{ + SoupSocketPrivate *priv; + GSocketClient *client; + GSocketConnection *conn; + + 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->gsock == NULL, SOUP_STATUS_MALFORMED); + g_return_val_if_fail (priv->remote_addr != NULL, SOUP_STATUS_MALFORMED); + + priv->connect_cancel = cancellable ? g_object_ref (cancellable) : g_cancellable_new (); + + client = new_socket_client (sock); + conn = g_socket_client_connect (client, + G_SOCKET_CONNECTABLE (priv->remote_addr), + priv->connect_cancel, error); g_object_unref (client); + + return socket_connect_finish (sock, conn); } /** @@ -800,8 +901,6 @@ guint soup_socket_connect_sync (SoupSocket *sock, GCancellable *cancellable) { SoupSocketPrivate *priv; - GSocketClient *client; - GSocketConnection *conn; GError *error = NULL; g_return_val_if_fail (SOUP_IS_SOCKET (sock), SOUP_STATUS_MALFORMED); @@ -810,29 +909,23 @@ soup_socket_connect_sync (SoupSocket *sock, GCancellable *cancellable) g_return_val_if_fail (priv->gsock == NULL, SOUP_STATUS_MALFORMED); g_return_val_if_fail (priv->remote_addr != NULL, SOUP_STATUS_MALFORMED); - if (cancellable) - g_object_ref (cancellable); - else - cancellable = g_cancellable_new (); - priv->connect_cancel = cancellable; - - client = g_socket_client_new (); - g_signal_connect (client, "event", - G_CALLBACK (proxy_socket_client_event), sock); - if (priv->use_proxy) - g_socket_client_add_application_proxy (client, "http"); + if (soup_socket_connect_sync_internal (sock, cancellable, &error)) + return SOUP_STATUS_OK; else - g_socket_client_set_enable_proxy (client, FALSE); - if (priv->timeout) - g_socket_client_set_timeout (client, priv->timeout); - conn = g_socket_client_connect (client, - G_SOCKET_CONNECTABLE (priv->remote_addr), - priv->connect_cancel, &error); - g_object_unref (client); - - return socket_connected (sock, conn, error); + return socket_legacy_error (sock, error); } +/** + * soup_socket_get_fd: + * @sock: a #SoupSocket + * + * Gets @sock's underlying file descriptor. + * + * Note that fiddling with the file descriptor may break the + * #SoupSocket. + * + * Return value: @sock's file descriptor. + */ int soup_socket_get_fd (SoupSocket *sock) { @@ -1012,37 +1105,11 @@ soup_socket_accept_certificate (GTlsConnection *conn, GTlsCertificate *cert, return TRUE; } -/** - * 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, GCancellable *cancellable) -{ - SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - - 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. - * - * Return value: success or failure - **/ -gboolean -soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host, - GCancellable *cancellable) +static gboolean +soup_socket_setup_ssl (SoupSocket *sock, + const char *ssl_host, + GCancellable *cancellable, + GError **error) { SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); GTlsBackend *backend = g_tls_backend_get_default (); @@ -1050,7 +1117,7 @@ soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host, if (G_IS_TLS_CONNECTION (priv->conn)) return TRUE; - if (g_cancellable_is_cancelled (cancellable)) + if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; priv->ssl = TRUE; @@ -1061,7 +1128,7 @@ soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host, identity = g_network_address_new (ssl_host, 0); conn = g_initable_new (g_tls_backend_get_client_connection_type (backend), - NULL, NULL, + cancellable, error, "base-io-stream", priv->conn, "server-identity", identity, "database", priv->ssl_creds, @@ -1085,7 +1152,7 @@ soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host, GTlsServerConnection *conn; conn = g_initable_new (g_tls_backend_get_server_connection_type (backend), - NULL, NULL, + cancellable, error, "base-io-stream", priv->conn, "certificate", priv->ssl_creds, "use-system-certdb", FALSE, @@ -1110,76 +1177,103 @@ soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host, return TRUE; } + +/** + * 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, GCancellable *cancellable) +{ + SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); + + return soup_socket_setup_ssl (sock, soup_address_get_name (priv->remote_addr), + cancellable, NULL); +} -guint +/** + * 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. + * + * Return value: success or failure + **/ +gboolean +soup_socket_start_proxy_ssl (SoupSocket *sock, const char *ssl_host, + GCancellable *cancellable) +{ + return soup_socket_setup_ssl (sock, ssl_host, cancellable, NULL); +} + +gboolean soup_socket_handshake_sync (SoupSocket *sock, - GCancellable *cancellable) + const char *ssl_host, + GCancellable *cancellable, + GError **error) { SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - GError *error = NULL; - priv->ssl = TRUE; - if (g_tls_connection_handshake (G_TLS_CONNECTION (priv->conn), - cancellable, &error)) - return SOUP_STATUS_OK; - else if (!priv->ssl_fallback && - g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS)) { - g_error_free (error); - return SOUP_STATUS_TLS_FAILED; - } else { - g_error_free (error); - return SOUP_STATUS_SSL_FAILED; - } + if (!soup_socket_setup_ssl (sock, ssl_host, cancellable, error)) + return FALSE; + + return g_tls_connection_handshake (G_TLS_CONNECTION (priv->conn), + cancellable, error); } static void handshake_async_ready (GObject *source, GAsyncResult *result, gpointer user_data) { - SoupSocketAsyncConnectData *data = user_data; - SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (data->sock); + GTask *task = user_data; GError *error = NULL; - guint status; - - if (priv->async_context && !priv->use_thread_context) - g_main_context_pop_thread_default (priv->async_context); if (g_tls_connection_handshake_finish (G_TLS_CONNECTION (source), result, &error)) - status = SOUP_STATUS_OK; - else if (!priv->ssl_fallback && - g_error_matches (error, G_TLS_ERROR, G_TLS_ERROR_NOT_TLS)) - status = SOUP_STATUS_TLS_FAILED; + g_task_return_boolean (task, TRUE); else - status = SOUP_STATUS_SSL_FAILED; - g_clear_error (&error); - - data->callback (data->sock, status, data->user_data); - g_object_unref (data->sock); - g_slice_free (SoupSocketAsyncConnectData, data); + g_task_return_error (task, error); + g_object_unref (task); } void -soup_socket_handshake_async (SoupSocket *sock, - GCancellable *cancellable, - SoupSocketCallback callback, - gpointer user_data) +soup_socket_handshake_async (SoupSocket *sock, + const char *ssl_host, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); - SoupSocketAsyncConnectData *data; + GTask *task; + GError *error = NULL; - priv->ssl = TRUE; + task = g_task_new (sock, cancellable, callback, user_data); - data = g_slice_new (SoupSocketAsyncConnectData); - data->sock = g_object_ref (sock); - data->callback = callback; - data->user_data = user_data; + if (!soup_socket_setup_ssl (sock, ssl_host, cancellable, &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } - if (priv->async_context && !priv->use_thread_context) - g_main_context_push_thread_default (priv->async_context); g_tls_connection_handshake_async (G_TLS_CONNECTION (priv->conn), G_PRIORITY_DEFAULT, cancellable, handshake_async_ready, - data); + task); +} + +gboolean +soup_socket_handshake_finish (SoupSocket *sock, + GAsyncResult *result, + GError **error) +{ + return g_task_propagate_boolean (G_TASK (result), error); } /** @@ -1242,8 +1336,10 @@ soup_socket_disconnect (SoupSocket *sock) */ g_object_ref (sock); - /* Give all readers a chance to notice the connection close */ - g_signal_emit (sock, signals[READABLE], 0); + if (priv->non_blocking) { + /* Give all readers a chance to notice the connection close */ + g_signal_emit (sock, signals[READABLE], 0); + } /* FIXME: can't disconnect until all data is read */ @@ -1278,6 +1374,9 @@ soup_socket_is_connected (SoupSocket *sock) * * Returns the #SoupAddress corresponding to the local end of @sock. * + * Calling this method on an unconnected socket is considered to be + * an error, and produces undefined results. + * * Return value: (transfer none): the #SoupAddress **/ SoupAddress * @@ -1293,13 +1392,25 @@ soup_socket_get_local_address (SoupSocket *sock) GSocketAddress *addr; struct sockaddr_storage sa; gssize sa_len; + GError *error = NULL; - addr = g_socket_get_local_address (priv->gsock, NULL); + if (priv->gsock == NULL) { + g_warning ("%s: socket not connected", G_STRLOC); + goto unlock; + } + + addr = g_socket_get_local_address (priv->gsock, &error); + if (addr == NULL) { + g_warning ("%s: %s", G_STRLOC, error->message); + g_error_free (error); + goto unlock; + } sa_len = g_socket_address_get_native_size (addr); g_socket_address_to_native (addr, &sa, sa_len, NULL); priv->local_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&sa, sa_len); g_object_unref (addr); } +unlock: g_mutex_unlock (&priv->addrlock); return priv->local_addr; @@ -1311,6 +1422,9 @@ soup_socket_get_local_address (SoupSocket *sock) * * Returns the #SoupAddress corresponding to the remote end of @sock. * + * Calling this method on an unconnected socket is considered to be + * an error, and produces undefined results. + * * Return value: (transfer none): the #SoupAddress **/ SoupAddress * @@ -1326,13 +1440,25 @@ soup_socket_get_remote_address (SoupSocket *sock) GSocketAddress *addr; struct sockaddr_storage sa; gssize sa_len; + GError *error = NULL; + + if (priv->gsock == NULL) { + g_warning ("%s: socket not connected", G_STRLOC); + goto unlock; + } - addr = g_socket_get_remote_address (priv->gsock, NULL); + addr = g_socket_get_remote_address (priv->gsock, &error); + if (addr == NULL) { + g_warning ("%s: %s", G_STRLOC, error->message); + g_error_free (error); + goto unlock; + } sa_len = g_socket_address_get_native_size (addr); g_socket_address_to_native (addr, &sa, sa_len, NULL); priv->remote_addr = soup_address_new_from_sockaddr ((struct sockaddr *)&sa, sa_len); g_object_unref (addr); } +unlock: g_mutex_unlock (&priv->addrlock); return priv->remote_addr; @@ -1344,18 +1470,24 @@ soup_socket_get_http_proxy_uri (SoupSocket *sock) SoupSocketPrivate *priv = SOUP_SOCKET_GET_PRIVATE (sock); GSocketAddress *addr; GProxyAddress *paddr; + SoupURI *uri; if (!priv->gsock) return NULL; addr = g_socket_get_remote_address (priv->gsock, NULL); - if (!addr || !G_IS_PROXY_ADDRESS (addr)) + if (!addr || !G_IS_PROXY_ADDRESS (addr)) { + if (addr) + g_object_unref (addr); return NULL; + } paddr = G_PROXY_ADDRESS (addr); if (strcmp (g_proxy_address_get_protocol (paddr), "http") != 0) return NULL; - return soup_uri_new (g_proxy_address_get_uri (paddr)); + uri = soup_uri_new (g_proxy_address_get_uri (paddr)); + g_object_unref (addr); + return uri; } static gboolean @@ -1530,7 +1662,7 @@ soup_socket_read_until (SoupSocket *sock, gpointer buffer, gsize len, SOUP_FILTER_INPUT_STREAM (priv->istream), buffer, len, boundary, boundary_len, !priv->non_blocking, - got_boundary, cancellable, &my_err); + TRUE, got_boundary, cancellable, &my_err); status = translate_read_status (sock, cancellable, my_nread, nread, my_err, error); } diff --git a/libsoup/soup-socket.h b/libsoup/soup-socket.h index ed405bdc..5c1264f9 100644 --- a/libsoup/soup-socket.h +++ b/libsoup/soup-socket.h @@ -26,11 +26,11 @@ typedef struct { GObjectClass parent_class; /* signals */ - void (*readable) (SoupSocket *); - void (*writable) (SoupSocket *); - void (*disconnected) (SoupSocket *); + void (*readable) (SoupSocket *sock); + void (*writable) (SoupSocket *sock); + void (*disconnected) (SoupSocket *sock); - void (*new_connection) (SoupSocket *, SoupSocket *); + void (*new_connection) (SoupSocket *listener, SoupSocket *new_sock); /* Padding for future expansion */ void (*_libsoup_reserved1) (void); diff --git a/libsoup/soup-status.c b/libsoup/soup-status.c index 7b048a80..6dbb9edb 100644 --- a/libsoup/soup-status.c +++ b/libsoup/soup-status.c @@ -68,7 +68,7 @@ **/ /** - * SoupKnownStatusCode: + * SoupStatus: * @SOUP_STATUS_NONE: No status available. (Eg, the message has not * been sent yet) * @SOUP_STATUS_CANCELLED: Message was cancelled locally @@ -150,14 +150,21 @@ * * These represent the known HTTP status code values, plus various * network and internal errors. + * + * Note that no libsoup functions take or return this type directly; + * any function that works with status codes will accept unrecognized + * status codes as well. + * + * Prior to 2.44 this type was called + * <literal>SoupKnownStatusCode</literal>, but the individual values + * have always had the names they have now. **/ /** * SOUP_HTTP_ERROR: * - * A #GError domain representing an HTTP status. Use a - * #SoupKnownStatusCode for the <structfield>code</structfield> - * value. + * A #GError domain representing an HTTP status. Use a #SoupStatus for + * the <structfield>code</structfield> value. **/ diff --git a/libsoup/soup-status.h b/libsoup/soup-status.h index fc8d5a36..fb4147c6 100644 --- a/libsoup/soup-status.h +++ b/libsoup/soup-status.h @@ -8,7 +8,7 @@ #ifndef SOUP_STATUS_H #define SOUP_STATUS_H 1 -#include <glib.h> +#include <libsoup/soup-types.h> G_BEGIN_DECLS @@ -91,14 +91,93 @@ typedef enum { SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED = 505, SOUP_STATUS_INSUFFICIENT_STORAGE = 507, /* WebDAV search */ SOUP_STATUS_NOT_EXTENDED = 510 /* RFC 2774 */ -} SoupKnownStatusCode; +} SoupStatus; const char *soup_status_get_phrase (guint status_code); +SOUP_AVAILABLE_IN_2_26 guint soup_status_proxify (guint status_code); #define SOUP_HTTP_ERROR soup_http_error_quark() GQuark soup_http_error_quark (void); +#ifndef SOUP_DISABLE_DEPRECATED +/* For introspection purposes, we create a duplicate enum type under + * the deprecated type name, since some (all?) bindings don't handle + * enum type typedefs the way we want. + */ +typedef enum { + SOUP_KNOWN_STATUS_CODE_NONE, + + SOUP_KNOWN_STATUS_CODE_CANCELLED = 1, + SOUP_KNOWN_STATUS_CODE_CANT_RESOLVE, + SOUP_KNOWN_STATUS_CODE_CANT_RESOLVE_PROXY, + SOUP_KNOWN_STATUS_CODE_CANT_CONNECT, + SOUP_KNOWN_STATUS_CODE_CANT_CONNECT_PROXY, + SOUP_KNOWN_STATUS_CODE_SSL_FAILED, + SOUP_KNOWN_STATUS_CODE_IO_ERROR, + SOUP_KNOWN_STATUS_CODE_MALFORMED, + SOUP_KNOWN_STATUS_CODE_TRY_AGAIN, + SOUP_KNOWN_STATUS_CODE_TOO_MANY_REDIRECTS, + SOUP_KNOWN_STATUS_CODE_TLS_FAILED, + + SOUP_KNOWN_STATUS_CODE_CONTINUE = 100, + SOUP_KNOWN_STATUS_CODE_SWITCHING_PROTOCOLS = 101, + SOUP_KNOWN_STATUS_CODE_PROCESSING = 102, + + SOUP_KNOWN_STATUS_CODE_OK = 200, + SOUP_KNOWN_STATUS_CODE_CREATED = 201, + SOUP_KNOWN_STATUS_CODE_ACCEPTED = 202, + SOUP_KNOWN_STATUS_CODE_NON_AUTHORITATIVE = 203, + SOUP_KNOWN_STATUS_CODE_NO_CONTENT = 204, + SOUP_KNOWN_STATUS_CODE_RESET_CONTENT = 205, + SOUP_KNOWN_STATUS_CODE_PARTIAL_CONTENT = 206, + SOUP_KNOWN_STATUS_CODE_MULTI_STATUS = 207, + + SOUP_KNOWN_STATUS_CODE_MULTIPLE_CHOICES = 300, + SOUP_KNOWN_STATUS_CODE_MOVED_PERMANENTLY = 301, + SOUP_KNOWN_STATUS_CODE_FOUND = 302, + SOUP_KNOWN_STATUS_CODE_MOVED_TEMPORARILY = 302, + SOUP_KNOWN_STATUS_CODE_SEE_OTHER = 303, + SOUP_KNOWN_STATUS_CODE_NOT_MODIFIED = 304, + SOUP_KNOWN_STATUS_CODE_USE_PROXY = 305, + SOUP_KNOWN_STATUS_CODE_NOT_APPEARING_IN_THIS_PROTOCOL = 306, + SOUP_KNOWN_STATUS_CODE_TEMPORARY_REDIRECT = 307, + + SOUP_KNOWN_STATUS_CODE_BAD_REQUEST = 400, + SOUP_KNOWN_STATUS_CODE_UNAUTHORIZED = 401, + SOUP_KNOWN_STATUS_CODE_PAYMENT_REQUIRED = 402, + SOUP_KNOWN_STATUS_CODE_FORBIDDEN = 403, + SOUP_KNOWN_STATUS_CODE_NOT_FOUND = 404, + SOUP_KNOWN_STATUS_CODE_METHOD_NOT_ALLOWED = 405, + SOUP_KNOWN_STATUS_CODE_NOT_ACCEPTABLE = 406, + SOUP_KNOWN_STATUS_CODE_PROXY_AUTHENTICATION_REQUIRED = 407, + SOUP_KNOWN_STATUS_CODE_PROXY_UNAUTHORIZED = SOUP_KNOWN_STATUS_CODE_PROXY_AUTHENTICATION_REQUIRED, + SOUP_KNOWN_STATUS_CODE_REQUEST_TIMEOUT = 408, + SOUP_KNOWN_STATUS_CODE_CONFLICT = 409, + SOUP_KNOWN_STATUS_CODE_GONE = 410, + SOUP_KNOWN_STATUS_CODE_LENGTH_REQUIRED = 411, + SOUP_KNOWN_STATUS_CODE_PRECONDITION_FAILED = 412, + SOUP_KNOWN_STATUS_CODE_REQUEST_ENTITY_TOO_LARGE = 413, + SOUP_KNOWN_STATUS_CODE_REQUEST_URI_TOO_LONG = 414, + SOUP_KNOWN_STATUS_CODE_UNSUPPORTED_MEDIA_TYPE = 415, + SOUP_KNOWN_STATUS_CODE_REQUESTED_RANGE_NOT_SATISFIABLE = 416, + SOUP_KNOWN_STATUS_CODE_INVALID_RANGE = SOUP_KNOWN_STATUS_CODE_REQUESTED_RANGE_NOT_SATISFIABLE, + SOUP_KNOWN_STATUS_CODE_EXPECTATION_FAILED = 417, + SOUP_KNOWN_STATUS_CODE_UNPROCESSABLE_ENTITY = 422, + SOUP_KNOWN_STATUS_CODE_LOCKED = 423, + SOUP_KNOWN_STATUS_CODE_FAILED_DEPENDENCY = 424, + + SOUP_KNOWN_STATUS_CODE_INTERNAL_SERVER_ERROR = 500, + SOUP_KNOWN_STATUS_CODE_NOT_IMPLEMENTED = 501, + SOUP_KNOWN_STATUS_CODE_BAD_GATEWAY = 502, + SOUP_KNOWN_STATUS_CODE_SERVICE_UNAVAILABLE = 503, + SOUP_KNOWN_STATUS_CODE_GATEWAY_TIMEOUT = 504, + SOUP_KNOWN_STATUS_CODE_HTTP_VERSION_NOT_SUPPORTED = 505, + SOUP_KNOWN_STATUS_CODE_INSUFFICIENT_STORAGE = 507, + SOUP_KNOWN_STATUS_CODE_NOT_EXTENDED = 510 +} SoupKnownStatusCode; +#endif + G_END_DECLS #endif /* SOUP_STATUS_H */ diff --git a/libsoup/soup-tld.c b/libsoup/soup-tld.c index 0c40b675..2e3da62d 100644 --- a/libsoup/soup-tld.c +++ b/libsoup/soup-tld.c @@ -17,6 +17,15 @@ #include "soup.h" #include "soup-tld-private.h" +/** + * SECTION:soup-tld + * @short_description: Top-Level Domain Utilities + * + * These functions can be used to parse hostnames to attempt to determine + * what part of the name belongs to the domain owner, and what part is + * simply a "public suffix" such as ".com". + */ + static void soup_tld_ensure_rules_hash_table (void); static const char *soup_tld_get_base_domain_internal (const char *hostname, guint additional_domains, @@ -27,8 +36,7 @@ static SoupTLDEntry tld_entries[] = { #include "tld_data.inc" }; -/** - * Stores the entries data in a hash table to ease and speed up +/* Stores the entries data in a hash table to ease and speed up * searches. */ static void @@ -49,8 +57,7 @@ soup_tld_ensure_rules_hash_table (void) /** * soup_tld_get_base_domain: - * @tld: a #SoupTLD - * @hostname: a UTF-8 hostname in its canonical representation form + * @hostname: a hostname * @error: return location for a #GError, or %NULL to ignore * errors. See #SoupTLDError for the available error codes * @@ -59,9 +66,14 @@ soup_tld_ensure_rules_hash_table (void) * plus the second level domain, for example for myhost.mydomain.com * it will return mydomain.com. * - * This method only works for valid UTF-8 hostnames in their canonical - * representation form, so you should use g_hostname_to_unicode() to - * get the canonical representation if that is not the case. + * Note that %NULL will be returned for private URLs (those not ending + * with any well known TLD) because choosing a base domain for them + * would be totally arbitrary. + * + * Prior to libsoup 2.46, this function required that @hostname be in + * UTF-8 if it was an IDN. From 2.46 on, the name can be in either + * UTF-8 or ASCII format (and the return value will be in the same + * format). * * Returns: a pointer to the start of the base domain in @hostname. If * an error occurs, %NULL will be returned and @error set. @@ -72,22 +84,21 @@ const char * soup_tld_get_base_domain (const char *hostname, GError **error) { g_return_val_if_fail (hostname, NULL); - g_return_val_if_fail (!g_hostname_is_ascii_encoded (hostname), FALSE); return soup_tld_get_base_domain_internal (hostname, 1, error); } /** * soup_tld_domain_is_public_suffix: - * @tld: a #SoupTLD - * @domain: a UTF-8 domain in its canonical representation form + * @domain: a domain name * * Looks whether the @domain passed as argument is a public domain * suffix (.org, .com, .co.uk, etc) or not. * - * This method only works for valid UTF-8 domains in their canonical - * representation form, so you should use g_hostname_to_unicode() to - * get the canonical representation if that is not the case. + * Prior to libsoup 2.46, this function required that @domain be in + * UTF-8 if it was an IDN. From 2.46 on, the name can be in either + * UTF-8 or ASCII format (and the return value will be in the same + * format). * * Returns: %TRUE if it is a public domain, %FALSE otherwise. * @@ -102,12 +113,14 @@ soup_tld_domain_is_public_suffix (const char *domain) g_return_val_if_fail (domain, FALSE); /* Skip the leading '.' if present */ - if (*domain == '.' && !(++domain)) + if (*domain == '.' && !*(++domain)) g_return_val_if_reached (FALSE); base_domain = soup_tld_get_base_domain_internal (domain, 0, &error); - if (base_domain) + if (g_strcmp0 (domain, base_domain)) { + g_clear_error (&error); return FALSE; + } if (g_error_matches (error, SOUP_TLD_ERROR, SOUP_TLD_ERROR_NO_BASE_DOMAIN)) { g_error_free (error); @@ -125,6 +138,31 @@ soup_tld_domain_is_public_suffix (const char *domain) return TRUE; } +/** + * SOUP_TLD_ERROR: + * + * The #GError domain for soup-tld-related errors. + * + * Since: 2.40 + */ +/** + * SoupTLDError: + * @SOUP_TLD_ERROR_INVALID_HOSTNAME: A hostname was syntactically + * invalid. + * @SOUP_TLD_ERROR_IS_IP_ADDRESS: The passed-in "hostname" was + * actually an IP address (and thus has no base domain or + * public suffix). + * @SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS: The passed-in hostname + * did not have enough components. Eg, calling + * soup_tld_get_base_domain() on <literal>"co.uk"</literal>. + * @SOUP_TLD_ERROR_NO_BASE_DOMAIN: The passed-in hostname has + * no recognized public suffix. + * + * Error codes for %SOUP_TLD_ERROR. + * + * Since: 2.40 + */ + GQuark soup_tld_error_quark (void) { @@ -137,8 +175,10 @@ soup_tld_error_quark (void) static const char * soup_tld_get_base_domain_internal (const char *hostname, guint additional_domains, GError **error) { - char *prev_domain, *cur_domain, *tld, *next_dot; + char *prev_domain, *cur_domain, *next_dot; gint add_domains; + const char *orig_hostname = NULL, *tld; + char *utf8_hostname = NULL; soup_tld_ensure_rules_hash_table (); @@ -149,6 +189,17 @@ soup_tld_get_base_domain_internal (const char *hostname, guint additional_domain return NULL; } + if (g_hostname_is_ascii_encoded (hostname)) { + orig_hostname = hostname; + hostname = utf8_hostname = g_hostname_to_unicode (hostname); + if (!hostname) { + g_set_error_literal (error, SOUP_TLD_ERROR, + SOUP_TLD_ERROR_INVALID_HOSTNAME, + _("Invalid hostname")); + return NULL; + } + } + cur_domain = (char *) hostname; tld = cur_domain; prev_domain = NULL; @@ -167,6 +218,7 @@ soup_tld_get_base_domain_internal (const char *hostname, guint additional_domain g_set_error_literal (error, SOUP_TLD_ERROR, SOUP_TLD_ERROR_INVALID_HOSTNAME, _("Invalid hostname")); + g_free (utf8_hostname); return NULL; } @@ -178,15 +230,9 @@ soup_tld_get_base_domain_internal (const char *hostname, guint additional_domain /* If we match a *. rule and there were no previous exceptions * nor previous domains then treat it as an exact match. */ - if (!prev_domain) { - g_set_error_literal (error, SOUP_TLD_ERROR, - SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS, - _("Not enough domains")); - return NULL; - } - tld = prev_domain; + tld = prev_domain ? prev_domain : cur_domain; break; - } else if (*flags == SOUP_TLD_RULE_NORMAL || !next_dot) { + } else if (*flags == SOUP_TLD_RULE_NORMAL) { tld = cur_domain; break; } else if (*flags & SOUP_TLD_RULE_EXCEPTION) { @@ -202,6 +248,7 @@ soup_tld_get_base_domain_internal (const char *hostname, guint additional_domain g_set_error_literal (error, SOUP_TLD_ERROR, SOUP_TLD_ERROR_NO_BASE_DOMAIN, _("Hostname has no base domain")); + g_free (utf8_hostname); return NULL; } @@ -209,6 +256,37 @@ soup_tld_get_base_domain_internal (const char *hostname, guint additional_domain cur_domain = next_dot + 1; } + if (orig_hostname) { + int dots; + const char *p; + + /* Count the number of dots that appear after tld in + * utf8_hostname, and then find the corresponding spot + * in orig_hostname; + */ + for (p = tld, dots = 0; *p; p++) { + if (*p == '.') + dots++; + } + + for (p = orig_hostname + strlen (orig_hostname); p > orig_hostname; p--) { + if (*(p - 1) == '.') { + if (dots) + dots--; + else + break; + } + } + /* It's not possible for utf8_hostname to have had + * more dots than orig_hostname. + */ + g_assert (dots == 0); + + tld = p; + g_free (utf8_hostname); + hostname = orig_hostname; + } + /* Include the additional number of domains requested. */ add_domains = additional_domains; while (tld != hostname) { diff --git a/libsoup/soup-tld.h b/libsoup/soup-tld.h index 38de46a6..4b099a26 100644 --- a/libsoup/soup-tld.h +++ b/libsoup/soup-tld.h @@ -10,14 +10,17 @@ G_BEGIN_DECLS +SOUP_AVAILABLE_IN_2_40 const char *soup_tld_get_base_domain (const char *hostname, GError **error); +SOUP_AVAILABLE_IN_2_40 gboolean soup_tld_domain_is_public_suffix (const char *domain); /* Errors */ -#define SOUP_TLD_ERROR soup_tld_error_quark() +SOUP_AVAILABLE_IN_2_40 GQuark soup_tld_error_quark (void); +#define SOUP_TLD_ERROR soup_tld_error_quark() typedef enum { SOUP_TLD_ERROR_INVALID_HOSTNAME, diff --git a/libsoup/soup-types.h b/libsoup/soup-types.h index d0220399..0776bdbf 100644 --- a/libsoup/soup-types.h +++ b/libsoup/soup-types.h @@ -8,6 +8,7 @@ #include <gio/gio.h> +#include <libsoup/soup-version.h> #include <libsoup/soup-status.h> G_BEGIN_DECLS @@ -19,6 +20,8 @@ typedef struct _SoupCookie SoupCookie; typedef struct _SoupCookieJar SoupCookieJar; typedef struct _SoupDate SoupDate; typedef struct _SoupMessage SoupMessage; +typedef struct _SoupRequest SoupRequest; +typedef struct _SoupRequestHTTP SoupRequestHTTP; typedef struct _SoupServer SoupServer; typedef struct _SoupSession SoupSession; typedef struct _SoupSessionAsync SoupSessionAsync; diff --git a/libsoup/soup-uri.c b/libsoup/soup-uri.c index 4be679d5..42c2f680 100644 --- a/libsoup/soup-uri.c +++ b/libsoup/soup-uri.c @@ -105,12 +105,63 @@ * Since: 2.24 **/ +/** + * SOUP_URI_SCHEME_HTTP: + * + * "http" as an interned string; you can compare this directly to a + * #SoupURI's <literal>scheme</literal> field using + * <literal>==</literal>. + */ +/** + * SOUP_URI_SCHEME_HTTPS: + * + * "https" as an interned string; you can compare this directly to a + * #SoupURI's <literal>scheme</literal> field using + * <literal>==</literal>. + */ +/** + * SOUP_URI_SCHEME_FTP: + * + * "ftp" as an interned string; you can compare this directly to a + * #SoupURI's <literal>scheme</literal> field using + * <literal>==</literal>. + * + * Since: 2.30 + */ +/** + * SOUP_URI_SCHEME_FILE: + * + * "file" as an interned string; you can compare this directly to a + * #SoupURI's <literal>scheme</literal> field using + * <literal>==</literal>. + * + * Since: 2.30 + */ +/** + * SOUP_URI_SCHEME_DATA: + * + * "data" as an interned string; you can compare this directly to a + * #SoupURI's <literal>scheme</literal> field using + * <literal>==</literal>. + * + * Since: 2.30 + */ +/** + * SOUP_URI_SCHEME_RESOURCE: + * + * "data" as an interned string; you can compare this directly to a + * #SoupURI's <literal>scheme</literal> field using + * <literal>==</literal>. + * + * Since: 2.42 + */ + static void append_uri_encoded (GString *str, const char *in, const char *extra_enc_chars); static char *uri_normalized_copy (const char *str, int length, const char *unescape_extra); gpointer _SOUP_URI_SCHEME_HTTP, _SOUP_URI_SCHEME_HTTPS; gpointer _SOUP_URI_SCHEME_FTP; -gpointer _SOUP_URI_SCHEME_FILE, _SOUP_URI_SCHEME_DATA; +gpointer _SOUP_URI_SCHEME_FILE, _SOUP_URI_SCHEME_DATA, _SOUP_URI_SCHEME_RESOURCE; static inline const char * soup_uri_parse_scheme (const char *scheme, int len) @@ -119,6 +170,8 @@ soup_uri_parse_scheme (const char *scheme, int len) return SOUP_URI_SCHEME_HTTP; } else if (len == 5 && !g_ascii_strncasecmp (scheme, "https", len)) { return SOUP_URI_SCHEME_HTTPS; + } else if (len == 8 && !g_ascii_strncasecmp (scheme, "resource", len)) { + return SOUP_URI_SCHEME_RESOURCE; } else { char *lower_scheme; @@ -210,10 +263,13 @@ soup_uri_new_with_base (SoupURI *base, const char *uri_string) end = hash; } - /* Find scheme: initial [a-z+.-]* substring until ":" */ + /* Find scheme */ p = uri_string; while (p < end && (g_ascii_isalpha (*p) || - *p == '.' || *p == '+' || *p == '-')) + (p > uri_string && (g_ascii_isdigit (*p) || + *p == '.' || + *p == '+' || + *p == '-')))) p++; if (p > uri_string && *p == ':') { @@ -237,21 +293,23 @@ soup_uri_new_with_base (SoupURI *base, const char *uri_string) if (at && at < path) { colon = strchr (uri_string, ':'); if (colon && colon < at) { - uri->password = uri_decoded_copy (colon + 1, - at - colon - 1); + uri->password = soup_uri_decoded_copy (colon + 1, + at - colon - 1, NULL); } else { uri->password = NULL; colon = at; } - uri->user = uri_decoded_copy (uri_string, - colon - uri_string); + uri->user = soup_uri_decoded_copy (uri_string, + colon - uri_string, NULL); uri_string = at + 1; } else uri->user = uri->password = NULL; /* Find host and port. */ if (*uri_string == '[') { + const char *pct; + uri_string++; hostend = strchr (uri_string, ']'); if (!hostend || hostend > path) { @@ -262,13 +320,20 @@ soup_uri_new_with_base (SoupURI *base, const char *uri_string) colon = hostend + 1; else colon = NULL; + + pct = memchr (uri_string, '%', hostend - uri_string); + if (!pct || (pct[1] == '2' && pct[2] == '5')) { + uri->host = soup_uri_decoded_copy (uri_string, + hostend - uri_string, NULL); + } else + uri->host = g_strndup (uri_string, hostend - uri_string); } else { colon = memchr (uri_string, ':', path - uri_string); hostend = colon ? colon : path; + uri->host = soup_uri_decoded_copy (uri_string, + hostend - uri_string, NULL); } - uri->host = uri_decoded_copy (uri_string, hostend - uri_string); - if (colon && colon != path - 1) { char *portend; uri->port = strtoul (colon + 1, &portend, 10); @@ -436,21 +501,9 @@ soup_uri_new (const char *uri_string) } -/** - * soup_uri_to_string: - * @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 (SoupURI *uri, gboolean just_path_and_query) +soup_uri_to_string_internal (SoupURI *uri, gboolean just_path_and_query, + gboolean force_port) { GString *str; char *return_result; @@ -458,7 +511,7 @@ soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query) g_return_val_if_fail (uri != NULL, NULL); g_warn_if_fail (SOUP_URI_IS_VALID (uri)); - str = g_string_sized_new (20); + str = g_string_sized_new (40); if (uri->scheme && !just_path_and_query) g_string_append_printf (str, "%s:", uri->scheme); @@ -469,12 +522,20 @@ soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query) g_string_append_c (str, '@'); } if (strchr (uri->host, ':')) { + const char *pct; + g_string_append_c (str, '['); - g_string_append (str, uri->host); + pct = strchr (uri->host, '%'); + if (pct) { + g_string_append_printf (str, "%.*s%%25%s", + (int) (pct - uri->host), + uri->host, pct + 1); + } else + 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)) + if (uri->port && (force_port || uri->port != soup_scheme_default_port (uri->scheme))) g_string_append_printf (str, ":%u", uri->port); if (!uri->path && (uri->query || uri->fragment)) g_string_append_c (str, '/'); @@ -505,6 +566,28 @@ soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query) } /** + * soup_uri_to_string: + * @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. + * + * Note that the output will never contain a password, even if @uri + * does. + * + * Return value: a string representing @uri, which the caller must free. + **/ +char * +soup_uri_to_string (SoupURI *uri, gboolean just_path_and_query) +{ + return soup_uri_to_string_internal (uri, just_path_and_query, FALSE); +} + +/** * soup_uri_copy: * @uri: a #SoupURI * @@ -641,13 +724,14 @@ soup_uri_encode (const char *part, const char *escape_extra) #define HEXCHAR(s) ((XDIGIT (s[1]) << 4) + XDIGIT (s[2])) char * -uri_decoded_copy (const char *part, int length) +soup_uri_decoded_copy (const char *part, int length, int *decoded_length) { unsigned char *s, *d; - char *decoded = g_strndup (part, length); + char *decoded; g_return_val_if_fail (part != NULL, NULL); + decoded = g_strndup (part, length); s = d = (unsigned char *)decoded; do { if (*s == '%') { @@ -662,6 +746,9 @@ uri_decoded_copy (const char *part, int length) *d++ = *s; } while (*s++); + if (decoded_length) + *decoded_length = d - (unsigned char *)decoded - 1; + return decoded; } @@ -682,7 +769,7 @@ soup_uri_decode (const char *part) { g_return_val_if_fail (part != NULL, NULL); - return uri_decoded_copy (part, strlen (part)); + return soup_uri_decoded_copy (part, strlen (part), NULL); } static char * @@ -707,7 +794,7 @@ uri_normalized_copy (const char *part, int length, c = HEXCHAR (s); if (soup_char_is_uri_unreserved (c) || - strchr (unescape_extra, c)) { + (c && strchr (unescape_extra, c))) { *d++ = c; s += 3; } else { @@ -801,20 +888,6 @@ soup_uri_uses_default_port (SoupURI *uri) } /** - * 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_get_scheme: * @uri: a #SoupURI * @@ -1152,7 +1225,7 @@ soup_uri_set_fragment (SoupURI *uri, const char *fragment) * * Return value: the new #SoupURI * - * Since: 2.26.3 + * Since: 2.28 **/ SoupURI * soup_uri_copy_host (SoupURI *uri) @@ -1179,7 +1252,7 @@ soup_uri_copy_host (SoupURI *uri) * * Return value: a hash * - * Since: 2.26.3 + * Since: 2.28 **/ guint soup_uri_host_hash (gconstpointer key) @@ -1203,7 +1276,7 @@ soup_uri_host_hash (gconstpointer key) * Return value: whether or not the URIs are equal in scheme, host, * and port. * - * Since: 2.26.3 + * Since: 2.28 **/ gboolean soup_uri_host_equal (gconstpointer v1, gconstpointer v2) @@ -1224,4 +1297,47 @@ soup_uri_host_equal (gconstpointer v1, gconstpointer v2) return g_ascii_strcasecmp (one->host, two->host) == 0; } +gboolean +soup_uri_is_http (SoupURI *uri, char **aliases) +{ + int i; + + if (uri->scheme == SOUP_URI_SCHEME_HTTP) + return TRUE; + else if (uri->scheme == SOUP_URI_SCHEME_HTTPS) + return FALSE; + else if (!aliases) + return FALSE; + + for (i = 0; aliases[i]; i++) { + if (uri->scheme == aliases[i]) + return TRUE; + } + + if (!aliases[1] && !strcmp (aliases[0], "*")) + return TRUE; + else + return FALSE; +} + +gboolean +soup_uri_is_https (SoupURI *uri, char **aliases) +{ + int i; + + if (uri->scheme == SOUP_URI_SCHEME_HTTPS) + return TRUE; + else if (uri->scheme == SOUP_URI_SCHEME_HTTP) + return FALSE; + else if (!aliases) + return FALSE; + + for (i = 0; aliases[i]; i++) { + if (uri->scheme == aliases[i]) + return TRUE; + } + + return FALSE; +} + G_DEFINE_BOXED_TYPE (SoupURI, soup_uri, soup_uri_copy, soup_uri_free) diff --git a/libsoup/soup-uri.h b/libsoup/soup-uri.h index b851dbec..fca97f76 100644 --- a/libsoup/soup-uri.h +++ b/libsoup/soup-uri.h @@ -31,14 +31,15 @@ struct _SoupURI { GType soup_uri_get_type (void); #define SOUP_TYPE_URI (soup_uri_get_type ()) -#define SOUP_URI_SCHEME_HTTP _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_HTTP, "http") -#define SOUP_URI_SCHEME_HTTPS _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_HTTPS, "https") -#define SOUP_URI_SCHEME_FTP _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_FTP, "ftp") -#define SOUP_URI_SCHEME_FILE _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_FILE, "file") -#define SOUP_URI_SCHEME_DATA _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_DATA, "data") +#define SOUP_URI_SCHEME_HTTP _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_HTTP, "http") +#define SOUP_URI_SCHEME_HTTPS _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_HTTPS, "https") +#define SOUP_URI_SCHEME_FTP _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_FTP, "ftp") +#define SOUP_URI_SCHEME_FILE _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_FILE, "file") +#define SOUP_URI_SCHEME_DATA _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_DATA, "data") +#define SOUP_URI_SCHEME_RESOURCE _SOUP_ATOMIC_INTERN_STRING (_SOUP_URI_SCHEME_RESOURCE, "resource") extern gpointer _SOUP_URI_SCHEME_HTTP, _SOUP_URI_SCHEME_HTTPS; extern gpointer _SOUP_URI_SCHEME_FTP; -extern gpointer _SOUP_URI_SCHEME_FILE, _SOUP_URI_SCHEME_DATA; +extern gpointer _SOUP_URI_SCHEME_FILE, _SOUP_URI_SCHEME_DATA, _SOUP_URI_SCHEME_RESOURCE; SoupURI *soup_uri_new_with_base (SoupURI *base, const char *uri_string); @@ -62,24 +63,31 @@ char *soup_uri_normalize (const char *part, gboolean soup_uri_uses_default_port (SoupURI *uri); +SOUP_AVAILABLE_IN_2_32 const char *soup_uri_get_scheme (SoupURI *uri); void soup_uri_set_scheme (SoupURI *uri, const char *scheme); +SOUP_AVAILABLE_IN_2_32 const char *soup_uri_get_user (SoupURI *uri); void soup_uri_set_user (SoupURI *uri, const char *user); +SOUP_AVAILABLE_IN_2_32 const char *soup_uri_get_password (SoupURI *uri); void soup_uri_set_password (SoupURI *uri, const char *password); +SOUP_AVAILABLE_IN_2_32 const char *soup_uri_get_host (SoupURI *uri); void soup_uri_set_host (SoupURI *uri, const char *host); +SOUP_AVAILABLE_IN_2_32 guint soup_uri_get_port (SoupURI *uri); void soup_uri_set_port (SoupURI *uri, guint port); +SOUP_AVAILABLE_IN_2_32 const char *soup_uri_get_path (SoupURI *uri); void soup_uri_set_path (SoupURI *uri, const char *path); +SOUP_AVAILABLE_IN_2_32 const char *soup_uri_get_query (SoupURI *uri); void soup_uri_set_query (SoupURI *uri, const char *query); @@ -88,12 +96,16 @@ void soup_uri_set_query_from_form (SoupURI *uri, void soup_uri_set_query_from_fields (SoupURI *uri, const char *first_field, ...) G_GNUC_NULL_TERMINATED; +SOUP_AVAILABLE_IN_2_32 const char *soup_uri_get_fragment (SoupURI *uri); void soup_uri_set_fragment (SoupURI *uri, const char *fragment); +SOUP_AVAILABLE_IN_2_28 SoupURI *soup_uri_copy_host (SoupURI *uri); +SOUP_AVAILABLE_IN_2_28 guint soup_uri_host_hash (gconstpointer key); +SOUP_AVAILABLE_IN_2_28 gboolean soup_uri_host_equal (gconstpointer v1, gconstpointer v2); diff --git a/libsoup/soup-value-utils.c b/libsoup/soup-value-utils.c index b929544b..e1f7c92f 100644 --- a/libsoup/soup-value-utils.c +++ b/libsoup/soup-value-utils.c @@ -15,7 +15,7 @@ /** * SECTION:soup-value-utils - * @short_description: #GValue utilities + * @short_description: GValue utilities * * These methods are useful for manipulating #GValue<!-- -->s, and in * particular, arrays and hash tables of #GValue<!-- -->s, in a diff --git a/libsoup/soup-version.c b/libsoup/soup-version.c new file mode 100644 index 00000000..31e7262d --- /dev/null +++ b/libsoup/soup-version.c @@ -0,0 +1,307 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-version.c: Version information + * + * Copyright (C) 2012 Igalia S.L. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "soup-version.h" + +/** + * SECTION:soup-version + * @short_description: Variables and functions to check the libsoup version + **/ + +/** + * SOUP_MAJOR_VERSION: + * + * Like soup_get_major_version(), but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + * + * Since: 2.42 + */ + +/** + * SOUP_MINOR_VERSION: + * + * Like soup_get_minor_version(), but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + * + * Since: 2.42 + */ + +/** + * SOUP_MICRO_VERSION: + * + * Like soup_get_micro_version(), but from the headers used at + * application compile time, rather than from the library linked + * against at application run time. + * + * Since: 2.42 + */ + +/** + * SOUP_CHECK_VERSION: + * @major: major version (e.g. 2 for version 2.42.0) + * @minor: minor version (e.g. 42 for version 2.42.0) + * @micro: micro version (e.g. 0 for version 2.42.0) + * + * Macro to test the version of libsoup being compiled against. + * + * Returns: %TRUE if the version of the libsoup header files + * is the same as or newer than the passed-in version. + * + * Since: 2.42 + */ + +/** + * soup_get_major_version: + * + * Returns the major version number of the libsoup library. + * (e.g. in libsoup version 2.42.0 this is 2.) + * + * This function is in the library, so it represents the libsoup library + * your code is running against. Contrast with the #SOUP_MAJOR_VERSION + * macro, which represents the major version of the libsoup headers you + * have included when compiling your code. + * + * Returns: the major version number of the libsoup library + * + * Since: 2.42 + */ +guint +soup_get_major_version (void) +{ + return SOUP_MAJOR_VERSION; +} + +/** + * soup_get_minor_version: + * + * Returns the minor version number of the libsoup library. + * (e.g. in libsoup version 2.42.0 this is 42.) + * + * This function is in the library, so it represents the libsoup library + * your code is running against. Contrast with the #SOUP_MINOR_VERSION + * macro, which represents the minor version of the libsoup headers you + * have included when compiling your code. + * + * Returns: the minor version number of the libsoup library + * + * Since: 2.42 + */ +guint +soup_get_minor_version (void) +{ + return SOUP_MINOR_VERSION; +} + +/** + * soup_get_micro_version: + * + * Returns the micro version number of the libsoup library. + * (e.g. in libsoup version 2.42.0 this is 0.) + * + * This function is in the library, so it represents the libsoup library + * your code is running against. Contrast with the #SOUP_MICRO_VERSION + * macro, which represents the micro version of the libsoup headers you + * have included when compiling your code. + * + * Returns: the micro version number of the libsoup library + * + * Since: 2.42 + */ +guint +soup_get_micro_version (void) +{ + return SOUP_MICRO_VERSION; +} + +/** + * soup_check_version: + * @major: the major version to check + * @minor: the minor version to check + * @micro: the micro version to check + * + * Like SOUP_CHECK_VERSION, but the check for soup_check_version is + * at runtime instead of compile time. This is useful for compiling + * against older versions of libsoup, but using features from newer + * versions. + * + * Returns: %TRUE if the version of the libsoup currently loaded + * is the same as or newer than the passed-in version. + * + * Since: 2.42 + */ +gboolean +soup_check_version (guint major, + guint minor, + guint micro) +{ + return SOUP_CHECK_VERSION (major, minor, micro); +} + +/** + * SOUP_VERSION_MIN_REQUIRED: + * + * A macro that should be defined by the user prior to including + * libsoup.h. The definition should be one of the predefined libsoup + * version macros: %SOUP_VERSION_2_24, %SOUP_VERSION_2_26, ... + * + * This macro defines the earliest version of libsoup that the package + * is required to be able to compile against. + * + * If the compiler is configured to warn about the use of deprecated + * functions, then using functions that were deprecated in version + * %SOUP_VERSION_MIN_REQUIRED or earlier will cause warnings (but + * using functions deprecated in later releases will not). + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_MAX_ALLOWED: + * + * A macro that should be defined by the user prior to including + * libsoup.h. The definition should be one of the predefined libsoup + * version macros: %SOUP_VERSION_2_24, %SOUP_VERSION_2_26, ... + * + * This macro defines the latest version of the libsoup API that the + * package is allowed to make use of. + * + * If the compiler is configured to warn about the use of deprecated + * functions, then using functions added after version + * %SOUP_VERSION_MAX_ALLOWED will cause warnings. + * + * Unless you are using SOUP_CHECK_VERSION() or the like to compile + * different code depending on the libsoup version, then this should be + * set to the same value as %SOUP_VERSION_MIN_REQUIRED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_24: + * + * A macro that evaluates to the 2.24 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_26: + * + * A macro that evaluates to the 2.26 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_28: + * + * A macro that evaluates to the 2.28 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_30: + * + * A macro that evaluates to the 2.30 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_32: + * + * A macro that evaluates to the 2.32 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_34: + * + * A macro that evaluates to the 2.34 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_36: + * + * A macro that evaluates to the 2.36 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_38: + * + * A macro that evaluates to the 2.38 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_40: + * + * A macro that evaluates to the 2.40 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_42: + * + * A macro that evaluates to the 2.42 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.42 + */ + +/** + * SOUP_VERSION_2_44: + * + * A macro that evaluates to the 2.44 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.44 + */ + +/** + * SOUP_VERSION_2_46: + * + * A macro that evaluates to the 2.46 version of libsoup, in a format + * that can be used by %SOUP_VERSION_MIN_REQUIRED and + * %SOUP_VERSION_MAX_ALLOWED. + * + * Since: 2.46 + */ diff --git a/libsoup/soup-version.h.in b/libsoup/soup-version.h.in new file mode 100644 index 00000000..000b9456 --- /dev/null +++ b/libsoup/soup-version.h.in @@ -0,0 +1,261 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * soup-version.h: Version information + * + * Copyright (C) 2012 Igalia S.L. + */ + +#ifndef SOUP_VERSION_H +#define SOUP_VERSION_H + +#include <glib.h> + +G_BEGIN_DECLS + +#define SOUP_MAJOR_VERSION (@SOUP_MAJOR_VERSION@) +#define SOUP_MINOR_VERSION (@SOUP_MINOR_VERSION@) +#define SOUP_MICRO_VERSION (@SOUP_MICRO_VERSION@) + +#define SOUP_CHECK_VERSION(major, minor, micro) \ + (SOUP_MAJOR_VERSION > (major) || \ + (SOUP_MAJOR_VERSION == (major) && SOUP_MINOR_VERSION > (minor)) || \ + (SOUP_MAJOR_VERSION == (major) && SOUP_MINOR_VERSION == (minor) && \ + SOUP_MICRO_VERSION >= (micro))) + +guint soup_get_major_version (void); + +guint soup_get_minor_version (void); + +guint soup_get_micro_version (void); + +gboolean soup_check_version (guint major, + guint minor, + guint micro); + +/* Deprecation / Availability macros */ + +#define SOUP_ENCODE_VERSION(major,minor) ((major) << 16 | (minor) << 8) + +#define SOUP_VERSION_2_24 (SOUP_ENCODE_VERSION (2, 24)) +#define SOUP_VERSION_2_26 (SOUP_ENCODE_VERSION (2, 26)) +#define SOUP_VERSION_2_28 (SOUP_ENCODE_VERSION (2, 28)) +#define SOUP_VERSION_2_30 (SOUP_ENCODE_VERSION (2, 30)) +#define SOUP_VERSION_2_32 (SOUP_ENCODE_VERSION (2, 32)) +#define SOUP_VERSION_2_34 (SOUP_ENCODE_VERSION (2, 34)) +#define SOUP_VERSION_2_36 (SOUP_ENCODE_VERSION (2, 36)) +#define SOUP_VERSION_2_38 (SOUP_ENCODE_VERSION (2, 38)) +#define SOUP_VERSION_2_40 (SOUP_ENCODE_VERSION (2, 40)) +#define SOUP_VERSION_2_42 (SOUP_ENCODE_VERSION (2, 42)) +#define SOUP_VERSION_2_44 (SOUP_ENCODE_VERSION (2, 44)) +#define SOUP_VERSION_2_46 (SOUP_ENCODE_VERSION (2, 46)) + +/* evaluates to the current stable version; for development cycles, + * this means the next stable target + */ +#if (SOUP_MINOR_VERSION % 2) +#define SOUP_VERSION_CUR_STABLE (SOUP_ENCODE_VERSION (SOUP_MAJOR_VERSION, SOUP_MINOR_VERSION + 1)) +#else +#define SOUP_VERSION_CUR_STABLE (SOUP_ENCODE_VERSION (SOUP_MAJOR_VERSION, SOUP_MINOR_VERSION)) +#endif + +/* evaluates to the previous stable version */ +#if (SOUP_MINOR_VERSION % 2) +#define SOUP_VERSION_PREV_STABLE (SOUP_ENCODE_VERSION (SOUP_MAJOR_VERSION, SOUP_MINOR_VERSION - 1)) +#else +#define SOUP_VERSION_PREV_STABLE (SOUP_ENCODE_VERSION (SOUP_MAJOR_VERSION, SOUP_MINOR_VERSION - 2)) +#endif + +#ifndef SOUP_VERSION_MIN_REQUIRED +# define SOUP_VERSION_MIN_REQUIRED (SOUP_VERSION_CUR_STABLE) +#elif SOUP_VERSION_MIN_REQUIRED == 0 +# undef SOUP_VERSION_MIN_REQUIRED +# define SOUP_VERSION_MIN_REQUIRED (SOUP_VERSION_CUR_STABLE + 2) +#endif + +#if !defined (SOUP_VERSION_MAX_ALLOWED) || (SOUP_VERSION_MAX_ALLOWED == 0) +# undef SOUP_VERSION_MAX_ALLOWED +# define SOUP_VERSION_MAX_ALLOWED (SOUP_VERSION_CUR_STABLE) +#endif + +/* sanity checks */ +#if SOUP_VERSION_MIN_REQUIRED > SOUP_VERSION_CUR_STABLE +#error "SOUP_VERSION_MIN_REQUIRED must be <= SOUP_VERSION_CUR_STABLE" +#endif +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_MIN_REQUIRED +#error "SOUP_VERSION_MAX_ALLOWED must be >= SOUP_VERSION_MIN_REQUIRED" +#endif +#if SOUP_VERSION_MIN_REQUIRED < SOUP_VERSION_2_24 +#error "SOUP_VERSION_MIN_REQUIRED must be >= SOUP_VERSION_2_24" +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_24 +# define SOUP_DEPRECATED_IN_2_24 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_24_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_24 +# define SOUP_DEPRECATED_IN_2_24_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_24 +# define SOUP_AVAILABLE_IN_2_24 GLIB_UNAVAILABLE(2, 24) +#else +# define SOUP_AVAILABLE_IN_2_24 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_26 +# define SOUP_DEPRECATED_IN_2_26 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_26_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_26 +# define SOUP_DEPRECATED_IN_2_26_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_26 +# define SOUP_AVAILABLE_IN_2_26 GLIB_UNAVAILABLE(2, 26) +#else +# define SOUP_AVAILABLE_IN_2_26 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_28 +# define SOUP_DEPRECATED_IN_2_28 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_28_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_28 +# define SOUP_DEPRECATED_IN_2_28_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_28 +# define SOUP_AVAILABLE_IN_2_28 GLIB_UNAVAILABLE(2, 28) +#else +# define SOUP_AVAILABLE_IN_2_28 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_30 +# define SOUP_DEPRECATED_IN_2_30 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_30_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_30 +# define SOUP_DEPRECATED_IN_2_30_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_30 +# define SOUP_AVAILABLE_IN_2_30 GLIB_UNAVAILABLE(2, 30) +#else +# define SOUP_AVAILABLE_IN_2_30 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_32 +# define SOUP_DEPRECATED_IN_2_32 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_32_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_32 +# define SOUP_DEPRECATED_IN_2_32_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_32 +# define SOUP_AVAILABLE_IN_2_32 GLIB_UNAVAILABLE(2, 32) +#else +# define SOUP_AVAILABLE_IN_2_32 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_34 +# define SOUP_DEPRECATED_IN_2_34 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_34_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_34 +# define SOUP_DEPRECATED_IN_2_34_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_34 +# define SOUP_AVAILABLE_IN_2_34 GLIB_UNAVAILABLE(2, 34) +#else +# define SOUP_AVAILABLE_IN_2_34 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_36 +# define SOUP_DEPRECATED_IN_2_36 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_36_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_36 +# define SOUP_DEPRECATED_IN_2_36_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_36 +# define SOUP_AVAILABLE_IN_2_36 GLIB_UNAVAILABLE(2, 36) +#else +# define SOUP_AVAILABLE_IN_2_36 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_38 +# define SOUP_DEPRECATED_IN_2_38 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_38_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_38 +# define SOUP_DEPRECATED_IN_2_38_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_38 +# define SOUP_AVAILABLE_IN_2_38 GLIB_UNAVAILABLE(2, 38) +#else +# define SOUP_AVAILABLE_IN_2_38 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_40 +# define SOUP_DEPRECATED_IN_2_40 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_40_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_40 +# define SOUP_DEPRECATED_IN_2_40_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_40 +# define SOUP_AVAILABLE_IN_2_40 GLIB_UNAVAILABLE(2, 40) +#else +# define SOUP_AVAILABLE_IN_2_40 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_42 +# define SOUP_DEPRECATED_IN_2_42 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_42_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_42 +# define SOUP_DEPRECATED_IN_2_42_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_42 +# define SOUP_AVAILABLE_IN_2_42 GLIB_UNAVAILABLE(2, 42) +#else +# define SOUP_AVAILABLE_IN_2_42 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_44 +# define SOUP_DEPRECATED_IN_2_44 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_44_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_44 +# define SOUP_DEPRECATED_IN_2_44_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_44 +# define SOUP_AVAILABLE_IN_2_44 GLIB_UNAVAILABLE(2, 44) +#else +# define SOUP_AVAILABLE_IN_2_44 +#endif + +#if SOUP_VERSION_MIN_REQUIRED >= SOUP_VERSION_2_46 +# define SOUP_DEPRECATED_IN_2_46 GLIB_DEPRECATED +# define SOUP_DEPRECATED_IN_2_46_FOR(f) GLIB_DEPRECATED_FOR(f) +#else +# define SOUP_DEPRECATED_IN_2_46 +# define SOUP_DEPRECATED_IN_2_46_FOR(f) +#endif + +#if SOUP_VERSION_MAX_ALLOWED < SOUP_VERSION_2_46 +# define SOUP_AVAILABLE_IN_2_46 GLIB_UNAVAILABLE(2, 46) +#else +# define SOUP_AVAILABLE_IN_2_46 +#endif + +G_END_DECLS + +#endif /* SOUP_VERSION_H */ diff --git a/libsoup/soup-xmlrpc.c b/libsoup/soup-xmlrpc.c index f7908585..e2890100 100644 --- a/libsoup/soup-xmlrpc.c +++ b/libsoup/soup-xmlrpc.c @@ -290,6 +290,11 @@ soup_xmlrpc_build_method_response (GValue *value) } static char * +soup_xmlrpc_build_faultv (int fault_code, + const char *fault_format, + va_list args) G_GNUC_PRINTF (2, 0); + +static char * soup_xmlrpc_build_faultv (int fault_code, const char *fault_format, va_list args) { xmlDoc *doc; @@ -593,29 +598,32 @@ soup_xmlrpc_parse_method_call (const char *method_call, int length, xmlMethodName = xmlNodeGetContent (node); node = find_real_node (node->next); - if (!node || strcmp ((const char *)node->name, "params") != 0) - goto fail; + if (node) { + if (strcmp ((const char *)node->name, "params") != 0) + goto fail; #ifdef G_GNUC_BEGIN_IGNORE_DEPRECATIONS G_GNUC_BEGIN_IGNORE_DEPRECATIONS #endif - *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") != 0 || - !parse_value (xval, &value)) { - g_value_array_free (*params); - goto fail; - } - g_value_array_append (*params, &value); - g_value_unset (&value); + *params = soup_value_array_new (); + 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") != 0 || + !parse_value (xval, &value)) { + g_value_array_free (*params); + goto fail; + } + g_value_array_append (*params, &value); + g_value_unset (&value); - param = find_real_node (param->next); - } + param = find_real_node (param->next); + } #ifdef G_GNUC_END_IGNORE_DEPRECATIONS G_GNUC_END_IGNORE_DEPRECATIONS #endif + } else + *params = soup_value_array_new (); success = TRUE; *method_name = g_strdup ((char *)xmlMethodName); diff --git a/libsoup/soup.h b/libsoup/soup.h index d4ddcead..82a26329 100644 --- a/libsoup/soup.h +++ b/libsoup/soup.h @@ -15,10 +15,13 @@ extern "C" { #include <libsoup/soup-auth-domain.h> #include <libsoup/soup-auth-domain-basic.h> #include <libsoup/soup-auth-domain-digest.h> +#include <libsoup/soup-auth-manager.h> +#include <libsoup/soup-cache.h> #include <libsoup/soup-content-decoder.h> #include <libsoup/soup-content-sniffer.h> #include <libsoup/soup-cookie.h> #include <libsoup/soup-cookie-jar.h> +#include <libsoup/soup-cookie-jar-db.h> #include <libsoup/soup-cookie-jar-text.h> #include <libsoup/soup-date.h> #include <libsoup/soup-enum-types.h> @@ -29,10 +32,14 @@ extern "C" { #include <libsoup/soup-method.h> #include <libsoup/soup-misc.h> #include <libsoup/soup-multipart.h> -#include <libsoup/soup-password-manager.h> +#include <libsoup/soup-multipart-input-stream.h> #include <libsoup/soup-proxy-resolver.h> #include <libsoup/soup-proxy-resolver-default.h> #include <libsoup/soup-proxy-uri-resolver.h> +#include <libsoup/soup-request.h> +#include <libsoup/soup-request-data.h> +#include <libsoup/soup-request-file.h> +#include <libsoup/soup-request-http.h> #include <libsoup/soup-server.h> #include <libsoup/soup-session-async.h> #include <libsoup/soup-session-feature.h> @@ -42,6 +49,7 @@ extern "C" { #include <libsoup/soup-tld.h> #include <libsoup/soup-uri.h> #include <libsoup/soup-value-utils.h> +#include <libsoup/soup-version.h> #include <libsoup/soup-xmlrpc.h> #ifdef __cplusplus diff --git a/libsoup/tld-parser.py b/libsoup/tld-parser.py index c1a0346f..5d9d2ba5 100755 --- a/libsoup/tld-parser.py +++ b/libsoup/tld-parser.py @@ -5,13 +5,14 @@ # Based on tld-parser.c Copyright (C) 2012 Igalia S.L. import sys +import codecs SOUP_TLD_RULE_NORMAL = 0 SOUP_TLD_RULE_MATCH_ALL = 1 << 0 SOUP_TLD_RULE_EXCEPTION = 1 << 1 -tlds_file = open(sys.argv[1]) -inc_file = open(sys.argv[2], 'w') +tlds_file = codecs.open(sys.argv[1], encoding='utf-8') +inc_file = codecs.open(sys.argv[2], 'w', encoding='utf-8') first = True for rule in tlds_file: |
