diff options
author | Mark Doffman <mark.doffman@codethink.co.uk> | 2014-04-01 20:02:03 +0000 |
---|---|---|
committer | Mark Doffman <mark.doffman@codethink.co.uk> | 2014-04-01 20:02:03 +0000 |
commit | ce764489e358bad6b49418f5c8bc7b25a4b1815e (patch) | |
tree | bdf7d009702adc91e63d261f7d924d9e5bf94cff | |
parent | 0d45677b0a20270735e4d18e969a4991a4e67078 (diff) | |
parent | 6c14af04775a9de4dfa3fa0cc15a7ad0462ef3d9 (diff) | |
download | libsoup-ce764489e358bad6b49418f5c8bc7b25a4b1815e.tar.gz |
Merge tag '2.46.0' into baserock/morphbaserock/morph
2.46.0
269 files changed, 29849 insertions, 12717 deletions
@@ -1,9 +1,6 @@ -*~ *.o *.lo *.la -*.stamp -*.bak .deps .libs INSTALL @@ -11,16 +8,15 @@ Makefile Makefile.in aclocal.m4 autom4te.cache -config.guess +build-aux config.h config.h.in +config.h.in~ config.log config.status -config.sub configure -depcomp -docs/reference/html-build.stamp -docs/reference/html.stamp +docs/reference/*.stamp +docs/reference/*.bak docs/reference/html docs/reference/libsoup-2.4-decl-list.txt docs/reference/libsoup-2.4-decl.txt @@ -36,40 +32,39 @@ docs/reference/libsoup-2.4.signals docs/reference/libsoup-2.4.types docs/reference/tmpl docs/reference/xml +examples/get +examples/simple-httpd +examples/simple-proxy gtk-doc.make -install-sh libsoup-zip libsoup-2.4.pc libsoup-gnome-2.4.pc libsoup/soup-enum-types.* -libsoup/soup-marshal.* libsoup/Soup-2.4.gir libsoup/Soup-2.4.typelib libsoup/SoupGNOME-2.4.gir libsoup/SoupGNOME-2.4.typelib libsoup/tld_data.inc +libsoup/soup-version.h libtool -ltmain.sh m4/gtk-doc.m4 m4/intltool.m4 m4/libtool.m4 m4/lt*.m4 -missing -mkinstalldirs po/*.gmo po/libsoup.pot po/Makefile.in.in po/POTFILES po/stamp-it stamp-h1 +tests/*.log +tests/*.trs tests/*-test +tests/*.test tests/date -tests/dns -tests/get -tests/getbug tests/header-parsing tests/httpd.conf +tests/ntlm-test-helper tests/pull-api -tests/simple-httpd -tests/simple-proxy +tests/soup-tests.gresource tests/uri-parsing diff --git a/Makefile.am b/Makefile.am index 18bf80af..c8fa95ce 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ ## Process this file with automake to produce Makefile.in ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} -SUBDIRS = libsoup po tests docs +SUBDIRS = libsoup po tests examples docs EXTRA_DIST = \ data/effective_tld_names.dat \ @@ -9,7 +9,9 @@ EXTRA_DIST = \ libsoup-gnome-2.4.pc.in \ gtk-doc.make \ libsoup-zip.in \ - m4/introspection.m4 \ + glib-tap.mk \ + tap-driver.sh \ + tap-test \ Makefile.glib DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc --enable-introspection diff --git a/Makefile.glib b/Makefile.glib index 78eb07d7..c28c983b 100644 --- a/Makefile.glib +++ b/Makefile.glib @@ -128,7 +128,7 @@ _GLIB_DISTCLEANFILES = _GLIB_V_GEN = $(_glib_v_gen_$(V)) _glib_v_gen_ = $(_glib_v_gen_$(AM_DEFAULT_VERBOSITY)) -_glib_v_gen_0 = @echo " GEN " $(subst .stamp,,$@); +_glib_v_gen_0 = @echo " GEN " $(subst .stamp,,$@); ### glib-genmarshal @@ -154,8 +154,8 @@ _glib_marshal_sources = $(filter-out %.h,$(filter-out $(GLIB_GENERATED),$($(_gli define _glib_make_genmarshal_rules $(if $(_glib_marshal_sources),,$(error Need to define $(_glib_marshal_sources_var) for $(1).[ch])) -$(1).list.stamp: $(_glib_marshal_sources) - $$(_GLIB_V_GEN) LC_ALL=C sed -ne 's/.*_$(_glib_marshal_prefix)_\([_A-Z]*\).*/\1/p' $$^ | sort -u | sed -e 's/__/:/' -e 's/_/,/g' > $(1).list.tmp && \ +$(1).list.stamp: $(_glib_marshal_sources) Makefile + $$(_GLIB_V_GEN) LC_ALL=C sed -ne 's/.*_$(_glib_marshal_prefix)_\([_A-Z]*\).*/\1/p' $$(filter-out Makefile, $$^) | sort -u | sed -e 's/__/:/' -e 's/_/,/g' > $(1).list.tmp && \ (cmp -s $(1).list.tmp $(1).list || cp $(1).list.tmp $(1).list) && \ rm -f $(1).list.tmp && \ echo timestamp > $$@ @@ -200,7 +200,7 @@ _glib_enum_types_h_sources = $(filter %.h,$(_glib_enum_types_sources)) define _glib_make_mkenums_rules $(if $(_glib_enum_types_sources),,$(error Need to define $(_glib_enum_types_sources_var) for $(1).[ch])) -$(1).h.stamp: $(_glib_enum_types_h_sources) +$(1).h.stamp: $(_glib_enum_types_h_sources) Makefile $$(_GLIB_V_GEN) $$(GLIB_MKENUMS) \ --fhead "/* Generated by glib-mkenums. Do not edit */\n\n#ifndef $(_glib_enum_types_guard)\n#define $(_glib_enum_types_guard)\n\n" \ $$(GLIB_MKENUMS_H_FLAGS) \ @@ -208,7 +208,7 @@ $(1).h.stamp: $(_glib_enum_types_h_sources) --fhead "#include <glib-object.h>\n\nG_BEGIN_DECLS\n" \ --vhead "GType @enum_name@_get_type (void) G_GNUC_CONST;\n#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())\n" \ --ftail "G_END_DECLS\n\n#endif /* $(_glib_enum_types_guard) */" \ - $$^ > $(1).h.tmp && \ + $$(filter-out Makefile, $$^) > $(1).h.tmp && \ (cmp -s $(1).h.tmp $(1).h || cp $(1).h.tmp $(1).h) && \ rm -f $(1).h.tmp && \ echo timestamp > $$@ @@ -216,16 +216,16 @@ $(1).h.stamp: $(_glib_enum_types_h_sources) $(1).h: $(1).h.stamp @true -$(1).c.stamp: $(_glib_enum_types_h_sources) +$(1).c.stamp: $(_glib_enum_types_h_sources) Makefile $$(_GLIB_V_GEN) $$(GLIB_MKENUMS) \ --fhead "/* Generated by glib-mkenums. Do not edit */\n\n#include \"$(notdir $(1)).h\"\n" \ $$(GLIB_MKENUMS_C_FLAGS) \ $$($(_glib_enum_types_prefix)_MKENUMS_C_FLAGS) \ - --fhead "$$(foreach f,$$(^F),\n#include \"$$(f)\")\n\n" \ + --fhead "$$(foreach f,$$(filter-out Makefile,$$(^F)),\n#include \"$$(f)\")\n\n" \ --vhead "GType\n@enum_name@_get_type (void)\n{\n static volatile gsize g_define_type_id__volatile = 0;\n\n if (g_once_init_enter (&g_define_type_id__volatile))\n {\n static const G@Type@Value values[] = {\n" \ --vprod " { @VALUENAME@, \"@VALUENAME@\", \"@valuenick@\" },\n" \ --vtail " { 0, NULL, NULL }\n };\n GType g_define_type_id =\n g_@type@_register_static (g_intern_static_string (\"@EnumName@\"), values);\n g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);\n }\n\n return g_define_type_id__volatile;\n}\n" \ - $$^ > $(1).c.tmp && \ + $$(filter-out Makefile, $$^) > $(1).c.tmp && \ (cmp -s $(1).c.tmp $(1).c || cp $(1).c.tmp $(1).c) && \ rm -f $(1).c.tmp && \ echo timestamp > $$@ @@ -254,13 +254,13 @@ _glib_enums_xml_namespace = $(subst .enums.xml,,$(notdir $(1))) define _glib_make_enums_xml_rule $(if $(_glib_enums_xml_sources),,$(error Need to define $(_glib_enums_xml_sources_var) for $(1))) -$(1): $(_glib_enums_xml_sources) - $$(_GLIB_V_GEN) $$(GLIB_MKENUMS) --comments '<!-- @comment@ -->' --fhead "<schemalist>" --vhead " <@type@ id='$(_glib_enums_xml_namespace).@EnumName@'>" --vprod " <value nick='@valuenick@' value='@valuenum@'/>" --vtail " </@type@>" --ftail "</schemalist>" $$^ > $$@.tmp && mv $$@.tmp $$@ +$(1): $(_glib_enums_xml_sources) Makefile + $$(_GLIB_V_GEN) $$(GLIB_MKENUMS) --comments '<!-- @comment@ -->' --fhead "<schemalist>" --vhead " <@type@ id='$(_glib_enums_xml_namespace).@EnumName@'>" --vprod " <value nick='@valuenick@' value='@valuenum@'/>" --vtail " </@type@>" --ftail "</schemalist>" $$(filter-out Makefile, $$^) > $$@.tmp && mv $$@.tmp $$@ endef _GLIB_V_CHECK = $(_glib_v_check_$(V)) _glib_v_check_ = $(_glib_v_check_$(AM_DEFAULT_VERBOSITY)) -_glib_v_check_0 = @echo " CHECK " $(subst .valid,.xml,$@); +_glib_v_check_0 = @echo " CHECK " $(subst .valid,.xml,$@); define _glib_make_schema_validate_rule $(subst .xml,.valid,$(1)): $(_GLIB_ENUMS_XML_GENERATED) $(1) @@ -1,3 +1,777 @@ +Changes in libsoup from 2.45.92 to 2.46.0: + + * (No changes, just a version bump) + +Changes in libsoup from 2.45.90 to 2.45.92: + + * Fixed problems with using an http proxy on port 80. [patch + from Slava Monich on libsoup-list] + + * Plugged a small per-connection leak. [patch from Slava + Monich on libsoup-list] + + * Belatedly bumped up the glib requirement to 2.38, which is + required for the TAP driver. + + * Fixed up some leftover issues from the test porting to + gtestutils: + + test-utils: change http_debug command-line flag + xmlrpc-test: use g_test_skip() + redirect-test: remove a flaky test + auth-test: remove some erroneous SKIP_IF_NO_APACHEs + tests: split up some test programs into more tests + tests: remove debug_printf()s that are redundant with test names + tests: use g_test_bug() to annotate test cases + +Changes in libsoup from 2.45.3 to 2.45.90: + + * Fixed a problem where libsoup would use the "http" proxy + settings for "https" requests [#724316, Matt Barnes] + + * Updated SoupContentSniffer to match the current version of + the MIME sniffing specification [#648849 / #715126, Gustavo + Noronha Silva] + + * Updated the soup-tld APIs to accept ASCII-encoded hostnames + in addition to UTF-8 ones. + + * Ported the test programs to the gtestutils framework, added + support for installed tests, and made the tests use the TAP + driver. + + Some tests still need to be split up into more pieces, and + the debug output is now somewhat less useful in some cases. + + * Fixed the test programs to not depend on local proxy + settings. + + * Added some more checks to header-tests [#722341, Lionel + Landwerlin] + + * Fixed the "simple-proxy" example program, which had been + broken for a while. + + * Updated translations: + Korean + +Changes in libsoup from 2.44.1 to 2.45.3: + + * The documentation has finally been updated to reflect the + new APIs added in 2.42. + + * Added GBytes-valued :request-body-data and + :response-body-data properties to SoupMessage, which should + help some bindings. + + * We now set TCP_NODELAY on sockets, improving throughput a + bit. In particular, this avoids an unnecessary extra round + trip in the TLS handshake. + + * The SoupSession APIs that return GErrors now return the + actual underlying gio errors in the event of connection + failures and the like. + + * Updated the copy of the Public Suffix list used by soup-tld. + + * Fixed a hang with internet radio streams in Rhythmbox (and + some other places). [#710494, Dan] + + * Fixed a connection leak when cancelling the close of + a message GInputStream. [#711260, Dan] + + * Updated the soup_server_pause_message() / + soup_server_unpause_message() documentation to clarify when + you can and can't call them. [#710602, Philip Withnall] + + * soup_message_set_request() and soup_message_set_response() + now g_warn_if_fail() if you pass an invalid Content-Type. + (And they also have better introspection annotations.) + [#686766, Dan] + + * Plugged a few memory leaks [#711085, Christophe Fergeau] + + * Fixed connection-test to pass with current glib [#711361, + Colin Walters] + + * session-test now passes when run against the dummy TLS + backend (ie, if you don't have glib-networking installed), + as long as you have the latest glib. + + * Fixed build with -Werror=format-nonliteral [#720082, Ryan + Lortie] + + * Fix build with --without-ntlm [#710267, Dan] + + * Fixed a few warnings [Dan, Fabiano Fidêncio] + + * Tests are now more verbose by default under "make check", + since current automake just redirects all the output to a + log file anyway. + + * Updated translations: + Chinese + +Changes in libsoup from 2.44.0 to 2.44.1: + + * If you called g_input_stream_close() on SoupMessage or + SoupRequests's GInputStream before you finished reading it, + it would block until the rest of the response had been read + (which in the case of, eg, an infinite audio stream, meant + that it would block forever). This was an unintended change + from 2.42 and is now fixed. [#695652, Dan] + + * soup_session_queue_message() on a plain SoupSession (not + SoupSessionAsync) was operating synchronously rather than + asynchronously as it should have. [#707711, Dan] + + * soup-form methods now use the HTML5 x-www-form-urlencoded + rules rather than the HTML4 ones. (In particular, they leave + "-", "_", and "." unencoded.) [#708621, Alban Browaeys] + + * The test programs now explicitly request the "memory" + GSettings backend, meaning they won't print a warning if + they get it accidentally, and they won't accidentally use + the dconf proxy settings if the dconf backend is available. + [Dan] + + * Fix SoupSession so that if you override the proxy + configuration at construct time, it doesn't try to resolve + the default GProxyResolver. (This is important in particular + for programs that expect to run outside a user session, + which would otherwise spew errors from the dconf GSettings + backend about not being able to connect to dconf.) Likewise + with TLS settings and the TLS backend. [#708696, Dan] + + * Fix the SoupServer:tls-certificate property; + soup_server_is_https() had not been updated to recognize it, + and so would return FALSE, which in turn meant that the + server would return 400 Bad Request in response to https + requests. [#709647, Fabiano Fidêncio] + + * Fixed a sporadic failure in tests/connection-test. [Dan] + + * Updated translations: + Tajik + +Changes in libsoup from 2.43.92 to 2.44.0: + + * New/updated translations: + Dutch, Indonesian + +Changes in libsoup from 2.43.90 to 2.43.92: + + * Fixed a bug in the connection-pool code, which would in + certain cases accidentally keep using a connection after a + message was cancelled while in progress, causing the next + request on that connection to get the response to the + previous request, etc. [#708006, David Woodhouse] + + * Fixed some problems when falling back from samba + single-sign-on-based NTLM to ordinary ask-for-a-password + NTLM. [#703186, David Woodhouse] + + * When sending a conditional GET request, SoupCache now + preserves the original message's list of disabled features, + ensure that it gets back the same kind of response the + original message would have. [#706338, Andre Moreira + Magalhaes] + + * Fixed a warning when the remote host closes the connection + while we are writing something using chunked encoding. + [#703297, Dan] + + * Added SoupServer:http-aliases and :https-aliases properties, + to specify URI schemes that should be treated as though they + were http (just like SoupSession:http-aliases and + :https-aliases, but on the server side). [#703694, Dan] + + * Fixed race conditions in cache-test and timeout-test that + could case spurious failures. [#698305, #660581, Dan] + +Changes in libsoup from 2.43.5 to 2.43.90: + + * Fixed the handling of unsatisfiable range requests in + SoupServer [pointed out on mailing list, Dan]. Also, added + more documentation clarifying that you don't need to handle + range requests yourself in many cases. + + * Fixed the handling of IPv6 address literals with scope IDs. + (Requires the latest glib as well for the complete fix.) + [#669724, Dan] + +Changes in libsoup from 2.43.4 to 2.43.5: + + * SoupProxyURIResolver is now deprecated in favor of the + SoupSession:proxy-resolver property (which takes a + GProxyResolver). [#680273, Dan] + + * The SoupKnownStatusCode enum is now called SoupStatus. The + old name continues to exist as an alias, but is deprecated. + (This change has no visible effect in C; it is primarily to + help language bindings, so that, eg, SOUP_STATUS_NOT_FOUND + maps to "Soup.Status.NOT_FOUND" rather than + "Soup.KnownStatusCode.NOT_FOUND".) [#684409, Dan] + + * Fixed the parsing of URI schemes in SoupURI (in particular, + to allow scheme names with digits in them). [#703776, Dan] + + * Fixed SoupLogger to print a message's response headers even + if the message gets cancelled before the complete response + body is received. [#703200, Andres Gomez] + + * Fixed a build problem in non-UTF-8 locales [#702534, Ross + Lagerwall] + + * SoupSession now warns if you use + soup_session_pause_message() or + soup_session_unpause_message() on a synchronous message + (which has never worked, though that fact wasn't + documented). [#703461, Philip Withnall] + +Changes in libsoup from 2.43.2 to 2.43.4: + + * Fixed a bug that could cause synchronous sessions to get + stuck in a state where no new messages would ever get + processed. [#703463, Philip Withnall] + + * Fixed another memory leak in SoupSocket (found while + added a test case for #700472) + + * Switched to using g_cclosure_marshal_generic() rather than + using glib-genmarshal. [#686042, Olivier Blin] + + * Changed SoupServer to call unref() on the query hash table + after calling the handler, rather than destroy(), so that + the handler can keep a copy of the query data if it wants. + [#702793, Bernhard Schuster] + + * Fixed a few introspection annotations + + * Updated examples/get to use SoupLogger and to allow + redirecting the output to a file [#703231, #703229, Andres + Gomez] + +Changes in libsoup from 2.43.1 to 2.43.2: + + * Fixed an authentication error when using NTLM when + connecting to an https site over a proxy; the code was + getting confused and thinking that the 200 OK response to + the CONNECT meant that NTLM auth had succeeded. [#698728, + Dan] + + * Fixed a memory leak in SoupSocket. [#700472, Richard + Röjfors] + + * Fixed a missing include error on some platforms [#700003, + Erik van Pienbroek] + + * Fixed warnings when running against the "dummy" TLS backend. + [#700518, Dan] + +Changes in libsoup from 2.42.1 to 2.43.1: + + * Including <libsoup/soup.h> no longer pulls in the system + networking headers. This may cause some packages to no + longer compile, if they were accidentally depending on this. + Adding "#include <gio/gnetworking.h>" will fix them on both + unix and Windows. (This was done as part of fixing the build + on Windows.) [#692134, Dan] + + * Fixed SoupSession:proxy-resolver [#698163, Dan] + + * Added soup_message_set_priority(), to mark messages as being + high, low, or normal priority, and update the message queue + to prioritize them accordingly. [#696277, Sergio] + + * Fixed several test programs to still work if glib-networking + isn't installed [Dan], and fixed another to still work if + the kernel has no IPv6 support. [#698220, Dan] + +Changes in libsoup from 2.42.0 to 2.42.1: + + * Fixed SoupProxyResolverDefault, which got broken in 2.42.92. + [#697028, Dan] + + * Fixed a gigantic memory leak when using SoupCache. [#696594, + Xan] + + * Fixed a build problem on Windows. [#696354, Kalev Lember] + + * Fixed ntlm-test to pass whether or not Samba ntlm_auth + support was compiled in. [#697510, Dan] + + * New/updated translations: + Basque, Turkish + +Changes in libsoup from 2.41.92 to 2.42.0: + + * Fixed a compiler warning on 32bit in a test program. [Kalev + Lember] + + * New/updated translations: + Hindi, Kannada, Korean, Malayalam, Marathi, Odia, Persian, + Tadjik, Tamil, Telugu + +Changes in libsoup from 2.41.91 to 2.41.92: + + * Fixed a bug that caused libsoup to retry an incorrect + password repeatedly, forever, in a certain case that + affected Google calendars in evolution in particuar. + [Red Hat bug #916224, Dan] + + * Also added code to make such infinite retry loops impossible + in the future. [Dan] + + * Fixed SoupRequestData's handling of URIs with "%00" in them. + [#695246, Žan Doberšek] + + * Added the SoupSession:proxy-resolver property, to override + the GProxyResolver used by a session. (This means there are + now three different ways of controlling proxy resolution in + SoupSession... this will be cleaned up a bit after 2.42.) + [#680273, Dan] + + * Added missing G_BEGIN_DECLS/G_END_DECLS to + soup-message-headers.h, so that its functions can be called + from C++. [Carlos Garcia Campos] + + * Updated translations: + Assamese, Belarusian, Brazilian Portuguese, Catalan + (Valencian), Catalan, Danish, Estonian, French, Greek, + Gujarati, Hungarian, Italian, Latvian, Portuguese, Russian, + Slovenian, Thai + +Changes in libsoup from 2.41.90 to 2.41.91: + + * Fixed a crash that showed up with XMLRPC requests in + WebKitGTK. [#694920, Sergio] + + * Fixed SoupCache to update the cached headers when it + receives a 304 Not Modified response, and added a test for + this. [#695121, Sergio] + + * libsoup now builds under automake 1.13 (and "make check" + works under the parallel test harness which is the default + in 1.13) [#694135] + + * The tests/ directory now contains only actual test programs + that are run by "make check", and the programs that are + intended more as example code are under examples/. + + * New/updated translations: + Aragonese, Chinese (traditional), Czech, Galician, Hebrew, + Lithuanian, Norwegian bokmål, Punjabi, Spanish, Uyghur, + Vietnamese + +Changes in libsoup from 2.41.5 to 2.41.90: + + * Added SoupSession:local-address property, which allows you + to force connections to bind to a particular local address + (eg, to control the interface that is used). [#693215, Jonh + Wendell] + + * Fixed SoupCache to properly handle messages that get + cancelled, and added tests for this. [#692310, Sergio] + + * Fixed a reference leak in SoupCache that resulted in + epiphany hanging for several seconds on exit and then + eventually printing "Cache flush finished despite X pending + requests". And added more tests. [#682527, Sergio] + + * Fixed SoupAuthNTLM so that SoupSession:authenticate gets + emitted with retrying=TRUE if the first attempt fails (ie, + make it work the same way as SoupAuthBasic and + SoupAuthDigest). [#693222, Dan] + + * Fixed the SoupSession:add-feature-by-type property to accept + non-SoupSessionFeature features as well (eg, auth types) + [Dan] + + * Fixed a build bug that would break all the apache-based + tests if you didn't have PHP installed. [#693311, Dan] + + * Updated translations: + Malayalam, Polish, Serbian, Slovak + +Changes in libsoup from 2.41.4 to 2.41.5: + + * Reverted the change to SoupURI's password handling from + 2.41.4, since it turns out to have broken some things. + [#692149, Dan] + + * Avoid a g_return_if_fail() when loading + SoupSession:ssl-ca-file fails. [#691930, Guillaume + Desmottes] + + * Fixed a bug in SoupBodyInputStream that caused redirects in + WebKitGTK to hang. [#692026, Sergio] + + * Updated translations: + Belarusian, Chinese (traditional), German, Italian, + Norwegian bokmål, Serbian, Uyghur + +Changes in libsoup from 2.41.3 to 2.41.4: + + * Lots of docs fixes, including catching up with some (but not + all!) of the API additions and deprecations, and a new + chapter with hints on porting from + SoupSessionAsync/SoupSessionSync to the new-and-improved + plain SoupSession. + + * The plain SoupSession type now supports + soup_session_queue_message() (with SoupSessionAsync + semantics) and soup_session_send_message() (with + SoupSessionSync semantics), and there are now + soup_session_new() and soup_session_new_with_options(). + + * The mirroring of the SoupMessage API onto SoupRequestHTTP, + added in 2.41.3, has been reverted. However, new APIs + soup_session_send() and soup_session_send_async() have been + added that let you use the GInputStream-based API with + SoupMessages rather than SoupRequest, so if you're doing + HTTP-specific stuff, you can just use that instead. + + * soup_message_get_https_status() now returns the certificate + and flags for unsuccessful https connections as well as + successful ones. [#690176] + + * Fixed a deadlock when calling soup_session_abort() on a + SoupSessionSync in some cases. [#691399] + + * Internal SoupCache rewrites/improvements [#682112, Sergio] + + * Plugged a memory leak in SoupCache [#690382, Sudarsana + Nagineni] and one in SoupAuthDigest [#690142] + + * LIBSOUP_DISABLE_DEPRECATED has been renamed to + SOUP_DISABLE_DEPRECATED, but that's just to keep gtk-doc + happy, and you shouldn't use it. You should use + SOUP_VERSION_MIN_REQUIRED instead. + + * Fixed the samba-windbind-based NTLM support, which appears + to have been broken before. + + * SoupAuthManager is now a public class (so you can remove it + as a feature from a session, or disable it for a particular + message). It also has a new method + soup_auto_manager_use_auth(), which can be used to "preload" + authentication for a host so that libsoup will use + authentication on the very first request. + + * SoupURI now treats "http://user@example.com" as having a + password of "" rather than NULL, since a NULL password would + not be valid for any known HTTP auth type. + + * build: libsoup now uses autoreconf instead of + gnome-autogen.sh, and no longer uses AM_GLIB_GNU_GETTEXT + [Javier Jardon]. + + * Updated translations: + Assamese, Bulgarian, Estonian, Friulian, Galician, Hebrew, + Polish, Slovenian, Spanish + +Changes in libsoup from 2.41.2 to 2.41.3 (codename: "I Left My +Deprecated APIs in A Coruña"): + + * BUILD DEPENDENCY CHANGES: libsoup-gnome no longer depends on + libgnome-keyring, and the sqlite3 dependency has been moved + from libsoup-gnome to libsoup proper. (See below). + + + * SoupRequest is now stable API. SoupRequester, however, is + deprecated. Instead you can now call soup_session_request() + or soup_session_request_uri() to create a SoupRequest. + + Some documentation has been updated to reflect this, but + much more still needs to be (in particular the "Client-side + Tutorial"). + + * SoupRequestHTTP now has a number of fields and methods that + mirror the SoupMessage data, so you don't have to use + soup_request_http_get_message() in many cases. On the flip + side, there is also now soup_message_get_request(). And you + can create a SoupRequestHTTP directly (and override its + request method) by using soup_session_request_http() or + soup_session_request_http_uri()). + + * soup_message_set_chunk_allocator() is now deprecated; apps + that want to do streaming reads should just use SoupRequest, + which is vastly more sane. + + + * SoupPasswordManager is now deprecated, and + SoupPasswordManagerGNOME is now a no-op (and libsoup-gnome + no longer links against libgnome-keyring). [#594377, #679866] + + * SoupCookieJarSqlite is now deprecated in favor of + SoupCookieJarDB, which is exactly the same thing except that + it's in libsoup itself rather than being in libsoup-gnome + (something that many people have requested). This means that + libsoup now requires sqlite3... if this offends you horribly + then you have a few months to speak up... + + * SoupProxyResolverGNOME is now deprecated; there hasn't been + any real reason to use it since SoupProxyResolverDefault was + added. + + * As a result of the last three items, libsoup-gnome now + consists entirely of deprecated APIs, and there is no reason + you should use it any more (though packagers need to keep + building it, for backward compatibility). + + + * SoupSession is no longer an abstract class, and you can + create a plain SoupSession, which behaves in a more + traditionally-gio-like way (allowing a mix of sync and async + methods, etc). This "plain" SoupSession also has more sane + default values of certain properties, and has certain + SoupSessionFeatures built in. + + This will eventually replace SoupSessionAsync and + SoupSessionSync completely, but most of the documentation + hasn't yet been updated at this point... + + This change involved merging the majority of the + SoupSessionAsync and SoupSessionSync code into SoupSession, + getting rid of lots of redundancy in the process. There may + be some bug fallout from this (probably on the + SoupSessionSync side, since WebKit's tests tend to shake out + all SoupSessionAsync bugs). However, this should help to + avoid SoupSessionSync-only bugs in the future, since much + more of the code is now shared. + + + * Usernames and passwords passed into SoupSession a URI will + now be cleared after they're used, so that if they are + wrong, the authenticate signal will be emitted on the next + round. [#689673, Martin Robinson] + + * SoupURI now leaves "%00" in URIs as-is, rather than decoding + it to "\0", which was not intended and is never useful. + + * Fixed a bug in SoupBodyOutputStream that could cause libsoup + to sometimes use blocking I/O rather than non-blocking when + writing chunked message bodies. [#688974, Milan Plzik] + + * Fixed a bug in SoupFilterInputStream that could cause some + non-blocking reads to suck up CPU while waiting for the + network. (This was noticed with multipart/x-mixed-replace + processing; it's not clear if it affected anything else.) + [Gustavo] + + * tests: misc small fixes + + + * New/updated translations: + Assamese, Galician, Japanese, Odia, Polish, Spanish + +Changes in libsoup from 2.41.1 to 2.41.2: + + * libsoup-2.4.so and libsoup-gnome-2.4.so now only export the + symbols that are part of the public API. (Plus, temporarily, + one additional symbol, soup_message_io_cleanup, which is + used by gvfs and possibly a few other modules, even though + it shouldn't be. If you copied soup-input-stream.c from + gvfs, you should port your code to use SoupRequest instead.) + [Dan, #595176] + + * Added SOUP_VERSION_MIN_REQUIRED / SOUP_VERSION_MAX_ALLOWED + macros like the corresponding glib ones, to allow libsoup + users to request per-version deprecation/availability + warnings. [Dan] + + * Fixed a crash caused by a race condition in SoupSessionSync, + and reorganized some code to avoid other possible similar + race conditions. [#684238, Dan] + + * Fixed a crash when a DNS resolution failed, caused by a bug + in the GTask porting in 2.41.1. [#688330, Milan Crha] + + * Fixed a problem that would cause g_warning()s in epiphany + when browsing sites that (incorrectly) returned empty + Cache-Control headers. [#683433, Dan] + + * We now add a Host header to HTTP/1.0 requests as well as + HTTP/1.1 ones. [Dan] + + * Fixed a bug in the printing of IPv6 address literals in the + Host header in SoupLogger. (They were being sent across the + wire correctly, they were just printed wrong by SoupLogger.) + [Dan] + + * Belatedly added soup-multipart-input-stream.h to soup.h. + [Dan] + + * Removed an evil hack in the long-deprecated + SoupProxyResolver code (not to be confused with + SoupProxyURIResolver), to avoid warnings with glib master. + If you had previously implemented a custom + SoupProxyResolver, then it will probably (silently) stop + working, but you should have ported it to + SoupProxyURIResolver years ago anyway... [Dan, #687659] + + * Fixed a few race conditions in the test programs that could + cause "make check" to fail on slow or heavily-loaded + machines. [Dan] + + * Further cleaned up and reorganized the internal HTTP I/O + codepaths, in preparation for an improved SoupCache. + [#682112, Sergio] (This change should not actually be + externally noticeable. But if SoupContentDecoder or + SoupContentSniffer turns out to be broken in this release, + this would be why). + + * New/updated translations: + Slovak, Uyghur + +Changes in libsoup from 2.40.1 to 2.41.1: + + * Changed the behavior of NTLM authentication to be more like + what other apps apparently do. Now if the user does not + specify a domain in the username (eg, "MYDOMAIN\username"), + then we will not specify a domain in the NTLM response + (rather than assuming that the user is in the server's + default domain). People who get broken by this change should + be able to fix it by including an explicit domain in their + username, but the theory is that no one should get broken by + this... If this change does turn out to hurt more than it + helps then it may be reverted later. [#624613, Dan, based on + a suggestion from David Woodhouse] + + * Fixed a crash caused by a race condition in SoupSessionSync. + [#684238, Dan] + + * SoupRequest now supports resource:// URIs, for reading from + gresource. [#682721, Carlos] + + * Added new compile-time and runtime APIs for checking the + libsoup version. [#684514, Martin Robinson] + + * Updated to take advantage of (and require) glib 2.35; + removed all g_type_init() calls and ported to GTask. [Dan] + + * Added support for Apache 2.4 to the unit tests. [Dan] + + * New translations: + Uzbek (Cyrillic) + +Changes in libsoup from 2.40.0 to 2.40.1: + + * Improved the parsing of multipart/x-mixed-replace responses. + [#685752, Gustavo] + + * Fixed handling of IPv6 address literals. [#684990, Dan] + + * New/updated translations: + Catalan, Catalan (Valencian), Norwegian bokmål + +Changes in libsoup from 2.39.92 to 2.40.0: + + * New/updated translations: + Bengali (India), Bulgarian, Chinese (Simplified), Estonian, + German, Hebrew, Hindi, Hungarian, Latvian, Malayalam, + Punjabi, Tamil, Telugu, Thai, Ukranian + +Changes in libsoup from 2.39.91 to 2.39.92: + + * Fixed some g_warnings (and a possible crash) with the + soup_request_send_async(). [#683404, Dan] + + * Fixed a hang with SoupSessionSync [#682923, Dan] + + * Handle empty "Cache-Control" headers. [Sergio] + + * New/updated translations: + Assamese, Belarusian, Brazilian Portuguese, British English, + Chinese (Traditional), Czech, Danish, French, Galician, + Greek, Gujarati, Hungarian, Indonesian, Italian, Lithuanian, + Marathi, Persian, Polish, Portuguese, Russian, Serbian, + Slovenian, Spanish + +Changes in libsoup from 2.39.90 to 2.39.91: + + * Added missing (transfer full) annotation to + soup_cookie_jar_add_cookie() and + soup_cookie_jar_add_cookie_with_first_party(), fixing + crashes in bindings. [#682554, Daniel Drake] + + * Fixed a crash [#682569, Alexander Larsson] and a win32 build + problem [#683200, Kalev Lember] introduced by the memory + leak fixes in 2.39.90 + + * Fixed the SoupMessage:network-event signal, which had been + skipping the G_SOCKET_CLIENT_COMPLETE state in tunneled + https connections. And added a test for this. [Dan] + + * New/updated translations: + French, Japanese, Korean, Latvian, Lithuanian, Polish, + Punjabi, Swedish, Tamil + +Changes in libsoup from 2.39.5 to 2.39.90: + + * Added SoupMultipartInputStream, for handling multipart + responses (particularly multipart/x-mixed-replace). + [#656684, Gustavo] + + * Fixed a potential crash in SoupSessionAsync after the + session is finalized. + + * Fixed a regression in soup_tls_is_public_suffix() [#681085, + Sergio] + + * Added a SOUP_MESSAGE_IDEMPOTENT flag, so that apps can + bypass the "POSTs must be sent on new connections" check, + which was causing evolution-ews to have to create a new + connection for every request. [#681493, Milan Crha] + + * Changed SoupSession so that pending SoupMessages now hold a + ref on the session. It is possible that this will break code + that was depending on the old, dumb, behavior (where + unreffing the session with messages pending would cause + those messages to be cancelled), in which case this will be + reverted before 2.40. + + * Fixed memory leaks found by valgrind. + + * Cleaned up some code in SoupCache. [#681509, Sergio] + + * New/updated translations: + Bengali (India), Chinese (Traditional), German, Marathi, + Turkish, Ukranian + +Changes in libsoup from 2.39.4.1 to 2.39.5: + + * Fixed several bugs in the soup-message-io updates that could + cause hangs or I/O errors. [#679527 and other bugs not filed + in bugzilla] + + * Fixed SoupServer:async-context to work properly again + + * Further fixes to soup_uri_normalize() when using the + "unescape_extra" parameter. [#680018] + + * Fixed soup_xmlrpc_parse_method_call() to handle the case + where there is no <params> element (which is legal). + [#671661] + + * Fixed the deprecation warning on soup_message_headers_get() + [#680143] + + * Added warnings to some erroneous SoupSocket usages rather + than returning bogus data. [#673083, Simon McVittie] + + * Fixed build under Windows/MinGW + + * SoupSocket no longer emits the "readable" signal when a + socket is disconnected if that socket is non-blocking. + + * Updated public suffix list to the current version. + + * New/Updated translations: + Assamese, Chinese (simplified), Esperanto, Galician, Greek, Gujarati, + Hebrew, Norwegian bokmål, Russian, Serbian, Slovenian + Changes in libsoup from 2.39.4 to 2.39.4.1: * Fixed indentation problems in tld-parser.py so it will work @@ -1,21 +1,33 @@ #!/bin/sh # Run this to generate all the initial makefiles, etc. -srcdir=`dirname $0` -test -z "$srcdir" && srcdir=. +test -n "$srcdir" || srcdir=`dirname "$0"` +test -n "$srcdir" || srcdir=. -PKG_NAME="libsoup" +olddir=`pwd` +cd $srcdir -(test -f $srcdir/configure.ac \ - && test -f $srcdir/libsoup.doap \ - && test -d $srcdir/libsoup) || { - echo -n "**Error**: Directory "\`$srcdir\'" does not look like the" - echo " top-level $PKG_NAME directory" - exit 1 -} +AUTORECONF=`which autoreconf` +if test -z $AUTORECONF; then + echo "*** No autoreconf found, please intall it ***" + exit 1 +fi -which gnome-autogen.sh || { - echo "You need to install gnome-common from the GNOME CVS" - exit 1 -} -USE_GNOME2_MACROS=1 . gnome-autogen.sh +INTLTOOLIZE=`which intltoolize` +if test -z $INTLTOOLIZE; then + echo "*** No intltoolize found, please install the intltool package ***" + exit 1 +fi + +GTKDOCIZE=`which gtkdocize` +if test -z $GTKDOCIZE; then + echo "*** No GTK-Doc found, please install it ***" + exit 1 +fi + +gtkdocize || exit $? +intltoolize --automake --copy +autoreconf --force --install --verbose + +cd $olddir +test -n "$NOCONFIGURE" || "$srcdir/configure" "$@" diff --git a/configure.ac b/configure.ac index 47daa7dd..96bb378a 100644 --- a/configure.ac +++ b/configure.ac @@ -2,24 +2,36 @@ dnl ******************************************* dnl *** Initialize automake and set version *** dnl ******************************************* +m4_define([soup_major_version], [2]) +m4_define([soup_minor_version], [46]) +m4_define([soup_micro_version], [0]) + AC_PREREQ(2.63) -AC_INIT([libsoup],[2.39.4.1],[http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup]) +AC_INIT([libsoup],[soup_major_version.soup_minor_version.soup_micro_version],[http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup]) AC_CONFIG_SRCDIR([libsoup-2.4.pc.in]) AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_AUX_DIR([build-aux]) AM_INIT_AUTOMAKE([1.11 foreign no-dist-gzip dist-xz -Wno-portability]) m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])],) AC_PROG_MAKE_SET +SOUP_MAJOR_VERSION=soup_major_version +SOUP_MINOR_VERSION=soup_minor_version +SOUP_MICRO_VERSION=soup_micro_version +AC_SUBST(SOUP_MAJOR_VERSION) +AC_SUBST(SOUP_MINOR_VERSION) +AC_SUBST(SOUP_MICRO_VERSION) + SOUP_API_VERSION=2.4 AC_SUBST(SOUP_API_VERSION) # Increment on interface addition. Reset on removal. -SOUP_AGE=5 +SOUP_AGE=7 # Increment on interface add, remove, or change. -SOUP_CURRENT=6 +SOUP_CURRENT=8 # Increment on source change. Reset when CURRENT changes. SOUP_REVISION=0 @@ -52,12 +64,6 @@ fi AC_SUBST(SOUP_DEBUG_FLAGS) -# Set the maintainer flags -#if test -d .git; then -# SOUP_MAINTAINER_FLAGS="-DG_DISABLE_DEPRECATED" -#fi -AC_SUBST(SOUP_MAINTAINER_FLAGS) - dnl *************************** dnl *** Checks for programs *** dnl *************************** @@ -72,20 +78,26 @@ dnl *********************** dnl *** Checks for glib *** dnl *********************** -GLIB_REQUIRED=2.33.1 +GLIB_REQUIRED=2.38.0 AM_PATH_GLIB_2_0($GLIB_REQUIRED,,,gobject gio) if test "$GLIB_LIBS" = ""; then AC_MSG_ERROR(GLIB $GLIB_REQUIRED or later is required to build libsoup) fi -GLIB_CFLAGS="$GLIB_CFLAGS -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_34" +GLIB_CFLAGS="$GLIB_CFLAGS -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_38" GLIB_MAKEFILE='$(top_srcdir)/Makefile.glib' AC_SUBST(GLIB_MAKEFILE) +GLIB_TESTS + PKG_CHECK_MODULES(XML, libxml-2.0) AC_SUBST(XML_CFLAGS) AC_SUBST(XML_LIBS) +PKG_CHECK_MODULES(SQLITE, sqlite3) +AC_SUBST(SQLITE_CFLAGS) +AC_SUBST(SQLITE_LIBS) + dnl *********************** dnl *** Check for Win32 *** dnl *********************** @@ -110,14 +122,11 @@ IT_PROG_INTLTOOL([0.35.0]) GETTEXT_PACKAGE=libsoup AC_SUBST([GETTEXT_PACKAGE]) AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE],["$GETTEXT_PACKAGE"],[The gettext domain name]) -AM_GLIB_GNU_GETTEXT dnl ******************* dnl *** Misc checks *** dnl ******************* AC_CHECK_FUNCS(gmtime_r) -AC_CHECK_FUNCS(mmap) -AC_CHECK_FUNC(socket, , AC_CHECK_LIB(socket, socket)) dnl ********************* dnl *** GNOME support *** @@ -128,32 +137,12 @@ AC_ARG_WITH(gnome, :, [if test $os_win32 = yes; then with_gnome=no; else with_gnome=yes; fi]) AC_MSG_RESULT($with_gnome) -if test $with_gnome != no -a $os_win32 != yes; then - PKG_CHECK_MODULES(GNOME_KEYRING, gnome-keyring-1, :, - AC_MSG_ERROR( -[Could not find gnome-keyring devel files. -Configure with --without-gnome if you wish to build only libsoup -without GNOME-specific features.])) -fi -AC_SUBST(GNOME_KEYRING_CFLAGS) -AC_SUBST(GNOME_KEYRING_LIBS) - AM_CONDITIONAL(BUILD_LIBSOUP_GNOME, test $with_gnome != no) if test $with_gnome != no; then AC_DEFINE(HAVE_GNOME, 1, [Defined if GNOME support is enabled]) - - PKG_CHECK_MODULES(SQLITE, sqlite3, :, [AC_MSG_ERROR(dnl -[Could not find sqlite3 devel files: - -$SQLITE_PKG_ERRORS - -Pass "--without-gnome" to configure if you want to build libsoup -without GNOME support.])]) fi AC_SUBST(HAVE_GNOME) -AC_SUBST(SQLITE_CFLAGS) -AC_SUBST(SQLITE_LIBS) dnl *************** @@ -172,7 +161,7 @@ save_LIBS="$LIBS" CFLAGS="$CFLAGS $GLIB_CFLAGS" LIBS="$LIBS $GLIB_LIBS" AC_RUN_IFELSE([AC_LANG_PROGRAM([#include <gio/gio.h>], - [g_type_init (); return !g_tls_backend_supports_tls (g_tls_backend_get_default ());])], + [return !g_tls_backend_supports_tls (g_tls_backend_get_default ());])], [have_glib_networking=yes], [have_glib_networking=no], [have_glib_networking="unknown (cross-compiling)"]) @@ -195,11 +184,6 @@ dnl ****************************** dnl *** Stuff for regression tests dnl ****************************** AC_MSG_NOTICE([checking for programs needed for regression tests]) -MISSING_REGRESSION_TEST_PACKAGES="" - -if test "$have_glib_networking" = "no"; then - MISSING_REGRESSION_TEST_PACKAGES="$MISSING_REGRESSION_TEST_PACKAGES glib-networking" -fi AC_ARG_WITH(apache-httpd, AS_HELP_STRING([--with-apache-httpd], [Path to apache httpd (for tests)]), @@ -211,6 +195,11 @@ if test "$APACHE_HTTPD" != "no"; then case $apache_version in 2.2.*) AC_MSG_RESULT([$apache_version (ok)]) + apache_version=2.2 + ;; + 2.4.*) + AC_MSG_RESULT([$apache_version (ok)]) + apache_version=2.4 ;; *) AC_MSG_RESULT([$apache_version (ignoring)]) @@ -227,7 +216,7 @@ if test "$APACHE_HTTPD" != "no"; then AS_HELP_STRING([--with-apache-module-dir], [Apache modules dirs (for tests)]), apache_module_dirs="$withval", [apache_prefix=`dirname \`dirname $APACHE_HTTPD\`` - mpm=`$APACHE_HTTPD -V | sed -ne 's/^Server MPM: */-/p' | tr 'A-Z' 'a-z'` + mpm=`$APACHE_HTTPD -V -C "ServerName localhost" | sed -ne 's/^Server MPM: */-/p' | tr 'A-Z' 'a-z'` # This only works with bash, but should fail harmlessly in sh apache_module_dirs=`echo $apache_prefix/lib{64,}/{apache,apache2,http,http2,httpd}{$mpm,}{/modules,}`]) for dir in $apache_module_dirs; do @@ -256,19 +245,18 @@ fi if test "$APACHE_HTTPD" != "no" -a -n "$APACHE_MODULE_DIR" -a -n "$APACHE_SSL_MODULE_DIR"; then AC_DEFINE(HAVE_APACHE, 1, [Whether or not apache can be used for tests]) - have_apache=1 - if test -z "$APACHE_PHP_MODULE_DIR"; then - MISSING_REGRESSION_TEST_PACKAGES="$MISSING_REGRESSION_TEST_PACKAGES mod_php5" + if test $apache_version = 2.2; then + AC_DEFINE(HAVE_APACHE_2_2, 1, [Apache is 2.2.x]) + else + AC_DEFINE(HAVE_APACHE_2_4, 1, [Apache is 2.4.x]) fi + have_apache=1 else have_apache=0 - if test "$APACHE_HTTPD" = "no" -o -z "$APACHE_MODULE_DIR"; then - MISSING_REGRESSION_TEST_PACKAGES="$MISSING_REGRESSION_TEST_PACKAGES apache" - else - MISSING_REGRESSION_TEST_PACKAGES="$MISSING_REGRESSION_TEST_PACKAGES mod_ssl" - fi fi -AM_CONDITIONAL(HAVE_APACHE, test $have_apache = 1) +AM_CONDITIONAL(HAVE_APACHE, test "$have_apache" = 1) +AM_CONDITIONAL(HAVE_APACHE_2_2, test "$apache_version" = 2.2) +AM_CONDITIONAL(HAVE_APACHE_2_4, test "$apache_version" = 2.4) if test "$have_apache" = 1; then AC_CHECK_PROGS(PHP, php php5) @@ -279,36 +267,27 @@ if test "$have_apache" = 1; then IF_HAVE_PHP="" else have_php=no - IF_HAVE_PHP="#" - MISSING_REGRESSION_TEST_PACKAGES="$MISSING_REGRESSION_TEST_PACKAGES php5" + IF_HAVE_PHP="\#" fi AC_MSG_RESULT($have_php) + AC_SUBST(IF_HAVE_PHP) if test "$have_php" = yes; then - AC_MSG_CHECKING([for xmlrpc-epi-php]) + AC_MSG_CHECKING([for php-xmlrpc]) if $PHP --rf xmlrpc_server_create | grep -q "does not exist"; then - have_xmlrpc_epi_php=no - MISSING_REGRESSION_TEST_PACKAGES="$MISSING_REGRESSION_TEST_PACKAGES php-xmlrpc" + have_php_xmlrpc=no else - have_xmlrpc_epi_php=yes + have_php_xmlrpc=yes + AC_DEFINE(HAVE_PHP_XMLRPC, 1, [Have php-xmlrpc]) fi - AC_MSG_RESULT($have_xmlrpc_epi_php) + AC_MSG_RESULT($have_php_xmlrpc) fi fi -AC_SUBST(IF_HAVE_PHP) -AM_CONDITIONAL(HAVE_XMLRPC_EPI_PHP, test "$have_xmlrpc_epi_php" = yes) - AC_PATH_PROG(CURL, curl, no) if test "$CURL" != no; then AC_DEFINE(HAVE_CURL, 1, [Whether or not curl can be used for tests]) -else - MISSING_REGRESSION_TEST_PACKAGES="$MISSING_REGRESSION_TEST_PACKAGES curl" fi -AM_CONDITIONAL(HAVE_CURL, test "$CURL" != no) - -AC_SUBST(MISSING_REGRESSION_TEST_PACKAGES) -AM_CONDITIONAL(MISSING_REGRESSION_TEST_PACKAGES, test -n "$MISSING_REGRESSION_TEST_PACKAGES") dnl ********************************************************** dnl *** path of NTLM single-sign-on helper ntlm_auth @@ -341,6 +320,7 @@ if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then -Wall -Wstrict-prototypes -Werror=missing-prototypes \ -Werror=implicit-function-declaration \ -Werror=pointer-arith -Werror=init-self -Werror=format=2 \ + -Wno-format-zero-length \ -Werror=missing-include-dirs -Werror=aggregate-return \ -Werror=declaration-after-statement" fi @@ -359,15 +339,9 @@ AC_CONFIG_FILES([ po/Makefile.in po/Makefile tests/Makefile - tests/httpd.conf + examples/Makefile docs/Makefile docs/reference/Makefile + libsoup/soup-version.h ]) AC_OUTPUT - -if test -n "$MISSING_REGRESSION_TEST_PACKAGES"; then - echo "" - echo Some regression tests will not be run due to missing packages: - echo $MISSING_REGRESSION_TEST_PACKAGES - echo "" -fi diff --git a/data/effective_tld_names.dat b/data/effective_tld_names.dat index 0df8800d..95a555aa 100644 --- a/data/effective_tld_names.dat +++ b/data/effective_tld_names.dat @@ -1,44 +1,6 @@ -// ***** BEGIN LICENSE BLOCK ***** -// Version: MPL 1.1/GPL 2.0/LGPL 2.1 -// -// The contents of this file are subject to the Mozilla Public License Version -// 1.1 (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// http://www.mozilla.org/MPL/ -// -// Software distributed under the License is distributed on an "AS IS" basis, -// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License -// for the specific language governing rights and limitations under the -// License. -// -// The Original Code is the Public Suffix List. -// -// The Initial Developer of the Original Code is -// Jo Hermans <jo.hermans@gmail.com>. -// Portions created by the Initial Developer are Copyright (C) 2007 -// the Initial Developer. All Rights Reserved. -// -// Contributor(s): -// Ruben Arakelyan <ruben@rubenarakelyan.com> -// Gervase Markham <gerv@gerv.net> -// Pamela Greene <pamg.bugs@gmail.com> -// David Triendl <david@triendl.name> -// Jothan Frakes <jothan@gmail.com> -// The kind representatives of many TLD registries -// -// Alternatively, the contents of this file may be used under the terms of -// either the GNU General Public License Version 2 or later (the "GPL"), or -// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), -// in which case the provisions of the GPL or the LGPL are applicable instead -// of those above. If you wish to allow use of your version of this file only -// under the terms of either the GPL or the LGPL, and not to allow others to -// use your version of this file under the terms of the MPL, indicate your -// decision by deleting the provisions above and replace them with the notice -// and other provisions required by the GPL or the LGPL. If you do not delete -// the provisions above, a recipient may use your version of this file under -// the terms of any one of the MPL, the GPL or the LGPL. -// -// ***** END LICENSE BLOCK ***** +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. // ===BEGIN ICANN DOMAINS=== @@ -213,17 +175,16 @@ it.ao // aq : http://en.wikipedia.org/wiki/.aq aq -// ar : http://en.wikipedia.org/wiki/.ar -*.ar -!congresodelalengua3.ar -!educ.ar -!gobiernoelectronico.ar -!mecon.ar -!nacion.ar -!nic.ar -!promocion.ar -!retina.ar -!uba.ar +// ar : https://nic.ar/normativa-vigente.xhtml +ar +com.ar +edu.ar +gob.ar +int.ar +mil.ar +net.ar +org.ar +tur.ar // arpa : http://en.wikipedia.org/wiki/.arpa // Confirmed by registry <iana-questions@icann.org> 2008-06-18 @@ -252,18 +213,18 @@ or.at // au : http://en.wikipedia.org/wiki/.au // http://www.auda.org.au/ // 2LDs -com.au
-net.au
-org.au
-edu.au
-gov.au
-csiro.au
-asn.au
-id.au
-// Historic 2LDs (closed to new registration, but sites still exist)
-info.au
-conf.au
-oz.au
+com.au +net.au +org.au +edu.au +gov.au +asn.au +id.au +csiro.au +// Historic 2LDs (closed to new registration, but sites still exist) +info.au +conf.au +oz.au // CGDNs - http://www.cgdn.org.au/ act.au nsw.au @@ -283,9 +244,8 @@ tas.edu.au vic.edu.au wa.edu.au act.gov.au -// Removed at request of Shae.Donelan@services.nsw.gov.au, 2010-03-04 -// nsw.gov.au -nt.gov.au +// nsw.gov.au Bug 547985 - Removed at request of <Shae.Donelan@services.nsw.gov.au> +// nt.gov.au Bug 940478 - Removed at request of Greg Connors <Greg.Connors@nt.gov.au> qld.gov.au sa.gov.au tas.gov.au @@ -388,7 +348,7 @@ z.bg 6.bg 7.bg 8.bg -9.bg +9.bg // bh : http://en.wikipedia.org/wiki/.bh bh @@ -440,7 +400,7 @@ mil.bo tv.bo // br : http://registro.br/dominio/dpn.html -// Updated by registry <fneves@registro.br> 2011-03-01 +// Submitted by registry <fneves@registro.br> 2011-03-01 br adm.br adv.br @@ -453,13 +413,13 @@ b.br bio.br blog.br bmd.br -can.br cim.br cng.br cnt.br com.br coop.br ecn.br +eco.br edu.br emp.br eng.br @@ -480,6 +440,7 @@ ind.br inf.br jor.br jus.br +leg.br lel.br mat.br med.br @@ -729,6 +690,14 @@ inf.cu // cv : http://en.wikipedia.org/wiki/.cv cv +// cw : http://www.una.cw/cw_registry/ +// Confirmed by registry <registry@una.net> 2013-03-26 +cw +com.cw +edu.cw +net.cw +org.cw + // cx : http://en.wikipedia.org/wiki/.cx // list of other 2nd level tlds ? cx @@ -817,7 +786,7 @@ org.ee fie.ee // eg : http://en.wikipedia.org/wiki/.eg -eg +eg com.eg edu.eg eun.eg @@ -916,13 +885,12 @@ pvt.ge // gf : http://en.wikipedia.org/wiki/.gf gf -// gg : http://www.channelisles.net/applic/avextn.shtml +// gg : http://www.channelisles.net/register-domains/ +// Confirmed by registry <nigel@channelisles.net> 2013-11-28 gg co.gg -org.gg net.gg -sch.gg -gov.gg +org.gg // gh : http://en.wikipedia.org/wiki/.gh // see also: http://www.nic.gh/reg_now.php @@ -987,9 +955,15 @@ gov.gr // gs : http://en.wikipedia.org/wiki/.gs gs -// gt : http://www.gt/politicas.html -*.gt -!www.gt +// gt : http://www.gt/politicas_de_registro.html +gt +com.gt +edu.gt +gob.gt +ind.gt +mil.gt +net.gt +org.gt // gu : http://gadao.gov.gu/registration.txt *.gu @@ -1027,7 +1001,7 @@ org.hk 网絡.hk 组织.hk 組織.hk -組织.hk +組织.hk // hm : http://en.wikipedia.org/wiki/.hm hm @@ -1103,13 +1077,14 @@ tozsde.hu utazas.hu video.hu -// id : http://en.wikipedia.org/wiki/.id -// see also: https://register.pandi.or.id/ +// id : https://register.pandi.or.id/ id ac.id +biz.id co.id go.id mil.id +my.id net.id or.id sch.id @@ -1122,16 +1097,18 @@ gov.ie // il : http://en.wikipedia.org/wiki/.il *.il -// im : https://www.nic.im/pdfs/imfaqs.pdf +// im : https://www.nic.im/ +// Submitted by registry <info@nic.im> 2013-11-15 im +ac.im co.im +com.im ltd.co.im -plc.co.im net.im -gov.im org.im -nic.im -ac.im +plc.co.im +tt.im +tv.im // in : http://en.wikipedia.org/wiki/.in // see also: http://www.inregistry.in/policies/ @@ -1203,9 +1180,9 @@ int.is it gov.it edu.it -// list of reserved geo-names : +// list of reserved geo-names : // http://www.nic.it/documenti/regolamenti-e-linee-guida/regolamento-assegnazione-versione-6.0.pdf -// (There is also a list of reserved geo-names corresponding to Italian +// (There is also a list of reserved geo-names corresponding to Italian // municipalities : http://www.nic.it/documenti/appendice-c.pdf , but it is // not included here.) agrigento.it @@ -1484,13 +1461,12 @@ vi.it viterbo.it vt.it -// je : http://www.channelisles.net/applic/avextn.shtml +// je : http://www.channelisles.net/register-domains/ +// Confirmed by registry <nigel@channelisles.net> 2013-11-28 je co.je -org.je net.je -sch.je -gov.je +org.je // jm : http://www.com.jm/register.html *.jm @@ -1511,10 +1487,9 @@ jobs // jp : http://en.wikipedia.org/wiki/.jp // http://jprs.co.jp/en/jpdomain.html -// Submitted by registry <yone@jprs.co.jp> 2008-06-11 -// Updated by registry <yone@jprs.co.jp> 2008-12-04 +// Submitted by registry <info@jprs.jp> 2012-05-28 jp -// jp organizational type names +// jp organizational type names ac.jp ad.jp co.jp @@ -1524,125 +1499,1750 @@ gr.jp lg.jp ne.jp or.jp +// jp preficture type names +aichi.jp +akita.jp +aomori.jp +chiba.jp +ehime.jp +fukui.jp +fukuoka.jp +fukushima.jp +gifu.jp +gunma.jp +hiroshima.jp +hokkaido.jp +hyogo.jp +ibaraki.jp +ishikawa.jp +iwate.jp +kagawa.jp +kagoshima.jp +kanagawa.jp +kochi.jp +kumamoto.jp +kyoto.jp +mie.jp +miyagi.jp +miyazaki.jp +nagano.jp +nagasaki.jp +nara.jp +niigata.jp +oita.jp +okayama.jp +okinawa.jp +osaka.jp +saga.jp +saitama.jp +shiga.jp +shimane.jp +shizuoka.jp +tochigi.jp +tokushima.jp +tokyo.jp +tottori.jp +toyama.jp +wakayama.jp +yamagata.jp +yamaguchi.jp +yamanashi.jp // jp geographic type names // http://jprs.jp/doc/rule/saisoku-1.html -*.aichi.jp -*.akita.jp -*.aomori.jp -*.chiba.jp -*.ehime.jp -*.fukui.jp -*.fukuoka.jp -*.fukushima.jp -*.gifu.jp -*.gunma.jp -*.hiroshima.jp -*.hokkaido.jp -*.hyogo.jp -*.ibaraki.jp -*.ishikawa.jp -*.iwate.jp -*.kagawa.jp -*.kagoshima.jp -*.kanagawa.jp *.kawasaki.jp *.kitakyushu.jp *.kobe.jp -*.kochi.jp -*.kumamoto.jp -*.kyoto.jp -*.mie.jp -*.miyagi.jp -*.miyazaki.jp -*.nagano.jp -*.nagasaki.jp *.nagoya.jp -*.nara.jp -*.niigata.jp -*.oita.jp -*.okayama.jp -*.okinawa.jp -*.osaka.jp -*.saga.jp -*.saitama.jp *.sapporo.jp *.sendai.jp -*.shiga.jp -*.shimane.jp -*.shizuoka.jp -*.tochigi.jp -*.tokushima.jp -*.tokyo.jp -*.tottori.jp -*.toyama.jp -*.wakayama.jp -*.yamagata.jp -*.yamaguchi.jp -*.yamanashi.jp *.yokohama.jp -!metro.tokyo.jp -!pref.aichi.jp -!pref.akita.jp -!pref.aomori.jp -!pref.chiba.jp -!pref.ehime.jp -!pref.fukui.jp -!pref.fukuoka.jp -!pref.fukushima.jp -!pref.gifu.jp -!pref.gunma.jp -!pref.hiroshima.jp -!pref.hokkaido.jp -!pref.hyogo.jp -!pref.ibaraki.jp -!pref.ishikawa.jp -!pref.iwate.jp -!pref.kagawa.jp -!pref.kagoshima.jp -!pref.kanagawa.jp -!pref.kochi.jp -!pref.kumamoto.jp -!pref.kyoto.jp -!pref.mie.jp -!pref.miyagi.jp -!pref.miyazaki.jp -!pref.nagano.jp -!pref.nagasaki.jp -!pref.nara.jp -!pref.niigata.jp -!pref.oita.jp -!pref.okayama.jp -!pref.okinawa.jp -!pref.osaka.jp -!pref.saga.jp -!pref.saitama.jp -!pref.shiga.jp -!pref.shimane.jp -!pref.shizuoka.jp -!pref.tochigi.jp -!pref.tokushima.jp -!pref.tottori.jp -!pref.toyama.jp -!pref.wakayama.jp -!pref.yamagata.jp -!pref.yamaguchi.jp -!pref.yamanashi.jp -!city.chiba.jp -!city.fukuoka.jp -!city.hiroshima.jp !city.kawasaki.jp !city.kitakyushu.jp !city.kobe.jp -!city.kyoto.jp !city.nagoya.jp -!city.niigata.jp -!city.okayama.jp -!city.osaka.jp -!city.saitama.jp !city.sapporo.jp !city.sendai.jp -!city.shizuoka.jp !city.yokohama.jp +// 4th level registration +aisai.aichi.jp +ama.aichi.jp +anjo.aichi.jp +asuke.aichi.jp +chiryu.aichi.jp +chita.aichi.jp +fuso.aichi.jp +gamagori.aichi.jp +handa.aichi.jp +hazu.aichi.jp +hekinan.aichi.jp +higashiura.aichi.jp +ichinomiya.aichi.jp +inazawa.aichi.jp +inuyama.aichi.jp +isshiki.aichi.jp +iwakura.aichi.jp +kanie.aichi.jp +kariya.aichi.jp +kasugai.aichi.jp +kira.aichi.jp +kiyosu.aichi.jp +komaki.aichi.jp +konan.aichi.jp +kota.aichi.jp +mihama.aichi.jp +miyoshi.aichi.jp +nagakute.aichi.jp +nishio.aichi.jp +nisshin.aichi.jp +obu.aichi.jp +oguchi.aichi.jp +oharu.aichi.jp +okazaki.aichi.jp +owariasahi.aichi.jp +seto.aichi.jp +shikatsu.aichi.jp +shinshiro.aichi.jp +shitara.aichi.jp +tahara.aichi.jp +takahama.aichi.jp +tobishima.aichi.jp +toei.aichi.jp +togo.aichi.jp +tokai.aichi.jp +tokoname.aichi.jp +toyoake.aichi.jp +toyohashi.aichi.jp +toyokawa.aichi.jp +toyone.aichi.jp +toyota.aichi.jp +tsushima.aichi.jp +yatomi.aichi.jp +akita.akita.jp +daisen.akita.jp +fujisato.akita.jp +gojome.akita.jp +hachirogata.akita.jp +happou.akita.jp +higashinaruse.akita.jp +honjo.akita.jp +honjyo.akita.jp +ikawa.akita.jp +kamikoani.akita.jp +kamioka.akita.jp +katagami.akita.jp +kazuno.akita.jp +kitaakita.akita.jp +kosaka.akita.jp +kyowa.akita.jp +misato.akita.jp +mitane.akita.jp +moriyoshi.akita.jp +nikaho.akita.jp +noshiro.akita.jp +odate.akita.jp +oga.akita.jp +ogata.akita.jp +semboku.akita.jp +yokote.akita.jp +yurihonjo.akita.jp +aomori.aomori.jp +gonohe.aomori.jp +hachinohe.aomori.jp +hashikami.aomori.jp +hiranai.aomori.jp +hirosaki.aomori.jp +itayanagi.aomori.jp +kuroishi.aomori.jp +misawa.aomori.jp +mutsu.aomori.jp +nakadomari.aomori.jp +noheji.aomori.jp +oirase.aomori.jp +owani.aomori.jp +rokunohe.aomori.jp +sannohe.aomori.jp +shichinohe.aomori.jp +shingo.aomori.jp +takko.aomori.jp +towada.aomori.jp +tsugaru.aomori.jp +tsuruta.aomori.jp +abiko.chiba.jp +asahi.chiba.jp +chonan.chiba.jp +chosei.chiba.jp +choshi.chiba.jp +chuo.chiba.jp +funabashi.chiba.jp +futtsu.chiba.jp +hanamigawa.chiba.jp +ichihara.chiba.jp +ichikawa.chiba.jp +ichinomiya.chiba.jp +inzai.chiba.jp +isumi.chiba.jp +kamagaya.chiba.jp +kamogawa.chiba.jp +kashiwa.chiba.jp +katori.chiba.jp +katsuura.chiba.jp +kimitsu.chiba.jp +kisarazu.chiba.jp +kozaki.chiba.jp +kujukuri.chiba.jp +kyonan.chiba.jp +matsudo.chiba.jp +midori.chiba.jp +mihama.chiba.jp +minamiboso.chiba.jp +mobara.chiba.jp +mutsuzawa.chiba.jp +nagara.chiba.jp +nagareyama.chiba.jp +narashino.chiba.jp +narita.chiba.jp +noda.chiba.jp +oamishirasato.chiba.jp +omigawa.chiba.jp +onjuku.chiba.jp +otaki.chiba.jp +sakae.chiba.jp +sakura.chiba.jp +shimofusa.chiba.jp +shirako.chiba.jp +shiroi.chiba.jp +shisui.chiba.jp +sodegaura.chiba.jp +sosa.chiba.jp +tako.chiba.jp +tateyama.chiba.jp +togane.chiba.jp +tohnosho.chiba.jp +tomisato.chiba.jp +urayasu.chiba.jp +yachimata.chiba.jp +yachiyo.chiba.jp +yokaichiba.chiba.jp +yokoshibahikari.chiba.jp +yotsukaido.chiba.jp +ainan.ehime.jp +honai.ehime.jp +ikata.ehime.jp +imabari.ehime.jp +iyo.ehime.jp +kamijima.ehime.jp +kihoku.ehime.jp +kumakogen.ehime.jp +masaki.ehime.jp +matsuno.ehime.jp +matsuyama.ehime.jp +namikata.ehime.jp +niihama.ehime.jp +ozu.ehime.jp +saijo.ehime.jp +seiyo.ehime.jp +shikokuchuo.ehime.jp +tobe.ehime.jp +toon.ehime.jp +uchiko.ehime.jp +uwajima.ehime.jp +yawatahama.ehime.jp +echizen.fukui.jp +eiheiji.fukui.jp +fukui.fukui.jp +ikeda.fukui.jp +katsuyama.fukui.jp +mihama.fukui.jp +minamiechizen.fukui.jp +obama.fukui.jp +ohi.fukui.jp +ono.fukui.jp +sabae.fukui.jp +sakai.fukui.jp +takahama.fukui.jp +tsuruga.fukui.jp +wakasa.fukui.jp +ashiya.fukuoka.jp +buzen.fukuoka.jp +chikugo.fukuoka.jp +chikuho.fukuoka.jp +chikujo.fukuoka.jp +chikushino.fukuoka.jp +chikuzen.fukuoka.jp +chuo.fukuoka.jp +dazaifu.fukuoka.jp +fukuchi.fukuoka.jp +hakata.fukuoka.jp +higashi.fukuoka.jp +hirokawa.fukuoka.jp +hisayama.fukuoka.jp +iizuka.fukuoka.jp +inatsuki.fukuoka.jp +kaho.fukuoka.jp +kasuga.fukuoka.jp +kasuya.fukuoka.jp +kawara.fukuoka.jp +keisen.fukuoka.jp +koga.fukuoka.jp +kurate.fukuoka.jp +kurogi.fukuoka.jp +kurume.fukuoka.jp +minami.fukuoka.jp +miyako.fukuoka.jp +miyama.fukuoka.jp +miyawaka.fukuoka.jp +mizumaki.fukuoka.jp +munakata.fukuoka.jp +nakagawa.fukuoka.jp +nakama.fukuoka.jp +nishi.fukuoka.jp +nogata.fukuoka.jp +ogori.fukuoka.jp +okagaki.fukuoka.jp +okawa.fukuoka.jp +oki.fukuoka.jp +omuta.fukuoka.jp +onga.fukuoka.jp +onojo.fukuoka.jp +oto.fukuoka.jp +saigawa.fukuoka.jp +sasaguri.fukuoka.jp +shingu.fukuoka.jp +shinyoshitomi.fukuoka.jp +shonai.fukuoka.jp +soeda.fukuoka.jp +sue.fukuoka.jp +tachiarai.fukuoka.jp +tagawa.fukuoka.jp +takata.fukuoka.jp +toho.fukuoka.jp +toyotsu.fukuoka.jp +tsuiki.fukuoka.jp +ukiha.fukuoka.jp +umi.fukuoka.jp +usui.fukuoka.jp +yamada.fukuoka.jp +yame.fukuoka.jp +yanagawa.fukuoka.jp +yukuhashi.fukuoka.jp +aizubange.fukushima.jp +aizumisato.fukushima.jp +aizuwakamatsu.fukushima.jp +asakawa.fukushima.jp +bandai.fukushima.jp +date.fukushima.jp +fukushima.fukushima.jp +furudono.fukushima.jp +futaba.fukushima.jp +hanawa.fukushima.jp +higashi.fukushima.jp +hirata.fukushima.jp +hirono.fukushima.jp +iitate.fukushima.jp +inawashiro.fukushima.jp +ishikawa.fukushima.jp +iwaki.fukushima.jp +izumizaki.fukushima.jp +kagamiishi.fukushima.jp +kaneyama.fukushima.jp +kawamata.fukushima.jp +kitakata.fukushima.jp +kitashiobara.fukushima.jp +koori.fukushima.jp +koriyama.fukushima.jp +kunimi.fukushima.jp +miharu.fukushima.jp +mishima.fukushima.jp +namie.fukushima.jp +nango.fukushima.jp +nishiaizu.fukushima.jp +nishigo.fukushima.jp +okuma.fukushima.jp +omotego.fukushima.jp +ono.fukushima.jp +otama.fukushima.jp +samegawa.fukushima.jp +shimogo.fukushima.jp +shirakawa.fukushima.jp +showa.fukushima.jp +soma.fukushima.jp +sukagawa.fukushima.jp +taishin.fukushima.jp +tamakawa.fukushima.jp +tanagura.fukushima.jp +tenei.fukushima.jp +yabuki.fukushima.jp +yamato.fukushima.jp +yamatsuri.fukushima.jp +yanaizu.fukushima.jp +yugawa.fukushima.jp +anpachi.gifu.jp +ena.gifu.jp +gifu.gifu.jp +ginan.gifu.jp +godo.gifu.jp +gujo.gifu.jp +hashima.gifu.jp +hichiso.gifu.jp +hida.gifu.jp +higashishirakawa.gifu.jp +ibigawa.gifu.jp +ikeda.gifu.jp +kakamigahara.gifu.jp +kani.gifu.jp +kasahara.gifu.jp +kasamatsu.gifu.jp +kawaue.gifu.jp +kitagata.gifu.jp +mino.gifu.jp +minokamo.gifu.jp +mitake.gifu.jp +mizunami.gifu.jp +motosu.gifu.jp +nakatsugawa.gifu.jp +ogaki.gifu.jp +sakahogi.gifu.jp +seki.gifu.jp +sekigahara.gifu.jp +shirakawa.gifu.jp +tajimi.gifu.jp +takayama.gifu.jp +tarui.gifu.jp +toki.gifu.jp +tomika.gifu.jp +wanouchi.gifu.jp +yamagata.gifu.jp +yaotsu.gifu.jp +yoro.gifu.jp +annaka.gunma.jp +chiyoda.gunma.jp +fujioka.gunma.jp +higashiagatsuma.gunma.jp +isesaki.gunma.jp +itakura.gunma.jp +kanna.gunma.jp +kanra.gunma.jp +katashina.gunma.jp +kawaba.gunma.jp +kiryu.gunma.jp +kusatsu.gunma.jp +maebashi.gunma.jp +meiwa.gunma.jp +midori.gunma.jp +minakami.gunma.jp +naganohara.gunma.jp +nakanojo.gunma.jp +nanmoku.gunma.jp +numata.gunma.jp +oizumi.gunma.jp +ora.gunma.jp +ota.gunma.jp +shibukawa.gunma.jp +shimonita.gunma.jp +shinto.gunma.jp +showa.gunma.jp +takasaki.gunma.jp +takayama.gunma.jp +tamamura.gunma.jp +tatebayashi.gunma.jp +tomioka.gunma.jp +tsukiyono.gunma.jp +tsumagoi.gunma.jp +ueno.gunma.jp +yoshioka.gunma.jp +asaminami.hiroshima.jp +daiwa.hiroshima.jp +etajima.hiroshima.jp +fuchu.hiroshima.jp +fukuyama.hiroshima.jp +hatsukaichi.hiroshima.jp +higashihiroshima.hiroshima.jp +hongo.hiroshima.jp +jinsekikogen.hiroshima.jp +kaita.hiroshima.jp +kui.hiroshima.jp +kumano.hiroshima.jp +kure.hiroshima.jp +mihara.hiroshima.jp +miyoshi.hiroshima.jp +naka.hiroshima.jp +onomichi.hiroshima.jp +osakikamijima.hiroshima.jp +otake.hiroshima.jp +saka.hiroshima.jp +sera.hiroshima.jp +seranishi.hiroshima.jp +shinichi.hiroshima.jp +shobara.hiroshima.jp +takehara.hiroshima.jp +abashiri.hokkaido.jp +abira.hokkaido.jp +aibetsu.hokkaido.jp +akabira.hokkaido.jp +akkeshi.hokkaido.jp +asahikawa.hokkaido.jp +ashibetsu.hokkaido.jp +ashoro.hokkaido.jp +assabu.hokkaido.jp +atsuma.hokkaido.jp +bibai.hokkaido.jp +biei.hokkaido.jp +bifuka.hokkaido.jp +bihoro.hokkaido.jp +biratori.hokkaido.jp +chippubetsu.hokkaido.jp +chitose.hokkaido.jp +date.hokkaido.jp +ebetsu.hokkaido.jp +embetsu.hokkaido.jp +eniwa.hokkaido.jp +erimo.hokkaido.jp +esan.hokkaido.jp +esashi.hokkaido.jp +fukagawa.hokkaido.jp +fukushima.hokkaido.jp +furano.hokkaido.jp +furubira.hokkaido.jp +haboro.hokkaido.jp +hakodate.hokkaido.jp +hamatonbetsu.hokkaido.jp +hidaka.hokkaido.jp +higashikagura.hokkaido.jp +higashikawa.hokkaido.jp +hiroo.hokkaido.jp +hokuryu.hokkaido.jp +hokuto.hokkaido.jp +honbetsu.hokkaido.jp +horokanai.hokkaido.jp +horonobe.hokkaido.jp +ikeda.hokkaido.jp +imakane.hokkaido.jp +ishikari.hokkaido.jp +iwamizawa.hokkaido.jp +iwanai.hokkaido.jp +kamifurano.hokkaido.jp +kamikawa.hokkaido.jp +kamishihoro.hokkaido.jp +kamisunagawa.hokkaido.jp +kamoenai.hokkaido.jp +kayabe.hokkaido.jp +kembuchi.hokkaido.jp +kikonai.hokkaido.jp +kimobetsu.hokkaido.jp +kitahiroshima.hokkaido.jp +kitami.hokkaido.jp +kiyosato.hokkaido.jp +koshimizu.hokkaido.jp +kunneppu.hokkaido.jp +kuriyama.hokkaido.jp +kuromatsunai.hokkaido.jp +kushiro.hokkaido.jp +kutchan.hokkaido.jp +kyowa.hokkaido.jp +mashike.hokkaido.jp +matsumae.hokkaido.jp +mikasa.hokkaido.jp +minamifurano.hokkaido.jp +mombetsu.hokkaido.jp +moseushi.hokkaido.jp +mukawa.hokkaido.jp +muroran.hokkaido.jp +naie.hokkaido.jp +nakagawa.hokkaido.jp +nakasatsunai.hokkaido.jp +nakatombetsu.hokkaido.jp +nanae.hokkaido.jp +nanporo.hokkaido.jp +nayoro.hokkaido.jp +nemuro.hokkaido.jp +niikappu.hokkaido.jp +niki.hokkaido.jp +nishiokoppe.hokkaido.jp +noboribetsu.hokkaido.jp +numata.hokkaido.jp +obihiro.hokkaido.jp +obira.hokkaido.jp +oketo.hokkaido.jp +okoppe.hokkaido.jp +otaru.hokkaido.jp +otobe.hokkaido.jp +otofuke.hokkaido.jp +otoineppu.hokkaido.jp +oumu.hokkaido.jp +ozora.hokkaido.jp +pippu.hokkaido.jp +rankoshi.hokkaido.jp +rebun.hokkaido.jp +rikubetsu.hokkaido.jp +rishiri.hokkaido.jp +rishirifuji.hokkaido.jp +saroma.hokkaido.jp +sarufutsu.hokkaido.jp +shakotan.hokkaido.jp +shari.hokkaido.jp +shibecha.hokkaido.jp +shibetsu.hokkaido.jp +shikabe.hokkaido.jp +shikaoi.hokkaido.jp +shimamaki.hokkaido.jp +shimizu.hokkaido.jp +shimokawa.hokkaido.jp +shinshinotsu.hokkaido.jp +shintoku.hokkaido.jp +shiranuka.hokkaido.jp +shiraoi.hokkaido.jp +shiriuchi.hokkaido.jp +sobetsu.hokkaido.jp +sunagawa.hokkaido.jp +taiki.hokkaido.jp +takasu.hokkaido.jp +takikawa.hokkaido.jp +takinoue.hokkaido.jp +teshikaga.hokkaido.jp +tobetsu.hokkaido.jp +tohma.hokkaido.jp +tomakomai.hokkaido.jp +tomari.hokkaido.jp +toya.hokkaido.jp +toyako.hokkaido.jp +toyotomi.hokkaido.jp +toyoura.hokkaido.jp +tsubetsu.hokkaido.jp +tsukigata.hokkaido.jp +urakawa.hokkaido.jp +urausu.hokkaido.jp +uryu.hokkaido.jp +utashinai.hokkaido.jp +wakkanai.hokkaido.jp +wassamu.hokkaido.jp +yakumo.hokkaido.jp +yoichi.hokkaido.jp +aioi.hyogo.jp +akashi.hyogo.jp +ako.hyogo.jp +amagasaki.hyogo.jp +aogaki.hyogo.jp +asago.hyogo.jp +ashiya.hyogo.jp +awaji.hyogo.jp +fukusaki.hyogo.jp +goshiki.hyogo.jp +harima.hyogo.jp +himeji.hyogo.jp +ichikawa.hyogo.jp +inagawa.hyogo.jp +itami.hyogo.jp +kakogawa.hyogo.jp +kamigori.hyogo.jp +kamikawa.hyogo.jp +kasai.hyogo.jp +kasuga.hyogo.jp +kawanishi.hyogo.jp +miki.hyogo.jp +minamiawaji.hyogo.jp +nishinomiya.hyogo.jp +nishiwaki.hyogo.jp +ono.hyogo.jp +sanda.hyogo.jp +sannan.hyogo.jp +sasayama.hyogo.jp +sayo.hyogo.jp +shingu.hyogo.jp +shinonsen.hyogo.jp +shiso.hyogo.jp +sumoto.hyogo.jp +taishi.hyogo.jp +taka.hyogo.jp +takarazuka.hyogo.jp +takasago.hyogo.jp +takino.hyogo.jp +tamba.hyogo.jp +tatsuno.hyogo.jp +toyooka.hyogo.jp +yabu.hyogo.jp +yashiro.hyogo.jp +yoka.hyogo.jp +yokawa.hyogo.jp +ami.ibaraki.jp +asahi.ibaraki.jp +bando.ibaraki.jp +chikusei.ibaraki.jp +daigo.ibaraki.jp +fujishiro.ibaraki.jp +hitachi.ibaraki.jp +hitachinaka.ibaraki.jp +hitachiomiya.ibaraki.jp +hitachiota.ibaraki.jp +ibaraki.ibaraki.jp +ina.ibaraki.jp +inashiki.ibaraki.jp +itako.ibaraki.jp +iwama.ibaraki.jp +joso.ibaraki.jp +kamisu.ibaraki.jp +kasama.ibaraki.jp +kashima.ibaraki.jp +kasumigaura.ibaraki.jp +koga.ibaraki.jp +miho.ibaraki.jp +mito.ibaraki.jp +moriya.ibaraki.jp +naka.ibaraki.jp +namegata.ibaraki.jp +oarai.ibaraki.jp +ogawa.ibaraki.jp +omitama.ibaraki.jp +ryugasaki.ibaraki.jp +sakai.ibaraki.jp +sakuragawa.ibaraki.jp +shimodate.ibaraki.jp +shimotsuma.ibaraki.jp +shirosato.ibaraki.jp +sowa.ibaraki.jp +suifu.ibaraki.jp +takahagi.ibaraki.jp +tamatsukuri.ibaraki.jp +tokai.ibaraki.jp +tomobe.ibaraki.jp +tone.ibaraki.jp +toride.ibaraki.jp +tsuchiura.ibaraki.jp +tsukuba.ibaraki.jp +uchihara.ibaraki.jp +ushiku.ibaraki.jp +yachiyo.ibaraki.jp +yamagata.ibaraki.jp +yawara.ibaraki.jp +yuki.ibaraki.jp +anamizu.ishikawa.jp +hakui.ishikawa.jp +hakusan.ishikawa.jp +kaga.ishikawa.jp +kahoku.ishikawa.jp +kanazawa.ishikawa.jp +kawakita.ishikawa.jp +komatsu.ishikawa.jp +nakanoto.ishikawa.jp +nanao.ishikawa.jp +nomi.ishikawa.jp +nonoichi.ishikawa.jp +noto.ishikawa.jp +shika.ishikawa.jp +suzu.ishikawa.jp +tsubata.ishikawa.jp +tsurugi.ishikawa.jp +uchinada.ishikawa.jp +wajima.ishikawa.jp +fudai.iwate.jp +fujisawa.iwate.jp +hanamaki.iwate.jp +hiraizumi.iwate.jp +hirono.iwate.jp +ichinohe.iwate.jp +ichinoseki.iwate.jp +iwaizumi.iwate.jp +iwate.iwate.jp +joboji.iwate.jp +kamaishi.iwate.jp +kanegasaki.iwate.jp +karumai.iwate.jp +kawai.iwate.jp +kitakami.iwate.jp +kuji.iwate.jp +kunohe.iwate.jp +kuzumaki.iwate.jp +miyako.iwate.jp +mizusawa.iwate.jp +morioka.iwate.jp +ninohe.iwate.jp +noda.iwate.jp +ofunato.iwate.jp +oshu.iwate.jp +otsuchi.iwate.jp +rikuzentakata.iwate.jp +shiwa.iwate.jp +shizukuishi.iwate.jp +sumita.iwate.jp +takizawa.iwate.jp +tanohata.iwate.jp +tono.iwate.jp +yahaba.iwate.jp +yamada.iwate.jp +ayagawa.kagawa.jp +higashikagawa.kagawa.jp +kanonji.kagawa.jp +kotohira.kagawa.jp +manno.kagawa.jp +marugame.kagawa.jp +mitoyo.kagawa.jp +naoshima.kagawa.jp +sanuki.kagawa.jp +tadotsu.kagawa.jp +takamatsu.kagawa.jp +tonosho.kagawa.jp +uchinomi.kagawa.jp +utazu.kagawa.jp +zentsuji.kagawa.jp +akune.kagoshima.jp +amami.kagoshima.jp +hioki.kagoshima.jp +isa.kagoshima.jp +isen.kagoshima.jp +izumi.kagoshima.jp +kagoshima.kagoshima.jp +kanoya.kagoshima.jp +kawanabe.kagoshima.jp +kinko.kagoshima.jp +kouyama.kagoshima.jp +makurazaki.kagoshima.jp +matsumoto.kagoshima.jp +minamitane.kagoshima.jp +nakatane.kagoshima.jp +nishinoomote.kagoshima.jp +satsumasendai.kagoshima.jp +soo.kagoshima.jp +tarumizu.kagoshima.jp +yusui.kagoshima.jp +aikawa.kanagawa.jp +atsugi.kanagawa.jp +ayase.kanagawa.jp +chigasaki.kanagawa.jp +ebina.kanagawa.jp +fujisawa.kanagawa.jp +hadano.kanagawa.jp +hakone.kanagawa.jp +hiratsuka.kanagawa.jp +isehara.kanagawa.jp +kaisei.kanagawa.jp +kamakura.kanagawa.jp +kiyokawa.kanagawa.jp +matsuda.kanagawa.jp +minamiashigara.kanagawa.jp +miura.kanagawa.jp +nakai.kanagawa.jp +ninomiya.kanagawa.jp +odawara.kanagawa.jp +oi.kanagawa.jp +oiso.kanagawa.jp +sagamihara.kanagawa.jp +samukawa.kanagawa.jp +tsukui.kanagawa.jp +yamakita.kanagawa.jp +yamato.kanagawa.jp +yokosuka.kanagawa.jp +yugawara.kanagawa.jp +zama.kanagawa.jp +zushi.kanagawa.jp +aki.kochi.jp +geisei.kochi.jp +hidaka.kochi.jp +higashitsuno.kochi.jp +ino.kochi.jp +kagami.kochi.jp +kami.kochi.jp +kitagawa.kochi.jp +kochi.kochi.jp +mihara.kochi.jp +motoyama.kochi.jp +muroto.kochi.jp +nahari.kochi.jp +nakamura.kochi.jp +nankoku.kochi.jp +nishitosa.kochi.jp +niyodogawa.kochi.jp +ochi.kochi.jp +okawa.kochi.jp +otoyo.kochi.jp +otsuki.kochi.jp +sakawa.kochi.jp +sukumo.kochi.jp +susaki.kochi.jp +tosa.kochi.jp +tosashimizu.kochi.jp +toyo.kochi.jp +tsuno.kochi.jp +umaji.kochi.jp +yasuda.kochi.jp +yusuhara.kochi.jp +amakusa.kumamoto.jp +arao.kumamoto.jp +aso.kumamoto.jp +choyo.kumamoto.jp +gyokuto.kumamoto.jp +hitoyoshi.kumamoto.jp +kamiamakusa.kumamoto.jp +kashima.kumamoto.jp +kikuchi.kumamoto.jp +kosa.kumamoto.jp +kumamoto.kumamoto.jp +mashiki.kumamoto.jp +mifune.kumamoto.jp +minamata.kumamoto.jp +minamioguni.kumamoto.jp +nagasu.kumamoto.jp +nishihara.kumamoto.jp +oguni.kumamoto.jp +ozu.kumamoto.jp +sumoto.kumamoto.jp +takamori.kumamoto.jp +uki.kumamoto.jp +uto.kumamoto.jp +yamaga.kumamoto.jp +yamato.kumamoto.jp +yatsushiro.kumamoto.jp +ayabe.kyoto.jp +fukuchiyama.kyoto.jp +higashiyama.kyoto.jp +ide.kyoto.jp +ine.kyoto.jp +joyo.kyoto.jp +kameoka.kyoto.jp +kamo.kyoto.jp +kita.kyoto.jp +kizu.kyoto.jp +kumiyama.kyoto.jp +kyotamba.kyoto.jp +kyotanabe.kyoto.jp +kyotango.kyoto.jp +maizuru.kyoto.jp +minami.kyoto.jp +minamiyamashiro.kyoto.jp +miyazu.kyoto.jp +muko.kyoto.jp +nagaokakyo.kyoto.jp +nakagyo.kyoto.jp +nantan.kyoto.jp +oyamazaki.kyoto.jp +sakyo.kyoto.jp +seika.kyoto.jp +tanabe.kyoto.jp +uji.kyoto.jp +ujitawara.kyoto.jp +wazuka.kyoto.jp +yamashina.kyoto.jp +yawata.kyoto.jp +asahi.mie.jp +inabe.mie.jp +ise.mie.jp +kameyama.mie.jp +kawagoe.mie.jp +kiho.mie.jp +kisosaki.mie.jp +kiwa.mie.jp +komono.mie.jp +kumano.mie.jp +kuwana.mie.jp +matsusaka.mie.jp +meiwa.mie.jp +mihama.mie.jp +minamiise.mie.jp +misugi.mie.jp +miyama.mie.jp +nabari.mie.jp +shima.mie.jp +suzuka.mie.jp +tado.mie.jp +taiki.mie.jp +taki.mie.jp +tamaki.mie.jp +toba.mie.jp +tsu.mie.jp +udono.mie.jp +ureshino.mie.jp +watarai.mie.jp +yokkaichi.mie.jp +furukawa.miyagi.jp +higashimatsushima.miyagi.jp +ishinomaki.miyagi.jp +iwanuma.miyagi.jp +kakuda.miyagi.jp +kami.miyagi.jp +kawasaki.miyagi.jp +kesennuma.miyagi.jp +marumori.miyagi.jp +matsushima.miyagi.jp +minamisanriku.miyagi.jp +misato.miyagi.jp +murata.miyagi.jp +natori.miyagi.jp +ogawara.miyagi.jp +ohira.miyagi.jp +onagawa.miyagi.jp +osaki.miyagi.jp +rifu.miyagi.jp +semine.miyagi.jp +shibata.miyagi.jp +shichikashuku.miyagi.jp +shikama.miyagi.jp +shiogama.miyagi.jp +shiroishi.miyagi.jp +tagajo.miyagi.jp +taiwa.miyagi.jp +tome.miyagi.jp +tomiya.miyagi.jp +wakuya.miyagi.jp +watari.miyagi.jp +yamamoto.miyagi.jp +zao.miyagi.jp +aya.miyazaki.jp +ebino.miyazaki.jp +gokase.miyazaki.jp +hyuga.miyazaki.jp +kadogawa.miyazaki.jp +kawaminami.miyazaki.jp +kijo.miyazaki.jp +kitagawa.miyazaki.jp +kitakata.miyazaki.jp +kitaura.miyazaki.jp +kobayashi.miyazaki.jp +kunitomi.miyazaki.jp +kushima.miyazaki.jp +mimata.miyazaki.jp +miyakonojo.miyazaki.jp +miyazaki.miyazaki.jp +morotsuka.miyazaki.jp +nichinan.miyazaki.jp +nishimera.miyazaki.jp +nobeoka.miyazaki.jp +saito.miyazaki.jp +shiiba.miyazaki.jp +shintomi.miyazaki.jp +takaharu.miyazaki.jp +takanabe.miyazaki.jp +takazaki.miyazaki.jp +tsuno.miyazaki.jp +achi.nagano.jp +agematsu.nagano.jp +anan.nagano.jp +aoki.nagano.jp +asahi.nagano.jp +azumino.nagano.jp +chikuhoku.nagano.jp +chikuma.nagano.jp +chino.nagano.jp +fujimi.nagano.jp +hakuba.nagano.jp +hara.nagano.jp +hiraya.nagano.jp +iida.nagano.jp +iijima.nagano.jp +iiyama.nagano.jp +iizuna.nagano.jp +ikeda.nagano.jp +ikusaka.nagano.jp +ina.nagano.jp +karuizawa.nagano.jp +kawakami.nagano.jp +kiso.nagano.jp +kisofukushima.nagano.jp +kitaaiki.nagano.jp +komagane.nagano.jp +komoro.nagano.jp +matsukawa.nagano.jp +matsumoto.nagano.jp +miasa.nagano.jp +minamiaiki.nagano.jp +minamimaki.nagano.jp +minamiminowa.nagano.jp +minowa.nagano.jp +miyada.nagano.jp +miyota.nagano.jp +mochizuki.nagano.jp +nagano.nagano.jp +nagawa.nagano.jp +nagiso.nagano.jp +nakagawa.nagano.jp +nakano.nagano.jp +nozawaonsen.nagano.jp +obuse.nagano.jp +ogawa.nagano.jp +okaya.nagano.jp +omachi.nagano.jp +omi.nagano.jp +ookuwa.nagano.jp +ooshika.nagano.jp +otaki.nagano.jp +otari.nagano.jp +sakae.nagano.jp +sakaki.nagano.jp +saku.nagano.jp +sakuho.nagano.jp +shimosuwa.nagano.jp +shinanomachi.nagano.jp +shiojiri.nagano.jp +suwa.nagano.jp +suzaka.nagano.jp +takagi.nagano.jp +takamori.nagano.jp +takayama.nagano.jp +tateshina.nagano.jp +tatsuno.nagano.jp +togakushi.nagano.jp +togura.nagano.jp +tomi.nagano.jp +ueda.nagano.jp +wada.nagano.jp +yamagata.nagano.jp +yamanouchi.nagano.jp +yasaka.nagano.jp +yasuoka.nagano.jp +chijiwa.nagasaki.jp +futsu.nagasaki.jp +goto.nagasaki.jp +hasami.nagasaki.jp +hirado.nagasaki.jp +iki.nagasaki.jp +isahaya.nagasaki.jp +kawatana.nagasaki.jp +kuchinotsu.nagasaki.jp +matsuura.nagasaki.jp +nagasaki.nagasaki.jp +obama.nagasaki.jp +omura.nagasaki.jp +oseto.nagasaki.jp +saikai.nagasaki.jp +sasebo.nagasaki.jp +seihi.nagasaki.jp +shimabara.nagasaki.jp +shinkamigoto.nagasaki.jp +togitsu.nagasaki.jp +tsushima.nagasaki.jp +unzen.nagasaki.jp +ando.nara.jp +gose.nara.jp +heguri.nara.jp +higashiyoshino.nara.jp +ikaruga.nara.jp +ikoma.nara.jp +kamikitayama.nara.jp +kanmaki.nara.jp +kashiba.nara.jp +kashihara.nara.jp +katsuragi.nara.jp +kawai.nara.jp +kawakami.nara.jp +kawanishi.nara.jp +koryo.nara.jp +kurotaki.nara.jp +mitsue.nara.jp +miyake.nara.jp +nara.nara.jp +nosegawa.nara.jp +oji.nara.jp +ouda.nara.jp +oyodo.nara.jp +sakurai.nara.jp +sango.nara.jp +shimoichi.nara.jp +shimokitayama.nara.jp +shinjo.nara.jp +soni.nara.jp +takatori.nara.jp +tawaramoto.nara.jp +tenkawa.nara.jp +tenri.nara.jp +uda.nara.jp +yamatokoriyama.nara.jp +yamatotakada.nara.jp +yamazoe.nara.jp +yoshino.nara.jp +aga.niigata.jp +agano.niigata.jp +gosen.niigata.jp +itoigawa.niigata.jp +izumozaki.niigata.jp +joetsu.niigata.jp +kamo.niigata.jp +kariwa.niigata.jp +kashiwazaki.niigata.jp +minamiuonuma.niigata.jp +mitsuke.niigata.jp +muika.niigata.jp +murakami.niigata.jp +myoko.niigata.jp +nagaoka.niigata.jp +niigata.niigata.jp +ojiya.niigata.jp +omi.niigata.jp +sado.niigata.jp +sanjo.niigata.jp +seiro.niigata.jp +seirou.niigata.jp +sekikawa.niigata.jp +shibata.niigata.jp +tagami.niigata.jp +tainai.niigata.jp +tochio.niigata.jp +tokamachi.niigata.jp +tsubame.niigata.jp +tsunan.niigata.jp +uonuma.niigata.jp +yahiko.niigata.jp +yoita.niigata.jp +yuzawa.niigata.jp +beppu.oita.jp +bungoono.oita.jp +bungotakada.oita.jp +hasama.oita.jp +hiji.oita.jp +himeshima.oita.jp +hita.oita.jp +kamitsue.oita.jp +kokonoe.oita.jp +kuju.oita.jp +kunisaki.oita.jp +kusu.oita.jp +oita.oita.jp +saiki.oita.jp +taketa.oita.jp +tsukumi.oita.jp +usa.oita.jp +usuki.oita.jp +yufu.oita.jp +akaiwa.okayama.jp +asakuchi.okayama.jp +bizen.okayama.jp +hayashima.okayama.jp +ibara.okayama.jp +kagamino.okayama.jp +kasaoka.okayama.jp +kibichuo.okayama.jp +kumenan.okayama.jp +kurashiki.okayama.jp +maniwa.okayama.jp +misaki.okayama.jp +nagi.okayama.jp +niimi.okayama.jp +nishiawakura.okayama.jp +okayama.okayama.jp +satosho.okayama.jp +setouchi.okayama.jp +shinjo.okayama.jp +shoo.okayama.jp +soja.okayama.jp +takahashi.okayama.jp +tamano.okayama.jp +tsuyama.okayama.jp +wake.okayama.jp +yakage.okayama.jp +aguni.okinawa.jp +ginowan.okinawa.jp +ginoza.okinawa.jp +gushikami.okinawa.jp +haebaru.okinawa.jp +higashi.okinawa.jp +hirara.okinawa.jp +iheya.okinawa.jp +ishigaki.okinawa.jp +ishikawa.okinawa.jp +itoman.okinawa.jp +izena.okinawa.jp +kadena.okinawa.jp +kin.okinawa.jp +kitadaito.okinawa.jp +kitanakagusuku.okinawa.jp +kumejima.okinawa.jp +kunigami.okinawa.jp +minamidaito.okinawa.jp +motobu.okinawa.jp +nago.okinawa.jp +naha.okinawa.jp +nakagusuku.okinawa.jp +nakijin.okinawa.jp +nanjo.okinawa.jp +nishihara.okinawa.jp +ogimi.okinawa.jp +okinawa.okinawa.jp +onna.okinawa.jp +shimoji.okinawa.jp +taketomi.okinawa.jp +tarama.okinawa.jp +tokashiki.okinawa.jp +tomigusuku.okinawa.jp +tonaki.okinawa.jp +urasoe.okinawa.jp +uruma.okinawa.jp +yaese.okinawa.jp +yomitan.okinawa.jp +yonabaru.okinawa.jp +yonaguni.okinawa.jp +zamami.okinawa.jp +abeno.osaka.jp +chihayaakasaka.osaka.jp +chuo.osaka.jp +daito.osaka.jp +fujiidera.osaka.jp +habikino.osaka.jp +hannan.osaka.jp +higashiosaka.osaka.jp +higashisumiyoshi.osaka.jp +higashiyodogawa.osaka.jp +hirakata.osaka.jp +ibaraki.osaka.jp +ikeda.osaka.jp +izumi.osaka.jp +izumiotsu.osaka.jp +izumisano.osaka.jp +kadoma.osaka.jp +kaizuka.osaka.jp +kanan.osaka.jp +kashiwara.osaka.jp +katano.osaka.jp +kawachinagano.osaka.jp +kishiwada.osaka.jp +kita.osaka.jp +kumatori.osaka.jp +matsubara.osaka.jp +minato.osaka.jp +minoh.osaka.jp +misaki.osaka.jp +moriguchi.osaka.jp +neyagawa.osaka.jp +nishi.osaka.jp +nose.osaka.jp +osakasayama.osaka.jp +sakai.osaka.jp +sayama.osaka.jp +sennan.osaka.jp +settsu.osaka.jp +shijonawate.osaka.jp +shimamoto.osaka.jp +suita.osaka.jp +tadaoka.osaka.jp +taishi.osaka.jp +tajiri.osaka.jp +takaishi.osaka.jp +takatsuki.osaka.jp +tondabayashi.osaka.jp +toyonaka.osaka.jp +toyono.osaka.jp +yao.osaka.jp +ariake.saga.jp +arita.saga.jp +fukudomi.saga.jp +genkai.saga.jp +hamatama.saga.jp +hizen.saga.jp +imari.saga.jp +kamimine.saga.jp +kanzaki.saga.jp +karatsu.saga.jp +kashima.saga.jp +kitagata.saga.jp +kitahata.saga.jp +kiyama.saga.jp +kouhoku.saga.jp +kyuragi.saga.jp +nishiarita.saga.jp +ogi.saga.jp +omachi.saga.jp +ouchi.saga.jp +saga.saga.jp +shiroishi.saga.jp +taku.saga.jp +tara.saga.jp +tosu.saga.jp +yoshinogari.saga.jp +arakawa.saitama.jp +asaka.saitama.jp +chichibu.saitama.jp +fujimi.saitama.jp +fujimino.saitama.jp +fukaya.saitama.jp +hanno.saitama.jp +hanyu.saitama.jp +hasuda.saitama.jp +hatogaya.saitama.jp +hatoyama.saitama.jp +hidaka.saitama.jp +higashichichibu.saitama.jp +higashimatsuyama.saitama.jp +honjo.saitama.jp +ina.saitama.jp +iruma.saitama.jp +iwatsuki.saitama.jp +kamiizumi.saitama.jp +kamikawa.saitama.jp +kamisato.saitama.jp +kasukabe.saitama.jp +kawagoe.saitama.jp +kawaguchi.saitama.jp +kawajima.saitama.jp +kazo.saitama.jp +kitamoto.saitama.jp +koshigaya.saitama.jp +kounosu.saitama.jp +kuki.saitama.jp +kumagaya.saitama.jp +matsubushi.saitama.jp +minano.saitama.jp +misato.saitama.jp +miyashiro.saitama.jp +miyoshi.saitama.jp +moroyama.saitama.jp +nagatoro.saitama.jp +namegawa.saitama.jp +niiza.saitama.jp +ogano.saitama.jp +ogawa.saitama.jp +ogose.saitama.jp +okegawa.saitama.jp +omiya.saitama.jp +otaki.saitama.jp +ranzan.saitama.jp +ryokami.saitama.jp +saitama.saitama.jp +sakado.saitama.jp +satte.saitama.jp +sayama.saitama.jp +shiki.saitama.jp +shiraoka.saitama.jp +soka.saitama.jp +sugito.saitama.jp +toda.saitama.jp +tokigawa.saitama.jp +tokorozawa.saitama.jp +tsurugashima.saitama.jp +urawa.saitama.jp +warabi.saitama.jp +yashio.saitama.jp +yokoze.saitama.jp +yono.saitama.jp +yorii.saitama.jp +yoshida.saitama.jp +yoshikawa.saitama.jp +yoshimi.saitama.jp +aisho.shiga.jp +gamo.shiga.jp +higashiomi.shiga.jp +hikone.shiga.jp +koka.shiga.jp +konan.shiga.jp +kosei.shiga.jp +koto.shiga.jp +kusatsu.shiga.jp +maibara.shiga.jp +moriyama.shiga.jp +nagahama.shiga.jp +nishiazai.shiga.jp +notogawa.shiga.jp +omihachiman.shiga.jp +otsu.shiga.jp +ritto.shiga.jp +ryuoh.shiga.jp +takashima.shiga.jp +takatsuki.shiga.jp +torahime.shiga.jp +toyosato.shiga.jp +yasu.shiga.jp +akagi.shimane.jp +ama.shimane.jp +gotsu.shimane.jp +hamada.shimane.jp +higashiizumo.shimane.jp +hikawa.shimane.jp +hikimi.shimane.jp +izumo.shimane.jp +kakinoki.shimane.jp +masuda.shimane.jp +matsue.shimane.jp +misato.shimane.jp +nishinoshima.shimane.jp +ohda.shimane.jp +okinoshima.shimane.jp +okuizumo.shimane.jp +shimane.shimane.jp +tamayu.shimane.jp +tsuwano.shimane.jp +unnan.shimane.jp +yakumo.shimane.jp +yasugi.shimane.jp +yatsuka.shimane.jp +arai.shizuoka.jp +atami.shizuoka.jp +fuji.shizuoka.jp +fujieda.shizuoka.jp +fujikawa.shizuoka.jp +fujinomiya.shizuoka.jp +fukuroi.shizuoka.jp +gotemba.shizuoka.jp +haibara.shizuoka.jp +hamamatsu.shizuoka.jp +higashiizu.shizuoka.jp +ito.shizuoka.jp +iwata.shizuoka.jp +izu.shizuoka.jp +izunokuni.shizuoka.jp +kakegawa.shizuoka.jp +kannami.shizuoka.jp +kawanehon.shizuoka.jp +kawazu.shizuoka.jp +kikugawa.shizuoka.jp +kosai.shizuoka.jp +makinohara.shizuoka.jp +matsuzaki.shizuoka.jp +minamiizu.shizuoka.jp +mishima.shizuoka.jp +morimachi.shizuoka.jp +nishiizu.shizuoka.jp +numazu.shizuoka.jp +omaezaki.shizuoka.jp +shimada.shizuoka.jp +shimizu.shizuoka.jp +shimoda.shizuoka.jp +shizuoka.shizuoka.jp +susono.shizuoka.jp +yaizu.shizuoka.jp +yoshida.shizuoka.jp +ashikaga.tochigi.jp +bato.tochigi.jp +haga.tochigi.jp +ichikai.tochigi.jp +iwafune.tochigi.jp +kaminokawa.tochigi.jp +kanuma.tochigi.jp +karasuyama.tochigi.jp +kuroiso.tochigi.jp +mashiko.tochigi.jp +mibu.tochigi.jp +moka.tochigi.jp +motegi.tochigi.jp +nasu.tochigi.jp +nasushiobara.tochigi.jp +nikko.tochigi.jp +nishikata.tochigi.jp +nogi.tochigi.jp +ohira.tochigi.jp +ohtawara.tochigi.jp +oyama.tochigi.jp +sakura.tochigi.jp +sano.tochigi.jp +shimotsuke.tochigi.jp +shioya.tochigi.jp +takanezawa.tochigi.jp +tochigi.tochigi.jp +tsuga.tochigi.jp +ujiie.tochigi.jp +utsunomiya.tochigi.jp +yaita.tochigi.jp +aizumi.tokushima.jp +anan.tokushima.jp +ichiba.tokushima.jp +itano.tokushima.jp +kainan.tokushima.jp +komatsushima.tokushima.jp +matsushige.tokushima.jp +mima.tokushima.jp +minami.tokushima.jp +miyoshi.tokushima.jp +mugi.tokushima.jp +nakagawa.tokushima.jp +naruto.tokushima.jp +sanagochi.tokushima.jp +shishikui.tokushima.jp +tokushima.tokushima.jp +wajiki.tokushima.jp +adachi.tokyo.jp +akiruno.tokyo.jp +akishima.tokyo.jp +aogashima.tokyo.jp +arakawa.tokyo.jp +bunkyo.tokyo.jp +chiyoda.tokyo.jp +chofu.tokyo.jp +chuo.tokyo.jp +edogawa.tokyo.jp +fuchu.tokyo.jp +fussa.tokyo.jp +hachijo.tokyo.jp +hachioji.tokyo.jp +hamura.tokyo.jp +higashikurume.tokyo.jp +higashimurayama.tokyo.jp +higashiyamato.tokyo.jp +hino.tokyo.jp +hinode.tokyo.jp +hinohara.tokyo.jp +inagi.tokyo.jp +itabashi.tokyo.jp +katsushika.tokyo.jp +kita.tokyo.jp +kiyose.tokyo.jp +kodaira.tokyo.jp +koganei.tokyo.jp +kokubunji.tokyo.jp +komae.tokyo.jp +koto.tokyo.jp +kouzushima.tokyo.jp +kunitachi.tokyo.jp +machida.tokyo.jp +meguro.tokyo.jp +minato.tokyo.jp +mitaka.tokyo.jp +mizuho.tokyo.jp +musashimurayama.tokyo.jp +musashino.tokyo.jp +nakano.tokyo.jp +nerima.tokyo.jp +ogasawara.tokyo.jp +okutama.tokyo.jp +ome.tokyo.jp +oshima.tokyo.jp +ota.tokyo.jp +setagaya.tokyo.jp +shibuya.tokyo.jp +shinagawa.tokyo.jp +shinjuku.tokyo.jp +suginami.tokyo.jp +sumida.tokyo.jp +tachikawa.tokyo.jp +taito.tokyo.jp +tama.tokyo.jp +toshima.tokyo.jp +chizu.tottori.jp +hino.tottori.jp +kawahara.tottori.jp +koge.tottori.jp +kotoura.tottori.jp +misasa.tottori.jp +nanbu.tottori.jp +nichinan.tottori.jp +sakaiminato.tottori.jp +tottori.tottori.jp +wakasa.tottori.jp +yazu.tottori.jp +yonago.tottori.jp +asahi.toyama.jp +fuchu.toyama.jp +fukumitsu.toyama.jp +funahashi.toyama.jp +himi.toyama.jp +imizu.toyama.jp +inami.toyama.jp +johana.toyama.jp +kamiichi.toyama.jp +kurobe.toyama.jp +nakaniikawa.toyama.jp +namerikawa.toyama.jp +nanto.toyama.jp +nyuzen.toyama.jp +oyabe.toyama.jp +taira.toyama.jp +takaoka.toyama.jp +tateyama.toyama.jp +toga.toyama.jp +tonami.toyama.jp +toyama.toyama.jp +unazuki.toyama.jp +uozu.toyama.jp +yamada.toyama.jp +arida.wakayama.jp +aridagawa.wakayama.jp +gobo.wakayama.jp +hashimoto.wakayama.jp +hidaka.wakayama.jp +hirogawa.wakayama.jp +inami.wakayama.jp +iwade.wakayama.jp +kainan.wakayama.jp +kamitonda.wakayama.jp +katsuragi.wakayama.jp +kimino.wakayama.jp +kinokawa.wakayama.jp +kitayama.wakayama.jp +koya.wakayama.jp +koza.wakayama.jp +kozagawa.wakayama.jp +kudoyama.wakayama.jp +kushimoto.wakayama.jp +mihama.wakayama.jp +misato.wakayama.jp +nachikatsuura.wakayama.jp +shingu.wakayama.jp +shirahama.wakayama.jp +taiji.wakayama.jp +tanabe.wakayama.jp +wakayama.wakayama.jp +yuasa.wakayama.jp +yura.wakayama.jp +asahi.yamagata.jp +funagata.yamagata.jp +higashine.yamagata.jp +iide.yamagata.jp +kahoku.yamagata.jp +kaminoyama.yamagata.jp +kaneyama.yamagata.jp +kawanishi.yamagata.jp +mamurogawa.yamagata.jp +mikawa.yamagata.jp +murayama.yamagata.jp +nagai.yamagata.jp +nakayama.yamagata.jp +nanyo.yamagata.jp +nishikawa.yamagata.jp +obanazawa.yamagata.jp +oe.yamagata.jp +oguni.yamagata.jp +ohkura.yamagata.jp +oishida.yamagata.jp +sagae.yamagata.jp +sakata.yamagata.jp +sakegawa.yamagata.jp +shinjo.yamagata.jp +shirataka.yamagata.jp +shonai.yamagata.jp +takahata.yamagata.jp +tendo.yamagata.jp +tozawa.yamagata.jp +tsuruoka.yamagata.jp +yamagata.yamagata.jp +yamanobe.yamagata.jp +yonezawa.yamagata.jp +yuza.yamagata.jp +abu.yamaguchi.jp +hagi.yamaguchi.jp +hikari.yamaguchi.jp +hofu.yamaguchi.jp +iwakuni.yamaguchi.jp +kudamatsu.yamaguchi.jp +mitou.yamaguchi.jp +nagato.yamaguchi.jp +oshima.yamaguchi.jp +shimonoseki.yamaguchi.jp +shunan.yamaguchi.jp +tabuse.yamaguchi.jp +tokuyama.yamaguchi.jp +toyota.yamaguchi.jp +ube.yamaguchi.jp +yuu.yamaguchi.jp +chuo.yamanashi.jp +doshi.yamanashi.jp +fuefuki.yamanashi.jp +fujikawa.yamanashi.jp +fujikawaguchiko.yamanashi.jp +fujiyoshida.yamanashi.jp +hayakawa.yamanashi.jp +hokuto.yamanashi.jp +ichikawamisato.yamanashi.jp +kai.yamanashi.jp +kofu.yamanashi.jp +koshu.yamanashi.jp +kosuge.yamanashi.jp +minami-alps.yamanashi.jp +minobu.yamanashi.jp +nakamichi.yamanashi.jp +nanbu.yamanashi.jp +narusawa.yamanashi.jp +nirasaki.yamanashi.jp +nishikatsura.yamanashi.jp +oshino.yamanashi.jp +otsuki.yamanashi.jp +showa.yamanashi.jp +tabayama.yamanashi.jp +tsuru.yamanashi.jp +uenohara.yamanashi.jp +yamanakako.yamanashi.jp +yamanashi.yamanashi.jp // ke : http://www.kenic.or.ke/index.php?option=com_content&task=view&id=117&Itemid=145 *.ke @@ -1961,8 +3561,13 @@ gov.mr // ms : http://en.wikipedia.org/wiki/.ms ms -// mt : https://www.nic.org.mt/dotmt/ -*.mt +// mt : https://www.nic.org.mt/go/policy +// Submitted by registry <help@nic.org.mt> 2013-11-19 +mt +com.mt +edu.mt +net.mt +org.mt // mu : http://en.wikipedia.org/wiki/.mu mu @@ -2579,6 +4184,7 @@ name.my // mz : http://www.gobin.info/domainname/mz-template.doc *.mz +!teledata.mz // na : http://www.na-nic.com.na/ // http://www.info.na/domain/ @@ -2628,13 +4234,16 @@ other.nf store.nf // ng : http://psg.com/dns/ng/ -// Submitted by registry <randy@psg.com> 2008-06-17 -ac.ng +ng com.ng edu.ng -gov.ng +name.ng net.ng org.ng +sch.ng +gov.ng +mil.ng +mobi.ng // ni : http://www.nic.ni/dominios.htm *.ni @@ -2650,7 +4259,7 @@ bv.nl // no : http://www.norid.no/regelverk/index.en.html // The Norwegian registry has declined to notify us of updates. The web pages // referenced below are the official source of the data. There is also an -// announce mailing list: +// announce mailing list: // https://postlister.uninett.no/sympa/info/norid-diskusjon no // Norid generic domains : http://www.norid.no/regelverk/vedlegg-c.en.html @@ -3435,17 +5044,16 @@ nu *.nz // om : http://en.wikipedia.org/wiki/.om -*.om -!mediaphone.om -!nawrastelecom.om -!nawras.om -!omanmobile.om -!omanpost.om -!omantel.om -!rakpetroleum.om -!siemens.om -!songfest.om -!statecouncil.om +om +co.om +com.om +edu.om +gov.om +med.om +museum.om +net.om +org.om +pro.om // org : http://en.wikipedia.org/wiki/.org org @@ -3714,6 +5322,9 @@ org.pn edu.pn net.pn +// post : http://en.wikipedia.org/wiki/.post +post + // pr : http://www.nic.pr/index.asp?f=1 pr com.pr @@ -3772,8 +5383,16 @@ ed.pw go.pw belau.pw -// py : http://www.nic.py/faq_a.html#faq_b -*.py +// py : http://www.nic.py/pautas.html#seccion_9 +// Confirmed by registry 2012-10-03 +py +com.py +coop.py +edu.py +gov.py +mil.py +net.py +org.py // qa : http://domains.qa/en/ qa @@ -4004,6 +5623,7 @@ net.sd org.sd edu.sd med.sd +tv.sd gov.sd info.sd @@ -4051,7 +5671,7 @@ x.se y.se z.se -// sg : http://www.nic.net.sg/sub_policies_agreement/2ld.html +// sg : http://www.nic.net.sg/page/registration-policies-procedures-and-guidelines sg com.sg net.sg @@ -4060,9 +5680,13 @@ gov.sg edu.sg per.sg -// sh : http://www.nic.sh/rules.html -// list of 2nd level domains ? +// sh : http://www.nic.sh/registrar.html sh +com.sh +net.sh +gov.sh +org.sh +mil.sh // si : http://en.wikipedia.org/wiki/.si si @@ -4123,8 +5747,18 @@ store.st // su : http://en.wikipedia.org/wiki/.su su -// sv : http://www.svnet.org.sv/svpolicy.html -*.sv +// sv : http://www.svnet.org.sv/niveldos.pdf +sv +com.sv +edu.sv +gob.sv +org.sv +red.sv + +// sx : http://en.wikipedia.org/wiki/.sx +// Confirmed by registry <jcvignes@openregistry.com> 2012-05-31 +sx +gov.sx // sy : http://en.wikipedia.org/wiki/.sy // see also: http://www.gobin.info/domainname/sy.doc @@ -4157,8 +5791,7 @@ tel tf // tg : http://en.wikipedia.org/wiki/.tg -// http://www.nic.tg/nictg/index.php implies no reserved 2nd-level domains, -// although this contradicts wikipedia. +// http://www.nic.tg/ tg // th : http://en.wikipedia.org/wiki/.th @@ -4172,7 +5805,7 @@ mi.th net.th or.th -// tj : http://www.nic.tj/policy.htm +// tj : http://www.nic.tj/policy.html tj ac.tj biz.tj @@ -4197,9 +5830,16 @@ tk tl gov.tl -// tm : http://www.nic.tm/rules.html -// list of 2nd level tlds ? +// tm : http://www.nic.tm/local.html tm +com.tm +co.tm +org.tm +net.tm +nom.tm +gov.tm +mil.tm +edu.tm // tn : http://en.wikipedia.org/wiki/.tn // http://whois.ati.tn/ @@ -4286,101 +5926,133 @@ club.tw 組織.tw 商業.tw -// tz : http://en.wikipedia.org/wiki/.tz -// Submitted by registry <randy@psg.com> 2008-06-17 -// Updated from http://www.tznic.or.tz/index.php/domains.html 2010-10-25 +// tz : http://www.tznic.or.tz/index.php/domains +// Confirmed by registry <manager@tznic.or.tz> 2013-01-22 ac.tz co.tz go.tz +hotel.tz +info.tz +me.tz mil.tz +mobi.tz ne.tz or.tz sc.tz +tv.tz -// ua : http://www.nic.net.ua/ +// ua : https://hostmaster.ua/policy/?ua +// Submitted by registry <dk@cctld.ua> 2012-04-27 ua +// ua 2LD com.ua edu.ua gov.ua in.ua net.ua org.ua -// ua geo-names +// ua geographic names +// https://hostmaster.ua/2ld/ cherkassy.ua +cherkasy.ua chernigov.ua +chernihiv.ua +chernivtsi.ua chernovtsy.ua ck.ua cn.ua +cr.ua crimea.ua cv.ua dn.ua dnepropetrovsk.ua +dnipropetrovsk.ua +dominic.ua donetsk.ua dp.ua if.ua ivano-frankivsk.ua kh.ua +kharkiv.ua kharkov.ua kherson.ua khmelnitskiy.ua +khmelnytskyi.ua kiev.ua kirovograd.ua km.ua kr.ua +krym.ua ks.ua kv.ua +kyiv.ua lg.ua +lt.ua lugansk.ua lutsk.ua +lv.ua lviv.ua mk.ua +mykolaiv.ua nikolaev.ua od.ua +odesa.ua odessa.ua pl.ua poltava.ua +rivne.ua rovno.ua rv.ua +sb.ua sebastopol.ua +sevastopol.ua +sm.ua sumy.ua te.ua ternopil.ua +uz.ua uzhgorod.ua vinnica.ua +vinnytsia.ua vn.ua +volyn.ua +yalta.ua zaporizhzhe.ua -zp.ua +zaporizhzhia.ua zhitomir.ua +zhytomyr.ua +zp.ua zt.ua // Private registries in .ua co.ua pp.ua -// ug : http://www.registry.co.ug/ +// ug : https://www.registry.co.ug/ ug co.ug +or.ug ac.ug sc.ug go.ug ne.ug -or.ug +com.ug +org.ug // uk : http://en.wikipedia.org/wiki/.uk +// Submitted by registry <noc@nominet.org.uk> 2012-10-02 +// and tweaked by us pending further consultation. *.uk *.sch.uk !bl.uk !british-library.uk -!icnet.uk !jet.uk !mod.uk +!national-library-scotland.uk !nel.uk -!nhs.uk !nic.uk !nls.uk -!national-library-scotland.uk !parliament.uk -!police.uk // us : http://en.wikipedia.org/wiki/.us us @@ -4464,7 +6136,7 @@ k12.de.us k12.fl.us k12.ga.us k12.gu.us -// k12.hi.us Hawaii has a state-wide DOE login: bug 614565 +// k12.hi.us Bug 614565 - Hawaii has a state-wide DOE login k12.ia.us k12.id.us k12.il.us @@ -4495,7 +6167,7 @@ k12.pa.us k12.pr.us k12.ri.us k12.sc.us -k12.sd.us +// k12.sd.us Bug 934131 - Removed at request of James Booze <James.Booze@k12.sd.us> k12.tn.us k12.tx.us k12.ut.us @@ -4616,25 +6288,32 @@ lib.vt.us lib.va.us lib.wa.us lib.wi.us -lib.wv.us +// lib.wv.us Bug 941670 - Removed at request of Larry W Arnold <arnold@wvlc.lib.wv.us> lib.wy.us -// k12.ma.us contains school districts in Massachusetts. The 4LDs are +// k12.ma.us contains school districts in Massachusetts. The 4LDs are // managed indepedently except for private (PVT), charter (CHTR) and -// parochial (PAROCH) schools. Those are delegated dorectly to the +// parochial (PAROCH) schools. Those are delegated dorectly to the // 5LD operators. <k12-ma-hostmaster _ at _ rsuc.gweep.net> pvt.k12.ma.us chtr.k12.ma.us paroch.k12.ma.us -// uy : http://www.antel.com.uy/ -*.uy +// uy : http://www.nic.org.uy/ +uy +com.uy +edu.uy +gub.uy +mil.uy +net.uy +org.uy -// uz : http://www.reg.uz/registerr.html -// are there other 2nd level tlds ? +// uz : http://www.reg.uz/ uz -com.uz co.uz +com.uz +net.uz +org.uz // va : http://en.wikipedia.org/wiki/.va va @@ -4649,8 +6328,19 @@ gov.vc mil.vc edu.vc -// ve : http://registro.nic.ve/nicve/registro/index.html -*.ve +// ve : https://registro.nic.ve/ +// Confirmed by registry 2012-10-04 +ve +co.ve +com.ve +e12.ve +edu.ve +gov.ve +info.ve +mil.ve +net.ve +org.ve +web.ve // vg : http://en.wikipedia.org/wiki/.vg vg @@ -4708,13 +6398,13 @@ yt // <URL> // xn--mgbaam7a8h ("Emerat" Arabic) : AE -//http://nic.ae/english/arabicdomain/rules.jsp +// http://nic.ae/english/arabicdomain/rules.jsp امارات -// xn--54b7fta0cc ("Bangla" Bangla) : BD +// xn--54b7fta0cc ("Bangla" Bangla) : BD বাংলা -// xn--fiqs8s ("China" Chinese-Han-Simplified <.Zhonggou>) : CN +// xn--fiqs8s ("China" Chinese-Han-Simplified <.Zhonggou>) : CN // CNNIC // http://cnnic.cn/html/Dir/2005/10/11/3218.htm 中国 @@ -4724,60 +6414,60 @@ yt // http://cnnic.cn/html/Dir/2005/10/11/3218.htm 中國 -// xn--lgbbat1ad8j ("Algeria / Al Jazair" Arabic) : DZ +// xn--lgbbat1ad8j ("Algeria / Al Jazair" Arabic) : DZ الجزائر // xn--wgbh1c ("Egypt" Arabic .masr) : EG // http://www.dotmasr.eg/ مصر -// xn--node ("ge" Georgian (Mkhedruli)) : GE +// xn--node ("ge" Georgian (Mkhedruli)) : GE გე // xn--j6w193g ("Hong Kong" Chinese-Han) : HK // https://www2.hkirc.hk/register/rules.jsp 香港 -// xn--h2brj9c ("Bharat" Devanagari) : IN +// xn--h2brj9c ("Bharat" Devanagari) : IN // India भारत -// xn--mgbbh1a71e ("Bharat" Arabic) : IN +// xn--mgbbh1a71e ("Bharat" Arabic) : IN // India بھارت -// xn--fpcrj9c3d ("Bharat" Telugu) : IN +// xn--fpcrj9c3d ("Bharat" Telugu) : IN // India భారత్ -// xn--gecrj9c ("Bharat" Gujarati) : IN +// xn--gecrj9c ("Bharat" Gujarati) : IN // India ભારત -// xn--s9brj9c ("Bharat" Gurmukhi) : IN +// xn--s9brj9c ("Bharat" Gurmukhi) : IN // India ਭਾਰਤ -// xn--45brj9c ("Bharat" Bengali) : IN +// xn--45brj9c ("Bharat" Bengali) : IN // India ভারত -// xn--xkc2dl3a5ee0h ("India" Tamil) : IN +// xn--xkc2dl3a5ee0h ("India" Tamil) : IN // India இந்தியா -// xn--mgba3a4f16a ("Iran" Persian) : IR +// xn--mgba3a4f16a ("Iran" Persian) : IR ایران -// xn--mgba3a4fra ("Iran" Arabic) : IR +// xn--mgba3a4fra ("Iran" Arabic) : IR ايران -//xn--mgbayh7gpa ("al-Ordon" Arabic) JO -//National Information Technology Center (NITC) -//Royal Scientific Society, Al-Jubeiha +// xn--mgbayh7gpa ("al-Ordon" Arabic) : JO +// National Information Technology Center (NITC) +// Royal Scientific Society, Al-Jubeiha الاردن -// xn--3e0b707e ("Republic of Korea" Hangul) : KR +// xn--3e0b707e ("Republic of Korea" Hangul) : KR 한국 // xn--fzc2c9e2c ("Lanka" Sinhalese-Sinhala) : LK @@ -4788,10 +6478,10 @@ yt // http://nic.lk இலங்கை -// xn--mgbc0a9azcg ("Morocco / al-Maghrib" Arabic) : MA +// xn--mgbc0a9azcg ("Morocco / al-Maghrib" Arabic) : MA المغرب -// xn--mgb9awbf ("Oman" Arabic) : OM +// xn--mgb9awbf ("Oman" Arabic) : OM عمان // xn--ygbi2ammx ("Falasteen" Arabic) : PS @@ -4799,7 +6489,7 @@ yt // http://www.pnina.ps فلسطين -// xn--90a3ac ("srb" Cyrillic) : RS +// xn--90a3ac ("srb" Cyrillic) : RS срб // xn--p1ai ("rf" Russian-Cyrillic) : RU @@ -4814,19 +6504,19 @@ yt // http://www.nic.net.sa/ السعودية -// xn--mgberp4a5d4a87g ("AlSaudiah" Arabic) variant : SA +// xn--mgberp4a5d4a87g ("AlSaudiah" Arabic) variant : SA السعودیة -// xn--mgbqly7c0a67fbc ("AlSaudiah" Arabic) variant : SA +// xn--mgbqly7c0a67fbc ("AlSaudiah" Arabic) variant : SA السعودیۃ -// xn--mgbqly7cvafr ("AlSaudiah" Arabic) variant : SA +// xn--mgbqly7cvafr ("AlSaudiah" Arabic) variant : SA السعوديه -// xn--ogbpf8fl ("Syria" Arabic) : SY +// xn--ogbpf8fl ("Syria" Arabic) : SY سورية -// xn--mgbtf8fl ("Syria" Arabic) variant : SY +// xn--mgbtf8fl ("Syria" Arabic) variant : SY سوريا // xn--yfro4i67o Singapore ("Singapore" Chinese-Han) : SG @@ -4851,13 +6541,13 @@ yt // http://www.twnic.net/english/dn/dn_07a.htm 台湾 -// xn--nnx388a ("Taiwan") variant : TW +// xn--nnx388a ("Taiwan") variant : TW 臺灣 -// xn--j1amh ("ukr" Cyrillic) : UA +// xn--j1amh ("ukr" Cyrillic) : UA укр -// xn--mgb2ddes ("AlYemen" Arabic) : YE +// xn--mgb2ddes ("AlYemen" Arabic) : YE اليمن // xxx : http://icmregistry.com @@ -4875,30 +6565,464 @@ xxx // zw : http://en.wikipedia.org/wiki/.zw *.zw -// ===END ICANN DOMAINS=== -// ===BEGIN PRIVATE DOMAINS=== -// info.at : http://www.info.at/ -biz.at -info.at +// xn--80asehdb : 2013-07-14 CORE Association +онлайн -// priv.at : http://www.nic.priv.at/ -// Submitted by registry <lendl@nic.at> 2008-06-09 -priv.at +// xn--80aswg : 2013-07-14 CORE Association +сайт -// co.ca : http://registry.co.ca -co.ca +// xn--ngbc5azd : 2013-07-14 International Domain Registry Pty. Ltd. +شبكة + +// xn--unup4y : 2013-07-14 Spring Fields, LLC +游戏 + +// xn--vhquv : 2013-08-28 Dash McCook, LLC +企业 + +// camera : 2013-08-28 Atomic Maple, LLC +camera + +// clothing : 2013-08-28 Steel Lake, LLC +clothing + +// lighting : 2013-08-28 John McCook, LLC +lighting + +// singles : 2013-08-28 Fern Madison, LLC +singles + +// ventures : 2013-08-28 Binky Lake, LLC +ventures + +// voyage : 2013-08-28 Ruby House, LLC +voyage + +// guru : 2013-08-28 Pioneer Cypress, LLC +guru + +// holdings : 2013-08-28 John Madison, LLC +holdings + +// equipment : 2013-08-28 Corn Station, LLC +equipment + +// bike : 2013-08-28 Grand Hollow, LLC +bike + +// estate : 2013-08-28 Trixy Park, LLC +estate + +// tattoo : 2013-08-30 Uniregistry,Corp. +tattoo + +// xn--3ds443g : 2013-09-09 TLD Registry Limited +在线 + +// xn--fiq228c5hs : 2013-09-09 TLD Registry Limited +中文网 + +// land : 2013-09-10 Pine Moon, LLC +land + +// plumbing : 2013-09-10 Spring Tigers, LLC +plumbing + +// contractors : 2013-09-10 Magic Woods, LLC +contractors + +// sexy : 2013-09-11 Uniregistry,Corp. +sexy + +// menu : 2013-09-11 Wedding TLD2, LLC +menu + +// xn--rhqv96g : 2013-09-11 Stable Tone Limited +世界 + +// uno : 2013-09-11 Dot Latin, LLC +uno + +// gallery : 2013-09-13 Sugar House, LLC +gallery + +// technology : 2013-09-13 Auburn Falls +technology + +// xn--3bst00m : 2013-09-13 Eagle Horizon Limited +集团 + +// reviews : 2013-09-13 Extra Cover, LLC +reviews + +// guide : 2013-09-13 Snow Moon, LLC +guide + +// xn--6qq986b3x1 : 2013-09-13 Tycoon Treasure Limited +我爱你 + +// graphics : 2013-09-13 Over Madison, LLC +graphics + +// construction : 2013-09-13 Fox Dynamite, LLC +construction + +// onl : 2013-09-16 I-Registry Ltd. +onl + +// xn--q9jyb4c : 2013-09-17 Charleston Road Registry +みんな + +// diamonds : 2013-09-23 John Edge, LLC +diamonds + +// kiwi : 2013-09-23 Dot Kiwi Limited +kiwi + +// enterprises : 2013-09-23 Snow Oaks LLC +enterprises + +// today : 2013-09-23 Pearl Woods, LLC +today + +// futbol : 2013-09-23 Atomic Falls, LLC +futbol + +// photography : 2013-09-23 Sugar Glen, LLC +photography + +// tips : 2013-09-23 Corn Willow, LLC +tips + +// directory : 2013-09-23 Extra Madison, LLC +directory + +// kitchen : 2013-09-23 Just Goodbye, LLC +kitchen + +// xn--6frz82g : 2013-09-24 Afilias Limited +移动 + +// kim : 2013-09-24 Afilias Limited +kim + +// xn--cg4bki : 2013-09-27 Samsung SDS Co., LTD +삼성 + +// monash : 2013-10-01 Monash University +monash + +// wed : 2013-10-02 Atgron, Inc. +wed + +// pink : 2013-10-02 Afilias Limited +pink + +// ruhr : 2013-10-02 regiodot GmbH & Co. KG +ruhr + +// buzz : 2013-10-03 DOTSTRATEGY CO. +buzz + +// careers : 2013-10-03 Wild Corner, LLC +careers + +// shoes : 2013-10-03 Binky Galley, LLC +shoes + +// xn--4gbrim : 2013-10-07 Suhub Electronic Establishment +موقع + +// career : 2013-10-09 dotCareer, LLC +career + +// otsuka : 2013-10-11 Otsuka Holdings Co. Ltd. +otsuka + +// xn--fiQ64b : 2013-10-14 CITIC Group Corporation +中信 + +// wed : 2013-10-02 Atgron, Inc. +wed + +// pink : 2013-10-02 Afilias Limited +pink + +// ruhr : 2013-10-02 regiodot GmbH & Co. KG +ruhr + +// buzz : 2013-10-03 DOTSTRATEGY CO. +buzz + +// careers : 2013-10-03 Wild Corner, LLC +careers + +// shoes : 2013-10-03 Binky Galley, LLC +shoes + +// xn--4gbrim : 2013-10-07 Suhub Electronic Establishment +موقع + +// career : 2013-10-09 dotCareer, LLC +career + +// otsuka : 2013-10-11 Otsuka Holdings Co. Ltd. +otsuka + +// xn--fiQ64b : 2013-10-14 CITIC Group Corporation +中信 + +// gift : 2013-10-18 Uniregistry Corp. +gift + +// recipes : 2013-10-18 Grand Island, LLC +recipes + +// coffee : 2013-10-18 Trixy Cover, LLC +coffee + +// luxury : 2013-10-18 Luxury Partners, LLC +luxury + +// domains : 2013-10-18 Sugar Cross, LLC +domains + +// photos : 2013-10-18 Sea Corner, LLC +photos + +// limo : 2013-10-18 Hidden Frostbite, LLC +limo + +// viajes : 2013-10-18 Black Madison, LLC +viajes + +// wang : 2013-10-24 Zodiac Leo Limited +wang + +// democrat : 2013-10-24 United TLD Holdco Ltd. +democrat + +// mango : 2013-10-25 PUNTO FA S.L. +mango + +// cab : 2013-10-25 Half Sunset, LLC +cab + +// support : 2013-10-25 Grand Orchard, LLC +support + +// dance : 2013-10-25 United TLD Holdco Ltd. +dance + +// nagoya : 2013-10-25 GMO Registry, Inc. +nagoya + +// computer : 2013-10-25 Pine Mill, LLC +computer + +// wien : 2013-10-28 punkt.wien GmbH +wien + +// berlin : 2013-10-31 dotBERLIN GmbH & Co. KG +berlin + +// codes : 2013-10-31 Puff Willow, LLC +codes + +// email : 2013-10-31 Spring Madison, LLC +email + +// xn--mgbab2bd : 2013-10-31 CORE Association +بازار + +// repair : 2013-11-07 Lone Sunset, LLC +repair + +// holiday : 2013-11-07 Goose Woods, LLC +holiday + +// center : 2013-11-07 Tin Mill, LLC +center + +// systems : 2013-11-07 Dash Cypress, LLC +systems + +// wiki : 2013-11-07 Top Level Design, LLC +wiki + +// ceo : 2013-11-07 CEOTLD Pty Ltd +ceo + +// international : 2013-11-07 Wild Way, LLC +international + +// solar : 2013-11-07 Ruby Town, LLC +solar + +// company : 2013-11-07 Silver Avenue, LLC +company + +// education : 2013-11-07 Brice Way, LLC +education + +// training : 2013-11-07 Wild Willow, LLC +training + +// academy : 2013-11-07 Half Oaks, LLC +academy + +// marketing : 2013-11-07 Fern Pass, LLC +marketing + +// florist : 2013-11-08 Half Cypress, LLC +florist + +// solutions : 2013-11-07 Silver Cover, LLC +solutions + +// build : 2013-11-07 Plan Bee LLC +build + +// institute : 2013-11-07 Outer Maple, LLC +institute + +// builders : 2013-11-07 Atomic Madison, LLC +builders + +// red : 2013-11-07 Afilias Limited +red + +// blue : 2013-11-07 Afilias Limited +blue + +// ninja : 2013-11-07 United TLD Holdco Ltd. +ninja + +// business : 2013-11-07 Spring Cross, LLC +business + +// gal : 2013-11-07 Asociación puntoGAL +gal + +// social : 2013-11-07 United TLD Holdco Ltd. +social + +// house : 2013-11-07 Sugar Park, LLC +house + +// camp : 2013-11-07 Delta Dynamite, LLC +camp + +// immobilien : 2013-11-07 United TLD Holdco Ltd. +immobilien + +// moda : 2013-11-07 United TLD Holdco Ltd. +moda + +// glass : 2013-11-07 Black Cover, LLC +glass + +// management : 2013-11-07 John Goodbye, LLC +management + +// kaufen : 2013-11-07 United TLD Holdco Ltd. +kaufen + +// farm : 2013-11-07 Just Maple, LLC +farm + +// xn--55qw42g : 2013-11-08 China Organizational Name Administration Center +公益 + +// xn--zfr164b : 2013-11-08 China Organizational Name Administration Center +政务 + +// club : 2013-11-08 .CLUB DOMAINS, LLC +club + +// voting : 2013-11-13 Valuetainment Corp. +voting + +// TOKYO : 2013-11-13 GMO Registry, Inc. +TOKYO + +// moe : 2013-11-13 Interlink Co., Ltd. +moe + + +// ===END ICANN DOMAINS=== +// ===BEGIN PRIVATE DOMAINS=== + +// Amazon CloudFront : https://aws.amazon.com/cloudfront/ +// Submitted by Donavan Miller <donavanm@amazon.com> 2013-03-22 +cloudfront.net + +// Amazon Elastic Compute Cloud: https://aws.amazon.com/ec2/ +// Submitted by Osman Surkatty <osmans@amazon.com> 2013-04-02 +compute.amazonaws.com +us-east-1.amazonaws.com +compute-1.amazonaws.com +z-1.compute-1.amazonaws.com +z-2.compute-1.amazonaws.com +ap-northeast-1.compute.amazonaws.com +ap-southeast-1.compute.amazonaws.com +ap-southeast-2.compute.amazonaws.com +eu-west-1.compute.amazonaws.com +sa-east-1.compute.amazonaws.com +us-gov-west-1.compute.amazonaws.com +us-west-1.compute.amazonaws.com +us-west-2.compute.amazonaws.com + +// Amazon Elastic Beanstalk : https://aws.amazon.com/elasticbeanstalk/ +// Submitted by Adam Stein <astein@amazon.com> 2013-04-02 +elasticbeanstalk.com + +// Amazon Elastic Load Balancing : https://aws.amazon.com/elasticloadbalancing/ +// Submitted by Scott Vidmar <svidmar@amazon.com> 2013-03-27 +elb.amazonaws.com + +// Amazon S3 : https://aws.amazon.com/s3/ +// Submitted by Courtney Eckhardt <coec@amazon.com> 2013-03-22 +s3.amazonaws.com +s3-us-west-2.amazonaws.com +s3-us-west-1.amazonaws.com +s3-eu-west-1.amazonaws.com +s3-ap-southeast-1.amazonaws.com +s3-ap-southeast-2.amazonaws.com +s3-ap-northeast-1.amazonaws.com +s3-sa-east-1.amazonaws.com +s3-us-gov-west-1.amazonaws.com +s3-fips-us-gov-west-1.amazonaws.com +s3-website-us-east-1.amazonaws.com +s3-website-us-west-2.amazonaws.com +s3-website-us-west-1.amazonaws.com +s3-website-eu-west-1.amazonaws.com +s3-website-ap-southeast-1.amazonaws.com +s3-website-ap-southeast-2.amazonaws.com +s3-website-ap-northeast-1.amazonaws.com +s3-website-sa-east-1.amazonaws.com +s3-website-us-gov-west-1.amazonaws.com + +// BetaInABox +// Submitted by adrian@betainabox.com 2012-09-13 +betainabox.com // CentralNic : http://www.centralnic.com/names/domains -// Confirmed by registry <gavin.brown@centralnic.com> 2008-06-09 +// Submitted by registry <gavin.brown@centralnic.com> 2012-09-27 +ae.org ar.com br.com cn.com +com.de de.com eu.com gb.com +gb.net gr.com hu.com +hu.net +jp.net jpn.com kr.com no.com @@ -4906,44 +7030,36 @@ qc.com ru.com sa.com se.com +se.net uk.com +uk.net us.com +us.org uy.com za.com -gb.net -jp.net -se.net -uk.net -ae.org -us.org -com.de - -// Opera Software, A.S.A. -// Requested by Yngve Pettersen <yngve@opera.com> 2009-11-26 -operaunite.com - -// Google, Inc. -// Requested by Eduardo Vela <evn@google.com> 2010-09-06 -appspot.com - -// iki.fi : Submitted by Hannu Aronsson <haa@iki.fi> 2009-11-05 -iki.fi // c.la : http://www.c.la/ c.la -// ZaNiC : http://www.za.net/ -// Confirmed by registry <hostmaster@nic.za.net> 2009-10-03 -za.net -za.org +// cloudControl : https://www.cloudcontrol.com/ +// Submitted by Tobias Wilken <tw@cloudcontrol.com> 2013-07-23 +cloudcontrolled.com +cloudcontrolapp.com + +// co.ca : http://registry.co.ca/ +co.ca // CoDNS B.V. -// Added 2010-05-23. co.nl co.no -// Mainseek Sp. z o.o. : http://www.co.pl/ -co.pl +// Cupcake : https://cupcake.io/ +// Submitted by Jonathan Rudenberg <jonathan@cupcake.io> 2013-10-08 +cupcake.is + +// DreamHost : http://www.dreamhost.com/ +// Submitted by Andrew Farmer <andrew.farmer@dreamhost.com> 2012-10-02 +dreamhosters.com // DynDNS.com : http://www.dyndns.com/services/dns/dyndns/ dyndns-at-home.com @@ -5226,4 +7342,104 @@ webhop.org worse-than.tv writesthisblog.com +// Fastly Inc. http://www.fastly.com/ +// Submitted by Vladimir Vuksan <vladimir@fastly.com> 2013-05-31 +a.ssl.fastly.net +b.ssl.fastly.net +global.ssl.fastly.net +a.prod.fastly.net +global.prod.fastly.net + +// GitHub, Inc. +// Submitted by Ben Toews <btoews@github.com> 2013-04-18 +github.io + +// GlobeHosting, Inc. +// Submitted by Zoltan Egresi <egresi@globehosting.com> 2013-07-12 +ro.com + +// Google, Inc. +// Submitted by Eduardo Vela <evn@google.com> 2012-10-24 +appspot.com +blogspot.be +blogspot.bj +blogspot.ca +blogspot.cf +blogspot.ch +blogspot.co.at +blogspot.co.il +blogspot.co.nz +blogspot.co.uk +blogspot.com +blogspot.com.ar +blogspot.com.au +blogspot.com.br +blogspot.com.es +blogspot.cv +blogspot.cz +blogspot.de +blogspot.dk +blogspot.fi +blogspot.fr +blogspot.gr +blogspot.hk +blogspot.hu +blogspot.ie +blogspot.in +blogspot.it +blogspot.jp +blogspot.kr +blogspot.mr +blogspot.mx +blogspot.nl +blogspot.no +blogspot.pt +blogspot.re +blogspot.ro +blogspot.se +blogspot.sg +blogspot.sk +blogspot.td +blogspot.tw +codespot.com +googleapis.com +googlecode.com + +// Heroku : https://www.heroku.com/ +// Submitted by Tom Maher <tmaher@heroku.com> 2013-05-02 +herokuapp.com +herokussl.com + +// iki.fi +// Submitted by Hannu Aronsson <haa@iki.fi> 2009-11-05 +iki.fi + +// info.at : http://www.info.at/ +biz.at +info.at + +// Michau Enterprises Limited : http://www.co.pl/ +co.pl + +// NYC.mn : http://www.information.nyc.mn +// Submitted by Matthew Brown <mattbrown@nyc.mn> 2013-03-11 +nyc.mn + +// Opera Software, A.S.A. +// Submitted by Yngve Pettersen <yngve@opera.com> 2009-11-26 +operaunite.com + +// Red Hat, Inc. OpenShift : https://openshift.redhat.com/ +// Submitted by Tim Kramer <tkramer@rhcloud.com> 2012-10-24 +rhcloud.com + +// priv.at : http://www.nic.priv.at/ +// Submitted by registry <lendl@nic.at> 2008-06-09 +priv.at + +// ZaNiC : http://www.za.net/ +// Submitted by registry <hostmaster@nic.za.net> 2009-10-03 +za.net +za.org + // ===END PRIVATE DOMAINS=== diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am index f21e710e..d47693a1 100644 --- a/docs/reference/Makefile.am +++ b/docs/reference/Makefile.am @@ -13,7 +13,7 @@ DOC_MAIN_SGML_FILE=$(DOC_MODULE)-docs.sgml DOC_SOURCE_DIR=../../libsoup # Extra options to supply to gtkdoc-scan. -SCAN_OPTIONS=--deprecated-guards=LIBSOUP_DISABLE_DEPRECATED --rebuild-types +SCAN_OPTIONS=--deprecated-guards=SOUP_DISABLE_DEPRECATED --rebuild-types --ignore-decorators='SOUP_DEPRECATED\w*\s*\([^)]*\)|SOUP_DEPRECATED\w*|SOUP_AVAILABLE\w*' # Extra options to supply to gtkdoc-scangobj. SCANGOBJ_OPTIONS= @@ -29,16 +29,23 @@ HFILE_GLOB= CFILE_GLOB= # Header files to ignore when scanning. -IGNORE_HFILES= soup.h soup-marshal.h soup-enum-types.h \ +IGNORE_HFILES= soup.h soup-enum-types.h \ soup-message-private.h soup-session-private.h \ soup-auth-basic.h soup-auth-digest.h soup-auth-ntlm.h \ - soup-connection.h soup-connection-ntlm.h \ - soup-dns.h soup-auth-manager.h soup-auth-manager-ntlm.h \ - soup-message-queue.h soup-path-map.h soup-ssl.h \ + soup-connection.h soup-connection-auth.h \ + soup-message-queue.h soup-path-map.h soup-gnome-features.h \ soup-proxy-resolver.h soup-proxy-resolver-gnome.h \ soup-proxy-resolver-static.h soup-directory-input-stream.h \ soup-http-input-stream.h soup-password-manager.h \ - soup-password-manager-gnome.h + soup-password-manager-gnome.h soup-converter-wrapper.h \ + soup-body-input-stream.h soup-body-output-stream.h \ + soup-client-input-stream.h soup-content-processor.h \ + soup-content-sniffer-stream.h soup-io-stream.h \ + soup-cache-input-stream.h soup-filter-input-stream.h \ + soup-cookie-jar-sqlite.h soup-requester.h soup-tld-private.h \ + soup-misc-private.h soup-proxy-uri-resolver.h \ + soup-proxy-resolver-wrapper.h soup-proxy-uri-resolver.h \ + soup-cache-private.h # Images to copy into HTML directory. HTML_IMAGES = @@ -47,7 +54,9 @@ HTML_IMAGES = content_files = \ build-howto.xml \ client-howto.xml \ - server-howto.xml + request-howto.xml \ + server-howto.xml \ + session-porting.xml # Other files to distribute. extra_files = @@ -64,7 +73,6 @@ GTKDOC_CFLAGS = \ GTKDOC_LIBS = \ $(top_builddir)/libsoup/libsoup-2.4.la \ - $(top_builddir)/libsoup/libsoup-gnome-2.4.la \ $(GLIB_LIBS) # include common portion ... diff --git a/docs/reference/build-howto.xml b/docs/reference/build-howto.xml index 975dfd55..bec9a454 100644 --- a/docs/reference/build-howto.xml +++ b/docs/reference/build-howto.xml @@ -35,41 +35,52 @@ that first appeared in version 2.4") and is essentially just part of the package name. </para> +</refsect2> + +<refsect2> +<title>API Availability and Deprecation Warnings</title> + <para> -If you are using any of the GNOME-specific features of -<application>libsoup</application> (such as automatic proxy -configuration), you must require -"<literal>libsoup-gnome-2.4</literal>" instead: +If you want to restrict your program to a particular +<application>libsoup</application> version or range of versions, you +can define <link +linkend="SOUP-VERSION-MIN-REQUIRED:CAPS"><literal>SOUP_VERSION_MIN_REQUIRED</literal></link> +and/or <link +linkend="SOUP-VERSION-MAX-ALLOWED:CAPS"><literal>SOUP_VERSION_MAX_ALLOWED</literal></link>. +Eg: </para> <informalexample><programlisting> -PKG_CHECK_MODULES(LIBSOUP, [libsoup-gnome-2.4 >= 2.26]) -AC_SUBST(LIBSOUP_CFLAGS) -AC_SUBST(LIBSOUP_LIBS) +LIBSOUP_CFLAGS="$LIBSOUP_CFLAGS -DSOUP_VERSION_MIN_REQUIRED=SOUP_VERSION_2_36" +LIBSOUP_CFLAGS="$LIBSOUP_CFLAGS -DSOUP_VERSION_MAX_ALLOWED=SOUP_VERSION_2_40" </programlisting></informalexample> <para> -You can also make <application>libsoup-gnome</application> an optional -dependency: +The <literal>SOUP_VERSION_MIN_REQUIRED</literal> declaration states +that the code is not expected to compile on versions of +<application>libsoup</application> older than the indicated version +(here, 2.36), and so the compiler should print warnings if the code +uses functions that were deprecated as of that release. </para> -<informalexample><programlisting> -PKG_CHECK_MODULES(LIBSOUP_GNOME, - [libsoup-gnome-2.4 >= 2.26], - [LIBSOUP_CFLAGS="$LIBSOUP_GNOME_CFLAGS" - LIBSOUP_LIBS="$LIBSOUP_GNOME_LIBS" - AC_DEFINE(HAVE_LIBSOUP_GNOME, 1, [Have libsoup-gnome])], - [PKG_CHECK_MODULES(LIBSOUP, [libsoup-2.4 >= 2.26])]) -AC_SUBST(LIBSOUP_CFLAGS) -AC_SUBST(LIBSOUP_LIBS) -</programlisting></informalexample> +<para> +The <literal>SOUP_VERSION_MAX_ALLOWED</literal> declaration states +that the code <emphasis>is</emphasis> expected to compile on versions +of <application>libsoup</application> up to the indicated version +(here, 2.40), and so, when compiling the program against a newer +version than that, the compiler should print warnings if the code uses +functions that did not yet exist in the max-allowed release. +</para> <para> -This will allow the application to be built with either plain -<application>libsoup</application> or with -<application>libsoup-gnome</application>, and it will define the C -preprocessor symbol <literal>HAVE_LIBSOUP_GNOME</literal> if -<application>libsoup-gnome</application> features are available. +You can use <link +linkend="SOUP-CHECK-VERSION:CAPS"><literal>SOUP_CHECK_VERSION</literal></link> +to check the version of libsoup at compile time, to compile different +code for different <application>libsoup</application> versions. (If +you are setting <literal>SOUP_VERSION_MIN_REQUIRED</literal> and +<literal>SOUP_VERSION_MAX_ALLOWED</literal> to different versions, as +in the example above, then you almost certainly need to be doing +this.) </para> </refsect2> @@ -86,18 +97,8 @@ Code using <application>libsoup</application> should do: </programlisting></informalexample> <para> -or, for <application>libsoup-gnome</application>: -</para> - -<informalexample><programlisting> -#include <libsoup/soup-gnome.h> -</programlisting></informalexample> - -<para> -Including individual headers besides the two main header files is not -recommended. You may include both <literal>soup.h</literal> and -<literal>soup-gnome.h</literal> (though this is not required; the -latter automatically includes the former). +Including individual headers rather than <literal>soup.h</literal> is not +recommended. </para> </refsect2> diff --git a/docs/reference/client-howto.xml b/docs/reference/client-howto.xml index a53f2ac3..e694cb72 100644 --- a/docs/reference/client-howto.xml +++ b/docs/reference/client-howto.xml @@ -3,16 +3,26 @@ "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"> <refentry id="libsoup-client-howto"> <refmeta> -<refentrytitle>Soup Client Basics</refentrytitle> +<refentrytitle>libsoup Client Basics</refentrytitle> <manvolnum>3</manvolnum> <refmiscinfo>LIBSOUP Library</refmiscinfo> </refmeta> <refnamediv> -<refname>Soup Client Basics</refname><refpurpose>Client-side tutorial</refpurpose> +<refname>libsoup Client Basics</refname><refpurpose>Client-side tutorial</refpurpose> </refnamediv> <refsect2> +<para> +This section explains how to use <application>libsoup</application> as +an HTTP client using several new APIs introduced in version 2.42. If +you want to be compatible with older versions of +<application>libsoup</application>, consult the documentation for that +version. +</para> +</refsect2> + +<refsect2> <title>Creating a <type>SoupSession</type></title> <para> @@ -24,35 +34,8 @@ authentication information, etc. </para> <para> -There are two subclasses of <link -linkend="SoupSession"><type>SoupSession</type></link> that you can use, with -slightly different behavior: -</para> - -<itemizedlist> - <listitem><para> - <link linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link>, - which uses callbacks and the glib main loop to provide - asynchronous I/O. - </para></listitem> - - <listitem><para> - <link linkend="SoupSessionSync"><type>SoupSessionSync</type></link>, - which uses blocking I/O rather than callbacks, making it more - suitable for threaded applications. - </para></listitem> -</itemizedlist> - -<para> -If you want to do a mix of mainloop-based and blocking I/O, you will -need to create two different session objects. -</para> - -<para> -When you create the session (with <link -linkend="soup-session-async-new-with-options"><function>soup_session_async_new_with_options</function></link> -or <link -linkend="soup-session-sync-new-with-options"><function>soup_session_sync_new_with_options</function></link>), +When you create the session with <link +linkend="soup-session-new-with-options"><function>soup_session_new_with_options</function></link>, you can specify various additional options: </para> @@ -64,7 +47,7 @@ you can specify various additional options: the session will have open at one time. (Once it reaches this limit, it will either close idle connections, or wait for existing connections to free up before starting - new requests.) + new requests.) The default value is 10. </para></listitem> </varlistentry> <varlistentry> @@ -72,44 +55,61 @@ you can specify various additional options: <listitem><para> Allows you to set the maximum total number of connections the session will have open <emphasis>to a single - host</emphasis> at one time. + host</emphasis> at one time. The default value is 2. </para></listitem> </varlistentry> <varlistentry> - <term><link linkend="SOUP-SESSION-USE-NTLM:CAPS"><literal>SOUP_SESSION_USE_NTLM</literal></link></term> + <term><link linkend="SOUP-SESSION-USER-AGENT:CAPS"><literal>SOUP_SESSION_USER_AGENT</literal></link></term> <listitem><para> - If <literal>TRUE</literal>, then Microsoft NTLM - authentication will be used if available (and will be - preferred to HTTP Basic or Digest authentication). - If <literal>FALSE</literal>, NTLM authentication won't be - used, even if it's the only authentication type available. - (NTLM works differently from the standard HTTP - authentication types, so it needs to be handled - specially.) + Allows you to set a User-Agent string that will be sent + on all outgoing requests. </para></listitem> </varlistentry> <varlistentry> - <term><link linkend="SOUP-SESSION-SSL-CA-FILE:CAPS"><literal>SOUP_SESSION_SSL_CA_FILE</literal></link></term> + <term><link linkend="SOUP-SESSION-ACCEPT-LANGUAGE:CAPS"><literal>SOUP_SESSION_ACCEPT_LANGUAGE</literal></link> + and <link linkend="SOUP-SESSION-ACCEPT-LANGUAGE-AUTO:CAPS"><literal>SOUP_SESSION_ACCEPT_LANGUAGE_AUTO</literal></link></term> <listitem><para> - Points to a file containing certificates for recognized - SSL Certificate Authorities. If this is set, then HTTPS - connections will be checked against these authorities, and - rejected if they can't be verified. (Otherwise all SSL - certificates will be accepted automatically.) + Allow you to set an Accept-Language header on all outgoing + requests. <literal>SOUP_SESSION_ACCEPT_LANGUAGE</literal> + takes a list of language tags to use, while + <literal>SOUP_SESSION_ACCEPT_LANGUAGE_AUTO</literal> + automatically generates the list from the user's locale + settings. </para></listitem> </varlistentry> <varlistentry> - <term><link linkend="SOUP-SESSION-ASYNC-CONTEXT:CAPS"><literal>SOUP_SESSION_ASYNC_CONTEXT</literal></link></term> + <term><link linkend="SOUP-SESSION-HTTP-ALIASES:CAPS"><literal>SOUP_SESSION_HTTP_ALIASES</literal></link> + and <link linkend="SOUP-SESSION-HTTPS-ALIASES:CAPS"><literal>SOUP_SESSION_HTTPS_ALIASES</literal></link></term> <listitem><para> - A <link - linkend="GMainContext"><type>GMainContext</type></link> - which the session will use for asynchronous operations. - This can be set if you want to use a - <type>SoupSessionAsync</type> in a thread other than the - main thread. + Allow you to tell the session to recognize additional URI + schemes as aliases for "<literal>http</literal>" or + <literal>https</literal>. You can set this if you are + using URIs with schemes like "<literal>dav</literal>" or + "<literal>webcal</literal>" (and in particular, you need + to set this if the server you are talking to might return + redirects with such a scheme). </para></listitem> </varlistentry> <varlistentry> + <term><link linkend="SOUP-SESSION-PROXY-RESOLVER:CAPS"><literal>SOUP_SESSION_PROXY_RESOLVER</literal></link> and <link linkend="SOUP-SESSION-PROXY-URI:CAPS"><literal>SOUP_SESSION_PROXY_URI</literal></link></term> + <listitem> + <para> + <link linkend="SOUP-SESSION-PROXY-RESOLVER:CAPS"><literal>SOUP_SESSION_PROXY_RESOLVER</literal></link> + specifies a <link + linkend="GProxyResolver"><type>GProxyResolver</type></link> + to use to determine the HTTP proxies to use. By default, + this is set to the resolver returned by <link + linkend="g-proxy-resolver-get-default"><function>g_proxy_resolver_get_default</function></link>, + so you do not need to set it yourself. + </para> + <para> + Alternatively, if you want all requests to go through a + single proxy, you can set <link + linkend="SOUP-SESSION-PROXY-URI:CAPS"><literal>SOUP_SESSION_PROXY_URI</literal></link>. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><link linkend="SOUP-SESSION-ADD-FEATURE:CAPS"><literal>SOUP_SESSION_ADD_FEATURE</literal></link> and <link linkend="SOUP-SESSION-ADD-FEATURE-BY-TYPE:CAPS"><literal>SOUP_SESSION_ADD_FEATURE_BY_TYPE</literal></link></term> <listitem><para> These allow you to specify <link @@ -121,10 +121,15 @@ you can specify various additional options: </variablelist> <para> +Other properties are also available; see the <link +linkend="SoupSession"><type>SoupSession</type></link> documentation for +more details. +</para> + +<para> If you don't need to specify any options, you can just use <link -linkend="soup-session-async-new"><function>soup_session_async_new</function></link> or -<link linkend="soup-session-sync-new"><function>soup_session_sync_new</function></link>, -which take no arguments. +linkend="soup-session-new"><function>soup_session_new</function></link>, +which takes no arguments. </para> </refsect2> @@ -143,8 +148,19 @@ options at session-construction-time, or afterward via the <link linkend="soup-session-add-feature"><function>soup_session_add_feature</function></link> and <link linkend="soup-session-add-feature-by-type"><function>soup_session_add_feature_by_type</function></link> -functions. Some of the features available in -<application>libsoup</application> are: +functions. +</para> + +<para> +A <link +linkend="SoupContentDecoder"><type>SoupContentDecoder</type></link> is +added for you automatically. This advertises to servers that the +client supports compression, and automatically decompresses compressed +responses. +</para> + +<para> +Some other available features that you can add include: </para> <variablelist> @@ -156,33 +172,27 @@ functions. Some of the features available in </para></listitem> </varlistentry> <varlistentry> - <term><link linkend="SoupCookieJar"><type>SoupCookieJar</type></link> and <link linkend="SoupCookieJarText"><type>SoupCookieJarText</type></link></term> + <term> + <link linkend="SoupCookieJar"><type>SoupCookieJar</type></link>, + <link linkend="SoupCookieJarText"><type>SoupCookieJarText</type></link>, + and <link linkend="SoupCookieJarDB"><type>SoupCookieJarDB</type></link> + </term> <listitem><para> Support for HTTP cookies. <type>SoupCookieJar</type> provides non-persistent cookie storage, while <type>SoupCookieJarText</type> uses a text file to keep - track of cookies between sessions. - </para></listitem> - </varlistentry> -</variablelist> - -<para> -And in <application>libsoup-gnome</application>: -</para> - -<variablelist> - <varlistentry> - <term><link linkend="SOUP-TYPE-PROXY-RESOLVER-GNOME:CAPS"><type>SoupProxyResolverGNOME</type></link></term> - <listitem><para> - A feature that automatically determines the correct HTTP - proxy to use for requests. + track of cookies between sessions, and + <type>SoupCookieJarDB</type> uses a + <application>SQLite</application> database. </para></listitem> </varlistentry> <varlistentry> - <term><link linkend="SoupCookieJarSqlite"><type>SoupCookieJarSqlite</type></link></term> + <term><link linkend="SoupContentSniffer"><type>SoupContentSniffer</type></link></term> <listitem><para> - Support for HTTP cookies stored in an - <application>SQLite</application> database. + Uses the HTML5 sniffing rules to attempt to + determine the Content-Type of a response when the + server does not identify the Content-Type, or appears to + have provided an incorrect one. </para></listitem> </varlistentry> </variablelist> @@ -190,7 +200,7 @@ And in <application>libsoup-gnome</application>: <para> Use the "add_feature_by_type" property/function to add features that don't require any configuration (such as <link -linkend="SOUP-TYPE-PROXY-RESOLVER-GNOME:CAPS"><type>SoupProxyResolverGNOME</type></link>), +linkend="SoupContentSniffer"><type>SoupContentSniffer</type></link>), and the "add_feature" property/function to add features that must be constructed first (such as <link linkend="SoupLogger"><type>SoupLogger</type></link>). For example, an @@ -198,11 +208,10 @@ application might do something like the following: </para> <informalexample><programlisting> - session = soup_session_async_new_with_options ( -#ifdef HAVE_LIBSOUP_GNOME - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_GNOME, -#endif + session = soup_session_new_with_options ( + SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_SNIFFER, NULL); + if (debug_level) { SoupLogger *logger; @@ -218,7 +227,7 @@ application might do something like the following: <title>Creating and Sending SoupMessages</title> <para> -Once you have a session, you do HTTP traffic using <link +Once you have a session, you send HTTP requests using <link linkend="SoupMessage"><type>SoupMessage</type></link>. In the simplest case, you only need to create the message and it's ready to send: </para> @@ -242,7 +251,7 @@ request headers and body of the message: msg = soup_message_new ("POST", "http://example.com/form.cgi"); soup_message_set_request (msg, "application/x-www-form-urlencoded", - SOUP_MEMORY_COPY, formdata, strlen (formdata)); + SOUP_MEMORY_COPY, formdata, strlen (formdata)); soup_message_headers_append (msg->request_headers, "Referer", referring_url); </programlisting></informalexample> @@ -269,28 +278,56 @@ flag. <para> To send a message and wait for the response, use <link -linkend="soup-session-send-message"><function>soup_session_send_message</function></link>: +linkend="soup-session-send"><function>soup_session_send</function></link>: </para> <informalexample><programlisting> - guint status; + GInputStream *stream; + GError *error = NULL; - status = soup_session_send_message (session, msg); + stream = soup_session_send (session, msg, cancellable, &error); </programlisting></informalexample> <para> -(If you use <function>soup_session_send_message</function> with a -<link linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link>, -it will run the main loop itself until the message is complete.) +At the point when <function>soup_session_send</function> returns, the +request will have been sent, and the response headers read back in; +you can examine the message's <structfield>status_code</structfield>, +<structfield>reason_phrase</structfield>, and +<structfield>response_headers</structfield> fields to see the response +metadata. To get the response body, read from the returned <link +linkend="GInputStream"><type>GInputStream</type></link>, and close it +when you are done. </para> <para> -The return value from <function>soup_session_send_message</function> -is a <link linkend="libsoup-2.4-soup-status">libsoup status code</link>, -indicating either a transport error that prevented the message from -being sent, or the HTTP status that was returned by the server in -response to the message. (The status is also available as -<literal>msg->status_code</literal>.) +Note that <function>soup_session_send</function> only returns an error +if a transport-level problem occurs (eg, it could not connect to the +host, or the request was cancelled). Use the message's +<structfield>status_code</structfield> field to determine whether the +request was successful or not at the HTTP level (ie, "<literal>200 +OK</literal>" vs "<literal>401 Bad Request</literal>"). +</para> + +<para> +If you would prefer to have <application>libsoup</application> gather +the response body for you and then return it all at once, you can use +the older +<link linkend="soup-session-send-message"><function>soup_session_send_message</function></link> +API: +</para> + +<informalexample><programlisting> + guint status; + + status = soup_session_send_message (session, msg); +</programlisting></informalexample> + +<para> +In this case, the response body will be available in the message's +<structfield>response_body</structfield> field, and transport-level +errors will be indicated in the <structfield>status_code</structfield> +field via special pseudo-HTTP-status codes like <link +linkend="SOUP-STATUS-CANT-CONNECT:CAPS"><literal>SOUP_STATUS_CANT_CONNECT</literal></link>. </para> </refsect3> @@ -300,51 +337,63 @@ response to the message. (The status is also available as <para> To send a message asynchronously, use <link -linkend="soup-session-queue-message"><function>soup_session_queue_message</function></link>: +linkend="soup-session-send-async"><function>soup_session_send_async</function></link>: </para> <informalexample><programlisting> +{ ... - soup_session_queue_message (session, msg, my_callback, my_callback_data); + soup_session_send_async (session, msg, cancellable, my_callback, my_callback_data); ... } static void -my_callback (SoupSession *session, SoupMessage *msg, gpointer user_data) +my_callback (GObject *object, GAsyncResult *result, gpointer user_data) { - /* Handle the response here */ + GInputStream *stream; + GError *error = NULL; + + stream = soup_session_send_finish (SOUP_SESSION (object), result, &error); + ... } </programlisting></informalexample> <para> The message will be added to the session's queue, and eventually (when control is returned back to the main loop), it will be sent and the -response be will be read. When the message is complete, -<literal>callback</literal> will be invoked, along with the data you -passed to <function>soup_session_queue_message</function>. +response be will be read. When the message has been sent, and its +headers received, the callback will be invoked, in the standard +<link linkend="GAsyncReadyCallback"><type>GAsyncReadyCallback</type></link> +style. </para> <para> -<link -linkend="soup-session-queue-message"><function>soup_session_queue_message</function></link> -steals a reference to the message object, and unrefs it after the last -callback is invoked on it. So in the usual case, messages sent -asynchronously will be automatically freed for you without you needing -to do anything. (Of course, this wouldn't work when using the synchronous -API, since you will usually need continue working with the message -after calling <link -linkend="soup-session-send-message"><function>soup_session_send_message</function></link>, -so in that case, you must unref it explicitly when you are done with -it.) +As with synchronous sending, there is also an alternate API, <link +linkend="soup-session-queue-message"><function>soup_session_queue_message</function></link>, +in which your callback is not invoked until the response has been +completely read: </para> +<informalexample><programlisting> +{ + ... + soup_session_queue_message (session, msg, my_callback, my_callback_data); + ... +} + +static void +my_callback (SoupSession *session, SoupMessage *msg, gpointer user_data) +{ + /* msg->response_body contains the response */ +} +</programlisting></informalexample> + <para> -(If you use <link +<link linkend="soup-session-queue-message"><function>soup_session_queue_message</function></link> -with a <link -linkend="SoupSessionSync"><type>SoupSessionSync</type></link>, the -message will be sent in another thread, with the callback eventually -being invoked in the session's <link linkend="SOUP-SESSION-ASYNC-CONTEXT:CAPS"><literal>SOUP_SESSION_ASYNC_CONTEXT</literal></link>.) +is slightly unusual in that it steals a reference to the message +object, and unrefs it after the last callback is invoked on it. So +when using this API, you should not unref the message yourself. </para> </refsect3> @@ -355,19 +404,17 @@ being invoked in the session's <link linkend="SOUP-SESSION-ASYNC-CONTEXT:CAPS">< <title>Processing the Response</title> <para> -Once you have received the response from the server, synchronously or -asynchronously, you can look at the response fields in the -<literal>SoupMessage</literal> to decide what to do next. The -<structfield>status_code</structfield> and +Once you have received the initial response from the server, +synchronously or asynchronously, streaming or not, you can look at the +response fields in the <literal>SoupMessage</literal> to decide what +to do next. The <structfield>status_code</structfield> and <structfield>reason_phrase</structfield> fields contain the numeric status and textual status response from the server. <structfield>response_headers</structfield> contains the response headers, which you can investigate using <link -linkend="soup-message-headers-get"><function>soup_message_headers_get</function></link> and -<link - linkend="soup-message-headers-foreach"><function>soup_message_headers_foreach</function></link>. -The response body (if any) is in the -<structfield>response_body</structfield> field. +linkend="soup-message-headers-get"><function>soup_message_headers_get</function></link> +and <link +linkend="soup-message-headers-foreach"><function>soup_message_headers_foreach</function></link>. </para> <para> @@ -390,41 +437,6 @@ headers much better than functions like </refsect2> <refsect2> -<title>Intermediate/Automatic Processing</title> - -<para> -You can also connect to various <type>SoupMessage</type> signals to do -processing at intermediate stages of HTTP I/O. Eg, the <link -linkend="SoupMessage-got-chunk"><literal>got-chunk</literal></link> -signal is emitted as each piece of the response body is read (allowing -you to provide progress information when receiving a large response, -for example). <type>SoupMessage</type> also provides two convenience -methods, <link -linkend="soup-message-add-header-handler"><function>soup_message_add_header_handler</function></link>, -and <link -linkend="soup-message-add-status-code-handler"><function>soup_message_add_status_code_handler</function></link>, -which allow you to set up a signal handler that will only be invoked -for messages with certain response headers or status codes. -<type>SoupSession</type> uses this internally to handle authentication -and redirection. -</para> - -<para> -When using the synchronous API, the callbacks and signal handlers will -be invoked during the call to <link -linkend="soup-session-send-message"><function>soup_session_send_message</function></link>. -</para> - -<para> -To automatically set up handlers on all messages sent via a session, -you can connect to the session's <link -linkend="SoupSession-request-started"><literal>request_started</literal></link> -signal, and add handlers to each message from there. -</para> - -</refsect2> - -<refsect2> <title>Handling Authentication</title> <para> @@ -474,38 +486,39 @@ linkend="soup-session-unpause-message"><function>soup_session_unpause_message</f to resume the paused message. </para> +<para> +By default, NTLM authentication is not enabled. To add NTLM support to +a session, call: +</para> + +<informalexample><programlisting> + soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM); +</programlisting></informalexample> + +<para> +(You can also disable Basic or Digest authentication by calling <link +linkend="soup-session-remove-feature-by-type"><function>soup_session_remove_feature_by_type</function></link> +on <link linkend="SOUP-TYPE-AUTH-BASIC:CAPS"><literal>SOUP_TYPE_AUTH_BASIC</literal></link> +or <link linkend="SOUP-TYPE-AUTH-DIGEST:CAPS"><literal>SOUP_TYPE_AUTH_DIGEST</literal></link>.) +</para> + </refsect2> <refsect2> <title>Multi-threaded usage</title> <para> -The only explicitly thread-safe operations in -<application>libsoup</application> are <link -linkend="SoupSessionSync"><type>SoupSessionSync</type></link>'s -implementations of the <link -linkend="SoupSession"><type>SoupSession</type></link> methods. So -after creating a <type>SoupSessionSync</type>, you can call <link -linkend="soup-session-send-message"><function>soup_session_send_message</function></link> -and <link -linkend="soup-session-cancel-message"><function>soup_session_cancel_message</function></link> -on it from any thread. But, eg, while the session is processing a -message, you should not call any <link -linkend="SoupMessage"><type>SoupMessage</type></link> methods on it -from any thread other than the one in which it is being sent. (That -is, you should not call any <type>SoupMessage</type> methods on it -except from a message or session callback or signal handler.) +A <link linkend="SoupSession"><type>SoupSession</type></link> can be +used from multiple threads. However, if you are using the async APIs, +then each thread you use the session from must have its own +thread-default <link linkend="GMainContext"><type>GMainContext</type></link>. </para> <para> -All other objects (including <link -linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link>) -should only be used from a single thread, with objects that are also -only be used from that thread. (And in particular, if you set a -non-default <link -linkend="GMainContext"><type>GMainContext</type></link> on a session, -socket, etc, then you can only use that object from the thread in -which that <type>GMainContext</type> is running.) +<link linkend="SoupMessage"><type>SoupMessage</type></link> is +<emphasis>not</emphasis> thread-safe, so once you send a message on +the session, you must not interact with it from any thread other than +the one where it was sent. </para> </refsect2> @@ -515,7 +528,8 @@ which that <type>GMainContext</type> is running.) <para> A few sample programs are available in the -<application>libsoup</application> sources: +<application>libsoup</application> sources, in the +<literal>examples</literal> directory: </para> <itemizedlist> @@ -525,21 +539,6 @@ A few sample programs are available in the </para></listitem> <listitem><para> - <emphasis role="bold"><literal>getbug</literal></emphasis> is a trivial - demonstration of the <link - linkend="libsoup-2.4-XMLRPC-Support">XMLRPC</link> interface. - (<emphasis - role="bold"><literal>xmlrpc-test</literal></emphasis> provides - a slightly more complicated example.) - </para></listitem> - - <listitem><para> - <emphasis role="bold"><literal>auth-test</literal></emphasis> shows how to use - authentication handlers and status-code handlers, although in - a fairly unusual way. - </para></listitem> - - <listitem><para> <emphasis role="bold"><literal>simple-proxy</literal></emphasis> uses both the client and server APIs to create a simple (and not very RFC-compliant) proxy server. It shows how to use the <link @@ -551,11 +550,7 @@ A few sample programs are available in the </itemizedlist> <para> -More complicated examples are available in GNOME CVS. The <ulink -url="http://live.gnome.org/LibSoup"><application>libsoup</application> -pages</ulink> on the GNOME wiki include a <ulink -url="http://live.gnome.org/LibSoup/Users">list of applications using -<application>libsoup</application></ulink>. +More complicated examples are available in GNOME git. </para> </refsect2> diff --git a/docs/reference/libsoup-2.4-docs.sgml b/docs/reference/libsoup-2.4-docs.sgml index 21e19ea4..c0c8a05c 100644 --- a/docs/reference/libsoup-2.4-docs.sgml +++ b/docs/reference/libsoup-2.4-docs.sgml @@ -10,7 +10,9 @@ <title>Tutorial</title> <xi:include href="build-howto.xml"/> <xi:include href="client-howto.xml"/> + <xi:include href="request-howto.xml"/> <xi:include href="server-howto.xml"/> + <xi:include href="session-porting.xml"/> </chapter> <chapter> @@ -19,6 +21,7 @@ <xi:include href="xml/soup-auth-domain.xml"/> <xi:include href="xml/soup-auth-domain-basic.xml"/> <xi:include href="xml/soup-auth-domain-digest.xml"/> + <xi:include href="xml/soup-cache.xml"/> <xi:include href="xml/soup-cookie.xml"/> <xi:include href="xml/soup-message.xml"/> <xi:include href="xml/soup-message-headers.xml"/> @@ -26,6 +29,11 @@ <xi:include href="xml/soup-method.xml"/> <xi:include href="xml/soup-misc.xml"/> <xi:include href="xml/soup-multipart.xml"/> + <xi:include href="xml/soup-multipart-input-stream.xml"/> + <xi:include href="xml/soup-request.xml"/> + <xi:include href="xml/soup-request-http.xml"/> + <xi:include href="xml/soup-request-file.xml"/> + <xi:include href="xml/soup-request-data.xml"/> <xi:include href="xml/soup-server.xml"/> <xi:include href="xml/soup-session.xml"/> <xi:include href="xml/soup-session-async.xml"/> @@ -33,17 +41,19 @@ <xi:include href="xml/soup-status.xml"/> <xi:include href="xml/soup-tld.xml"/> <xi:include href="xml/soup-uri.xml"/> + <xi:include href="xml/soup-version.xml"/> </chapter> <chapter> <title>Additional Features</title> <xi:include href="xml/soup-session-feature.xml"/> + <xi:include href="xml/soup-auth-manager.xml"/> <xi:include href="xml/soup-content-decoder.xml"/> <xi:include href="xml/soup-content-sniffer.xml"/> <xi:include href="xml/soup-cookie-jar.xml"/> <xi:include href="xml/soup-cookie-jar-text.xml"/> + <xi:include href="xml/soup-cookie-jar-db.xml"/> <xi:include href="xml/soup-logger.xml"/> - <xi:include href="xml/soup-proxy-uri-resolver.xml"/> <xi:include href="xml/soup-proxy-resolver-default.xml"/> </chapter> @@ -55,27 +65,11 @@ </chapter> <chapter> - <title>GNOME integration</title> - <xi:include href="xml/soup-gnome-features.xml"/> - <xi:include href="xml/soup-cookie-jar-sqlite.xml"/> - </chapter> - - <chapter> <title>Low-level Networking API</title> <xi:include href="xml/soup-address.xml"/> <xi:include href="xml/soup-socket.xml"/> </chapter> - <chapter> - <title>Experimental streaming API</title> - <xi:include href="xml/soup-requester.xml"/> - <xi:include href="xml/soup-request.xml"/> - <xi:include href="xml/soup-request-http.xml"/> - <xi:include href="xml/soup-request-file.xml"/> - <xi:include href="xml/soup-request-data.xml"/> - <xi:include href="xml/soup-cache.xml"/> - </chapter> - <index> <title>Index</title> </index> diff --git a/docs/reference/libsoup-2.4-sections.txt b/docs/reference/libsoup-2.4-sections.txt index 39e8288a..03d35d4b 100644 --- a/docs/reference/libsoup-2.4-sections.txt +++ b/docs/reference/libsoup-2.4-sections.txt @@ -35,6 +35,11 @@ SoupChunkAllocator soup_message_set_chunk_allocator <SUBSECTION> soup_message_disable_feature +soup_message_get_soup_request +<SUBSECTION> +SoupMessagePriority +soup_message_get_priority +soup_message_set_priority <SUBSECTION> SOUP_MESSAGE_METHOD SOUP_MESSAGE_URI @@ -44,9 +49,12 @@ SOUP_MESSAGE_STATUS_CODE SOUP_MESSAGE_REASON_PHRASE SOUP_MESSAGE_SERVER_SIDE SOUP_MESSAGE_FIRST_PARTY +SOUP_MESSAGE_PRIORITY SOUP_MESSAGE_REQUEST_BODY +SOUP_MESSAGE_REQUEST_BODY_DATA SOUP_MESSAGE_REQUEST_HEADERS SOUP_MESSAGE_RESPONSE_BODY +SOUP_MESSAGE_RESPONSE_BODY_DATA SOUP_MESSAGE_RESPONSE_HEADERS SOUP_MESSAGE_TLS_CERTIFICATE SOUP_MESSAGE_TLS_ERRORS @@ -193,13 +201,14 @@ SOUP_STATUS_IS_SUCCESSFUL SOUP_STATUS_IS_REDIRECTION SOUP_STATUS_IS_CLIENT_ERROR SOUP_STATUS_IS_SERVER_ERROR -SoupKnownStatusCode +SoupStatus soup_status_get_phrase soup_status_proxify <SUBSECTION> SOUP_HTTP_ERROR <SUBSECTION Private> soup_http_error_quark +SoupKnownStatusCode </SECTION> <SECTION> @@ -241,6 +250,8 @@ SOUP_SERVER_TLS_CERTIFICATE SOUP_SERVER_ASYNC_CONTEXT SOUP_SERVER_RAW_PATHS SOUP_SERVER_SERVER_HEADER +SOUP_SERVER_HTTP_ALIASES +SOUP_SERVER_HTTPS_ALIASES <SUBSECTION Standard> SOUP_SERVER SOUP_IS_SERVER @@ -370,6 +381,7 @@ SOUP_ADDRESS_NAME SOUP_ADDRESS_PHYSICAL SOUP_ADDRESS_PORT SOUP_ADDRESS_SOCKADDR +SOUP_ADDRESS_PROTOCOL <SUBSECTION Standard> SOUP_ADDRESS SOUP_IS_ADDRESS @@ -386,11 +398,26 @@ SoupAddressClass <TITLE>SoupSession</TITLE> SoupSession <SUBSECTION> +soup_session_new +soup_session_new_with_options +<SUBSECTION> +soup_session_request +soup_session_request_uri +soup_session_request_http +soup_session_request_http_uri +SoupRequestError +SOUP_REQUEST_ERROR +<SUBSECTION> SoupSessionCallback soup_session_queue_message soup_session_requeue_message soup_session_send_message soup_session_cancel_message +<SUBSECTION> +soup_session_send +soup_session_send_async +soup_session_send_finish +<SUBSECTION> soup_session_prefetch_dns soup_session_prepare_for_uri soup_session_abort @@ -410,15 +437,18 @@ soup_session_remove_feature_by_type soup_session_get_features soup_session_get_feature soup_session_get_feature_for_message +soup_session_has_feature <SUBSECTION> SOUP_SESSION_PROXY_URI +SOUP_SESSION_PROXY_RESOLVER SOUP_SESSION_MAX_CONNS SOUP_SESSION_MAX_CONNS_PER_HOST -SOUP_SESSION_USE_NTLM -SOUP_SESSION_SSL_CA_FILE -SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE SOUP_SESSION_TLS_DATABASE +SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE +SOUP_SESSION_SSL_CA_FILE +SOUP_SESSION_SSL_STRICT SOUP_SESSION_ASYNC_CONTEXT +SOUP_SESSION_USE_THREAD_CONTEXT SOUP_SESSION_TIMEOUT SOUP_SESSION_IDLE_TIMEOUT SOUP_SESSION_USER_AGENT @@ -427,10 +457,9 @@ SOUP_SESSION_ADD_FEATURE_BY_TYPE SOUP_SESSION_REMOVE_FEATURE_BY_TYPE SOUP_SESSION_ACCEPT_LANGUAGE SOUP_SESSION_ACCEPT_LANGUAGE_AUTO -SOUP_SESSION_SSL_STRICT SOUP_SESSION_HTTP_ALIASES SOUP_SESSION_HTTPS_ALIASES -SOUP_SESSION_USE_THREAD_CONTEXT +SOUP_SESSION_LOCAL_ADDRESS <SUBSECTION Standard> SOUP_IS_SESSION SOUP_IS_SESSION_CLASS @@ -440,11 +469,13 @@ SOUP_SESSION_GET_CLASS SOUP_TYPE_SESSION SoupSessionClass soup_session_get_type +soup_request_error_quark <SUBSECTION Private> SoupConnection SoupConnectionState SoupMessageQueue SoupMessageQueueItem +SOUP_SESSION_USE_NTLM </SECTION> <SECTION> @@ -521,6 +552,7 @@ soup_auth_get_info <SUBSECTION> soup_auth_authenticate soup_auth_is_authenticated +soup_auth_is_ready <SUBSECTION> soup_auth_get_authorization soup_auth_get_protection_space @@ -551,6 +583,23 @@ soup_auth_save_password </SECTION> <SECTION> +<FILE>soup-auth-manager</FILE> +<TITLE>SoupAuthManager</TITLE> +SoupAuthManager +SOUP_TYPE_AUTH_MANAGER +soup_auth_manager_use_auth +<SUBSECTION Standard> +SoupAuthManagerPrivate +SoupAuthManagerClass +SOUP_AUTH_MANAGER +SOUP_IS_AUTH_MANAGER +SOUP_AUTH_MANAGER_CLASS +SOUP_IS_AUTH_MANAGER_CLASS +SOUP_AUTH_MANAGER_GET_CLASS +soup_auth_manager_get_type +</SECTION> + +<SECTION> <FILE>soup-socket</FILE> <TITLE>SoupSocket</TITLE> SoupSocket @@ -571,6 +620,7 @@ soup_socket_is_connected <SUBSECTION> soup_socket_get_local_address soup_socket_get_remote_address +soup_socket_get_fd <SUBSECTION> SoupSocketIOStatus soup_socket_read @@ -599,12 +649,6 @@ SOUP_SOCKET_CLASS SOUP_IS_SOCKET_CLASS SOUP_SOCKET_GET_CLASS SoupSocketClass -<SUBSECTION Private> -soup_socket_get_fd -soup_socket_handshake_async -soup_socket_handshake_sync -soup_socket_get_gsocket -soup_socket_get_iostream </SECTION> <SECTION> @@ -631,6 +675,7 @@ SOUP_URI_SCHEME_HTTPS SOUP_URI_SCHEME_DATA SOUP_URI_SCHEME_FILE SOUP_URI_SCHEME_FTP +SOUP_URI_SCHEME_RESOURCE soup_uri_uses_default_port SOUP_URI_IS_VALID SOUP_URI_VALID_FOR_HTTP @@ -656,8 +701,6 @@ soup_uri_get_fragment <SUBSECTION Standard> SOUP_TYPE_URI soup_uri_get_type -<SUBSECTION Private> -uri_decoded_copy </SECTION> <SECTION> @@ -705,8 +748,6 @@ soup_add_completion soup_add_idle soup_add_io_watch soup_add_timeout -<SUBSECTION> -soup_ssl_supported <SUBSECTION Private> soup_date_copy SOUP_TYPE_DATE @@ -721,6 +762,7 @@ SOUP_CHAR_HTTP_SEPARATOR SOUP_CHAR_URI_GEN_DELIMS SOUP_CHAR_URI_PERCENT_ENCODED SOUP_CHAR_URI_SUB_DELIMS +soup_ssl_supported </SECTION> <SECTION> @@ -927,6 +969,27 @@ soup_multipart_get_type </SECTION> <SECTION> +<FILE>soup-multipart-input-stream</FILE> +<TITLE>SoupMultipartInputStream</TITLE> +SoupMultipartInputStream +soup_multipart_input_stream_new +<SUBSECTION> +soup_multipart_input_stream_get_headers +soup_multipart_input_stream_next_part +soup_multipart_input_stream_next_part_async +soup_multipart_input_stream_next_part_finish +<SUBSECTION Standard> +SOUP_IS_MULTIPART_INPUT_STREAM +SOUP_IS_MULTIPART_INPUT_STREAM_CLASS +SOUP_MULTIPART_INPUT_STREAM +SOUP_MULTIPART_INPUT_STREAM_CLASS +SOUP_MULTIPART_INPUT_STREAM_GET_CLASS +SOUP_TYPE_MULTIPART_INPUT_STREAM +soup_multipart_input_stream_get_type +SoupMultipartInputStreamPrivate +</SECTION> + +<SECTION> <FILE>soup-cookie-jar-text</FILE> <TITLE>SoupCookieJarText</TITLE> SoupCookieJarText @@ -945,52 +1008,21 @@ soup_cookie_jar_text_get_type </SECTION> <SECTION> -<INCLUDE>libsoup/soup-gnome.h</INCLUDE> -<FILE>soup-cookie-jar-sqlite</FILE> -<TITLE>SoupCookieJarSqlite</TITLE> -SoupCookieJarSqlite -soup_cookie_jar_sqlite_new +<FILE>soup-cookie-jar-db</FILE> +<TITLE>SoupCookieJarDB</TITLE> +SoupCookieJarDB +soup_cookie_jar_db_new <SUBSECTION> -SOUP_COOKIE_JAR_SQLITE_FILENAME -<SUBSECTION Standard> -SoupCookieJarSqliteClass -SOUP_COOKIE_JAR_SQLITE -SOUP_COOKIE_JAR_SQLITE_CLASS -SOUP_COOKIE_JAR_SQLITE_GET_CLASS -SOUP_TYPE_COOKIE_JAR_SQLITE -SOUP_IS_COOKIE_JAR_SQLITE -SOUP_IS_COOKIE_JAR_SQLITE_CLASS -soup_cookie_jar_sqlite_get_type -</SECTION> - -<SECTION> -<FILE>soup-proxy-uri-resolver</FILE> -<TITLE>SoupProxyURIResolver</TITLE> -SoupProxyURIResolver -SoupProxyURIResolverCallback -soup_proxy_uri_resolver_get_proxy_uri_async -soup_proxy_uri_resolver_get_proxy_uri_sync -<SUBSECTION Standard> -SoupProxyURIResolverInterface -SOUP_IS_PROXY_URI_RESOLVER -SOUP_IS_PROXY_URI_RESOLVER_CLASS -SOUP_PROXY_URI_RESOLVER -SOUP_PROXY_URI_RESOLVER_CLASS -SOUP_PROXY_URI_RESOLVER_GET_CLASS -SOUP_TYPE_PROXY_URI_RESOLVER -soup_proxy_uri_resolver_get_type -</SECTION> - -<SECTION> -<INCLUDE>libsoup/soup-gnome.h</INCLUDE> -<FILE>soup-gnome-features</FILE> -SOUP_TYPE_GNOME_FEATURES_2_26 -SOUP_TYPE_PROXY_RESOLVER_GNOME +SOUP_COOKIE_JAR_DB_FILENAME <SUBSECTION Standard> -soup_proxy_resolver_gnome_get_type -soup_gnome_features_2_26_get_type -SOUP_TYPE_PASSWORD_MANAGER_GNOME -soup_password_manager_gnome_get_type +SoupCookieJarDBClass +SOUP_COOKIE_JAR_DB +SOUP_COOKIE_JAR_DB_CLASS +SOUP_COOKIE_JAR_DB_GET_CLASS +SOUP_TYPE_COOKIE_JAR_DB +SOUP_IS_COOKIE_JAR_DB +SOUP_IS_COOKIE_JAR_DB_CLASS +soup_cookie_jar_db_get_type </SECTION> <SECTION> @@ -999,6 +1031,7 @@ soup_password_manager_gnome_get_type SoupContentSniffer soup_content_sniffer_new soup_content_sniffer_sniff +soup_content_sniffer_get_buffer_size <SUBSECTION Standard> SOUP_CONTENT_SNIFFER SOUP_CONTENT_SNIFFER_CLASS @@ -1033,11 +1066,7 @@ SOUP_CACHE_GET_CLASS SoupCacheClass SoupCachePrivate <SUBSECTION Private> -soup_cache_generate_conditional_request -soup_cache_get_cacheability soup_cache_get_type -soup_cache_has_response -soup_cache_send_response SoupCacheResponse SoupCacheability </SECTION> @@ -1074,30 +1103,6 @@ SOUP_IS_PROXY_RESOLVER_DEFAULT_CLASS </SECTION> <SECTION> -<FILE>soup-requester</FILE> -<TITLE>SoupRequester</TITLE> -SoupRequester -soup_requester_new -soup_requester_request -soup_requester_request_uri -<SUBSECTION> -SoupRequesterError -SOUP_REQUESTER_ERROR -<SUBSECTION Standard> -soup_requester_get_type -SoupRequesterClass -SoupRequesterPrivate -SOUP_TYPE_REQUESTER -SOUP_REQUESTER -SOUP_REQUESTER_CLASS -SOUP_REQUESTER_GET_CLASS -SOUP_IS_REQUESTER -SOUP_IS_REQUESTER_CLASS -<SUBSECTION Private> -soup_requester_error_quark -</SECTION> - -<SECTION> <FILE>soup-request</FILE> <TITLE>SoupRequest</TITLE> SoupRequest @@ -1186,3 +1191,72 @@ SoupTLDError <SUBSECTION Private> soup_tld_error_quark </SECTION> + +<SECTION> +<FILE>soup-version</FILE> +<TITLE>Version Information</TITLE> +soup_get_major_version +soup_get_minor_version +soup_get_micro_version +soup_check_version +<SUBSECTION> +SOUP_MAJOR_VERSION +SOUP_MINOR_VERSION +SOUP_MICRO_VERSION +SOUP_CHECK_VERSION +<SUBSECTION> +SOUP_VERSION_MIN_REQUIRED +SOUP_VERSION_MAX_ALLOWED +SOUP_VERSION_2_24 +SOUP_VERSION_2_26 +SOUP_VERSION_2_28 +SOUP_VERSION_2_30 +SOUP_VERSION_2_32 +SOUP_VERSION_2_34 +SOUP_VERSION_2_36 +SOUP_VERSION_2_38 +SOUP_VERSION_2_40 +SOUP_VERSION_2_42 +SOUP_VERSION_2_44 +SOUP_VERSION_2_46 +<SUBSECTION Private> +SOUP_AVAILABLE_IN_2_24 +SOUP_AVAILABLE_IN_2_26 +SOUP_AVAILABLE_IN_2_28 +SOUP_AVAILABLE_IN_2_30 +SOUP_AVAILABLE_IN_2_32 +SOUP_AVAILABLE_IN_2_34 +SOUP_AVAILABLE_IN_2_36 +SOUP_AVAILABLE_IN_2_38 +SOUP_AVAILABLE_IN_2_40 +SOUP_AVAILABLE_IN_2_42 +SOUP_AVAILABLE_IN_2_44 +SOUP_AVAILABLE_IN_2_46 +SOUP_DEPRECATED_IN_2_24 +SOUP_DEPRECATED_IN_2_24_FOR +SOUP_DEPRECATED_IN_2_26 +SOUP_DEPRECATED_IN_2_26_FOR +SOUP_DEPRECATED_IN_2_28 +SOUP_DEPRECATED_IN_2_28_FOR +SOUP_DEPRECATED_IN_2_30 +SOUP_DEPRECATED_IN_2_30_FOR +SOUP_DEPRECATED_IN_2_32 +SOUP_DEPRECATED_IN_2_32_FOR +SOUP_DEPRECATED_IN_2_34 +SOUP_DEPRECATED_IN_2_34_FOR +SOUP_DEPRECATED_IN_2_36 +SOUP_DEPRECATED_IN_2_36_FOR +SOUP_DEPRECATED_IN_2_38 +SOUP_DEPRECATED_IN_2_38_FOR +SOUP_DEPRECATED_IN_2_40 +SOUP_DEPRECATED_IN_2_40_FOR +SOUP_DEPRECATED_IN_2_42 +SOUP_DEPRECATED_IN_2_42_FOR +SOUP_DEPRECATED_IN_2_44 +SOUP_DEPRECATED_IN_2_44_FOR +SOUP_DEPRECATED_IN_2_46 +SOUP_DEPRECATED_IN_2_46_FOR +SOUP_ENCODE_VERSION +SOUP_VERSION_CUR_STABLE +SOUP_VERSION_PREV_STABLE +</SECTION> diff --git a/docs/reference/request-howto.xml b/docs/reference/request-howto.xml new file mode 100644 index 00000000..55a46431 --- /dev/null +++ b/docs/reference/request-howto.xml @@ -0,0 +1,184 @@ +<?xml version="1.0"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"> +<refentry id="libsoup-request-howto"> +<refmeta> +<refentrytitle>libsoup Client SoupRequest API</refentrytitle> +<manvolnum>3</manvolnum> +<refmiscinfo>LIBSOUP Library</refmiscinfo> +</refmeta> + +<refnamediv> +<refname>libsoup Client SoupRequest API</refname><refpurpose>Using +libsoup with a mix of <literal>http</literal> and non-<literal>http</literal> URIs.</refpurpose> +</refnamediv> + +<refsect2> +<title><type>SoupRequest</type></title> + +<para> +<link linkend="SoupRequest"><type>SoupRequest</type></link> is an +abstract type representing a request for a particular URI. The +<type>SoupRequest</type> API is an alternative to the <link +linkend="SoupMessage"><type>SoupMessage</type></link>-based <link +linkend="SoupSession"><type>SoupSession</type></link> APIs which may be +useful to programs that want to deal with multiple kinds of URIs. +</para> + +<para> +<type>SoupRequest</type> officially became part of the +<application>libsoup</application> API in 2.42 with the addition of +<link +linkend="soup-session-request"><function>soup_session_request</function></link> +and the related functions. However, parts of it are also available as +far back as <application>libsoup</application> 2.34 via the +(now-deprecated) <type>SoupRequester</type> session feature, if you +define <literal>LIBSOUP_USE_UNSTABLE_REQUEST_API</literal> before +including the <application>libsoup</application> headers. +</para> + +<para> +Additionally, before <application>libsoup</application> 2.42, the +<type>SoupRequest</type> API was the only way to stream an HTTP +response body via <link +linkend="GInputStream"><type>GInputStream</type></link>. As of 2.42, +there are streaming APIs based on <type>SoupMessage</type> (<link +linkend="soup-session-send"><function>soup_session_send</function></link> +and <link +linkend="soup-session-send-async"><function>soup_session_send_async</function></link>), +so applications that are using <type>SoupRequest</type> with only +<literal>http</literal> and <literal>https</literal> URIs can be +ported to those APIs now. +</para> + +</refsect2> + +<refsect2> +<title>Creating a SoupRequest</title> + +<para> +There are four <type>SoupSession</type> methods for creating +<type>SoupRequest</type>s: +</para> + +<itemizedlist> + <listitem> + <para> + <link linkend="soup-session-request"><function>soup_session_request</function></link> + takes an arbitrary URI as a string, and returns a <link + linkend="SoupRequest"><type>SoupRequest</type></link>. + </para> + </listitem> + <listitem> + <para> + <link linkend="soup-session-request-uri"><function>soup_session_request_uri</function></link> + takes an arbitrary URI as a <link linkend="SoupURI"><type>SoupURI</type></link>, + and returns a <link linkend="SoupRequest"><type>SoupRequest</type></link>. + </para> + </listitem> + <listitem> + <para> + <link linkend="soup-session-request-http"><function>soup_session_request_http</function></link> + takes an HTTP method and an <literal>http</literal> or <literal>https</literal> URI as a string, and returns a <link + linkend="SoupRequestHTTP"><type>SoupRequestHTTP</type></link>. + </para> + </listitem> + <listitem> + <para> + <link linkend="soup-session-request-http-uri"><function>soup_session_request_http_uri</function></link> + takes an HTTP method and an <literal>http</literal> or <literal>https</literal> URI as a <link linkend="SoupURI"><type>SoupURI</type></link>, + and returns a <link linkend="SoupRequestHTTP"><type>SoupRequestHTTP</type></link>. + </para> + </listitem> +</itemizedlist> + +</refsect2> + +<refsect2> +<title>Sending a SoupRequest</title> + +<para> +Once you have created a <type>SoupRequest</type>, you can send it with +either <link +linkend="soup-request-send"><function>soup_request_send</function></link> +or <link +linkend="soup-request-send-async"><function>soup_request_send_async</function></link>. +This will provide you with a <link +linkend="GInputStream"><type>GInputStream</type></link> which you can +read to get the response body. +</para> + +<para> +After sending, you can use <link +linkend="soup-request-get-content-length"><function>soup_request_get_content_length</function></link> +and <link +linkend="soup-request-get-content-type"><function>soup_request_get_content_type</function></link> +to get information about the response body. +</para> + +<para> +As with the streaming <type>SoupMessage</type>-based APIs, +<function>soup_request_send</function> and +<function>soup_request_send_async</function> only return errors if a +transport-level problem occurs (eg, it could not connect to the host, +or the request was cancelled). In the case of an HTTP request, use the +message's <structfield>status_code</structfield> field to determine +whether the request was successful or not at the HTTP level (ie, "<literal>200 +OK</literal>" vs "<literal>401 Bad Request</literal>"). (You can call <link +linkend="soup-request-http-get-message"><function>soup_request_http_get_message</function></link> +to get the request's corresponding <link +linkend="SoupMessage"><type>SoupMessage</type></link>, to look at the +status code or other HTTP metadata.) +</para> + +</refsect2> + +<refsect2> +<title>Supported URI types, and adding your own</title> + +<para> +Different URI types are implemented by different subclasses of +<type>SoupRequest</type>. <application>libsoup</application> currently +implements three <type>SoupRequest</type> classes: +</para> + +<variablelist> + <varlistentry> + <term><link linkend="SoupRequestHTTP"><type>SoupRequestHTTP</type></link></term> + <listitem><para> + Handles <literal>http</literal> and + <literal>https</literal> URI. + </para></listitem> + </varlistentry> + <varlistentry> + <term><link linkend="SoupRequestData"><type>SoupRequestData</type></link></term> + <listitem><para> + Handles <literal>data</literal> URIs containing inline data. + </para></listitem> + </varlistentry> + <varlistentry> + <term><link linkend="SoupRequestFile"><type>SoupRequestFile</type></link></term> + <listitem><para> + Handles <literal>file</literal> and + <link linkend="GResource"><literal>resource</literal></link> URIs. + If you request a URI corresponding to a directory, this + will generate an HTML listing of the directory. + </para></listitem> + </varlistentry> +</variablelist> + +<para> +You can add additional URI types by implementing your own +<type>SoupRequest</type> subclass; set the +<type>SoupRequestClass</type>'s <structfield>schemes</structfield> +field to point to a <literal>NULL</literal>-terminated array of scheme +names, implement the various <type>SoupRequest</type> methods, and +then register the type with your <type>SoupSession</type> by calling +<link linkend="soup-session-add-feature-by-type"><function>soup_session_add_feature_by_type</function></link> +and passing the <link linkend="GType"><type>GType</type></link> of +your request class. +</para> + +</refsect2> + +</refentry> diff --git a/docs/reference/server-howto.xml b/docs/reference/server-howto.xml index 76c19182..05fb0c05 100644 --- a/docs/reference/server-howto.xml +++ b/docs/reference/server-howto.xml @@ -100,7 +100,7 @@ to set a callback to handle certain URI paths. <informalexample><programlisting> soup_server_add_handler (server, "/foo", server_callback, - data, destroy_notify); + data, destroy_notify); </programlisting></informalexample> <para> @@ -131,11 +131,11 @@ A handler callback looks something like this: <informalexample><programlisting> static void server_callback (SoupServer *server, - SoupMessage *msg, - const char *path, - GHashTable *query, - SoupClientContext *client, - gpointer user_data) + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *client, + gpointer user_data) { ... } @@ -192,11 +192,11 @@ data available at once. <informalexample><programlisting> static void server_callback (SoupServer *server, - SoupMessage *msg, - const char *path, - GHashTable *query, - SoupClientContext *client, - gpointer user_data) + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *client, + gpointer user_data) { MyServerData *server_data = user_data; const char *mime_type; @@ -219,7 +219,7 @@ server_callback (SoupServer *server, soup_message_set_status (msg, SOUP_STATUS_OK); soup_message_set_response (msg, mime_type, SOUP_MEMORY_COPY, - body->data, body->len); + body->data, body->len); } </programlisting></informalexample> @@ -264,7 +264,7 @@ is emitted indicating that the previous one was written successfully.) <para> The <emphasis role="bold"><literal>simple-proxy</literal></emphasis> -example in the <literal>tests/</literal> directory gives an example of +example in the <literal>examples/</literal> directory gives an example of using <literal>chunked</literal> encoding. </para> @@ -308,8 +308,8 @@ passed to the <literal>server_callback</literal>: <informalexample><programlisting> static gboolean auth_callback (SoupAuthDomain *domain, SoupMessage *msg, - const char *username, const char *password, - gpointer user_data) + const char *username, const char *password, + gpointer user_data) { MyServerData *server_data = user_data; MyUserData *user; @@ -360,8 +360,8 @@ domains. <para> If you want to require authentication for some requests under a certain path, but not all of them (eg, you want to authenticate -<literal>PUT</literal>s, but not <literal>GET</literal>s), use a -<link +<literal>PUT</literal> requests, but not <literal>GET</literal> +requests), use a <link linkend="SoupAuthDomainFilter"><type>SoupAuthDomainFilter</type></link>. </para> diff --git a/docs/reference/session-porting.xml b/docs/reference/session-porting.xml new file mode 100644 index 00000000..67a433a1 --- /dev/null +++ b/docs/reference/session-porting.xml @@ -0,0 +1,221 @@ +<?xml version="1.0"?> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"> +<refentry id="libsoup-session-porting"> +<refmeta> +<refentrytitle>Porting to the new SoupSession</refentrytitle> +<manvolnum>3</manvolnum> +<refmiscinfo>LIBSOUP Library</refmiscinfo> +</refmeta> + +<refnamediv> +<refname>Porting to the new SoupSession</refname><refpurpose>Notes on +porting from SoupSessionAsync and SoupSessionSync to SoupSession</refpurpose> +</refnamediv> + +<refsect2 id="intro"> +<title>Introduction</title> + +<para> +As of libsoup 2.42, <link +linkend="SoupSession"><type>SoupSession</type></link> is no longer an +abstract class, and the base <type>SoupSession</type> class is now +preferred over its traditional subclasses, <link +linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link> and +<link linkend="SoupSessionSync"><type>SoupSessionSync</type></link>. +</para> + +<para> +There are several changes in behavior between the old and new sessions +to be aware of. +</para> + +</refsect2> + +<refsect2 id="defaults"> +<title>Different defaults</title> + +<para> +The new <link linkend="SoupSession"><type>SoupSession</type></link> +has different (and hopefully better) defaults than <link +linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link> and +<link linkend="SoupSessionSync"><type>SoupSessionSync</type></link>: +</para> + +<itemizedlist> + <listitem> + <para> + The system TLS/SSL certificate database is used by default to + validate https certificates, and sites with invalid certificates + will refuse to load with a + <link linkend="SOUP-STATUS-SSL-FAILED:CAPS"><literal>SOUP_STATUS_SSL_FAILED</literal></link> + error. + </para> + <para> + You can still override the CA database as before, by setting the + <link linkend="SoupSession--ssl-ca-file"><type>"ssl-ca-file"</type></link> + property, although the + <link linkend="SoupSession--tls-database"><type>"tls-database"</type></link> + property is preferred, since it allows you to do proper error + handling. + </para> + <para> + If you want to accept all certificates, set + <link linkend="SoupSession--ssl-strict"><type>"ssl-strict"</type></link> to + <literal>FALSE</literal>. Note that libsoup will still check + certificates, it will just continue with the HTTP request even + if the certificate fails to validate. You can use + <link linkend="soup-message-get-https-status"><function>soup_message_get_https_status()</function></link> + to look at the certificate after the fact. + </para> + </listitem> + <listitem> + <para> + The + <link linkend="SoupSession--timeout"><type>"timeout"</type></link> + and + <link linkend="SoupSession--idle-timeout"><type>"idle-timeout"</type></link> + properties both default to 60 seconds. + </para> + </listitem> + <listitem> + <para> + The + <link linkend="SoupSession--http-aliases"><type>"http-aliases"</type></link> + property defaults to <literal>NULL</literal>, meaning that URI + schemes like "<literal>webcal</literal>" and + "<literal>dav</literal>" (and "<literal>ftp</literal>") are not + considered to be aliases for "<literal>http</literal>", and so + libsoup will not accept requests for such URIs, and will not + follow redirects to such URIs. + </para> + </listitem> + <listitem> + <para> + The new + <link linkend="SoupSession--proxy-resolver"><type>"proxy-resolver"</type></link> + property is now initialized to the default + <link linkend="GProxyResolver"><type>GProxyResolver</type></link>, + meaning that it will automatically use the user's system proxy + configuration. This replaces the use of the + <link linkend="SoupProxyResolverDefault"><type>SoupProxyResolverDefault</type></link>, + session feature in earlier releases. You can set this property to + <literal>NULL</literal> if you don't want to use proxies, and the + <link linkend="SoupSession--proxy-uri"><type>"proxy-uri"</type></link> + property still works if you want to use a single proxy for all requests. + </para> + </listitem> + <listitem> + <para> + Every session gets a + <link linkend="SoupContentDecoder"><type>SoupContentDecoder</type></link> + attached to it by default, meaning that it will automatically + handle (and request) "gzip"- and "deflate"-encoded response + bodies. + </para> + </listitem> +</itemizedlist> + +</refsect2> + +<refsect2 id="behavior"> +<title>Differences in feature behavior</title> + +<para> +If you are using NTLM authentication, the new <type>SoupSession</type> +behaves slightly differently from the old session types. +</para> + +<para> +First, the deprecated <link +linkend="SOUP-SESSION-USE-NTLM:CAPS"><literal>SOUP_SESSION_USE_NTLM</literal></link> +property is no longer supported. If you want to add support for NTLM +to a session, call <link +linkend="soup-session-add-feature-by-type"><function>soup_session_add_feature_by_type()</function></link>, +passing <link +linkend="SOUP-TYPE-AUTH-NTLM:CAPS"><literal>SOUP_TYPE_AUTH_NTLM</literal></link>. +</para> + +<para> +Second, with the old session types, enabling NTLM would cause all +(otherwise-unauthenticated) requests to be sent with an NTLM request +in the <literal>Authorization</literal> header. That is, libsoup would +assume that all servers supported NTLM, and would attempt to begin +negotiating NTLM authentication before the server ever returned a 401 +response. With the plain <type>SoupSession</type>, this no longer +happens. If you want the old behavior, you need to call <link +linkend="soup-auth-manager-use-auth"><function>soup_auth_manager_use_auth()</function></link> +for each host to "preload" the NTLM authentication: +</para> + +<informalexample><programlisting> + SoupAuthManager *auth_manager; + SoupAuth *auth; + SoupURI *uri; + + auth_manager = SOUP_AUTH_MANAGER (soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER)); + auth = g_object_new (SOUP_TYPE_AUTH_NTLM, NULL); + uri = soup_uri_new ("http://ntlm-using-host.example.com/"); + soup_auth_manager_use_auth (auth_manager, uri, auth); + g_object_unref (auth); + soup_uri_free (auth); +</programlisting></informalexample> + +</refsect2> + +<refsect2 id="apis"> +<title>Differences in SoupMessage-sending APIs</title> + +<para> +<type>SoupSessionAsync</type> always uses asynchronous I/O, and +<type>SoupSessionSync</type> always uses blocking I/O, regardless of +the operation. In the new <type>SoupSession</type>, <link +linkend="soup-session-queue-message"><function>soup_session_queue_message()</function></link> +uses asynchronous I/O (like <type>SoupSessionAsync</type>), and <link +linkend="soup-session-send-message"><function>soup_session_send_message()</function></link> +uses blocking I/O (like <type>SoupSessionSync</type>). There is no API +on the plain <type>SoupSession</type> that simulates the effect of +calling <function>soup_session_send_message()</function> on a +<type>SoupSessionAsync</type> (ie, running the main loop internally), +or of calling <function>soup_session_queue_message()</function> on a +<type>SoupSessionSync</type> (ie, automatically sending the request in +another thread). +</para> + +</refsect2> + +<refsect2 id="async"> +<title>Differences in Asynchronous I/O</title> + +<para> +As compared to <link +linkend="SoupSessionAsync"><type>SoupSessionAsync</type></link>, <link +linkend="SoupSession"><type>SoupSession</type></link> behaves more +like gio with respect to asynchronous I/O. +</para> + +<para> +In particular, the <link +linkend="SoupSession--async-context"><type>"async-context"</type></link> +and <link +linkend="SoupSession--use-thread-context"><type>"use-thread-context"</type></link> +properties are now effectively unused, and the session always queues +asynchronous requests in the <link +linkend="GMainContext"><type>GMainContext</type></link> that was is +the thread default when the asynchronous operation is started. Session +bookkeeping tasks (like closing idle connections) happen in the +context that was thread default when the session was created. +</para> + +<para> +Additionally, <link +linkend="soup-session-cancel-message"><function>soup_session_cancel_message()</function></link> +now acts asynchronously when you cancel an asynchronous request; +rather than having the request's callback be called from inside +<function>soup_session_cancel_message()</function>, it just gets called +when you need return to the main loop. +</para> + +</refsect2> + +</refentry> diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 00000000..1609ff24 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,14 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DSRCDIR=\""$(abs_srcdir)"\" \ + -DBUILDDIR=\""$(builddir)"\" \ + $(GLIB_CFLAGS) + +LIBS = \ + $(top_builddir)/libsoup/libsoup-2.4.la \ + $(GLIB_LIBS) + +noinst_PROGRAMS = \ + get \ + simple-httpd \ + simple-proxy diff --git a/examples/get.c b/examples/get.c new file mode 100644 index 00000000..a8888d49 --- /dev/null +++ b/examples/get.c @@ -0,0 +1,195 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2001-2003, Ximian, Inc. + * Copyright (C) 2013 Igalia, S.L. + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <libsoup/soup.h> + +static SoupSession *session; +static GMainLoop *loop; +static gboolean debug, head, quiet; +static const gchar *output_file_path = NULL; + +static void +finished (SoupSession *session, SoupMessage *msg, gpointer loop) +{ + g_main_loop_quit (loop); +} + +static void +get_url (const char *url) +{ + const char *name; + SoupMessage *msg; + const char *header; + FILE *output_file = NULL; + + msg = soup_message_new (head ? "HEAD" : "GET", url); + soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT); + + if (loop) { + g_object_ref (msg); + soup_session_queue_message (session, msg, finished, loop); + g_main_loop_run (loop); + } else + soup_session_send_message (session, msg); + + name = soup_message_get_uri (msg)->path; + + if (!debug) { + if (msg->status_code == SOUP_STATUS_SSL_FAILED) { + GTlsCertificateFlags flags; + + if (soup_message_get_https_status (msg, NULL, &flags)) + g_print ("%s: %d %s (0x%x)\n", name, msg->status_code, msg->reason_phrase, flags); + else + g_print ("%s: %d %s (no handshake status)\n", name, msg->status_code, msg->reason_phrase); + } else if (!quiet || SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) + g_print ("%s: %d %s\n", name, msg->status_code, msg->reason_phrase); + } + + if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) { + header = soup_message_headers_get_one (msg->response_headers, + "Location"); + if (header) { + SoupURI *uri; + char *uri_string; + + if (!debug && !quiet) + g_print (" -> %s\n", header); + + uri = soup_uri_new_with_base (soup_message_get_uri (msg), header); + uri_string = soup_uri_to_string (uri, FALSE); + get_url (uri_string); + g_free (uri_string); + soup_uri_free (uri); + } + } else if (!head && SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { + if (output_file_path) { + output_file = fopen (output_file_path, "w"); + if (!output_file) + g_printerr ("Error trying to create file %s.\n", output_file_path); + } else if (!quiet) + output_file = stdout; + + if (output_file) { + fwrite (msg->response_body->data, + 1, + msg->response_body->length, + output_file); + + if (output_file_path) + fclose (output_file); + } + } +} + +static const char *ca_file, *proxy; +static gboolean synchronous, ntlm; + +static GOptionEntry entries[] = { + { "ca-file", 'c', 0, + G_OPTION_ARG_STRING, &ca_file, + "Use FILE as the TLS CA file", "FILE" }, + { "debug", 'd', 0, + G_OPTION_ARG_NONE, &debug, + "Show HTTP headers", NULL }, + { "head", 'h', 0, + G_OPTION_ARG_NONE, &head, + "Do HEAD rather than GET", NULL }, + { "ntlm", 'n', 0, + G_OPTION_ARG_NONE, &ntlm, + "Use NTLM authentication", NULL }, + { "output", 'o', 0, + G_OPTION_ARG_STRING, &output_file_path, + "Write the received data to FILE instead of stdout", "FILE" }, + { "proxy", 'p', 0, + G_OPTION_ARG_STRING, &proxy, + "Use URL as an HTTP proxy", "URL" }, + { "quiet", 'q', 0, + G_OPTION_ARG_NONE, &quiet, + "Don't show HTTP status code", NULL }, + { "sync", 's', 0, + G_OPTION_ARG_NONE, &synchronous, + "Use SoupSessionSync rather than SoupSessionAsync", NULL }, + { NULL } +}; + +int +main (int argc, char **argv) +{ + GOptionContext *opts; + const char *url; + SoupURI *proxy_uri, *parsed; + GError *error = NULL; + SoupLogger *logger = NULL; + + opts = g_option_context_new (NULL); + g_option_context_add_main_entries (opts, entries, NULL); + if (!g_option_context_parse (opts, &argc, &argv, &error)) { + g_printerr ("Could not parse arguments: %s\n", + error->message); + g_printerr ("%s", + g_option_context_get_help (opts, TRUE, NULL)); + exit (1); + } + + if (argc != 2) { + g_printerr ("%s", + g_option_context_get_help (opts, TRUE, NULL)); + exit (1); + } + g_option_context_free (opts); + + url = argv[1]; + parsed = soup_uri_new (url); + if (!parsed) { + g_printerr ("Could not parse '%s' as a URL\n", url); + exit (1); + } + soup_uri_free (parsed); + + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_SSL_CA_FILE, ca_file, + SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER, + SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR, + SOUP_SESSION_USER_AGENT, "get ", + SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE, + NULL); + if (ntlm) + soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM); + + if (debug) { + logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1); + soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger)); + g_object_unref (logger); + } + + if (proxy) { + proxy_uri = soup_uri_new (proxy); + if (!proxy_uri) { + g_printerr ("Could not parse '%s' as URI\n", + proxy); + exit (1); + } + + g_object_set (G_OBJECT (session), + SOUP_SESSION_PROXY_URI, proxy_uri, + NULL); + soup_uri_free (proxy_uri); + } + + if (!synchronous) + loop = g_main_loop_new (NULL, TRUE); + + get_url (url); + + if (!synchronous) + g_main_loop_unref (loop); + + return 0; +} diff --git a/tests/simple-httpd.c b/examples/simple-httpd.c index 46f0988d..71ff874c 100644 --- a/tests/simple-httpd.c +++ b/examples/simple-httpd.c @@ -3,32 +3,14 @@ * Copyright (C) 2001-2003, Ximian, Inc. */ -#include "test-utils.h" - #include <dirent.h> #include <errno.h> -#include <fcntl.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <sys/stat.h> -#ifdef HAVE_MMAP -#include <sys/mman.h> -#endif - -#ifdef HAVE_MMAP -struct mapping { - void *start; - size_t length; -}; - -static void -free_mapping (gpointer data) -{ - struct mapping *mapping = data; - munmap (mapping->start, mapping->length); - g_slice_free (struct mapping, mapping); -} -#endif +#include <libsoup/soup.h> static int compare_strings (gconstpointer a, gconstpointer b) @@ -87,7 +69,6 @@ do_get (SoupServer *server, SoupMessage *msg, const char *path) { char *slash; struct stat st; - int fd; if (stat (path, &st) == -1) { if (errno == EPERM) @@ -130,40 +111,27 @@ do_get (SoupServer *server, SoupMessage *msg, const char *path) return; } - fd = open (path, O_RDONLY); - if (fd == -1) { - soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); - return; - } - if (msg->method == SOUP_METHOD_GET) { -#ifdef HAVE_MMAP - struct mapping *mapping = g_slice_new (struct mapping); + GMappedFile *mapping; SoupBuffer *buffer; - mapping->start = mmap (NULL, st.st_size, PROT_READ, - MAP_PRIVATE, fd, 0); - mapping->length = st.st_size; - buffer = soup_buffer_new_with_owner (mapping->start, - mapping->length, - mapping, free_mapping); + mapping = g_mapped_file_new (path, FALSE, NULL); + if (!mapping) { + soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + return; + } + + buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping), + g_mapped_file_get_length (mapping), + mapping, (GDestroyNotify)g_mapped_file_unref); soup_message_body_append_buffer (msg->response_body, buffer); soup_buffer_free (buffer); -#else - char *buf; - - buf = g_malloc (st.st_size); - read (fd, buf, st.st_size); - close (fd); - soup_message_body_append (msg->response_body, SOUP_MEMORY_TAKE, - buf, st.st_size); -#endif } else /* msg->method == SOUP_METHOD_HEAD */ { char *length; /* We could just use the same code for both GET and - * HEAD. But we'll optimize and avoid the extra - * malloc. + * HEAD (soup-message-server-io.c will fix things up). + * But we'll optimize and avoid the extra I/O. */ length = g_strdup_printf ("%lu", (gulong)st.st_size); soup_message_headers_append (msg->response_headers, @@ -245,40 +213,51 @@ quit (int sig) exit (0); } +static int port, ssl_port; +static const char *ssl_cert_file, *ssl_key_file; + +static GOptionEntry entries[] = { + { "cert-file", 'c', 0, + G_OPTION_ARG_STRING, &ssl_cert_file, + "Use FILE as the TLS certificate file", "FILE" }, + { "key-file", 'k', 0, + G_OPTION_ARG_STRING, &ssl_key_file, + "Use FILE as the TLS private key file", "FILE" }, + { "port", 'p', 0, + G_OPTION_ARG_INT, &port, + "Port to listen on", NULL }, + { "ssl-port", 's', 0, + G_OPTION_ARG_INT, &port, + "Port to listen on for TLS traffic", NULL }, + { NULL } +}; + int main (int argc, char **argv) { + GOptionContext *opts; GMainLoop *loop; SoupServer *server, *ssl_server; - int opt; - int port = SOUP_ADDRESS_ANY_PORT; - int ssl_port = SOUP_ADDRESS_ANY_PORT; - const char *ssl_cert_file = NULL, *ssl_key_file = NULL; + GError *error = NULL; + + opts = g_option_context_new (NULL); + g_option_context_add_main_entries (opts, entries, NULL); + if (!g_option_context_parse (opts, &argc, &argv, &error)) { + g_printerr ("Could not parse arguments: %s\n", + error->message); + g_printerr ("%s", + g_option_context_get_help (opts, TRUE, NULL)); + exit (1); + } + if (argc != 1) { + g_printerr ("%s", + g_option_context_get_help (opts, TRUE, NULL)); + exit (1); + } + g_option_context_free (opts); - g_type_init (); signal (SIGINT, quit); - while ((opt = getopt (argc, argv, "p:k:c:s:")) != -1) { - switch (opt) { - case 'p': - port = atoi (optarg); - break; - case 'k': - ssl_key_file = optarg; - break; - case 'c': - ssl_cert_file = optarg; - break; - case 's': - ssl_port = atoi (optarg); - break; - default: - g_printerr ("Usage: %s [-p port] [-c ssl-cert-file -k ssl-key-file [-s ssl-port]]\n", - argv[0]); - exit (1); - } - } - server = soup_server_new (SOUP_SERVER_PORT, port, SOUP_SERVER_SERVER_HEADER, "simple-httpd ", NULL); diff --git a/tests/simple-proxy.c b/examples/simple-proxy.c index f25f9a04..db8c9f96 100644 --- a/tests/simple-proxy.c +++ b/examples/simple-proxy.c @@ -3,7 +3,10 @@ * Copyright (C) 2001-2003, Ximian, Inc. */ -#include "test-utils.h" +#include <stdlib.h> +#include <string.h> + +#include <libsoup/soup.h> /* WARNING: this is really really really not especially compliant with * RFC 2616. But it does work for basic stuff. @@ -120,35 +123,44 @@ quit (int sig) exit (0); } +static int port; +static gboolean require_auth; + +static GOptionEntry entries[] = { + { "auth-domain", 'a', 0, + G_OPTION_ARG_NONE, &require_auth, + "Require authentication", NULL }, + { "port", 'p', 0, + G_OPTION_ARG_INT, &port, + "Port to listen on", NULL }, + { NULL } +}; + int main (int argc, char **argv) { + GOptionContext *opts; GMainLoop *loop; - int opt; - int port = SOUP_ADDRESS_ANY_PORT; - SoupAuthDomain *auth_domain = NULL; - - g_type_init (); - signal (SIGINT, quit); + GError *error = NULL; + + opts = g_option_context_new (NULL); + g_option_context_add_main_entries (opts, entries, NULL); + if (!g_option_context_parse (opts, &argc, &argv, &error)) { + g_printerr ("Could not parse arguments: %s\n", + error->message); + g_printerr ("%s", + g_option_context_get_help (opts, TRUE, NULL)); + exit (1); + } - while ((opt = getopt (argc, argv, "ap:")) != -1) { - switch (opt) { - case 'a': - auth_domain = soup_auth_domain_basic_new ( - SOUP_AUTH_DOMAIN_REALM, "simple-proxy", - SOUP_AUTH_DOMAIN_PROXY, TRUE, - SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback, - NULL); - break; - case 'p': - port = atoi (optarg); - break; - default: - g_printerr ("Usage: %s [-p port] [-n]\n", - argv[0]); - exit (1); - } + if (argc != 1) { + g_printerr ("%s", + g_option_context_get_help (opts, TRUE, NULL)); + exit (1); } + g_option_context_free (opts); + + signal (SIGINT, quit); server = soup_server_new (SOUP_SERVER_PORT, port, NULL); @@ -158,7 +170,14 @@ main (int argc, char **argv) } soup_server_add_handler (server, NULL, server_callback, NULL, NULL); - if (auth_domain) { + if (require_auth) { + SoupAuthDomain *auth_domain; + + auth_domain = soup_auth_domain_basic_new ( + SOUP_AUTH_DOMAIN_REALM, "simple-proxy", + SOUP_AUTH_DOMAIN_PROXY, TRUE, + SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, auth_callback, + NULL); soup_server_add_auth_domain (server, auth_domain); g_object_unref (auth_domain); } diff --git a/glib-tap.mk b/glib-tap.mk new file mode 100644 index 00000000..c97bf3f2 --- /dev/null +++ b/glib-tap.mk @@ -0,0 +1,134 @@ +# GLIB - Library of useful C routines + +TESTS_ENVIRONMENT= \ + G_TEST_SRCDIR="$(abs_srcdir)" \ + G_TEST_BUILDDIR="$(abs_builddir)" \ + G_DEBUG=gc-friendly \ + MALLOC_CHECK_=2 \ + MALLOC_PERTURB_=$$(($${RANDOM:-256} % 256)) +LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/tap-driver.sh +LOG_COMPILER = $(top_srcdir)/tap-test + +NULL = + +# initialize variables for unconditional += appending +BUILT_SOURCES = +BUILT_EXTRA_DIST = +CLEANFILES = *.log *.trs +DISTCLEANFILES = +MAINTAINERCLEANFILES = +EXTRA_DIST = +TESTS = + +installed_test_LTLIBRARIES = +installed_test_PROGRAMS = +installed_test_SCRIPTS = +nobase_installed_test_DATA = + +noinst_LTLIBRARIES = +noinst_PROGRAMS = +noinst_SCRIPTS = +noinst_DATA = + +check_LTLIBRARIES = +check_PROGRAMS = +check_SCRIPTS = +check_DATA = + +# We support a fairly large range of possible variables. It is expected that all types of files in a test suite +# will belong in exactly one of the following variables. +# +# First, we support the usual automake suffixes, but in lowercase, with the customary meaning: +# +# test_programs, test_scripts, test_data, test_ltlibraries +# +# The above are used to list files that are involved in both uninstalled and installed testing. The +# test_programs and test_scripts are taken to be actual testcases and will be run as part of the test suite. +# Note that _data is always used with the nobase_ automake variable name to ensure that installed test data is +# installed in the same way as it appears in the package layout. +# +# In order to mark a particular file as being only for one type of testing, use 'installed' or 'uninstalled', +# like so: +# +# installed_test_programs, uninstalled_test_programs +# installed_test_scripts, uninstalled_test_scripts +# installed_test_data, uninstalled_test_data +# installed_test_ltlibraries, uninstalled_test_ltlibraries +# +# Additionally, we support 'extra' infixes for programs and scripts. This is used for support programs/scripts +# that should not themselves be run as testcases (but exist to be used from other testcases): +# +# test_extra_programs, installed_test_extra_programs, uninstalled_test_extra_programs +# test_extra_scripts, installed_test_extra_scripts, uninstalled_test_extra_scripts +# +# Additionally, for _scripts and _data, we support the customary dist_ prefix so that the named script or data +# file automatically end up in the tarball. +# +# dist_test_scripts, dist_test_data, dist_test_extra_scripts +# dist_installed_test_scripts, dist_installed_test_data, dist_installed_test_extra_scripts +# dist_uninstalled_test_scripts, dist_uninstalled_test_data, dist_uninstalled_test_extra_scripts +# +# Note that no file is automatically disted unless it appears in one of the dist_ variables. This follows the +# standard automake convention of not disting programs scripts or data by default. +# +# test_programs, test_scripts, uninstalled_test_programs and uninstalled_test_scripts (as well as their disted +# variants) will be run as part of the in-tree 'make check'. These are all assumed to be runnable under +# gtester. That's a bit strange for scripts, but it's possible. + +TESTS += $(test_programs) $(test_scripts) $(uninstalled_test_programs) $(uninstalled_test_scripts) \ + $(dist_test_scripts) $(dist_uninstalled_test_scripts) + +# Note: build even the installed-only targets during 'make check' to ensure that they still work. +# We need to do a bit of trickery here and manage disting via EXTRA_DIST instead of using dist_ prefixes to +# prevent automake from mistreating gmake functions like $(wildcard ...) and $(addprefix ...) as if they were +# filenames, including removing duplicate instances of the opening part before the space, eg. '$(addprefix'. +all_test_programs = $(test_programs) $(uninstalled_test_programs) $(installed_test_programs) \ + $(test_extra_programs) $(uninstalled_test_extra_programs) $(installed_test_extra_programs) +all_test_scripts = $(test_scripts) $(uninstalled_test_scripts) $(installed_test_scripts) \ + $(test_extra_scripts) $(uninstalled_test_extra_scripts) $(installed_test_extra_scripts) +all_dist_test_scripts = $(dist_test_scripts) $(dist_uninstalled_test_scripts) $(dist_installed_test_scripts) \ + $(dist_test_extra_scripts) $(dist_uninstalled_test_extra_scripts) $(dist_installed_test_extra_scripts) +all_test_scripts += $(all_dist_test_scripts) +EXTRA_DIST += $(all_dist_test_scripts) +all_test_data = $(test_data) $(uninstalled_test_data) $(installed_test_data) +all_dist_test_data = $(dist_test_data) $(dist_uninstalled_test_data) $(dist_installed_test_data) +all_test_data += $(all_dist_test_data) +EXTRA_DIST += $(all_dist_test_data) +all_test_ltlibs = $(test_ltlibraries) $(uninstalled_test_ltlibraries) $(installed_test_ltlibraries) + +if ENABLE_ALWAYS_BUILD_TESTS +noinst_LTLIBRARIES += $(all_test_ltlibs) +noinst_PROGRAMS += $(all_test_programs) +noinst_SCRIPTS += $(all_test_scripts) +noinst_DATA += $(all_test_data) +else +check_LTLIBRARIES += $(all_test_ltlibs) +check_PROGRAMS += $(all_test_programs) +check_SCRIPTS += $(all_test_scripts) +check_DATA += $(all_test_data) +endif + +if ENABLE_INSTALLED_TESTS +installed_test_PROGRAMS += $(test_programs) $(installed_test_programs) \ + $(test_extra_programs) $(installed_test_extra_programs) +installed_test_SCRIPTS += $(test_scripts) $(installed_test_scripts) \ + $(test_extra_scripts) $(test_installed_extra_scripts) +installed_test_SCRIPTS += $(dist_test_scripts) $(dist_test_extra_scripts) \ + $(dist_installed_test_scripts) $(dist_installed_test_extra_scripts) +nobase_installed_test_DATA += $(test_data) $(installed_test_data) +nobase_installed_test_DATA += $(dist_test_data) $(dist_installed_test_data) +installed_test_LTLIBRARIES += $(test_ltlibraries) $(installed_test_ltlibraries) +installed_testcases = $(test_programs) $(installed_test_programs) \ + $(test_scripts) $(installed_test_scripts) \ + $(dist_test_scripts) $(dist_installed_test_scripts) + +installed_test_meta_DATA = $(installed_testcases:=.test) + +%.test: %$(EXEEXT) Makefile + $(AM_V_GEN) (echo '[Test]' > $@.tmp; \ + echo 'Type=session' >> $@.tmp; \ + echo 'Exec=$(installed_testdir)/$<' >> $@.tmp; \ + mv $@.tmp $@) + +CLEANFILES += $(installed_test_meta_DATA) +endif diff --git a/libsoup.doap b/libsoup.doap index 00028649..c39f2d5d 100644 --- a/libsoup.doap +++ b/libsoup.doap @@ -12,6 +12,7 @@ <download-page rdf:resource="http://download.gnome.org/sources/libsoup/"/> <bug-database rdf:resource="http://bugzilla.gnome.org/browse.cgi?product=libsoup"/> <mailing-list rdf:resource="http://mail.gnome.org/mailman/listinfo/libsoup-list"/> + <category rdf:resource="http://api.gnome.org/doap-extensions#platform"/> <maintainer> <foaf:Person> 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: diff --git a/m4/glibtests.m4 b/m4/glibtests.m4 new file mode 100644 index 00000000..7d5920a4 --- /dev/null +++ b/m4/glibtests.m4 @@ -0,0 +1,28 @@ +dnl GLIB_TESTS +dnl + +AC_DEFUN([GLIB_TESTS], +[ + AC_ARG_ENABLE(installed-tests, + AS_HELP_STRING([--enable-installed-tests], + [Enable installation of some test cases]), + [case ${enableval} in + yes) ENABLE_INSTALLED_TESTS="1" ;; + no) ENABLE_INSTALLED_TESTS="" ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-installed-tests]) ;; + esac]) + AM_CONDITIONAL([ENABLE_INSTALLED_TESTS], test "$ENABLE_INSTALLED_TESTS" = "1") + AC_ARG_ENABLE(always-build-tests, + AS_HELP_STRING([--enable-always-build-tests], + [Enable always building tests during 'make all']), + [case ${enableval} in + yes) ENABLE_ALWAYS_BUILD_TESTS="1" ;; + no) ENABLE_ALWAYS_BUILD_TESTS="" ;; + *) AC_MSG_ERROR([bad value ${enableval} for --enable-always-build-tests]) ;; + esac]) + AM_CONDITIONAL([ENABLE_ALWAYS_BUILD_TESTS], test "$ENABLE_ALWAYS_BUILD_TESTS" = "1") + if test "$ENABLE_INSTALLED_TESTS" = "1"; then + AC_SUBST(installed_test_metadir, [${datadir}/installed-tests/]AC_PACKAGE_NAME) + AC_SUBST(installed_testdir, [${libexecdir}/installed-tests/]AC_PACKAGE_NAME) + fi +]) @@ -1,22 +1,58 @@ +an as -bg be +bg +bn_IN +ca +ca@valencia +cs +da +de el +en_GB +eo es +et +eu +fa fr +fur gl +gu he +hi +hu id +it +ja +kn +ko lt +lv +ml +mr nb +nl +or pa pl +pt +pt_BR ro ru +sk sl sr sr@latin +sv +ta te +tg +th +tr +ug +uk +uz@cyrillic vi zh_CN zh_HK diff --git a/po/POTFILES.in b/po/POTFILES.in index a05aa79b..21c70d42 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,6 +1,9 @@ libsoup/soup-body-input-stream.c +libsoup/soup-cache-input-stream.c libsoup/soup-converter-wrapper.c +libsoup/soup-message-client-io.c libsoup/soup-message-io.c +libsoup/soup-message-server-io.c libsoup/soup-request.c -libsoup/soup-requester.c +libsoup/soup-session.c libsoup/soup-tld.c diff --git a/po/an.po b/po/an.po new file mode 100644 index 00000000..5d207a9a --- /dev/null +++ b/po/an.po @@ -0,0 +1,108 @@ +# Aragonese translation for libsoup. +# Copyright (C) 2013 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-19 15:37+0000\n" +"PO-Revision-Date: 2013-02-19 23:17+0100\n" +"Last-Translator: Daniel Martinez <entaltoaragon@gmail.com>\n" +"Language-Team: Aragonese <softaragones@googlegroups.com>\n" +"Language: an\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 +#: ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "A connexión remató inasperadament" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Petición de búsqueda no válida" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "No se puede truncar SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "O fluxo de ret se zarró inasperadament" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Falló en cachear completament o recurso" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "O búfer de salida ye masiau chicot" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "No se podió analisar a respuesta HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Codificación d'a respuesta HTTP no reconoixida" + +#: ../libsoup/soup-message-io.c:846 +#: ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "Se canceló a operación" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "A operación se blocará" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "No se podió analisar a solicitut HTTP" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "No s'ha proporcionau un URI" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "URI '%s' no válida: %s" + +#: ../libsoup/soup-session.c:4211 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "No se podió analisar l'URI «%s»" + +#: ../libsoup/soup-session.c:4248 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Esquema %s d'URI no suportau" + +#: ../libsoup/soup-session.c:4270 +#, c-format +msgid "Not an HTTP URI" +msgstr "No ye un URI HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "O nombre de l'equipo ye una adreza IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "O nombre de l'equipo no ye válido" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "O nombre de l'equipo no tiene un dominio base" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "No i hai suficients dominios" + @@ -2,71 +2,108 @@ # Copyright (C) 2012 libsoup's COPYRIGHT HOLDER # This file is distributed under the same license as the libsoup package. # -# Nilamdyuti Goswami <ngoswami@redhat.com>, 2012. +# Nilamdyuti Goswami <ngoswami@redhat.com>, 2012, 2013. msgid "" msgstr "" "Project-Id-Version: libsoup master\n" -"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug." -"cgi?product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-07-12 15:59+0000\n" -"PO-Revision-Date: 2012-07-13 20:11+0530\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-12 18:37+0530\n" "Last-Translator: Nilamdyuti Goswami <ngoswami@redhat.com>\n" -"Language-Team: as_IN <kde-i18n-doc@kde.org>\n" +"Language-Team: Assamese <kde-i18n-doc@kde.org>\n" "Language: as\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Lokalize 1.0\n" +"X-Generator: Lokalize 1.5\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "সংযোগ অপ্ৰত্যাশিতভাৱে অন্ত হল" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "অবৈধ সন্ধান অনুৰোধ" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream চুটি কৰিব নোৱাৰি" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "নেটৱাৰ্ক স্ট্ৰিম অপ্ৰত্যাশিতভাৱে বন্ধ হল" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "সম্পদক সম্পূৰ্ণভাৱে ক্যাশ কৰিবলে ব্যৰ্থ" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "আউটপুট বাফাৰ অতি সৰু" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "HTTP প্ৰতিক্ৰিয়া বিশ্লেষণ কৰিব পৰা নগল" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "অপৰিচিত HTTP প্ৰতিক্ৰিয়া এনক'ডিং" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "কাৰ্য্য বাতিল কৰা হৈছিল" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "কাৰ্য্য প্ৰতিৰোধ কৰিব" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "HTTP অনুৰোধ বিশ্লেষণ কৰিব পৰা নগল" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "কোনো URl প্ৰদান কৰা হোৱা নাই" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "অবৈধ '%s' URI: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "URI '%s' বিশ্লেষণ কৰিব পৰা নগল" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "অসমৰ্থিত URl আঁচনি '%s'" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "এটা HTTP URI নহয়" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "হস্টনাম এটা IP ঠিকনা" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "অবৈধ হস্টনাম" -#: ../libsoup/soup-tld.c:181 ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "হস্টনামৰ কোনো ভিত্তি ডমেইন নাই" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "পৰ্যাপ্ত ডমেইন নাই" @@ -5,62 +5,99 @@ msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" "product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-06-14 18:14+0000\n" -"PO-Revision-Date: 2012-07-15 14:23+0300\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2012-09-14 13:29+0300\n" "Last-Translator: Ihar Hrachyshka <ihar.hrachyshka@gmail.com>\n" "Language-Team: Belarusian <i18n-bel-gnome@googlegroups.com>\n" +"Language: be\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: be\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Злучэнне нечакана перарвана" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Хібны запыт пракручвання змесціва" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Немагчыма абрэзаць SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Сеткавы струмень нечакана закрыўся" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Не ўдалося цалкам змясціць рэсурс у кэш-памяці" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Выхадны буфер надта малы" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Не ўдалося разабраць HTTP-адказ" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Невядомае кадаванне HTTP-адказу" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Аперацыя была скасавана" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Аперацыя заблакіруе працэс" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Не ўдалося разабраць HTTP-запыт" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "URI-адрас не пададзены" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Хібны URI-адрас \"%s\": %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "Не ўдалося разабраць URI-адрас \"%s\"" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "URI-схема \"%s\" не падтрымліваецца" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Гэта не HTTP URI-адрас" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Назва хоста з'яўляецца IP-адрасам" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Хібная назва хоста" -#: ../libsoup/soup-tld.c:181 ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Назва хоста не мае базавага дамена" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "Не хапае даменаў" @@ -1,14 +1,14 @@ # Bulgarian translation of libsoup po-file. -# Copyright (C) 2012 Free Software Foundation, Inc. +# Copyright (C) 2012, 2013 Free Software Foundation, Inc. # This file is distributed under the same license as the libsoup package. -# Alexander Shopov <ash@kambanaria.org>, 2012. +# Alexander Shopov <ash@kambanaria.org>, 2012, 2013. # msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-07-05 12:37+0300\n" -"PO-Revision-Date: 2012-07-05 12:37+0300\n" +"POT-Creation-Date: 2013-01-12 06:56+0200\n" +"PO-Revision-Date: 2013-01-12 06:56+0200\n" "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n" "Language-Team: Bulgarian <dict@fsa-bg.org>\n" "Language: \n" @@ -17,53 +17,86 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Връзката прекъсна неочаквано" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:461 +msgid "Invalid seek request" +msgstr "Неправилна заявка за търсене" + +#: ../libsoup/soup-body-input-stream.c:489 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Потокът SoupBodyInputStream не може да бъде прекъснат" + +#: ../libsoup/soup-cache-input-stream.c:77 +msgid "Network stream unexpectedly closed" +msgstr "Потокът от мрежата неочаквано прекъсна" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Прекалено малък изходен буфер" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Отговорът от HTTP не може да бъде анализиран" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Непознато кодиране на отговора от HTTP" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Операцията е отменена" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Операцията ще блокира" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Заявката към HTTP не може да бъде анализирана" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Не е даден адрес" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Неправилен адрес на „%s“: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4032 #, c-format msgid "Could not parse URI '%s'" msgstr "Адресът „%s“ е неправилен" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4069 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Схемата на адреса не се поддържа „%s“" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4091 +#, c-format +msgid "Not an HTTP URI" +msgstr "Не е адрес URI за HTTP" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Името на машината е адрес по IP" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Неправилно име на машина" -#: ../libsoup/soup-tld.c:181 ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Името на машината не съдържа домейн" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "Няма достатъчно домейни" diff --git a/po/bn_IN.po b/po/bn_IN.po new file mode 100644 index 00000000..847d4b0d --- /dev/null +++ b/po/bn_IN.po @@ -0,0 +1,83 @@ +# Bengali (India) translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# +# Sayak Sarkar <sayak.bugsmith@gmail.com>, 2012. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2012-09-20 11:30+0000\n" +"PO-Revision-Date: 2012-09-20 22:38+0530\n" +"Last-Translator: Sayak Sarkar <sayak.bugsmith@gmail.com>\n" +"Language-Team: Bengali (India) <anubad@lists.ankur.org.in>\n" +"Language: bn\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.4\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "সংযোগ অপ্রত্যাশিতভাবে পর্যবসিত" + +#: ../libsoup/soup-body-input-stream.c:461 +msgid "Invalid seek request" +msgstr "অবৈধ সিক্ অনুরোধ" + +#: ../libsoup/soup-body-input-stream.c:489 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream ছাঁটা যাচ্ছে না।" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "আউটপুট বাফার অত্যন্ত ছোট" + +#: ../libsoup/soup-message-io.c:818 ../libsoup/soup-message-io.c:854 +msgid "Operation was cancelled" +msgstr "অপারেশন বাতিল হয়েছে" + +#: ../libsoup/soup-message-io.c:865 +msgid "Operation would block" +msgstr "অপারেশন ব্লক করবে" + +#: ../libsoup/soup-request.c:142 +#, c-format +msgid "No URI provided" +msgstr "কোন URI দেওয়া হয়নি" + +#: ../libsoup/soup-request.c:152 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "অবৈধ '%s' URI: %s" + +#: ../libsoup/soup-requester.c:219 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI '%s' পার্স করা যায়নি" + +#: ../libsoup/soup-requester.c:253 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "অসমর্থিত URI স্কিম '%s'" + +#: ../libsoup/soup-tld.c:154 +msgid "Hostname is an IP address" +msgstr "হোস্টনেমটি একটি IP অ্যাড্রেস" + +#: ../libsoup/soup-tld.c:175 +msgid "Invalid hostname" +msgstr "অবৈধ হোস্টনেম" + +#: ../libsoup/soup-tld.c:204 +msgid "Hostname has no base domain" +msgstr "হোস্টনেমটির কোনো বেস ডোমেইন নেই" + +#: ../libsoup/soup-tld.c:226 +msgid "Not enough domains" +msgstr "ডোমেইনগুলি পর্যাপ্ত নয়" diff --git a/po/ca.po b/po/ca.po new file mode 100644 index 00000000..60d22f79 --- /dev/null +++ b/po/ca.po @@ -0,0 +1,108 @@ +# Catalan translation for libsoup. +# Copyright (C) 2012 Free Software Foundation, Inc. +# This file is distributed under the same license as the libsoup package. +# Gil Forcada <gilforcada@guifi.net>, 2012, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-03-09 09:45+0000\n" +"PO-Revision-Date: 2012-09-23 17:22+0200\n" +"Last-Translator: Gil Forcada <gilforcada@guifi.net>\n" +"Language-Team: Catalan <tradgnome@softcatala.org>\n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bits\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Gtranslator 2.91.5\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "S'ha finalitzat la connexió inesperadament" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "La petició de cerca no és vàlida" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "No es pot truncar el SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "S'ha tancat inesperadament el flux de xarxa" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "No s'ha pogut carregar completament el recurs a la memòria cau" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "La memòria intermèdia de sortida és massa petita" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "No s'ha pogut analitzar la resposta HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "No es reconeix la codificació de la resposta HTTP" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "S'ha cancel·lat l'operació" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "L'operació bloquejaria" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "No s'ha pogut analitzar la petició HTTP" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "No s'ha proporcionat cap URI" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "L'URI «%s» no és vàlid: %s" + +#: ../libsoup/soup-session.c:4252 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "No s'ha pogut analitzar l'URI «%s»" + +#: ../libsoup/soup-session.c:4289 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "No es sap gestionar l'esquema d'URI «%s»" + +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "No és un URI HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "El nom d'ordinador és una adreça IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "El nom d'ordinador no és vàlid" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "El nom d'ordinador no té cap domini base" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "No hi ha prou dominis" diff --git a/po/ca@valencia.po b/po/ca@valencia.po new file mode 100644 index 00000000..04aee98f --- /dev/null +++ b/po/ca@valencia.po @@ -0,0 +1,107 @@ +# Catalan translation for libsoup. +# Copyright (C) 2012 Free Software Foundation, Inc. +# This file is distributed under the same license as the libsoup package. +# Gil Forcada <gilforcada@guifi.net>, 2012, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-03-12 23:07+0100\n" +"PO-Revision-Date: 2012-09-23 17:22+0200\n" +"Last-Translator: Gil Forcada <gilforcada@guifi.net>\n" +"Language-Team: Catalan <tradgnome@softcatala.org>\n" +"Language: ca-XV\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bits\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Gtranslator 2.91.5\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "S'ha finalitzat la connexió inesperadament" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "La petició de cerca no és vàlida" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "No es pot truncar el SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "S'ha tancat inesperadament el flux de xarxa" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "No s'ha pogut carregar completament el recurs a la memòria cau" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "La memòria intermèdia d'eixida és massa petita" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "No s'ha pogut analitzar la resposta HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "No es reconeix la codificació de la resposta HTTP" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "S'ha cancel·lat l'operació" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "L'operació bloquejaria" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "No s'ha pogut analitzar la petició HTTP" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "No s'ha proporcionat cap URI" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "L'URI «%s» no és vàlid: %s" + +#: ../libsoup/soup-session.c:4252 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "No s'ha pogut analitzar l'URI «%s»" + +#: ../libsoup/soup-session.c:4289 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "No es sap gestionar l'esquema d'URI «%s»" + +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "No és un URI HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "El nom d'ordinador és una adreça IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "El nom d'ordinador no és vàlid" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "El nom d'ordinador no té cap domini base" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "No hi ha prou dominis" diff --git a/po/cs.po b/po/cs.po new file mode 100644 index 00000000..3deeec02 --- /dev/null +++ b/po/cs.po @@ -0,0 +1,110 @@ +# Czech translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Marek Černocký <marek@manet.cz>, 2012, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-02-19 11:21+0100\n" +"Last-Translator: Marek Černocký <marek@manet.cz>\n" +"Language-Team: Czech <gnome-cs-list@gnome.org>\n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"X-Generator: Gtranslator 2.91.6\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Připojení bylo neočekávaně ukončeno" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Neplatný požadavek na posun" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Nelze zkrátit SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Síťový proud byl neočekávaně uzavřen" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Selhalo úplné uložení prostředku do mezipaměti" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Výstupní vyrovnávací paměť je příliš malá" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "Nelze zpracovat odpověď HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Nerozpoznáno kódování odpovědi HTTP" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "Operace byla zrušena" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "Operace by blokovala" + +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "Nelze zpracovat požadavek HTTP" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Není poskytnuta žádná adresa URI" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Neplatná adresa URI „%s“: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Nelze zpracovat adresu URI „%s“" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Nepodporované schéma URI „%s“" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Nejedná se o adresu HTTP URI" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Název počítače je adresa IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Neplatný název počítače" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Název počítače nemá základní doménu" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Nedostatek domén" diff --git a/po/da.po b/po/da.po new file mode 100644 index 00000000..fc187292 --- /dev/null +++ b/po/da.po @@ -0,0 +1,108 @@ +# Danish translation for libsoup. +# Copyright (C) 2013 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Ask Hjorth Larsen <asklarsen@gmail.com>, 2012. +# Joe Hansen (joedalton2@yahoo.dk), 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-03-12 23:14+0100\n" +"PO-Revision-Date: 2013-03-12 18:15+0200\n" +"Last-Translator: Joe Hansen <joedalton2@yahoo.dk>\n" +"Language-Team: Danish <dansk@dansk-gruppen.dk>\n" +"Language: da\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Forbindelsen blev uventet afbrudt" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Ugyldig søgeforespørgsel" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Kan ikke afkorte SoupBodyInputStream" + +# evt. Netværksstrømmen +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Netværksudsendelsen blev uventet lukket ned" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Kunne ikke lave fuldt mellemlager for ressourcen" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Outputbuffer er for lille" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Kunne ikke fortolke HTTP-svar" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Ej genkendt HTTP-svarkodning" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "Operationen blev annulleret" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "Operationen ville blokere" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Kunne ikke fortolke HTTP-forespørgsel" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Ingen URI givet" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Ugyldig \"%s\"-URI: %s" + +#: ../libsoup/soup-session.c:4252 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Kunne ikke fortolke URI \"%s\"" + +#: ../libsoup/soup-session.c:4289 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Uunderstøttet URI-skema \"%s\"" + +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "Ikke en HTTP URI" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Værtsnavn er en IP-adresse" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Ugyldigt værtsnavn" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Værtsnavnet har intet basisdomæne" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Ikke nok domæner" diff --git a/po/de.po b/po/de.po new file mode 100644 index 00000000..cdb26022 --- /dev/null +++ b/po/de.po @@ -0,0 +1,110 @@ +# German translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Tobias Endrigkeit <tobiasendrigkeit@googlemail.com>, 2012. +# Mario Blättermann <mario.blaettermann@gmail.com>, 2012, 2013. +# Andre Jonas <nipsky@googlemail.com>, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-03 21:07+0100\n" +"Last-Translator: Mario Blättermann <mario.blaettermann@gmail.com>\n" +"Language-Team: Deutsch <German <gnome-de@gnome.org>>\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Gtranslator 2.91.6\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Die Verbindung wurde unerwartet beendet" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Ungültige Suchanfrage" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream konnte nicht abgeschnitten werden" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Netzwerk-Stream wurde unerwartet geschlossen" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Das vollständige Zwischenspeichern der Ressource ist fehlgeschlagen" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Der Ausgabe-Zwischenspeicher ist zu klein" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "HTTP-Antwort konnte nicht verarbeitet werden" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Unbekannte Kodierung der HTTP-Antwort" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "Der Vorgang wurde abgebrochen" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "Der Vorgang würde gestoppt werden" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "HTTP-Anfrage konnte nicht verarbeitet werden" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Keine Adresse wurde bereitgestellt" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Ungültige »%s« Adresse: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Die Adresse »%s« konnte nicht verarbeitet werden" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Nicht unterstütztes Adressenschema »%s«" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Keine HTTP-Adresse" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Der Rechnername ist eine IP-Adresse" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Ungültiger Rechnername" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Der Rechnername hat keine Hauptdomäne" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Nicht genug Domänen" @@ -2,71 +2,110 @@ # Copyright (C) 2012 libsoup's COPYRIGHT HOLDER # This file is distributed under the same license as the libsoup package. # Tom Tryfonidis <tomtryf@gmail.com>, 2012. -# +# Dimitris Spingos (Δημήτρης Σπίγγος) <dmtrs32@gmail.com>, 2013. msgid "" msgstr "" "Project-Id-Version: libsoup master\n" -"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-06-14 18:14+0000\n" -"PO-Revision-Date: 2012-06-22 13:16+0200\n" -"Last-Translator: Tom Tryfonidis <tomtryf@gmail.com>\n" -"Language-Team: Greek <team@lists.gnome.gr>\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsou" +"p&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-09 11:44+0300\n" +"Last-Translator: Dimitris Spingos (Δημήτρης Σπίγγος) <dmtrs32@gmail.com>\n" +"Language-Team: team@gnome.gr\n" +"Language: el\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Virtaal 0.7.1\n" +"X-Project-Style: gnome\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 -#: ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Η σύνδεση τερματίστηκε απρόσμενα" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Άκυρη αίτηση αναζήτησης" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Αδυναμία περικοπής του SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Η ροή του δικτύου έκλεισε αναπάντεχα" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Αποτυχία πλήρους απόκρυψης του πόρου" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" -msgstr "Το buffer εξόδου είναι πολύ μικρό" +msgstr "Η ενδιάμεση μνήμη εξόδου είναι πολύ μικρή" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "Αδυναμία ανάλυσης της απάντησης HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Μη αναγνωρίσιμη κωδικοποίηση απάντησης HTTP" -#: ../libsoup/soup-message-io.c:836 -#: ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Η λειτουργία ακυρώθηκε" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Η λειτουργία θα μπλοκαριστεί" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "Αδυναμία ανάλυσης αιτήματος HTTP" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Δεν έχει δοθεί URI" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Μη έγκυρο '%s' URI: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "Αδυναμία ανάλυσης URI '%s'" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Μη υποστηριζόμενο URI σχήμα '%s'" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Δεν είναι URI HTTP" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Το όνομα συστήματος είναι μια διεύθυνση IP" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Μη έγκυρο όνομα συστήματος" -#: ../libsoup/soup-tld.c:181 -#: ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Το όνομα συστήματος δεν έχει βασικό τομέα" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "Δεν υπάρχουν αρκετοί τομείς" - diff --git a/po/en_GB.po b/po/en_GB.po new file mode 100644 index 00000000..1331d1e7 --- /dev/null +++ b/po/en_GB.po @@ -0,0 +1,84 @@ +# British English translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Bruce Cowan <bruce@bcowan.me.uk>, 2012. +# Chris Leonard <cjl@laptop.org>, 2012. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2012-09-10 19:10+0000\n" +"PO-Revision-Date: 2012-09-10 21:39-0400\n" +"Last-Translator: Chris Leonard <cjl@laptop.org>\n" +"Language-Team: Sugar Labs\n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Virtaal 0.7.0\n" +"X-Project-Style: gnome\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Connection terminated unexpectedly" + +#: ../libsoup/soup-body-input-stream.c:461 +msgid "Invalid seek request" +msgstr "Invalid seek request" + +#: ../libsoup/soup-body-input-stream.c:489 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Cannot truncate SoupBodyInputStream" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Output buffer is too small" + +#: ../libsoup/soup-message-io.c:818 ../libsoup/soup-message-io.c:854 +msgid "Operation was cancelled" +msgstr "Operation was cancelled" + +#: ../libsoup/soup-message-io.c:865 +msgid "Operation would block" +msgstr "Operation would block" + +#: ../libsoup/soup-request.c:142 +#, c-format +msgid "No URI provided" +msgstr "No URI provided" + +#: ../libsoup/soup-request.c:152 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Invalid '%s' URI: %s" + +#: ../libsoup/soup-requester.c:219 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Could not parse URI '%s'" + +#: ../libsoup/soup-requester.c:253 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Unsupported URI scheme '%s'" + +#: ../libsoup/soup-tld.c:154 +msgid "Hostname is an IP address" +msgstr "Hostname is an IP address" + +#: ../libsoup/soup-tld.c:175 +msgid "Invalid hostname" +msgstr "Invalid hostname" + +#: ../libsoup/soup-tld.c:204 +msgid "Hostname has no base domain" +msgstr "Hostname has no base domain" + +#: ../libsoup/soup-tld.c:226 +msgid "Not enough domains" +msgstr "Not enough domains" diff --git a/po/eo.po b/po/eo.po new file mode 100644 index 00000000..c1550eef --- /dev/null +++ b/po/eo.po @@ -0,0 +1,74 @@ +# Esperanto translation for libsoup. +# Copyright (C) 2012 Free Software Foundation, Inc. +# This file is distributed under the same license as the libsoup package. +# Kristjan SCHMIDT <kristjan.schmidt@googlemail.com>, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2012-07-31 00:03+0000\n" +"PO-Revision-Date: 2012-08-01 19:57+0200\n" +"Last-Translator: Kristjan SCHMIDT <kristjan.schmidt@googlemail.com>\n" +"Language-Team: Esperanto <gnome-l10n-eo@lists.launchpad.net>\n" +"Language: eo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +#: ../libsoup/soup-body-input-stream.c:136 +#: ../libsoup/soup-body-input-stream.c:167 +#: ../libsoup/soup-body-input-stream.c:200 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "La konekto estas neatendite fermita." + +#: ../libsoup/soup-converter-wrapper.c:191 +#, c-format +msgid "Output buffer is too small" +msgstr "La elig-bufro estas tro malgranda" + +#: ../libsoup/soup-message-io.c:817 ../libsoup/soup-message-io.c:853 +msgid "Operation was cancelled" +msgstr "La operacio estas ĉesigita" + +#: ../libsoup/soup-message-io.c:864 +msgid "Operation would block" +msgstr "La operacio estus haltigota." + +#: ../libsoup/soup-request.c:142 +#, c-format +msgid "No URI provided" +msgstr "Neniu URI estas diponigita" + +#: ../libsoup/soup-request.c:152 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Nevalida '%s' URI: %s" + +#: ../libsoup/soup-requester.c:219 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Ne eblis analizi na URI '%s'" + +#: ../libsoup/soup-requester.c:253 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Nesuptenata URI-skemo '%s'" + +#: ../libsoup/soup-tld.c:148 +msgid "Hostname is an IP address" +msgstr "La komputilnomo estas IP-adreso" + +#: ../libsoup/soup-tld.c:169 +msgid "Invalid hostname" +msgstr "Nevalida komputilnomo" + +#: ../libsoup/soup-tld.c:184 ../libsoup/soup-tld.c:226 +msgid "Not enough domains" +msgstr "Ne sufiĉe da retregionoj" + +#: ../libsoup/soup-tld.c:204 +msgid "Hostname has no base domain" +msgstr "La komputilnomo ne havas ĉefan retregionon" @@ -2,15 +2,15 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -# Daniel Mustieles <daniel.mustieles@gmail.com>, 2012. +# Daniel Mustieles <daniel.mustieles@gmail.com>, 2012, 2013. # msgid "" msgstr "" "Project-Id-Version: libsoup\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" "product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-07-16 22:04+0000\n" -"PO-Revision-Date: 2012-07-17 10:56+0200\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-02-19 12:33+0100\n" "Last-Translator: Daniel Mustieles <daniel.mustieles@gmail.com>\n" "Language-Team: Español <gnome-es-list@gnome.org>\n" "Language: \n" @@ -18,58 +18,92 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" +"X-Generator: Gtranslator 2.91.5\n" -#: ../libsoup/soup-body-input-stream.c:136 -#: ../libsoup/soup-body-input-stream.c:167 -#: ../libsoup/soup-body-input-stream.c:200 ../libsoup/soup-message-io.c:191 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "La conexión terminó inesperadamente" -#: ../libsoup/soup-converter-wrapper.c:189 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Petición de búsqueda no válida" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "No se puede truncar SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "El flujo de red se cerró inesperadamente" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Falló al cachear completamente el recurso" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "El búfer de salida es demasiado pequeño" -#: ../libsoup/soup-message-io.c:817 ../libsoup/soup-message-io.c:853 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "No se pudo analizar la respuesta HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Codificación de la respuesta HTTP no reconocida" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Se canceló la operación" -#: ../libsoup/soup-message-io.c:864 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "La operación se bloqueará" -#: ../libsoup/soup-request.c:142 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "No se pudo analizar la solicitud HTTP" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "No se ha proporcionado un URI" -#: ../libsoup/soup-request.c:152 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "URI «%s» no válida: %s" -#: ../libsoup/soup-requester.c:219 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "No se pudo analizar el URI «%s»" -#: ../libsoup/soup-requester.c:253 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Esquema %s de URI no soportado" -#: ../libsoup/soup-tld.c:143 +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "No es un URI HTTP" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "El nombre del equipo es una dirección IP" -#: ../libsoup/soup-tld.c:164 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "El nombre del equipo no es válido" -#: ../libsoup/soup-tld.c:179 ../libsoup/soup-tld.c:221 -msgid "Not enough domains" -msgstr "No hay suficientes dominios" - -#: ../libsoup/soup-tld.c:199 +#: ../libsoup/soup-tld.c:235 msgid "Hostname has no base domain" msgstr "El nombre del equipo no tiene un dominio base" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "No hay suficientes dominios" diff --git a/po/et.po b/po/et.po new file mode 100644 index 00000000..b57ce405 --- /dev/null +++ b/po/et.po @@ -0,0 +1,85 @@ +# Estonian translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Mattias Põldaru <mahfiaz@gmail.com>, 2012, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-03-12 16:51+0000\n" +"PO-Revision-Date: 2013-03-12 22:43+0300\n" +"Last-Translator: Mattias Põldaru <mahfiaz@gmail.com>\n" +"Language-Team: Estonian <gnome-et-list@gnome.org>\n" +"Language: et\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgid "Connection terminated unexpectedly" +msgstr "Ühendus katkes ootamatult" + +msgid "Invalid seek request" +msgstr "Sobimatu kerimise päring" + +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream-i pole võimalik lühendada" + +msgid "Network stream unexpectedly closed" +msgstr "Võrguvoog sulgus ootamatult" + +msgid "Failed to completely cache the resource" +msgstr "Ressursi täielik puhverdamine nurjus" + +#, c-format +msgid "Output buffer is too small" +msgstr "Väljundpuhver on liiga väike" + +msgid "Could not parse HTTP response" +msgstr "HTTP vastust polnud võimalik parsida" + +msgid "Unrecognized HTTP response encoding" +msgstr "Tundmatu HTTP vastuse kodeering" + +msgid "Operation was cancelled" +msgstr "Operatsioon katkestati" + +msgid "Operation would block" +msgstr "Operatsioon blokeeruks" + +msgid "Could not parse HTTP request" +msgstr "HTTP päringut polnud võimalik parsida" + +#, c-format +msgid "No URI provided" +msgstr "URI-d ei antud" + +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Sobimatu '%s' URI: %s" + +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI-d '%s' polnud võimalik parsida" + +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "URI skeem pole toetatud '%s'" + +#, c-format +msgid "Not an HTTP URI" +msgstr "Pole HTTP URI" + +msgid "Hostname is an IP address" +msgstr "Hostinimi on IP-aadress" + +msgid "Invalid hostname" +msgstr "Sobimatu hostinimi" + +msgid "Hostname has no base domain" +msgstr "Hostinimel puudub baasdomeen" + +msgid "Not enough domains" +msgstr "Pole piisavalt domeene" diff --git a/po/eu.po b/po/eu.po new file mode 100644 index 00000000..28bbd393 --- /dev/null +++ b/po/eu.po @@ -0,0 +1,106 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Iñaki Larrañaga Murgoitio <dooteo@zundan.com>, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-03-26 11:25+0100\n" +"PO-Revision-Date: 2013-03-24 16:37+0100\n" +"Last-Translator: Iñaki Larrañaga Murgoitio <dooteo@zundan.com>\n" +"Language-Team: Basque <itzulpena@euskalgnu.org>\n" +"Language: eu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.4\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Konexioa ustekabean amaitu da" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Bilaketa-eskaera baliogabea" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Ezin da SoupBodyInputStream trunkatu" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Sarearen korrontea ustekabean itxi da" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Huts egin du baliabidea erabat cachean gordetzean" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Irteeraren bufferra txikiegia da" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Ezin izan da HTTP erantzuna analizatu" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "HTTP erantzunaren kodeketa ezezaguna" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "Eragiketa bertan behera utzi da" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "Eragiketak blokea dezake" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Ezin izan da HTTP eskaera analizatu" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Ez da URIrik eman" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Baliogabeko '%s' URIa: %s" + +#: ../libsoup/soup-session.c:4252 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Ezin izan da '%s' URIa analizatu" + +#: ../libsoup/soup-session.c:4289 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Onartu gabeko '%s' URI eskema" + +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "Ez da HTTP URIa" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Ostalari-izena IP helbide bat da" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Baliogabeko ostalari-izena" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Ostalari-izenak ez dauka oinarrizko domeinurik" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Ez dago nahikoa domeinurik" diff --git a/po/fa.po b/po/fa.po new file mode 100644 index 00000000..74ee1e77 --- /dev/null +++ b/po/fa.po @@ -0,0 +1,109 @@ +# Persian translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Arash Mousavi <mousavi.arash@gmail.com>, 2012, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-23 18:07+0330\n" +"Last-Translator: Arash Mousavi <mousavi.arash@gmail.com>\n" +"Language-Team: Persian\n" +"Language: fa\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "اتصال به شکل غیرمنتظرهای بسته شد" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "درخواست جستجو نامعتبر" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "نمیتوان SoupBodyInputStream را کوتاه کرد" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "جریان شبکه بهطور غیرمنتظرهای بسته شد" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "بهطور کامل حافظهی نهان کردن منبع شکست خورد" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "میانگیر خروجی خیلی کوتاه است" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "نمیتوان پاسخ HTTP را تجزیه کرد" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "کدگذاری پاسخ HTTP شناخته نشد" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "عملیات لغو شده بود" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "عملیات میتوانست بسته شود" + +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "نمیتوان درخواست HTTP را تجزیه کرد" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "هیچ URIای داده نشده است" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "«%s» نامعتبر URI: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "نمیتوان URI را تجزیه کرد «%s»" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "شِما URI پشتیبانی نشده «%s»" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "آدرس HTTP نیست" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "نام میزبان یک آدرس آیپی است" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "ناممیزبان نامعتبر" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "ناممیزبان دامنهی پایه ندارد" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "دامنههای کافی موجود نیست" @@ -1,59 +1,109 @@ # French translation for libsoup. # Copyright (C) 2012 libsoup's COPYRIGHT HOLDER # This file is distributed under the same license as the libsoup package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# # Pierre Henry <pierrehenry73@yahoo.fr>, 2012. # Alain Lojewski <allomervan@gmail.com>, 2012. +# Mickael Albertus <mickael.albertus@gmail.com>, 2012. # msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" "product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-05-24 20:15+0000\n" -"PO-Revision-Date: 2012-05-24 19:14+0100\n" -"Last-Translator: Alain Lojewski <allomervan@gmail.com>\n" -"Language-Team: French <gnomefr@traduc.org>\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-12 17:51+0100\n" +"Last-Translator: Claude Paroz <claude@2xlibre.net>\n" +"Language-Team: GNOME French Team <gnomefr@traduc.org>\n" +"Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "La connexion a été interrompue de manière inattendue" -#: ../libsoup/soup-converter-wrapper.c:190 -#, c-format +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Requête de recherche invalide" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Impossible de tronquer le SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Le flux réseau s'est arrêté inopinément" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Impossible de mettre la ressource totalement en cache" + +#: ../libsoup/soup-converter-wrapper.c:192 msgid "Output buffer is too small" msgstr "La mémoire tampon de sortie est trop petite" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Impossible d'analyser la réponse HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Codage de réponse HTTP inconnu" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "L'opération a été annulée" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "L'opération aurait bloqué" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Impossible d'analyser la requête HTTP" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Aucun URI fourni" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "URI « %s » non valide : %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "Impossible d'analyser l'URI « %s »" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Le schéma d'URI « %s » n'est pas pris en charge" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Ce n'est pas un URI HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Le nom d'hôte est une adresse IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Nom d'hôte non valide" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Le nom d'hôte n'a pas de domaine de base" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Pas assez de domaines" diff --git a/po/fur.po b/po/fur.po new file mode 100644 index 00000000..04579ca1 --- /dev/null +++ b/po/fur.po @@ -0,0 +1,81 @@ +# Friulian translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# TmTFx <f.t.public@gmail.com>, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup gnome-3-6\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2012-12-10 10:22+0000\n" +"PO-Revision-Date: 2012-12-28 13:53+0100\n" +"Last-Translator: TmTFx <f.t.public@gmail.com>\n" +"Language-Team: Friulian <fur@li.org>\n" +"Language: fur\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "No si spietave il termin de conession" + +#: ../libsoup/soup-body-input-stream.c:461 +msgid "Invalid seek request" +msgstr "Domande di ricerche no valide" + +#: ../libsoup/soup-body-input-stream.c:489 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "No puès cjonçâ SoupBodyInputStream" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Il buffer di jessude al è masse piçul" + +#: ../libsoup/soup-message-io.c:818 ../libsoup/soup-message-io.c:854 +msgid "Operation was cancelled" +msgstr "L'operazion a je stade scancelade" + +#: ../libsoup/soup-message-io.c:865 +msgid "Operation would block" +msgstr "L'operazion a sarà blocade" + +#: ../libsoup/soup-request.c:142 +#, c-format +msgid "No URI provided" +msgstr "No'l è stât furnît un URI" + +#: ../libsoup/soup-request.c:152 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "URI '%s' no valit: %s" + +#: ../libsoup/soup-requester.c:219 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "No puès analizâ l'URI '%s'" + +#: ../libsoup/soup-requester.c:253 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Scheme URI '%s' no supuartât" + +#: ../libsoup/soup-tld.c:154 +msgid "Hostname is an IP address" +msgstr "Il non host al è un indiriz IP" + +#: ../libsoup/soup-tld.c:175 +msgid "Invalid hostname" +msgstr "Il non host a no'l è valit" + +#: ../libsoup/soup-tld.c:204 +msgid "Hostname has no base domain" +msgstr "Il non host a no'l a un domini di base" + +#: ../libsoup/soup-tld.c:226 +msgid "Not enough domains" +msgstr "No vonde dominis" @@ -1,71 +1,106 @@ # Galician translation for libsoup. # Copyright (C) 2012 libsoup's COPYRIGHT HOLDER # This file is distributed under the same license as the libsoup package. -# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. -# Fran Dieguez <frandieguez@gnome.org>, 2012. -# +# Fran Dieguez <frandieguez@gnome.org>, 2012, 2013. msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-06-24 23:26+0200\n" -"PO-Revision-Date: 2012-06-24 23:26+0200\n" +"POT-Creation-Date: 2013-02-20 10:39+0100\n" +"PO-Revision-Date: 2013-02-20 10:40+0200\n" "Last-Translator: Fran Dieguez <frandieguez@gnome.org>\n" -"Language-Team: Galician <gnome-l10n-gl@gnome.org>\n" +"Language-Team: gnome-l10n-gl@gnome.org\n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n!=1);\n" -"X-Generator: Gtranslator 2.91.5\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Virtaal 0.7.1\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "A conexión rematou de forma non esperada" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Petición de busca non válida" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Non é posíbel truncar SoupbodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Fluxo de rede pechado de forma non esperada" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Produciuse un fallo ao cachear completamente o recurso" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "O búfer de saída é demasiado pequeno" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Non é posíbel analizar a resposta HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Codificación da resposta HTTP non recoñecida" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "A operación foi cancelada" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "A operación bloquearase" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Non é posíbel analizar a consulta HTTP" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Non se forneceu un URI" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "URI «%s» non válida: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4211 #, c-format msgid "Could not parse URI '%s'" msgstr "Non é posíbel analizar o URI «%s»" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4248 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Esquema de URI «%s» non admitido" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4270 +#, c-format +msgid "Not an HTTP URI" +msgstr "Non é unha URI de HTTP" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "O nome do computador é un enderezo IP" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "O nome do computador non é válido" -#: ../libsoup/soup-tld.c:181 ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "O nome do computador non ten un dominio base" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "Non hai dominios dabondo" diff --git a/po/gu.po b/po/gu.po new file mode 100644 index 00000000..19d46368 --- /dev/null +++ b/po/gu.po @@ -0,0 +1,111 @@ +# Gujarati translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# +# , 2013. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug." +"cgi?product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-12 16:21+0530\n" +"Last-Translator: \n" +"Language-Team: American English <kde-i18n-doc@kde.org>\n" +"Language: gu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.0\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "જોડાણ અનિચ્છનીય રીતે તૂટી ગયુ" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "અયોગ્ય સીક માંગણી" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream કાઢી શકાતુ નથી" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "નેટવર્ક સ્ટ્રીમ અનિચ્છનીય રીતે બંધ થઇ ગઇ" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "સ્ત્રોતને સંપૂર્ણપણે કેશ કરવામાં નિષ્ફળતા" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "આઉટપુટ બફર ઘણુ નાનું છે" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "HTTP જવાબનું પદચ્છેદન કરી શક્યા નહિં" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "બિનઓળખાયેલ HTTP જવાબ એનકોડીંગ" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "ક્રિયા રદ થયેલ હતી" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "ક્રિયા રોકી રખાશે" + +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "HTTP માંગણીનુ પદચ્છેદન કરી શક્યા નહિં" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "URI પૂરુ પાડેલ નથી" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "અમાન્ય '%s' URI: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI '%s' ને પદચ્છેદન કરી શક્યા નહિં" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "બિનઆધારભૂત URI યોજના '%s'" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "HTTP URI નથી" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "યજમાનનામ એ IP સરનામું છે" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "અમાન્ય યજમાનનામ" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "યજમાન પાસે મૂળ ડોમેઇન નથી" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "પૂરતુ ડોમેઇન નથી" + @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: libsoup gnome\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-07-12 18:58+0300\n" -"PO-Revision-Date: 2012-07-12 18:58+0200\n" +"POT-Creation-Date: 2013-02-21 19:54+0200\n" +"PO-Revision-Date: 2013-02-21 19:55+0200\n" "Last-Translator: Yaron Shahrabani <sh.yaron@gmail.com>\n" "Language-Team: Hebrew <sh.yaron@gmail.com>\n" "Language: \n" @@ -20,57 +20,93 @@ msgstr "" "X-Poedit-Country: ISRAEL\n" "X-Poedit-SourceCharset: utf-8\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 -#: ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 +#: ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "החיבור הופסק באופן בלתי צפוי" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "בקשת חיפוש שגויה" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "לא ניתן לקצץ את SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "תזרים הרשת נסגר בפתאומיות" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "שמירת המשאב במלואו במטמון נכשלה" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "אוגר הפלט קטן מדי" -#: ../libsoup/soup-message-io.c:836 -#: ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "לא ניתן לנתח את תגובת ה־HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "קידוד תגובת ה־HTTP אינו מוכר" + +#: ../libsoup/soup-message-io.c:846 +#: ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "הפעולה בוטלה" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "הפעולה תיחסם" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "לא ניתן לנתח את תגובת ה־HTTP" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "לא סופקה כתובת" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "כתובת '%s' שגויה: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4211 #, c-format msgid "Could not parse URI '%s'" msgstr "לא ניתן לנתח את הכתובת '%s'" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4248 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "תבנית הכתובת אינה נתמכת '%s'" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4270 +#, c-format +msgid "Not an HTTP URI" +msgstr "כתובת שאינה HTTP" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "שם המארח הוא כתובת IP" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "שם מארח שגוי" -#: ../libsoup/soup-tld.c:181 -#: ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "לשם המארח אין שם מתחם בסיסי" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "אין מספיק שמות מתחם" diff --git a/po/hi.po b/po/hi.po new file mode 100644 index 00000000..de85b345 --- /dev/null +++ b/po/hi.po @@ -0,0 +1,113 @@ +# Hindi translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# 1 <1>, 2012. +# 1 Pratibha kumari <1pratibharoshan1526@gmail.com>, 2012. +# raj <raj>, 2013. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-20 16:47+0530\n" +"Last-Translator: raj <raj>\n" +"Language-Team: Hindi <kde-i18n-doc@kde.org>\n" +"Language: hi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Lokalize 1.5\n" +"X-Project-Style: gnome\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "मिलाप अनपेक्षित रुप से खतम हो गया " + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "अमान्य खोज अनुरोध" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream का नहीं कर सकता" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "संजाल स्ट्रीम अप्रत्याशित रूप से बंद हो गया" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "संसाधन को पूरी तरह कैश करने में विफल" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "निर्गम बफर बहुत छोटा है" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "HTTP अनुक्रिया विश्लेषित नहीं कर सका" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "अपरिचित HTTP अनुक्रिया एन्कोडिंग" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "आपरेशन रद्द कर दिया गया" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "आपरेशन बंद हो जाएगा" + +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "HTTP आग्रह विश्लेषित नहीं कर सका" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "यूआरआई नहीं प्रदान किया गया" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "यूआरआई '%s' अमान्य: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI '%s' पद व्याख्या नहीं हो सका" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "URI योजना '%s' को सहारा नहीं" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "कोई HTTP URI नहीं" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "मेजबाननाम एक आईपी पता है" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "मेजबाननाम मान्य नहीं" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "मेजबाननाम को आधार डोमेन नहीं है" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "डोमेन पर्याप्त नहीं" + diff --git a/po/hu.po b/po/hu.po new file mode 100644 index 00000000..dc3311ea --- /dev/null +++ b/po/hu.po @@ -0,0 +1,112 @@ +# Hungarian translation of libsoup +# Copyright (C) 2012. Free Software Foundation, Inc. +# This file is distributed under the same license as the libsoup package. +# +# Gabor Kelemen <kelemeng at gnome dot hu>, 2012. +# Balázs Úr <urbalazs at gmail dot com>, 2013. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-15 12:35+0100\n" +"Last-Translator: Balázs Úr <urbalazs at gmail dot com>\n" +"Language-Team: Hungarian <gnome-hu-list at gnome dot org>\n" +"Language: hu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Lokalize 1.2\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Kapcsolat váratlanul megszakítva" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Érvénytelen pozicionálási kérés" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "A SoupBodyInputStream nem csonkítható" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Az hálózati adatfolyam váratlanul lezárult" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Nem sikerült teljesen gyorsítótárazni az erőforrást" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "A kimeneti puffer túl kicsi" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "Nem dolgozható fel a HTTP válasz" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Ismeretlen HTTP válasz kódolás" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "A művelet megszakítva" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "A művelet blokkoló lenne" + +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "Nem dolgozható fel a HTTP kérés" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Nincs megadva URI" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Érvénytelen „%s” URI: „%s”" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Nem dolgozható fel a(z) „%s” URI" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Nem támogatott URI séma: „%s”" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Ez nem HTTP URI" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "A gépnév egy IP-cím" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Érvénytelen gépnév" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "A gépnévnek nincs alap tartománya" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Nincs elég tartomány" + @@ -1,68 +1,109 @@ # Indonesian translation for libsoup. # Copyright (C) 2012 libsoup's COPYRIGHT HOLDER # This file is distributed under the same license as the libsoup package. -# Andika Triwidada <andika@gmail.com>, 2012. # +# Andika Triwidada <andika@gmail.com>, 2012. +# Dirgita <dirgitadevina@yahoo.co.id>, 2012, 2013. msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" "product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-06-14 18:14+0000\n" -"PO-Revision-Date: 2012-06-28 10:25+0700\n" -"Last-Translator: Andika Triwidada <andika@gmail.com>\n" +"POT-Creation-Date: 2013-09-16 22:19+0000\n" +"PO-Revision-Date: 2013-09-18 20:55+0700\n" +"Last-Translator: Dirgita <dirgitadevina@yahoo.co.id>\n" "Language-Team: Indonesian <gnome@i15n.org>\n" +"Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.5\n" +"Plural-Forms: nplurals=1; plural=0;\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:140 +#: ../libsoup/soup-body-input-stream.c:171 +#: ../libsoup/soup-body-input-stream.c:204 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Sambungan terputus secara tak diharapkan" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:462 +msgid "Invalid seek request" +msgstr "Permintaan seek yang tak valid" + +#: ../libsoup/soup-body-input-stream.c:490 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Tak bisa memenggal SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:73 +msgid "Network stream unexpectedly closed" +msgstr "Lalu lintas jaringan putus tak terduga" + +#: ../libsoup/soup-cache-input-stream.c:290 +msgid "Failed to completely cache the resource" +msgstr "Gagal melengkapi tembolok sumber" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Penyangga keluaran terlalu kecil" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Tak dapat mengurai tanggapan HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Enkode tanggapan HTTP tak dikenal" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Operasi dibatalkan" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Operasi akan memblokir" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Tak dapat mengurai permintaan HTTP" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "URI tak diberikan" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "URI '%s' tak valid: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4259 #, c-format msgid "Could not parse URI '%s'" msgstr "Tak bisa mengurai URI '%s'" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4296 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Skema URI '%s' tak didukung" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4318 +#, c-format +msgid "Not an HTTP URI" +msgstr "Bukan URI HTTP" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Nama host adalah suatu alamat IP" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Nama host tak valid" -#: ../libsoup/soup-tld.c:181 ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Nama host tidak memiliki domain dasar" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "Tak cukup domain" diff --git a/po/it.po b/po/it.po new file mode 100644 index 00000000..ba800dba --- /dev/null +++ b/po/it.po @@ -0,0 +1,106 @@ +# Italian translations for libsoup package +# Copyright (C) 2012, 2013 the Free Software Foundation, Inc. +# This file is distributed under the same license as the libsoup package. +# Milo Casagrande <milo@casagrande.name>, 2012, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-03-11 16:59+0100\n" +"PO-Revision-Date: 2013-03-11 17:00+0100\n" +"Last-Translator: Milo Casagrande <milo@casagrande.name>\n" +"Language-Team: Italian <tp@lists.linux.it>\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8-bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Connessione terminata inaspettatamente" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Richiesta di posizionamento non valida" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Impossibile troncare SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Flusso di rete chiuso inaspettatamente" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Salvataggio in memoria della risorsa non riuscito" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Il buffer di uscita è troppo piccolo" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Impossibile analizzare la risposta HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Codifica risposta HTTP non riconosciuta" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "L'operazione è stata annullata" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "L'operazione potrebbe bloccarsi" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Impossibile analizzare la richiesta HTTP" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Nessun URI fornito" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "URI «%s» non valido: %s" + +#: ../libsoup/soup-session.c:4252 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Impossibile analizzare l'URI «%s»" + +#: ../libsoup/soup-session.c:4289 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Schema URI «%s» non supportato" + +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "Non è uno URI HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Il nome host è un indirizzo IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Nome host non valido" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Il nome host non ha un dominio di base" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Domini insufficienti" diff --git a/po/ja.po b/po/ja.po new file mode 100644 index 00000000..795fa84a --- /dev/null +++ b/po/ja.po @@ -0,0 +1,81 @@ +# libsoup ja.po +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Takayuki KUSANO <AE5T-KSN@asahi-net.or.jp>, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2012-11-02 19:31+0000\n" +"PO-Revision-Date: 2012-10-12 13:30+0900\n" +"Last-Translator: Takayuki KUSANO <AE5T-KSN@asahi-net.or.jp>\n" +"Language-Team: Japanese <gnome-translation@gnome.gr.jp>\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "接続が突然切断されました" + +#: ../libsoup/soup-body-input-stream.c:461 +msgid "Invalid seek request" +msgstr "無効な seek 要求" + +#: ../libsoup/soup-body-input-stream.c:489 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream は truncate できません" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "出力バッファーが小さすぎます" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "動作が中止されました" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "動作はブロックします" + +#: ../libsoup/soup-request.c:142 +#, c-format +msgid "No URI provided" +msgstr "URI が与えられていません" + +#: ../libsoup/soup-request.c:152 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "不正なスキーム '%s' の URI: %s" + +#: ../libsoup/soup-requester.c:219 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI '%s' をパースできませんでした" + +#: ../libsoup/soup-requester.c:253 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "サポートされていない URI のスキーム '%s'" + +#: ../libsoup/soup-tld.c:154 +msgid "Hostname is an IP address" +msgstr "ホスト名が IP アドレスです" + +#: ../libsoup/soup-tld.c:175 +msgid "Invalid hostname" +msgstr "不正なホスト名です" + +#: ../libsoup/soup-tld.c:204 +msgid "Hostname has no base domain" +msgstr "ホスト名にトップレベルドメインがありません" + +# ソースコードと http://www.nic.ad.jp/ja/dom/system.html 参照 +#: ../libsoup/soup-tld.c:226 +msgid "Not enough domains" +msgstr "ドメイン名が十分な数のラベルがありません" diff --git a/po/kn.po b/po/kn.po new file mode 100644 index 00000000..98bd11e4 --- /dev/null +++ b/po/kn.po @@ -0,0 +1,109 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Shankar Prasad <svenkate@redhat.com>, 2012, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=" +"libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-24 08:30-0400\n" +"Last-Translator: Shankar Prasad <svenkate@redhat.com>\n" +"Language-Team: Kannada <kde-i18n-doc@kde.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: kn\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Zanata 3.2.3\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 +#: ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "ಸಂಪರ್ಕವು ಅನಿರೀಕ್ಷಿತವಾಗಿ ಕೊನೆಗೊಂಡಿದೆ" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "ಅಮಾನ್ಯವಾದ ಕೋರಿಕೆಯ ಮನವಿ" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream ಅನ್ನು ತುಂಡರಿಸಲಾಗಿಲ್ಲ" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "ಜಾಲಬಂಧ ಸ್ಟ್ರೀಮ್ ಅನ್ನು ಅನಿರೀಕ್ಷಿತವಾಗಿ ಮುಚ್ಚಲಾಗಿದೆ" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "ಸಂಪನ್ಮೂಲವನ್ನು ಸಂಪೂರ್ಣವಾಗಿ ಕ್ಯಾಶ್ ಮಾಡುವಲ್ಲಿ ವಿಫಲಗೊಂಡಿದೆ" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "ಔಟ್ಪುಟ್ ಬಫರ್ ಬಹಳ ಚಿಕ್ಕದಾಗಿದೆ" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "HTTP ಪ್ರತ್ಯುತ್ತರವನ್ನು ಪಾರ್ಸ್ ಮಾಡಲಾಗಿಲ್ಲ" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "ಗುರುತಿಸಲಾಗದ HTTP ಪ್ರತಿಕ್ರಿಯೆ ಎನ್ಕೋಡಿಂಗ್" + +#: ../libsoup/soup-message-io.c:846 +#: ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "ಕಾರ್ಯಾಚರಣೆಯನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗಿದೆ" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "ಕಾರ್ಯಾಚರಣೆ ತಡೆಯಲ್ಪಡಬಹುದು" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "HTTP ಮನವಿಯನ್ನು ಪಾರ್ಸ್ ಮಾಡಲಾಗಿಲ್ಲ" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "ಯಾವುದೆ URI ಅನ್ನು ಒದಗಿಸಲಾಗಿಲ್ಲ." + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "ಅಮಾನ್ಯವಾದ '%s' URI:'%s" + +#: ../libsoup/soup-session.c:4252 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "'%s' URL ಅನ್ನು ಪಾರ್ಸ್ ಮಾಡಲಾಗಿಲ್ಲ" + +#: ../libsoup/soup-session.c:4289 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "ಬೆಂಬಲವಿಲ್ಲದ ಯುಆರ್ಐ ಮಾದರಿ '%s'" + +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "ಒಂದು HTTP URI ಅಲ್ಲ" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "ಆತಿಥೇಯವು ಒಂದು IP ವಿಳಾಸವಾಗಿದೆ" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "ಅಮಾನ್ಯವಾದ ಅತಿಥೇಯದ ಹೆಸರು" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "ಆತಿಥೇಯದ ಹೆಸರಿನಲ್ಲಿ ಯಾವುದೆ ಮೂಲ ಡೊಮೈನ್ ಇಲ್ಲ" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "ಬೇಕಾದಷ್ಟು ಡೊಮೈನ್ಗಳಿಲ್ಲ" diff --git a/po/ko.po b/po/ko.po new file mode 100644 index 00000000..c07240e5 --- /dev/null +++ b/po/ko.po @@ -0,0 +1,109 @@ +# Korean translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# +# eukim <eukim@redhat.com>, 2012. +# Changwoo Ryu <cwryu@debian.org>, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-22 04:46+0900\n" +"Last-Translator: Changwoo Ryu <cwryu@debian.org>\n" +"Language-Team: Korean <gnome-kr@googlegroups.com>\n" +"Language: ko\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Lokalize 1.0\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "예상치 않게 연결이 중지됨" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "탐색 요청이 잘못되었습니다" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream을 자를 수 없습니다" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "네트워크 스트림이 예상치 못하게 닫혔습니다" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "리소스를 완전히 캐시에 저장하는데 실패했습니다" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "출력 버퍼가 너무 작습니다" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "HTTP 응답을 구문 분석할 수 없습니다" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "HTTP 응답 인코딩을 알 수 없습니다" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "작업이 취소되었습니다" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "작업이 블럭되었습니다" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "HTTP 요청을 구문 분석할 수 없습니다" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "URI가 없습니다" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "잘못된 '%s' URI: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "'%s' URI를 구문 분석할 수 없습니다" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "'%s' URI 스킴을 지원하지 않습니다" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "HTTP URI가 아닙니다" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "호스트 이름은 IP 주소입니다" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "잘못된 호스트 이름입니다" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "호스트 이름에 기본 도메인이 없습니다" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "도메인이 충분하지 않습니다" @@ -1,73 +1,111 @@ # Lithuanian translation for libsoup. # Copyright (C) 2012 libsoup's COPYRIGHT HOLDER # This file is distributed under the same license as the libsoup package. -# Aurimas Černius <aurisc4@gmail.com>, 2012. +# Aurimas Černius <aurisc4@gmail.com>, 2012, 2013. # msgid "" msgstr "" "Project-Id-Version: libsoup master\n" -"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-07-14 11:19+0000\n" -"PO-Revision-Date: 2012-07-14 18:48+0300\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-02-24 21:55+0200\n" "Last-Translator: Aurimas Černius <aurisc4@gmail.com>\n" -"Language-Team: Lithuanian <gnome-lt@lists.akl.lt>\n" +"Language-Team: Lietuvių <gnome-lt@lists.akl.lt>\n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" +"%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Gtranslator 2.91.6\n" -#: ../libsoup/soup-body-input-stream.c:136 -#: ../libsoup/soup-body-input-stream.c:167 -#: ../libsoup/soup-body-input-stream.c:200 -#: ../libsoup/soup-message-io.c:193 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Ryšys netikėtai nutrūko" -#: ../libsoup/soup-converter-wrapper.c:189 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Netinkama paieškos užklausa" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Negalima trumpinti SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Tinklo srautas netikėtai užsivėrė" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Nepavyko visiškai patalpinti resurso į podėlį" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Išvesties buferis yra per mažas" -#: ../libsoup/soup-message-io.c:817 -#: ../libsoup/soup-message-io.c:853 +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "Nepavyko perskaityti URI „%s“Nepavyko perskaityti HTTP atsako" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Neatpažinta HTTP atsako koduotė" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Veiksmas buvo nutrauktas" -#: ../libsoup/soup-message-io.c:864 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Veiksmas blokuosis" -#: ../libsoup/soup-request.c:142 +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "Nepavyko perskaityti HTTP užklausos" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Nepateiktas URI" -#: ../libsoup/soup-request.c:152 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Netinkamas „%s“ URI: %s" -#: ../libsoup/soup-requester.c:219 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "Nepavyko perskaityti URI „%s“" -#: ../libsoup/soup-requester.c:253 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Nepalaikoma URI schema „%s“" -#: ../libsoup/soup-tld.c:146 +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Ne HTTP URI" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Serverio vardas arba IP adresas" -#: ../libsoup/soup-tld.c:167 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Netinkamas serverio vardas" -#: ../libsoup/soup-tld.c:182 -#: ../libsoup/soup-tld.c:220 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Serverio vardas neturi bazinio domeno" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "Nepakanka domenų" - diff --git a/po/lv.po b/po/lv.po new file mode 100644 index 00000000..c22e57a4 --- /dev/null +++ b/po/lv.po @@ -0,0 +1,113 @@ +# Latvian translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# +# Tranzistors <rudolfs.mazurs@gmail.com>, 2012. +# Rūdolfs Mazurs <rudolfs.mazurs@gmail.com>, 2012, 2013. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-17 20:11+0200\n" +"Last-Translator: Rūdolfs Mazurs <rudolfs.mazurs@gmail.com>\n" +"Language-Team: Latvian <lata-l10n@googlegroups.com>\n" +"Language: lv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : " +"2);\n" +"X-Generator: Lokalize 1.4\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Savienojums tika negaidīti pārtraukts" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Nederīgs meklēšanas pieprasījums" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Nevar apraut SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Tīkla straume negaidīti aizvērās" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Neizdevās resursu pilnībā noglabāt kešatmiņā" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Izvades buferis ir pārāk mazs" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "Nevarēja parsēt HTTP atbildi" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Neatpazīts HTTP atbildes kodējums" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "Darbība tika atcelta" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "Darbība bloķētu" + +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "Nevarēja parsēt HTTP pieprasījumu" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Nav sniegts URI" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Nederīgs “%s” URI — %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Nevarēja parsēt URI “%s”" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Neatbalstīta URI shēma “%s”" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Nav HTTP URI" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Datora nosaukums ir IP adrese" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Nederīgs datora nosaukums" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Datora nosaukumam nav bāzes domēna" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Nepietiekami daudz domēnu" + diff --git a/po/ml.po b/po/ml.po new file mode 100644 index 00000000..4b515208 --- /dev/null +++ b/po/ml.po @@ -0,0 +1,109 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Ani Peter <apeter@redhat.com>, 2012, 2013. +# Anish A <aneesh.nl@gmail.com>, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-03-24 12:31+0000\n" +"PO-Revision-Date: 2013-03-25 11:40+0530\n" +"Last-Translator: Ani Peter <peter.ani@gmail.com>\n" +"Language-Team: American English <kde-i18n-doc@kde.org>\n" +"Language: ml\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Lokalize 1.5\n" +"X-Project-Style: gnome\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "കണക്ഷന് അപ്രതീക്ഷതമായി അവസാനിയ്ക്കുന്നു" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "തെറ്റായ തെരച്ചില് ആവശ്യം" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream ട്രക്കേറ്റ് ചെയ്യുവാന് സാധ്യമല്ല" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "ശൃംഖല സ്ട്രീം വിചാരിക്കാതെ അടച്ചു" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "ശ്രോതസ്സ് കാഷ് ചെയ്യുന്നതു് പൂര്ണ്ണമായി പരാജയപ്പെട്ടു" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "ഔട്ട്പുട്ട് ബഫര് വളരെ ചെറുതാണു്" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "HTTP മറുപടി പാഴ്സ് ചെയ്യുവാന് സാധ്യമായില്ല" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "HTTP മറുപടിയുടെ രൂപം തിരിച്ചറിയാനായില്ല" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "പ്രക്രിയ റദ്ദാക്കിയിരിയ്ക്കുന്നു" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "പ്രക്രിയ തടസ്സപ്പെടുത്തുന്നു" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "HTTP മറുപടി പാഴ്സ് ചെയ്യുവാനായില്ല" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "യുആര്ഐ ലഭ്യമാക്കിയിട്ടില്ല" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "തെറ്റായ '%s' യുആര്ഐ: %s" + +#: ../libsoup/soup-session.c:4252 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "യുആര്ഐ '%s' പാഴ്സ് ചെയ്യുവാനായില്ല" + +#: ../libsoup/soup-session.c:4289 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "പിന്തുണയില്ലാത്ത യുആര്ഐ സ്കീം '%s'" + +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "HTTP URI അല്ല" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "ഹോസ്റ്റ്നാമം ഒരു ഐപി വിലാസമാകുന്നു" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "തെറ്റായ ഹോസ്റ്റ്നാമം" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "ഹോസ്റ്റ്നാമത്തിനു് ബെയിസ് ഡൊമെയില് ലഭ്യമല്ല" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "ആവശ്യമായ ഡൊമെയിനുകള് ലഭ്യമല്ല" + diff --git a/po/mr.po b/po/mr.po new file mode 100644 index 00000000..e2ca1630 --- /dev/null +++ b/po/mr.po @@ -0,0 +1,111 @@ +# Marathi translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# +# Sandeep Shedmake <sshedmak@redhat.com>, 2012, 2013. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-20 06:56+0530\n" +"Last-Translator: Sandeep Shedmake <sshedmak@redhat.com>\n" +"Language-Team: Marathi <maajhe-sanganak@freelists.org>\n" +"Language: mr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.5\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "जोडणी अनपेक्षितपणे बंद झाली" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "अवैध सीक विनंती" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream ट्रंकेट करणे अशक्य" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "नेटवर्क स्ट्रिम अनपेक्षितरित्या बंद झाले" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "रिसोअर्स संपूर्णपणे कॅशे करण्यास अपयशी" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "आउटपुट बफर खूपच लहान आहे" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "HTTP प्रतिसाद वाचणे अशक्य" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "अपरिचीत HTTP प्रतिसाद एंकोडिंग" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "कार्य रद्द केले" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "कार्य बंधिस्त करू शकते" + +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "HTTP विनंती वाचणे अशक्य" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "URI पुरवले नाही" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "अवैध '%s' URI: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI '%s' वाचणे अशक्य" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "असमर्थीत URI सुत्रयोजना '%s'" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "HTTP URI नाही" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "यजमाननाव IP पत्ता आहे" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "अवैध यजमाननाव" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "यजमाननावात बेस डोमैन नाही" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "अतिरिक्त डोमैन्स् आढळले नाही" + @@ -1,14 +1,14 @@ # Norwegian bokmål translation of libsoup. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. -# Kjartan Maraas <kmaraas@gnome.org>, 2012. +# Kjartan Maraas <kmaraas@gnome.org>, 2012-2013. # msgid "" msgstr "" -"Project-Id-Version: libsoup 3.5.x\n" +"Project-Id-Version: libsoup 3.7.x\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-07-10 07:56+0200\n" -"PO-Revision-Date: 2012-07-10 07:57+0200\n" +"POT-Creation-Date: 2013-02-25 17:06+0100\n" +"PO-Revision-Date: 2013-02-25 17:07+0100\n" "Last-Translator: Kjartan Maraas <kmaraas@gnome.org>\n" "Language-Team: Norwegian bokmål <i18n-nb@lister.ping.uio.no>\n" "Language: \n" @@ -16,53 +16,90 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Tilkoblingen ble brutt uventet" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Ugyldig søkeforespørsel" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Kan ikke avkorte SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Nettverksstrømmen ble lukket på uventet vis" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Klarte ikke å mellomlagre ressursen fullt ut" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Buffer for utdata er for liten" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Kunne ikke tolke HTTP-svar" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Ikke gjenkjent koding av HTTP-svar" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Operasjonen ble avbrutt" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Operasjonen ville blokkere" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Kunne ikke tolke HTTP-forespørsel" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Ingen URI ble oppgitt" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Ugyldig «%s» URI: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4211 #, c-format msgid "Could not parse URI '%s'" msgstr "Kunne ikke tolke URI «%s»" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4248 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "URI-skjema «%s» er ikke støttet" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4270 +#, c-format +msgid "Not an HTTP URI" +msgstr "Ikke en HTTP URI" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Vertsnavnet er en IP-adresse" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Ugyldig vertsnavn" -#: ../libsoup/soup-tld.c:181 ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Vertsnavnet har ikke noe grunndomene" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "Ikke mange nok domener" diff --git a/po/nl.po b/po/nl.po new file mode 100644 index 00000000..77fc8526 --- /dev/null +++ b/po/nl.po @@ -0,0 +1,109 @@ +# Dutch translation for libsoup. +# Copyright (C) 2013 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Reinout van Schouwen <reinout@gmail.com>, 2013. +# Reinout van Schouwen <reinouts@gnome.org>, 2013. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsou" +"p&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-09-16 22:19+0000\n" +"PO-Revision-Date: 2013-09-18 23:22+0200\n" +"Last-Translator: Reinout van Schouwen <reinouts@gnome.org>\n" +"Language-Team: Dutch <vertaling@vrijschrift.org>\n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Virtaal 0.7.1\n" +"X-Project-Style: gnome\n" + +#: ../libsoup/soup-body-input-stream.c:140 +#: ../libsoup/soup-body-input-stream.c:171 +#: ../libsoup/soup-body-input-stream.c:204 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Verbinding werd onverwacht verbroken" + +#: ../libsoup/soup-body-input-stream.c:462 +msgid "Invalid seek request" +msgstr "Ongeldige zoekaanvraag" + +#: ../libsoup/soup-body-input-stream.c:490 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Kan SoupBodyInputStream niet afkappen" + +#: ../libsoup/soup-cache-input-stream.c:73 +msgid "Network stream unexpectedly closed" +msgstr "Netwerkstroom onverwacht gesloten" + +#: ../libsoup/soup-cache-input-stream.c:290 +msgid "Failed to completely cache the resource" +msgstr "Volledig bufferen van de bron is mislukt" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Uitvoerbuffer is te klein" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Kan HTTP-antwoord niet verwerken" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Niet-herkende HTTP-antwoordcodering" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "Bewerking is geannuleerd" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "Bewerking zou blokkeren" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Kan HTTP-aanvraag niet verwerken" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Geen URI aangeleverd" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Ongeldige ‘%s’-URI: %s" + +#: ../libsoup/soup-session.c:4259 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Kon URI ‘%s’ niet verwerken" + +#: ../libsoup/soup-session.c:4296 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Niet-ondersteund URI-schema ‘%s’." + +#: ../libsoup/soup-session.c:4318 +#, c-format +msgid "Not an HTTP URI" +msgstr "Geen HTTP-URI" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Hostnaam is een IP-adres" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Ongeldige hostnaam" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Hostnaam heeft geen basisdomein" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Niet genoeg domeinen" diff --git a/po/or.po b/po/or.po new file mode 100644 index 00000000..f6be296d --- /dev/null +++ b/po/or.po @@ -0,0 +1,110 @@ +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Manoj Kumar Giri <mgiri@redhat.com>, 2012, 2013. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-22 18:02+0530\n" +"Last-Translator: Manoj Kumar Giri <mgiri@redhat.com>\n" +"Language-Team: Oriya <oriya-it@googlegroups.com>\n" +"Language: or\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.5\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "ସଂଯୋଗଟି ଅପ୍ରତ୍ୟାଶିତ ଭାବରେ ବନ୍ଦ ହୋଇଛି" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "ଅବୈଧ ଅନୁସନ୍ଧାନ ଅନୁରୋଧ" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr " SoupBodyInputStream କୁ ବିଚ୍ଛିନ୍ନ କରିହେବ ନାହିଁ " + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "ନେଟୱର୍କ୍ ଧାରା ଅପ୍ରତ୍ୟାଶିତ ଭାବରେ ବନ୍ଦ ହୋଇଛି" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "ଉତ୍ସକୁ ସମ୍ପୂର୍ଣ୍ଣ ଭାବରେ କ୍ୟାଶେ କରିବାରେ ବିଫଳ" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "ଫଳାଫଳ ବଫରଟି ଅତି ଛୋଟ ଅଟେ" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "HTTP ଉତ୍ତର ବିଶ୍ଳେଷଣ କରିପାରିଲା ନାହିଁ" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "ଅଚିହ୍ନା HTTP ଉତ୍ତର ସାଙ୍କେତିକରଣ" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "ପ୍ରୟୋଗକୁ ବାତିଲ କରାଯାଇଛି" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "ପ୍ରୟୋଗଟି ବନ୍ଦ ହୋଇପାରେ" + +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "HTTP ଅନୁରୋଧକୁ ବିଶ୍ଳେଷଣ କରିପାରିଲା ନାହିଁ" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "କୌଣସି URI ଦିଆଯାଇ ନାହିଁ" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "ଅବୈଧ '%s' URI: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI '%s'କୁ ବିଶ୍ଳେଷଣ କରିପାରିଲା ନାହିଁ" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "ଅସମର୍ଥିତ URI ଯୋଜନା '%s'" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "ଏହା ଏକ HTTP URI ନୁହଁ" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "ହୋଷ୍ଟନାମଟି ଏକ IP ଠିକଣା ଅଟେ" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "ଅବୈଧ ହୋଷ୍ଟ ନାମ" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "ହୋଷ୍ଟ ନାମରେ କୌଣସି ମୂଳ ଡମେନ ନଥାଏ" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "ଯଥେଷ୍ଟ ଡମେନ ନାହିଁ" + @@ -2,59 +2,110 @@ # Copyright (C) 2012 libsoup's COPYRIGHT HOLDER # This file is distributed under the same license as the libsoup package. # -# A S Alam <aalam@users.sf.net>, 2012. +# A S Alam <aalam@users.sf.net>, 2012, 2013. msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" "product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-05-16 03:05+0000\n" -"PO-Revision-Date: 2012-05-18 07:27+0530\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-02-26 07:36+0530\n" "Last-Translator: A S Alam <aalam@users.sf.net>\n" "Language-Team: Punjabi/Panjabi <punjabi-users@lists.sf.net>\n" +"Language: pa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: pa\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Lokalize 1.4\n" +"X-Generator: Lokalize 1.5\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "ਕੁਨੈਕਸ਼ਨ ਅਚਾਨਕ ਬੰਦ ਹੋ ਗਿਆ" -#: ../libsoup/soup-converter-wrapper.c:185 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "ਸੀਕ ਮੰਗ ਗਲਤ ਹੈ" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream ਨੂੰ ਛੋਟਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "ਨੈੱਟਵਰਕ ਸਟਰੀਮ ਅਚਾਨਕ ਬੰਦ ਹੋਈ" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "ਸਰੋਤ ਨੂੰ ਪੂਰੀ ਕੈਸ਼ ਕਰਨ ਲਈ ਫੇਲ੍ਹ ਹੈ" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "ਆਉਟਪੁੱਟ ਬਫ਼ਰ ਬਹੁਤ ਛੋਟਾ ਹੈ" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "HTTP ਨੂੰ ਪਾਰਸ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "ਬੇਪਛਾਣ HTTP ਜਵਾਬ ਇੰਕੋਡਿੰਗ" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "ਕਾਰਵਾਈ ਰੱਦ ਕੀਤੀ ਗਈ" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "ਕਾਰਵਾਈ ਉੱਤੇ ਪਾਬੰਦੀ ਹੋਵੇਗੀ" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "HTTP ਮੰਗ ਨੂੰ ਪਾਰਸ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "ਕੋਈ URI ਨਹੀਂ ਦਿੱਤਾ" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "ਗਲਤ '%s' URI: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "URI '%s' ਨੂੰ ਪਾਰਸ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "ਗ਼ੈਰ-ਸਹਾਇਕ URI ਸਕੀਮ '%s'" +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "HTTP URI ਨਹੀਂ ਹੈ" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "ਹੋਸਟ ਨਾਂ IP ਐਡਰੈਸ ਹੈ" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "ਗਲਤ ਹੋਸਟ-ਨਾਂ" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "ਹੋਸਟ-ਨਾਂ ਲਈ ਬੇਸ ਡੋਮੇਮ ਨਹੀਂ ਹੈ" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "ਲੋੜੀਦੀਆਂ ਡੋਮੇਨ ਨਹੀਂ ਹੈ" + @@ -4,12 +4,14 @@ # pomóc w jego rozwijaniu i pielęgnowaniu, napisz do nas: # gnomepl@aviary.pl # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +# Piotr Drąg <piotrdrag@gmail.com>, 2012-2013. +# Aviary.pl <gnomepl@aviary.pl>, 2012-2013. msgid "" msgstr "" "Project-Id-Version: libsoup\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-06-03 15:35+0200\n" -"PO-Revision-Date: 2012-06-03 15:36+0200\n" +"POT-Creation-Date: 2013-02-18 16:13+0100\n" +"PO-Revision-Date: 2013-02-18 16:14+0100\n" "Last-Translator: Piotr Drąg <piotrdrag@gmail.com>\n" "Language-Team: Polish <gnomepl@aviary.pl>\n" "Language: pl\n" @@ -21,41 +23,90 @@ msgstr "" "X-Poedit-Language: Polish\n" "X-Poedit-Country: Poland\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Połączenie zostało nieoczekiwanie zakończone" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Nieprawidłowe żądanie przewinięcia" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Nie można skrócić SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Strumień sieciowy został nieoczekiwanie zamknięty" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Całkowite umieszczenie zasobu w pamięci podręcznej się nie powiodło" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Bufor wyjściowy jest za mały" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Nie można przetworzyć odpowiedzi HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Nierozpoznane kodowanie odpowiedzi HTTP" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Anulowano działanie" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Działanie spowodowałoby zablokowanie" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Nie można przetworzyć żądania HTTP" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Nie podano adresu URI" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Nieprawidłowy adres URI \"%s\": %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "Nie można przetworzyć adresu URI \"%s\"" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Nieobsługiwany schemat adresu URI \"%s\"" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Nie jest adresem URI protokołu HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Nazwa komputera jest adresem IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Nieprawidłowa nazwa komputera" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Nazwa komputera nie posiada podstawowej domeny" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Brak wystarczającej liczby domen" diff --git a/po/pt.po b/po/pt.po new file mode 100644 index 00000000..7db149fb --- /dev/null +++ b/po/pt.po @@ -0,0 +1,105 @@ +# libsoup's Portuguese translation.
+# Copyright © 2012, 2013 libsoup
+# This file is distributed under the same license as the libsoup package.
+# Duarte Loreto <happyguy_pt@hotmail.com>, 2012, 2013.
+#
+msgid "" +msgstr "" +"Project-Id-Version: 3.8\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2013-03-11 22:54+0000\n" +"PO-Revision-Date: 2013-03-11 23:00+0100\n" +"Last-Translator: Duarte Loreto <happyguy_pt@hotmail.com>\n" +"Language-Team: Portuguese <gnome_pt@yahoogroups.com>\n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Ligação terminou inesperadamente" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Pedido de procura inválido" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Incapaz de truncar SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Fluxo de rede terminado inesperadamente" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Falha ao colocar o recurso totalmente em cache" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Buffer de resultado é demasiado pequeno" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Incapaz de processar a resposta HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Codificação de resposta HTTP desconhecida" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "A operação foi cancelada" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "A operação iria bloquear" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Incapaz de processar o pedido HTTP" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Nenhum URI especificado" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "URI '%s' inválido: %s" + +#: ../libsoup/soup-session.c:4252 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Incapaz de processar o URI '%s'" + +#: ../libsoup/soup-session.c:4289 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Esquema de URI '%s' não suportado" + +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "Não é um URI HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Nome da máquina é um endereço IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Nome de máquina inválido" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Nome de máquina não possui domínio base" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Domínios insuficientes" diff --git a/po/pt_BR.po b/po/pt_BR.po new file mode 100644 index 00000000..c567838b --- /dev/null +++ b/po/pt_BR.po @@ -0,0 +1,108 @@ +# Brazilian Portuguese translation for libsoup. +# Copyright (C) 2013 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Gabriel F. Vilar <cogumm@gmail.com>, 2012. +# Enrico Nicoletto <liverig@gmail.com>, 2013. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-03-06 19:50+0000\n" +"PO-Revision-Date: 2013-02-28 10:19-0300\n" +"Last-Translator: Enrico Nicoletto <liverig@gmail.com>\n" +"Language-Team: Brazilian Portuguese <gnome-pt_br-list@gnome.org>\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 1.5.4\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "A conexão terminou inesperadamente" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Requisição de busca inválida" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Não foi possível truncar SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "O fluxo de rede fechou de forma inesperada" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Falha ao armazenar completamente em cache o recurso" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "O buffer de saída é muito pequeno" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Não foi possível analisar a resposta HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Codificação de resposta HTTP não reconhecível" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "A operação foi cancelada" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "A operação será bloqueada" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Não foi possível analisar a solicitação HTTP" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Nenhuma URI foi fornecida" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "URI: %s inválida '%s'" + +#: ../libsoup/soup-session.c:4208 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Não foi possível analisar URI '%s'" + +#: ../libsoup/soup-session.c:4245 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Esquema de URI não suportado '%s'" + +#: ../libsoup/soup-session.c:4267 +#, c-format +msgid "Not an HTTP URI" +msgstr "Não é um URI do tipo HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "O nome da máquina é um endereço de IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "O nome da máquina é inválido" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Nome da máquina não está na base do domínio" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Não há domínios suficientes" @@ -1,54 +1,109 @@ -# Russian translation for libsoup. -# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER -# This file is distributed under the same license as the libsoup package. -# Yuri Myasoedov <omerta13@yandex.ru>, 2012. +# Russian translation for libsoup.
+# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER
+# This file is distributed under the same license as the libsoup package.
+#
+# Yuri Myasoedov <omerta13@yandex.ru>, 2012, 2013.
, 2013. # msgid "" msgstr "" "Project-Id-Version: libsoup master\n" -"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-04-22 08:40+0000\n" -"PO-Revision-Date: 2012-04-23 12:43+0300\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-03-15 11:36+0000\n" +"PO-Revision-Date: 2013-03-16 22:47+0400\n" "Last-Translator: Yuri Myasoedov <omerta13@yandex.ru>\n" -"Language-Team: Russian <gnome-cyr@gnome.org>\n" +"Language-Team: русский <gnome-cyr@gnome.org>\n" +"Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 -#: ../libsoup/soup-message-io.c:232 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Соединение было неожиданно разорвано" -#: ../libsoup/soup-converter-wrapper.c:185 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Неверный запрос поиска" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Не удалось отсечь SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Сетевой поток неожиданно закрылся" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Не удалось полностью закэшировать ресурс" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Слишком маленький буфер вывода" -#: ../libsoup/soup-message-io.c:818 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Не удалось разобрать HTTP-ответ" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Нераспознанная кодировка HTTP-ответа" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Действие отменено" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "Действие заблокировано" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Не удалось разобрать HTTP-запрос" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Не указан URI" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Недопустимый URI «%s»: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4252 #, c-format msgid "Could not parse URI '%s'" msgstr "Не удалось разобрать URI «%s»" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4289 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Неподдерживаемая схема URI «%s»" +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "Формат URI отличается от HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Имя компьютера является IP-адресом" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Неверное имя компьютера" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Имя компьютера не содержит доменной части" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Недостаточно доменных имён в адресе" diff --git a/po/sk.po b/po/sk.po new file mode 100644 index 00000000..cb442ca0 --- /dev/null +++ b/po/sk.po @@ -0,0 +1,114 @@ +# Slovak translation for libsoup. +# Copyright (C) 2012-2013 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Pavol Klačanský <pavol@klacansky.com>, 2012-2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-20 09:40+0000\n" +"PO-Revision-Date: 2013-02-20 20:57+0000\n" +"Last-Translator: Dušan Kazik <prescott66@gmail.com>\n" +"Language-Team: Slovak <gnome-sk-list@gnome.org>\n" +"Language: sk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n" +"X-Generator: Poedit 1.5.4\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Pripojenie bolo neočakávane ukončené" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Neplatná požiadavka na posunutie" + +# struct +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream sa nedá skrátiť" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Sieťový prúd bol neočakávane uzavretý" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Úplné načítanie zdroja do dočasnej pamäte zlyhalo" + +# PM: buffer by som preložil ako schránka +# PK: schranka je clipboard, buffer je jednoznacne vyrovnavacia pamet +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Výstupná vyrovnávacia pamäť je príliš malá" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Nepodarilo sa analyzovať odpoveď HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Nerozpoznané kódovanie odpovede HTTP" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "Operácia bola zrušená" + +# PK: tu neviem ako to povedat, malo by ist o to, ze proste ta operacia neni async +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "Operácia by blokovala spracovanie" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Nepodarilo sa analyzovať požiadavku HTTP" + +# error +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Nebol poskytnutý identifikátor URI" + +# first %s - scheme (http, ftp, ...) +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Neplatná schéma „%s“ identifikátora URI: %s" + +#: ../libsoup/soup-session.c:4211 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Nepodarilo sa analyzovať identifikátor URI „%s“" + +#: ../libsoup/soup-session.c:4248 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Nepodporovaná schéma „%s“ pre identifikátor URI" + +#: ../libsoup/soup-session.c:4270 +#, c-format +msgid "Not an HTTP URI" +msgstr "Nie je HTTP URI" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Názov hostiteľa je adresa IP" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Neplatný názov hostiteľa" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Názov hostiteľa neobsahuje základnú doménu" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Nedostatočný počet domén" @@ -7,75 +7,105 @@ msgid "" msgstr "" "Project-Id-Version: libsoup master\n" -"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-07-18 21:44+0000\n" -"PO-Revision-Date: 2012-07-19 09:57+0100\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-03-07 15:08+0000\n" +"PO-Revision-Date: 2013-03-07 17:00+0100\n" "Last-Translator: Matej Urbančič <mateju@svn.gnome.org>\n" "Language-Team: Slovenian <gnome-si@googlegroups.com>\n" "Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n%100==4 ? 3 : 0);\n" -"X-Poedit-Language: Slovenian\n" -"X-Poedit-Country: SLOVENIA\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 1 : n%100==2 ? 2 : n%100==3 || n" +"%100==4 ? 3 : 0);\n" "X-Poedit-SourceCharset: utf-8\n" +"X-Generator: Poedit 1.5.4\n" -#: ../libsoup/soup-body-input-stream.c:136 -#: ../libsoup/soup-body-input-stream.c:167 -#: ../libsoup/soup-body-input-stream.c:200 -#: ../libsoup/soup-message-io.c:191 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Povezava je nepričakovano končana" -#: ../libsoup/soup-converter-wrapper.c:189 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Neveljavna zahteva iskanja" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Ni mogoče porezati SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Omrežni pretok se je nepričakovano zaprl" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Ustvarjanje predpomnilnika vira je spodletelo" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Odvodni medpomnilnik je premajhen." -#: ../libsoup/soup-message-io.c:817 -#: ../libsoup/soup-message-io.c:853 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Odziva HTTP ni mogoče razčleniti" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Neprepoznano kodiranje odziva HTTP" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Opravilo je preklicano." -#: ../libsoup/soup-message-io.c:864 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Opravilo bi zaustavilo delovanje" -#: ../libsoup/soup-request.c:142 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Zahteve HTTP ni mogoče razčleniti" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Ni podanega naslova URI" -#: ../libsoup/soup-request.c:152 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Neveljaven naslov URI '%s': %s" -#: ../libsoup/soup-requester.c:219 +#: ../libsoup/soup-session.c:4252 #, c-format msgid "Could not parse URI '%s'" msgstr "Ni mogoče razčleniti naslova URI '%s'" -#: ../libsoup/soup-requester.c:253 +#: ../libsoup/soup-session.c:4289 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Nepodprta shema URI '%s'" -#: ../libsoup/soup-tld.c:148 +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "Naslov ni v obliki HTTP URI" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Ime gostitelja je naslov IP" -#: ../libsoup/soup-tld.c:169 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Neveljavno ime gostitelja" -#: ../libsoup/soup-tld.c:184 -#: ../libsoup/soup-tld.c:226 -msgid "Not enough domains" -msgstr "Ni dovolj domen" - -#: ../libsoup/soup-tld.c:204 +#: ../libsoup/soup-tld.c:235 msgid "Hostname has no base domain" msgstr "Ime gostitelja je brez osnovne domene" +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Ni dovolj domen" @@ -1,74 +1,108 @@ # Serbian translation of libsoup. # Courtesy of Prevod.org team (http://prevod.org/) -- 2012. # This file is distributed under the same license as the libsoup package. -# Мирослав Николић <miroslavnikolic@rocketmail.com>, 2011. +# Мирослав Николић <miroslavnikolic@rocketmail.com>, 2011, 2012, 2013. msgid "" msgstr "" "Project-Id-Version: libsoup master\n" -"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" -"product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-07-17 14:16+0000\n" -"PO-Revision-Date: 2011-03-05 22:03+0200\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsou" +"p&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-02-18 20:11+0200\n" "Last-Translator: Мирослав Николић <miroslavnikolic@rocketmail.com>\n" "Language-Team: Serbian <gnom@prevod.org>\n" +"Language: sr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: Serbian (sr)\n" "Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : " "n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Project-Style: gnome\n" -#: ../libsoup/soup-body-input-stream.c:136 -#: ../libsoup/soup-body-input-stream.c:167 -#: ../libsoup/soup-body-input-stream.c:200 ../libsoup/soup-message-io.c:191 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Веза је неочекивано затворена" -#: ../libsoup/soup-converter-wrapper.c:189 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Неисправан захтев претраге" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Не могу да скратим улазни ток тела Супе" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Мрежни ток је изненадно затворен" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Нисам успео у потпуности да сместим извориште у оставу " + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Међумеморија излаза је премала" -#: ../libsoup/soup-message-io.c:817 ../libsoup/soup-message-io.c:853 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Не могу да обрадим ХТТП одговор" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Непознато кодирање ХТТП одговора" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Радња је отказана" -#: ../libsoup/soup-message-io.c:864 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Поступак би блокирао" -#: ../libsoup/soup-request.c:142 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Не могу да обрадим ХТТП захтев" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Није наведена адреса" -#: ../libsoup/soup-request.c:152 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Неисправна „%s“ адреса: %s" -#: ../libsoup/soup-requester.c:219 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "Не могу да обрадим адресу „%s“" -#: ../libsoup/soup-requester.c:253 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Неподржана „%s“ шема адресе" -#: ../libsoup/soup-tld.c:148 +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Није ХТТП путања" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Назив домаћина је ИП адреса" -#: ../libsoup/soup-tld.c:169 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Неисправан назив домаћина" -#: ../libsoup/soup-tld.c:184 ../libsoup/soup-tld.c:226 -msgid "Not enough domains" -msgstr "Нема довољно домена" - -#: ../libsoup/soup-tld.c:204 +#: ../libsoup/soup-tld.c:235 msgid "Hostname has no base domain" msgstr "Назив домаћина нема основни домен" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Нема довољно домена" diff --git a/po/sr@latin.po b/po/sr@latin.po index 8fdeef4e..04d8dc0b 100644 --- a/po/sr@latin.po +++ b/po/sr@latin.po @@ -1,74 +1,108 @@ # Serbian translation of libsoup. # Courtesy of Prevod.org team (http://prevod.org/) -- 2012. # This file is distributed under the same license as the libsoup package. -# Miroslav Nikolić <miroslavnikolic@rocketmail.com>, 2011. +# Miroslav Nikolić <miroslavnikolic@rocketmail.com>, 2011, 2012, 2013. msgid "" msgstr "" "Project-Id-Version: libsoup master\n" -"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" -"product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-07-17 14:16+0000\n" -"PO-Revision-Date: 2011-03-05 22:03+0200\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsou" +"p&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-02-18 20:11+0200\n" "Last-Translator: Miroslav Nikolić <miroslavnikolic@rocketmail.com>\n" "Language-Team: Serbian <gnom@prevod.org>\n" +"Language: sr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: Serbian (sr)\n" "Plural-Forms: nplurals=4; plural=n==1? 3 : n%10==1 && n%100!=11 ? 0 : " "n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Project-Style: gnome\n" -#: ../libsoup/soup-body-input-stream.c:136 -#: ../libsoup/soup-body-input-stream.c:167 -#: ../libsoup/soup-body-input-stream.c:200 ../libsoup/soup-message-io.c:191 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Veza je neočekivano zatvorena" -#: ../libsoup/soup-converter-wrapper.c:189 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Neispravan zahtev pretrage" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Ne mogu da skratim ulazni tok tela Supe" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Mrežni tok je iznenadno zatvoren" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Nisam uspeo u potpunosti da smestim izvorište u ostavu " + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Međumemorija izlaza je premala" -#: ../libsoup/soup-message-io.c:817 ../libsoup/soup-message-io.c:853 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Ne mogu da obradim HTTP odgovor" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Nepoznato kodiranje HTTP odgovora" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Radnja je otkazana" -#: ../libsoup/soup-message-io.c:864 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Postupak bi blokirao" -#: ../libsoup/soup-request.c:142 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Ne mogu da obradim HTTP zahtev" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Nije navedena adresa" -#: ../libsoup/soup-request.c:152 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "Neispravna „%s“ adresa: %s" -#: ../libsoup/soup-requester.c:219 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "Ne mogu da obradim adresu „%s“" -#: ../libsoup/soup-requester.c:253 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Nepodržana „%s“ šema adrese" -#: ../libsoup/soup-tld.c:148 +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Nije HTTP putanja" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Naziv domaćina je IP adresa" -#: ../libsoup/soup-tld.c:169 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Neispravan naziv domaćina" -#: ../libsoup/soup-tld.c:184 ../libsoup/soup-tld.c:226 -msgid "Not enough domains" -msgstr "Nema dovoljno domena" - -#: ../libsoup/soup-tld.c:204 +#: ../libsoup/soup-tld.c:235 msgid "Hostname has no base domain" msgstr "Naziv domaćina nema osnovni domen" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Nema dovoljno domena" diff --git a/po/sv.po b/po/sv.po new file mode 100644 index 00000000..364abfa1 --- /dev/null +++ b/po/sv.po @@ -0,0 +1,75 @@ +# Swedish translation for libsoup. +# Copyright (C) 2012 Free Software Foundation, Inc. +# This file is distributed under the same license as the libsoup package. +# Daniel Nylander <po@danielnylander.se>, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-09-02 12:00+0200\n" +"PO-Revision-Date: 2012-09-02 12:02+0100\n" +"Last-Translator: Daniel Nylander <po@danielnylander.se>\n" +"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../libsoup/soup-body-input-stream.c:136 +#: ../libsoup/soup-body-input-stream.c:167 +#: ../libsoup/soup-body-input-stream.c:200 +#: ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Anslutningen avslutades oväntat" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Utmatningsbufferten är för liten" + +#: ../libsoup/soup-message-io.c:818 +#: ../libsoup/soup-message-io.c:854 +msgid "Operation was cancelled" +msgstr "Åtgärden avbröts" + +#: ../libsoup/soup-message-io.c:865 +msgid "Operation would block" +msgstr "Åtgärden skulle blockera" + +#: ../libsoup/soup-request.c:142 +#, c-format +msgid "No URI provided" +msgstr "Ingen URI angavs" + +#: ../libsoup/soup-request.c:152 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Ogiltig \"%s\"-URI: %s" + +#: ../libsoup/soup-requester.c:219 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Kunde inte tolka URI:n \"%s\"" + +#: ../libsoup/soup-requester.c:253 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "URI-schemat \"%s\" stöds inte" + +#: ../libsoup/soup-tld.c:154 +msgid "Hostname is an IP address" +msgstr "Värdnamnet är en IP-adress" + +#: ../libsoup/soup-tld.c:175 +msgid "Invalid hostname" +msgstr "Ogiltigt värdnamn" + +#: ../libsoup/soup-tld.c:204 +msgid "Hostname has no base domain" +msgstr "Värdnamnet har ingen basdomän" + +#: ../libsoup/soup-tld.c:226 +msgid "Not enough domains" +msgstr "Inte tillräckligt många domäner" + diff --git a/po/ta.po b/po/ta.po new file mode 100644 index 00000000..96b82ab4 --- /dev/null +++ b/po/ta.po @@ -0,0 +1,112 @@ +# Tamil translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# +# Dr.T.Vasudevan <agnihot3@gmail.com>, 2012. +# Shantha kumar <shkumar@redhat.com>, 2013. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug." +"cgi?product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-25 10:44+0530\n" +"Last-Translator: Shantha kumar <shkumar@redhat.com>\n" +"Language-Team: Tamil <>\n" +"Language: ta\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 1.0\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "இணைப்பு எதிர்பாராமல் துண்டிக்கப்பட்டது" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "செல்லுபடியாகாத தேடல் கோரிக்கை" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream ஐ பிரிக்க இயலாது" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "பிணைய ஸ்ட்ரீம் எதிர்பாராமல் முடிந்துவிட்டது" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "வளத்தை முழுவதுமாக தேக்ககப்படுத்துவதில் தோல்வியடைந்தது" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "வெளியீட்டு இடைநினைவு மிகச்சிறியது" + +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "HTTP பதிலை பாகுபடுத்த முடியவில்லை" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "அடையாளம் காண முடியாத HTTP பதிலளிப்பு குறியீடாக்கம்" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "செயல்பாடு ரத்து செய்யப்பட்டது" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "செயல்பாடு தடை செய்யும்" + +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "HTTP கோரிக்கையை பாகுபடுத்த முடியவில்லை" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "யூஆர்ஐ ஏதும் தரப்படவில்லை" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "செல்லுபடியாகாத '%s' URI: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "'%s' யூஆர்ஐ ஐ அலகிட முடியவில்லை" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "ஆதரவு இல்லாத யூஆர்ஐ திட்டம் '%s'" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "HTTP URI அல்ல" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "புரவலன் பெயர் ஒரு ஐபி முகவரி" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "செல்லுபடியாகாத கணிணிப்பெயர்" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "புரவலன் பெயருக்கு ஒரு செயற்களம் இல்லை" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "போதிய செயற்களம் இல்லை" + @@ -1,68 +1,112 @@ # Telugu translation for libsoup. # Copyright (C) 2012 libsoup's to Swecha Telugu localisation Team <localization@swecha.net> # This file is distributed under the same license as the libsoup package. -# Sasi Bhushan Boddepalli <sasi@swecha.net>, 2012. # +# Sasi Bhushan Boddepalli <sasi@swecha.net>, 2012. +# Krishnababu Krothapalli <kkrothap@redhat.com>, 2012, 2013. msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" "product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-06-18 13:31+0000\n" -"PO-Revision-Date: 2012-06-18 13:31+0000\n" -"Last-Translator: Sasi Bhushan Boddepalli <sasi@swecha.net>\n" -"Language-Team: Telugu <indlinux-telugu@lists.sourceforge.net>\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-25 14:39+0530\n" +"Last-Translator: Krishnababu Krothapalli <kkrothap@redhat.com>\n" +"Language-Team: Telugu <Fedora-trans-te@redhat.com>\n" +"Language: te\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Lokalize 1.5\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "కనెక్షను అనుకోకుండా అంతమైంది" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "చెల్లని సీక్ అభ్యర్ధన" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream ట్రంకేట్ చేయలేదు" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "నెట్వర్కు స్ట్రీమ్ అనుకోకుండా మూయబడింది" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "వనరును సంపూర్ణంగా క్యాచీ చేయుటకు విఫలమైంది" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "అవుట్పుట్ బఫర్ మరీ చిన్నది" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP response" +msgstr "HTTP స్పందనను పార్శ్ చేయలేక పోయింది" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "గుర్తించని HTTP స్పందన యెన్కోడింగ్" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "ఆపరేషన్ రద్దు చేయబడింది" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "ఆపరేషన్ బ్లాక్ చేస్తుంది" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +#| msgid "Could not parse URI '%s'" +msgid "Could not parse HTTP request" +msgstr "HTTP అభ్యర్ధనను పార్స్ చేయలేదు" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "URl అందించబడలేదు" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "చెల్లని '%s' URI: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "URI '%s' అన్వయించడం సాధ్యం కాదు" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "మద్దతు లేని URI స్కీమ్ '%s'" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "HTTP URI కాదు" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "హోస్ట్ పేరు ఒక IP చిరునామా" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "చెల్లని హోస్ట్ పేరు" -#: ../libsoup/soup-tld.c:181 ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "అతిధేయిపేరు అధార డొమైన్ కలిగిలేదు" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "సరిపోనన్ని డొమైన్ లు" + diff --git a/po/tg.po b/po/tg.po new file mode 100644 index 00000000..c8332dd7 --- /dev/null +++ b/po/tg.po @@ -0,0 +1,107 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Victor Ibragimov <victor.ibragimov@gmail.com>, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: Tajik Gnome\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-07-28 11:48+0000\n" +"PO-Revision-Date: 2013-10-10 16:30+0500\n" +"Last-Translator: Victor Ibragimov <victor.ibragimov@gmail.com>\n" +"Language-Team: \n" +"Language: Tajik\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.7\n" + +#: ../libsoup/soup-body-input-stream.c:140 +#: ../libsoup/soup-body-input-stream.c:171 +#: ../libsoup/soup-body-input-stream.c:204 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Пайвастшавӣ ногаҳон қатъ шудааст" + +#: ../libsoup/soup-body-input-stream.c:462 +msgid "Invalid seek request" +msgstr "Дархости ҷустуҷӯи нодуруст" + +#: ../libsoup/soup-body-input-stream.c:490 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream кӯтоҳ карда намешавад" + +#: ../libsoup/soup-cache-input-stream.c:73 +msgid "Network stream unexpectedly closed" +msgstr "Ҷараёни шабака ногаҳон манъ карда шудааст" + +#: ../libsoup/soup-cache-input-stream.c:290 +msgid "Failed to completely cache the resource" +msgstr "Зерҳофизаи манбаъ комилан захира карда нашуд" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Миёнҷии барориш хеле хурд аст" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Ҷавоби HTTP таҷзия карда намешавад" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Рамзгузории ҷавоби HTTP шинохта нашудааст" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "Амалиёт бекор шудааст" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "Амалиёт қатъ карда мешавад" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Дархости HTTP таҷзия карда намешавад" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "Ягон URI таъмин нашудааст" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Суроғаи '%s' URI нодуруст аст: %s" + +#: ../libsoup/soup-session.c:4301 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Суроғаи URI '%s' таҷзия карда нашуд" + +#: ../libsoup/soup-session.c:4338 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Нақшаи суроғаи URI '%s' дастгирӣ намешавад" + +#: ../libsoup/soup-session.c:4360 +#, c-format +msgid "Not an HTTP URI" +msgstr "Суроғаи HTTP URI намебошад" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Номи мизбон суроғаи IP мебошад" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Номи мизбони нодуруст" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Номи мизбон домени асосӣ надорад" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Доменҳои кофӣ надорад" diff --git a/po/th.po b/po/th.po new file mode 100644 index 00000000..b2c806a0 --- /dev/null +++ b/po/th.po @@ -0,0 +1,107 @@ +# Thai translation for libsoup. +# Copyright (C) 2012-2013 Free Software Foundation, Inc. +# This file is distributed under the same license as the libsoup package. +# Akom Chotiphantawanon <knight2000@gmail.com>, 2012. +# Theppitak Karoonboonyanan <thep@linux.thai.net>, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-09 11:38+0700\n" +"Last-Translator: Theppitak Karoonboonyanan <thep@linux.thai.net>\n" +"Language-Team: Thai <thai-l10n@googlegroups.com>\n" +"Language: th\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "การเชื่อมต่อยุติกะทันหัน" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "การร้องขอการเลื่อนตำแหน่งไม่ถูกต้อง" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "ไม่สามารถตัดท้าย SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "สตรีมเครือข่ายปิดกะทันหัน" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "ทำแคชทรัพยากรไม่เสร็จสมบูรณ์" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "บัฟเฟอร์ข้อมูลออกเล็กเกินไป" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "ไม่สามารถแจงคำตอบ HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "ไม่รู้จักรหัสอักขระของคำตอบ HTTP" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "การทำงานถูกยกเลิก" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "การทำงานถูกบล็อค" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "ไม่สามารถแจงคำร้อง HTTP" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "ไม่ได้ระบุ URI" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "URI '%s' ไม่ถูกต้อง: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "ไม่สามารถแจง URI '%s'" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "ไม่รองรับ URI แบบ '%s'" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "ไม่ใช่ URI ของ HTTP" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "ชื่อโฮสต์เป็นหมายเลขไอพี" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "ชื่อโฮสต์ผิดรูปแบบ" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "ชื่อโฮสต์ไม่มีโดเมนฐาน" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "มีโดเมนน้อยเกินไป" diff --git a/po/tr.po b/po/tr.po new file mode 100644 index 00000000..809ebf54 --- /dev/null +++ b/po/tr.po @@ -0,0 +1,110 @@ +# Turkish translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# +# Muhammet Kara <muhammetk@acikkaynak.name.tr>, 2012. +# Ozan Çağlayan <ozancag@gmail.com>, 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-04-03 16:00+0000\n" +"PO-Revision-Date: 2013-04-07 22:53+0300\n" +"Last-Translator: Ozan Çağlayan <ozancag@gmail.com>\n" +"Language-Team: Türkçe <>\n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Gtranslator 2.91.6\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Bağlantı beklenmeyen bir şekilde sonlandı" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Geçersiz arama talebi" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStream kesilemiyor" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Ağ akışı beklenmeyen bir şekilde sonlandı" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Özkaynak tamamen önbelleğe alınamadı" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Çıkış arabelleği çok küçük" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "HTTP cevabı ayrıştırılamadı" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "HTTP cevabı tanınmayan bir şekilde kodlanmış" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "İşlem iptal edildi" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "İşlem bloke edebilir" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "HTTP talebi ayrıştırılamadı" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "URI verilmedi" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Geçersiz '%s' URI: %s" + +#: ../libsoup/soup-session.c:4252 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI '%s' ayrıştırılamadı" + +#: ../libsoup/soup-session.c:4289 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Desteklenmeyen URI şeması '%s'" + +#: ../libsoup/soup-session.c:4311 +#, c-format +msgid "Not an HTTP URI" +msgstr "HTTP URI değil" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "Makine adı bir IP adresi" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "Geçersiz makine adı" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Ana makinenin temel etki alanı yok" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "Yeterli etki alanı yok" diff --git a/po/ug.po b/po/ug.po new file mode 100644 index 00000000..c5c366a6 --- /dev/null +++ b/po/ug.po @@ -0,0 +1,105 @@ +# Uyghur translation for libsoup. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Gheyret Kenji <gheyret@gmail.com>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-02-22 21:51+0900\n" +"Last-Translator: Gheyret Kenji <gheyret@gmail.com>\n" +"Language-Team: Uyghur Computer Science Association <UKIJ@yahoogroups.com>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "باغلىنىش تۇيۇقسىز توختاپ قالدى" + +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "ئىناۋەتسىز ئىزدەش ئىلتىماسى" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "ئېقىم SoupBodyInputStream نى قىسقارتقىلى بولمىدى" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "تور ئېقىمى تۇيۇقسىز يېپىلدى" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "مەنبەنى تولۇق غەملىۋېلىش مەغلۇپ بولدى" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "چىقىرىش يىغلەكى بەك كىچىك" + +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "HTTP ئىنكاسىنى يېشەلمىدى" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "HTTP ئىنكاسىنىڭ كودلىنىشىنى بىلگىلى بولمىدى" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 +msgid "Operation was cancelled" +msgstr "مەشغۇلات بىكار قىلىندى" + +#: ../libsoup/soup-message-io.c:893 +msgid "Operation would block" +msgstr "مەشغۇلات توسۇلىدۇ" + +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "HTTP ئىلتىماسىنى يېشەلمىدى" + +#: ../libsoup/soup-request.c:140 +#, c-format +msgid "No URI provided" +msgstr "URI تەمىنلەنمىدى" + +#: ../libsoup/soup-request.c:150 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "ئىناۋەتسىز ‹%s› URI: %s" + +#: ../libsoup/soup-session.c:4209 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI «%s» نى تەھلىل قىلالمىدى" + +#: ../libsoup/soup-session.c:4246 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "قوللىمايدىغان URI لايىھە ‹%s›" + +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "بۇ HTTP URI ئەمەس" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "كومپيۇتېر ئاتى دېگەن بىر دانە IP ئادرېستۇر" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "باش ماشىنا ئاتى ئىناۋەتسىز" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "كومپيۇتېر ئاتىدا ئاساسىي دائىرە يوق" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "يېتەرلىك دائىرە يوق" diff --git a/po/uk.po b/po/uk.po new file mode 100644 index 00000000..7f17ca6f --- /dev/null +++ b/po/uk.po @@ -0,0 +1,84 @@ +# Ukrainian translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Daniel <ted.korostiled@gmail.com>, 2012. +# Re. <ted.korostiled@gmail.com>, 2012. +msgid "" +msgstr "" +"Project-Id-Version: libsoup master\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-09-21 10:06+0300\n" +"PO-Revision-Date: 2012-09-21 10:09+0300\n" +"Last-Translator: Re. <ted.korostiled@gmail.com>\n" +"Language-Team: linux.org.ua\n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" +"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"X-Generator: Virtaal 0.7.1\n" +"X-Project-Style: gnome\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Несподівано зв'язок розірвано" + +#: ../libsoup/soup-body-input-stream.c:461 +msgid "Invalid seek request" +msgstr "Неправильний запит пошуку" + +#: ../libsoup/soup-body-input-stream.c:489 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Неможливо вкоротити SoupBodyInputStream" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Вихідний буфер надто малий" + +#: ../libsoup/soup-message-io.c:818 ../libsoup/soup-message-io.c:854 +msgid "Operation was cancelled" +msgstr "Операцію скасовано" + +#: ../libsoup/soup-message-io.c:865 +msgid "Operation would block" +msgstr "Операція заблокує" + +#: ../libsoup/soup-request.c:142 +#, c-format +msgid "No URI provided" +msgstr "Не надано URI" + +#: ../libsoup/soup-request.c:152 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Неправильний «%s» URI: %s" + +#: ../libsoup/soup-requester.c:219 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "Неможливо розібрати URI «%s»" + +#: ../libsoup/soup-requester.c:253 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Непідтримувана схема URI «%s»" + +#: ../libsoup/soup-tld.c:154 +msgid "Hostname is an IP address" +msgstr "Назва вузла — адреса IP" + +#: ../libsoup/soup-tld.c:175 +msgid "Invalid hostname" +msgstr "Неправильна назва вузла" + +#: ../libsoup/soup-tld.c:204 +msgid "Hostname has no base domain" +msgstr "Назва вузла не містить базового домену" + +#: ../libsoup/soup-tld.c:226 +msgid "Not enough domains" +msgstr "Не вистачає доменів" diff --git a/po/uz@cyrillic.po b/po/uz@cyrillic.po new file mode 100644 index 00000000..7060d2f1 --- /dev/null +++ b/po/uz@cyrillic.po @@ -0,0 +1,82 @@ +# Uzbek (Cyrillic) translation for libsoup. +# Copyright (C) 2012 libsoup's COPYRIGHT HOLDER +# This file is distributed under the same license as the libsoup package. +# Bahodir Mansurov <6ahodir@gmail.com>, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: libsoup gnome-3-6\n" +"Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" +"product=libsoup&keywords=I18N+L10N&component=general\n" +"POT-Creation-Date: 2012-10-16 12:52+0000\n" +"PO-Revision-Date: 2012-10-16 14:46-0500\n" +"Last-Translator: Bahodir Mansurov <6ahodir@gmail.com>\n" +"Language-Team: Uzbek (Cyrillic) <uz@cyrillic@li.org>\n" +"Language: uz@cyrillic\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 +msgid "Connection terminated unexpectedly" +msgstr "Алоқа кутилмаганда узилди" + +#: ../libsoup/soup-body-input-stream.c:461 +msgid "Invalid seek request" +msgstr "Нотўғри излаш сўрови" + +#: ../libsoup/soup-body-input-stream.c:489 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "SoupBodyInputStreamни тўхтатиб бўлмади" + +#: ../libsoup/soup-converter-wrapper.c:192 +#, c-format +msgid "Output buffer is too small" +msgstr "Чиқув буфери жуда кичик" + +#: ../libsoup/soup-message-io.c:818 ../libsoup/soup-message-io.c:854 +msgid "Operation was cancelled" +msgstr "Амал инкор этилди" + +#: ../libsoup/soup-message-io.c:865 +msgid "Operation would block" +msgstr "Амал тўсиб қўяди" + +#: ../libsoup/soup-request.c:142 +#, c-format +msgid "No URI provided" +msgstr "URI кўрсатилмаган" + +#: ../libsoup/soup-request.c:152 +#, c-format +msgid "Invalid '%s' URI: %s" +msgstr "Нотўғри URI '%s': '%s'" + +#: ../libsoup/soup-requester.c:219 +#, c-format +msgid "Could not parse URI '%s'" +msgstr "URI '%s' таҳлил қилиб бўлмади" + +#: ../libsoup/soup-requester.c:253 +#, c-format +msgid "Unsupported URI scheme '%s'" +msgstr "Қўллаб қувватланмаган '%s' URI чизмаси" + +#: ../libsoup/soup-tld.c:154 +msgid "Hostname is an IP address" +msgstr "Компьютер номи IP адресдан иборат" + +#: ../libsoup/soup-tld.c:175 +msgid "Invalid hostname" +msgstr "Нотўғри компьютер номи" + +#: ../libsoup/soup-tld.c:204 +msgid "Hostname has no base domain" +msgstr "Компьютер номида домен қисми йўқ" + +#: ../libsoup/soup-tld.c:226 +msgid "Not enough domains" +msgstr "Домен исмлари етарли емас" @@ -1,69 +1,107 @@ # Vietnamese translation for libsoup. # Copyright (C) 2012 libsoup's COPYRIGHT HOLDER # This file is distributed under the same license as the libsoup package. -# Nguyễn Thái Ngọc Duy <pclouds@gmail.com>, 2012. +# Nguyễn Thái Ngọc Duy <pclouds@gmail.com>, 2012-2013. # msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" "product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-06-28 03:27+0000\n" -"PO-Revision-Date: 2012-06-30 10:15+0700\n" +"POT-Creation-Date: 2013-02-18 10:20+0000\n" +"PO-Revision-Date: 2013-03-02 20:44+0700\n" "Last-Translator: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n" "Language-Team: Vietnamese <gnomevi-list@lists.sourceforge.net>\n" +"Language: vi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "Kết nối ngắt bất ngờ" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "Yêu cầu di chuyển (seek) không hợp lệ" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "Không thể cắt SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "Luồng mạng đóng bất ngờ" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "Lỗi lưu tạm (cache) toàn bộ tài nguyên" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "Vùng đệm xuất quá nhỏ" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "Không thể phân tích phản hồi HTTP" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "Không nhận ra bảng mã phản hồi HTTP" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "Thao tác bị huỷ" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "Thao tác có thể treo" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "Không thể phân tích phản hồi HTTP" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "Chưa có URI" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "URI '%s' không hợp lệ: %s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4209 #, c-format msgid "Could not parse URI '%s'" msgstr "Không thể phân tích URI '%s'" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4246 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "Không hỗ trợ kiểu URI '%s'" -#: ../libsoup/soup-tld.c:145 +#: ../libsoup/soup-session.c:4268 +#, c-format +msgid "Not an HTTP URI" +msgstr "Không phải HTTP URI" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "Tên máy là địa chỉ IP" -#: ../libsoup/soup-tld.c:166 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "Tên máy không hợp lệ" -#: ../libsoup/soup-tld.c:181 ../libsoup/soup-tld.c:219 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "Tên máy không có miền cơ bản" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "Không đủ miền" diff --git a/po/zh_CN.po b/po/zh_CN.po index 9c94b06a..3f746aa0 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -3,55 +3,109 @@ # This file is distributed under the same license as the libsoup package. # hmasterwang <hmasterwang@gmail.com>, 2012. # Alpha Cheng <hmasterwang@gmail.com>, 2012. +# Cheng Lu <chenglu1990@gmail.com>, 2012. +# YunQiang Su <wzssyqa@gmail.com>, 2012. # msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?" "product=libsoup&keywords=I18N+L10N&component=general\n" -"POT-Creation-Date: 2012-05-24 20:15+0000\n" -"PO-Revision-Date: 2012-05-22 17:55+0800\n" -"Last-Translator: Wylmer Wang <wantinghard@gmail.com>\n" -"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n" +"POT-Creation-Date: 2013-11-11 22:47+0000\n" +"PO-Revision-Date: 2013-09-07 14:07+0700\n" +"Last-Translator: 甘露(Gan Lu) <rhythm.gan@gmail.com>\n" +"Language-Team: Chinese (simplified) <i18n-zh@googlegroups.com>\n" +"Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 1.5.4\n" -#: ../libsoup/soup-body-input-stream.c:139 -#: ../libsoup/soup-body-input-stream.c:170 -#: ../libsoup/soup-body-input-stream.c:203 ../libsoup/soup-message-io.c:231 +#: ../libsoup/soup-body-input-stream.c:140 +#: ../libsoup/soup-body-input-stream.c:171 +#: ../libsoup/soup-body-input-stream.c:204 ../libsoup/soup-message-io.c:197 msgid "Connection terminated unexpectedly" msgstr "连接异常终止" -#: ../libsoup/soup-converter-wrapper.c:190 +#: ../libsoup/soup-body-input-stream.c:462 +msgid "Invalid seek request" +msgstr "无效的 seek 请求" + +#: ../libsoup/soup-body-input-stream.c:490 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "不能截断(truncate) SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:73 +msgid "Network stream unexpectedly closed" +msgstr "网络流意外关闭" + +#: ../libsoup/soup-cache-input-stream.c:290 +msgid "Failed to completely cache the resource" +msgstr "无法完全缓存该资源" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "输出缓冲区太小" -#: ../libsoup/soup-message-io.c:836 ../libsoup/soup-message-io.c:863 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "无法解析 HTTP 响应" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "未识别的 HTTP 响应编码" + +#: ../libsoup/soup-message-io.c:855 ../libsoup/soup-message-io.c:891 msgid "Operation was cancelled" msgstr "操作被取消" -#: ../libsoup/soup-message-io.c:874 +#: ../libsoup/soup-message-io.c:902 msgid "Operation would block" msgstr "操作将阻塞" -#: ../libsoup/soup-request.c:145 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "无法解析 HTTP 请求" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "未提供 URI" -#: ../libsoup/soup-request.c:155 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "无效的“%s”URI:%s" -#: ../libsoup/soup-requester.c:220 +#: ../libsoup/soup-session.c:4302 #, c-format msgid "Could not parse URI '%s'" msgstr "无法解析 URI:“%s”" -#: ../libsoup/soup-requester.c:254 +#: ../libsoup/soup-session.c:4339 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "不支持的 URI 方案:“%s”" + +#: ../libsoup/soup-session.c:4361 +#, c-format +msgid "Not an HTTP URI" +msgstr "不是一个 HTTP URI" + +#: ../libsoup/soup-tld.c:185 +msgid "Hostname is an IP address" +msgstr "主机名是一个 IP 地址" + +#: ../libsoup/soup-tld.c:206 +msgid "Invalid hostname" +msgstr "主机名无效" + +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "主机名没有基域" + +#: ../libsoup/soup-tld.c:257 +msgid "Not enough domains" +msgstr "没有足够的域" diff --git a/po/zh_HK.po b/po/zh_HK.po index 817d6d65..f9e17ba7 100644 --- a/po/zh_HK.po +++ b/po/zh_HK.po @@ -7,62 +7,100 @@ msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-07-16 08:47+0800\n" -"PO-Revision-Date: 2012-07-16 08:48+0800\n" +"POT-Creation-Date: 2013-03-01 22:41+0800\n" +"PO-Revision-Date: 2013-03-01 22:41+0800\n" "Last-Translator: Chao-Hsiung Liao <j_h_liau@yahoo.com.tw>\n" "Language-Team: Chinese (Hong Kong) <community@linuxhall.org>\n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" -#: ../libsoup/soup-body-input-stream.c:136 -#: ../libsoup/soup-body-input-stream.c:167 -#: ../libsoup/soup-body-input-stream.c:200 ../libsoup/soup-message-io.c:193 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "連線無預警的關閉了" -#: ../libsoup/soup-converter-wrapper.c:189 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "無效的搜尋要求" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "不能截短 SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "網絡串流無預警的關閉了" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "無法完整快取資源" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "輸出緩衝區太小" -#: ../libsoup/soup-message-io.c:817 ../libsoup/soup-message-io.c:853 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "無法解析 HTTP 回應" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "未辨識的 HTTP 回應編碼" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "操作已被取消" -#: ../libsoup/soup-message-io.c:864 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "操作會阻擋" -#: ../libsoup/soup-request.c:142 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "無法解析 HTTP 要求" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "未提供 URI" -#: ../libsoup/soup-request.c:152 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "無效的「%s」URI:%s" -#: ../libsoup/soup-requester.c:219 +#: ../libsoup/soup-session.c:4211 #, c-format msgid "Could not parse URI '%s'" msgstr "無法解析 URI「%s」" -#: ../libsoup/soup-requester.c:253 +#: ../libsoup/soup-session.c:4248 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "不支援的 URI scheme「%s」" -#: ../libsoup/soup-tld.c:146 +#: ../libsoup/soup-session.c:4270 +#, c-format +msgid "Not an HTTP URI" +msgstr "並非 HTTP URI" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "主機名稱是 IP 位址" -#: ../libsoup/soup-tld.c:167 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "主機名稱無效" -#: ../libsoup/soup-tld.c:182 ../libsoup/soup-tld.c:220 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "主機名稱沒有基礎網域" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "沒有足夠的網域" diff --git a/po/zh_TW.po b/po/zh_TW.po index f71178d8..d59a81ca 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -7,62 +7,100 @@ msgid "" msgstr "" "Project-Id-Version: libsoup master\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-07-16 08:47+0800\n" -"PO-Revision-Date: 2012-07-14 11:47+0800\n" +"POT-Creation-Date: 2013-03-01 22:41+0800\n" +"PO-Revision-Date: 2013-02-28 09:48+0800\n" "Last-Translator: Chao-Hsiung Liao <j_h_liau@yahoo.com.tw>\n" "Language-Team: Chinese (Taiwan) <chinese-l10n@googlegroups.com>\n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.5.4\n" -#: ../libsoup/soup-body-input-stream.c:136 -#: ../libsoup/soup-body-input-stream.c:167 -#: ../libsoup/soup-body-input-stream.c:200 ../libsoup/soup-message-io.c:193 +#: ../libsoup/soup-body-input-stream.c:141 +#: ../libsoup/soup-body-input-stream.c:172 +#: ../libsoup/soup-body-input-stream.c:205 ../libsoup/soup-message-io.c:191 msgid "Connection terminated unexpectedly" msgstr "連線無預警的關閉了" -#: ../libsoup/soup-converter-wrapper.c:189 +#: ../libsoup/soup-body-input-stream.c:463 +msgid "Invalid seek request" +msgstr "無效的搜尋要求" + +#: ../libsoup/soup-body-input-stream.c:491 +msgid "Cannot truncate SoupBodyInputStream" +msgstr "不能截短 SoupBodyInputStream" + +#: ../libsoup/soup-cache-input-stream.c:74 +msgid "Network stream unexpectedly closed" +msgstr "網路串流無預警的關閉了" + +#: ../libsoup/soup-cache-input-stream.c:291 +msgid "Failed to completely cache the resource" +msgstr "無法完整快取資源" + +#: ../libsoup/soup-converter-wrapper.c:192 #, c-format msgid "Output buffer is too small" msgstr "輸出緩衝區太小" -#: ../libsoup/soup-message-io.c:817 ../libsoup/soup-message-io.c:853 +#: ../libsoup/soup-message-client-io.c:41 +msgid "Could not parse HTTP response" +msgstr "無法解析 HTTP 回應" + +#: ../libsoup/soup-message-client-io.c:66 +msgid "Unrecognized HTTP response encoding" +msgstr "未辨識的 HTTP 回應編碼" + +#: ../libsoup/soup-message-io.c:846 ../libsoup/soup-message-io.c:882 msgid "Operation was cancelled" msgstr "操作已被取消" -#: ../libsoup/soup-message-io.c:864 +#: ../libsoup/soup-message-io.c:893 msgid "Operation would block" msgstr "操作會阻擋" -#: ../libsoup/soup-request.c:142 +#: ../libsoup/soup-message-server-io.c:40 +msgid "Could not parse HTTP request" +msgstr "無法解析 HTTP 要求" + +#: ../libsoup/soup-request.c:140 #, c-format msgid "No URI provided" msgstr "未提供 URI" -#: ../libsoup/soup-request.c:152 +#: ../libsoup/soup-request.c:150 #, c-format msgid "Invalid '%s' URI: %s" msgstr "無效的「%s」URI:%s" -#: ../libsoup/soup-requester.c:219 +#: ../libsoup/soup-session.c:4211 #, c-format msgid "Could not parse URI '%s'" msgstr "無法解析 URI「%s」" -#: ../libsoup/soup-requester.c:253 +#: ../libsoup/soup-session.c:4248 #, c-format msgid "Unsupported URI scheme '%s'" msgstr "不支援的 URI scheme「%s」" -#: ../libsoup/soup-tld.c:146 +#: ../libsoup/soup-session.c:4270 +#, c-format +msgid "Not an HTTP URI" +msgstr "並非 HTTP URI" + +#: ../libsoup/soup-tld.c:185 msgid "Hostname is an IP address" msgstr "主機名稱是 IP 位址" -#: ../libsoup/soup-tld.c:167 +#: ../libsoup/soup-tld.c:206 msgid "Invalid hostname" msgstr "主機名稱無效" -#: ../libsoup/soup-tld.c:182 ../libsoup/soup-tld.c:220 +#: ../libsoup/soup-tld.c:235 +msgid "Hostname has no base domain" +msgstr "主機名稱沒有基礎網域" + +#: ../libsoup/soup-tld.c:257 msgid "Not enough domains" msgstr "沒有足夠的網域" diff --git a/tap-driver.sh b/tap-driver.sh new file mode 100755 index 00000000..19aa531d --- /dev/null +++ b/tap-driver.sh @@ -0,0 +1,652 @@ +#! /bin/sh +# Copyright (C) 2011-2013 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to <bug-automake@gnu.org> or send patches to +# <automake-patches@gnu.org>. + +scriptversion=2011-12-27.17; # UTC + +# Make unconditional expansion of undefined variables an error. This +# helps a lot in preventing typo-related bugs. +set -u + +me=tap-driver.sh + +fatal () +{ + echo "$me: fatal: $*" >&2 + exit 1 +} + +usage_error () +{ + echo "$me: $*" >&2 + print_usage >&2 + exit 2 +} + +print_usage () +{ + cat <<END +Usage: + tap-driver.sh --test-name=NAME --log-file=PATH --trs-file=PATH + [--expect-failure={yes|no}] [--color-tests={yes|no}] + [--enable-hard-errors={yes|no}] [--ignore-exit] + [--diagnostic-string=STRING] [--merge|--no-merge] + [--comments|--no-comments] [--] TEST-COMMAND +The \`--test-name', \`--log-file' and \`--trs-file' options are mandatory. +END +} + +# TODO: better error handling in option parsing (in particular, ensure +# TODO: $log_file, $trs_file and $test_name are defined). +test_name= # Used for reporting. +log_file= # Where to save the result and output of the test script. +trs_file= # Where to save the metadata of the test run. +expect_failure=0 +color_tests=0 +merge=0 +ignore_exit=0 +comments=0 +diag_string='#' +while test $# -gt 0; do + case $1 in + --help) print_usage; exit $?;; + --version) echo "$me $scriptversion"; exit $?;; + --test-name) test_name=$2; shift;; + --log-file) log_file=$2; shift;; + --trs-file) trs_file=$2; shift;; + --color-tests) color_tests=$2; shift;; + --expect-failure) expect_failure=$2; shift;; + --enable-hard-errors) shift;; # No-op. + --merge) merge=1;; + --no-merge) merge=0;; + --ignore-exit) ignore_exit=1;; + --comments) comments=1;; + --no-comments) comments=0;; + --diagnostic-string) diag_string=$2; shift;; + --) shift; break;; + -*) usage_error "invalid option: '$1'";; + esac + shift +done + +test $# -gt 0 || usage_error "missing test command" + +case $expect_failure in + yes) expect_failure=1;; + *) expect_failure=0;; +esac + +if test $color_tests = yes; then + init_colors=' + color_map["red"]="[0;31m" # Red. + color_map["grn"]="[0;32m" # Green. + color_map["lgn"]="[1;32m" # Light green. + color_map["blu"]="[1;34m" # Blue. + color_map["mgn"]="[0;35m" # Magenta. + color_map["std"]="[m" # No color. + color_for_result["ERROR"] = "mgn" + color_for_result["PASS"] = "grn" + color_for_result["XPASS"] = "red" + color_for_result["FAIL"] = "red" + color_for_result["XFAIL"] = "lgn" + color_for_result["SKIP"] = "blu"' +else + init_colors='' +fi + +# :; is there to work around a bug in bash 3.2 (and earlier) which +# does not always set '$?' properly on redirection failure. +# See the Autoconf manual for more details. +:;{ + ( + # Ignore common signals (in this subshell only!), to avoid potential + # problems with Korn shells. Some Korn shells are known to propagate + # to themselves signals that have killed a child process they were + # waiting for; this is done at least for SIGINT (and usually only for + # it, in truth). Without the `trap' below, such a behaviour could + # cause a premature exit in the current subshell, e.g., in case the + # test command it runs gets terminated by a SIGINT. Thus, the awk + # script we are piping into would never seen the exit status it + # expects on its last input line (which is displayed below by the + # last `echo $?' statement), and would thus die reporting an internal + # error. + # For more information, see the Autoconf manual and the threads: + # <http://lists.gnu.org/archive/html/bug-autoconf/2011-09/msg00004.html> + # <http://mail.opensolaris.org/pipermail/ksh93-integration-discuss/2009-February/004121.html> + trap : 1 3 2 13 15 + if test $merge -gt 0; then + exec 2>&1 + else + exec 2>&3 + fi + "$@" + echo $? + ) | LC_ALL=C ${AM_TAP_AWK-awk} \ + -v me="$me" \ + -v test_script_name="$test_name" \ + -v log_file="$log_file" \ + -v trs_file="$trs_file" \ + -v expect_failure="$expect_failure" \ + -v merge="$merge" \ + -v ignore_exit="$ignore_exit" \ + -v comments="$comments" \ + -v diag_string="$diag_string" \ +' +# FIXME: the usages of "cat >&3" below could be optimized when using +# FIXME: GNU awk, and/on on systems that supports /dev/fd/. + +# Implementation note: in what follows, `result_obj` will be an +# associative array that (partly) simulates a TAP result object +# from the `TAP::Parser` perl module. + +## ----------- ## +## FUNCTIONS ## +## ----------- ## + +function fatal(msg) +{ + print me ": " msg | "cat >&2" + exit 1 +} + +function abort(where) +{ + fatal("internal error " where) +} + +# Convert a boolean to a "yes"/"no" string. +function yn(bool) +{ + return bool ? "yes" : "no"; +} + +function add_test_result(result) +{ + if (!test_results_index) + test_results_index = 0 + test_results_list[test_results_index] = result + test_results_index += 1 + test_results_seen[result] = 1; +} + +# Whether the test script should be re-run by "make recheck". +function must_recheck() +{ + for (k in test_results_seen) + if (k != "XFAIL" && k != "PASS" && k != "SKIP") + return 1 + return 0 +} + +# Whether the content of the log file associated to this test should +# be copied into the "global" test-suite.log. +function copy_in_global_log() +{ + for (k in test_results_seen) + if (k != "PASS") + return 1 + return 0 +} + +# FIXME: this can certainly be improved ... +function get_global_test_result() +{ + if ("ERROR" in test_results_seen) + return "ERROR" + if ("FAIL" in test_results_seen || "XPASS" in test_results_seen) + return "FAIL" + all_skipped = 1 + for (k in test_results_seen) + if (k != "SKIP") + all_skipped = 0 + if (all_skipped) + return "SKIP" + return "PASS"; +} + +function stringify_result_obj(result_obj) +{ + if (result_obj["is_unplanned"] || result_obj["number"] != testno) + return "ERROR" + + if (plan_seen == LATE_PLAN) + return "ERROR" + + if (result_obj["directive"] == "TODO") + return result_obj["is_ok"] ? "XPASS" : "XFAIL" + + if (result_obj["directive"] == "SKIP") + return result_obj["is_ok"] ? "SKIP" : COOKED_FAIL; + + if (length(result_obj["directive"])) + abort("in function stringify_result_obj()") + + return result_obj["is_ok"] ? COOKED_PASS : COOKED_FAIL +} + +function decorate_result(result) +{ + color_name = color_for_result[result] + if (color_name) + return color_map[color_name] "" result "" color_map["std"] + # If we are not using colorized output, or if we do not know how + # to colorize the given result, we should return it unchanged. + return result +} + +function report(result, details) +{ + if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/) + { + msg = ": " test_script_name + add_test_result(result) + } + else if (result == "#") + { + msg = " " test_script_name ":" + } + else + { + abort("in function report()") + } + if (length(details)) + msg = msg " " details + # Output on console might be colorized. + print decorate_result(result) msg + # Log the result in the log file too, to help debugging (this is + # especially true when said result is a TAP error or "Bail out!"). + print result msg | "cat >&3"; +} + +function testsuite_error(error_message) +{ + report("ERROR", "- " error_message) +} + +function handle_tap_result() +{ + details = result_obj["number"]; + if (length(result_obj["description"])) + details = details " " result_obj["description"] + + if (plan_seen == LATE_PLAN) + { + details = details " # AFTER LATE PLAN"; + } + else if (result_obj["is_unplanned"]) + { + details = details " # UNPLANNED"; + } + else if (result_obj["number"] != testno) + { + details = sprintf("%s # OUT-OF-ORDER (expecting %d)", + details, testno); + } + else if (result_obj["directive"]) + { + details = details " # " result_obj["directive"]; + if (length(result_obj["explanation"])) + details = details " " result_obj["explanation"] + } + + report(stringify_result_obj(result_obj), details) +} + +# `skip_reason` should be empty whenever planned > 0. +function handle_tap_plan(planned, skip_reason) +{ + planned += 0 # Avoid getting confused if, say, `planned` is "00" + if (length(skip_reason) && planned > 0) + abort("in function handle_tap_plan()") + if (plan_seen) + { + # Error, only one plan per stream is acceptable. + testsuite_error("multiple test plans") + return; + } + planned_tests = planned + # The TAP plan can come before or after *all* the TAP results; we speak + # respectively of an "early" or a "late" plan. If we see the plan line + # after at least one TAP result has been seen, assume we have a late + # plan; in this case, any further test result seen after the plan will + # be flagged as an error. + plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN) + # If testno > 0, we have an error ("too many tests run") that will be + # automatically dealt with later, so do not worry about it here. If + # $plan_seen is true, we have an error due to a repeated plan, and that + # has already been dealt with above. Otherwise, we have a valid "plan + # with SKIP" specification, and should report it as a particular kind + # of SKIP result. + if (planned == 0 && testno == 0) + { + if (length(skip_reason)) + skip_reason = "- " skip_reason; + report("SKIP", skip_reason); + } +} + +function extract_tap_comment(line) +{ + if (index(line, diag_string) == 1) + { + # Strip leading `diag_string` from `line`. + line = substr(line, length(diag_string) + 1) + # And strip any leading and trailing whitespace left. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + # Return what is left (if any). + return line; + } + return ""; +} + +# When this function is called, we know that line is a TAP result line, +# so that it matches the (perl) RE "^(not )?ok\b". +function setup_result_obj(line) +{ + # Get the result, and remove it from the line. + result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0) + sub("^(not )?ok[ \t]*", "", line) + + # If the result has an explicit number, get it and strip it; otherwise, + # automatically assing the next progresive number to it. + if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/) + { + match(line, "^[0-9]+") + # The final `+ 0` is to normalize numbers with leading zeros. + result_obj["number"] = substr(line, 1, RLENGTH) + 0 + line = substr(line, RLENGTH + 1) + } + else + { + result_obj["number"] = testno + } + + if (plan_seen == LATE_PLAN) + # No further test results are acceptable after a "late" TAP plan + # has been seen. + result_obj["is_unplanned"] = 1 + else if (plan_seen && testno > planned_tests) + result_obj["is_unplanned"] = 1 + else + result_obj["is_unplanned"] = 0 + + # Strip trailing and leading whitespace. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + + # This will have to be corrected if we have a "TODO"/"SKIP" directive. + result_obj["description"] = line + result_obj["directive"] = "" + result_obj["explanation"] = "" + + if (index(line, "#") == 0) + return # No possible directive, nothing more to do. + + # Directives are case-insensitive. + rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*" + + # See whether we have the directive, and if yes, where. + pos = match(line, rx "$") + if (!pos) + pos = match(line, rx "[^a-zA-Z0-9_]") + + # If there was no TAP directive, we have nothing more to do. + if (!pos) + return + + # Let`s now see if the TAP directive has been escaped. For example: + # escaped: ok \# SKIP + # not escaped: ok \\# SKIP + # escaped: ok \\\\\# SKIP + # not escaped: ok \ # SKIP + if (substr(line, pos, 1) == "#") + { + bslash_count = 0 + for (i = pos; i > 1 && substr(line, i - 1, 1) == "\\"; i--) + bslash_count += 1 + if (bslash_count % 2) + return # Directive was escaped. + } + + # Strip the directive and its explanation (if any) from the test + # description. + result_obj["description"] = substr(line, 1, pos - 1) + # Now remove the test description from the line, that has been dealt + # with already. + line = substr(line, pos) + # Strip the directive, and save its value (normalized to upper case). + sub("^[ \t]*#[ \t]*", "", line) + result_obj["directive"] = toupper(substr(line, 1, 4)) + line = substr(line, 5) + # Now get the explanation for the directive (if any), with leading + # and trailing whitespace removed. + sub("^[ \t]*", "", line) + sub("[ \t]*$", "", line) + result_obj["explanation"] = line +} + +function get_test_exit_message(status) +{ + if (status == 0) + return "" + if (status !~ /^[1-9][0-9]*$/) + abort("getting exit status") + if (status < 127) + exit_details = "" + else if (status == 127) + exit_details = " (command not found?)" + else if (status >= 128 && status <= 255) + exit_details = sprintf(" (terminated by signal %d?)", status - 128) + else if (status > 256 && status <= 384) + # We used to report an "abnormal termination" here, but some Korn + # shells, when a child process die due to signal number n, can leave + # in $? an exit status of 256+n instead of the more standard 128+n. + # Apparently, both behaviours are allowed by POSIX (2008), so be + # prepared to handle them both. See also Austing Group report ID + # 0000051 <http://www.austingroupbugs.net/view.php?id=51> + exit_details = sprintf(" (terminated by signal %d?)", status - 256) + else + # Never seen in practice. + exit_details = " (abnormal termination)" + return sprintf("exited with status %d%s", status, exit_details) +} + +function write_test_results() +{ + print ":global-test-result: " get_global_test_result() > trs_file + print ":recheck: " yn(must_recheck()) > trs_file + print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file + for (i = 0; i < test_results_index; i += 1) + print ":test-result: " test_results_list[i] > trs_file + close(trs_file); +} + +BEGIN { + +## ------- ## +## SETUP ## +## ------- ## + +'"$init_colors"' + +# Properly initialized once the TAP plan is seen. +planned_tests = 0 + +COOKED_PASS = expect_failure ? "XPASS": "PASS"; +COOKED_FAIL = expect_failure ? "XFAIL": "FAIL"; + +# Enumeration-like constants to remember which kind of plan (if any) +# has been seen. It is important that NO_PLAN evaluates "false" as +# a boolean. +NO_PLAN = 0 +EARLY_PLAN = 1 +LATE_PLAN = 2 + +testno = 0 # Number of test results seen so far. +bailed_out = 0 # Whether a "Bail out!" directive has been seen. + +# Whether the TAP plan has been seen or not, and if yes, which kind +# it is ("early" is seen before any test result, "late" otherwise). +plan_seen = NO_PLAN + +## --------- ## +## PARSING ## +## --------- ## + +is_first_read = 1 + +while (1) + { + # Involutions required so that we are able to read the exit status + # from the last input line. + st = getline + if (st < 0) # I/O error. + fatal("I/O error while reading from input stream") + else if (st == 0) # End-of-input + { + if (is_first_read) + abort("in input loop: only one input line") + break + } + if (is_first_read) + { + is_first_read = 0 + nextline = $0 + continue + } + else + { + curline = nextline + nextline = $0 + $0 = curline + } + # Copy any input line verbatim into the log file. + print | "cat >&3" + # Parsing of TAP input should stop after a "Bail out!" directive. + if (bailed_out) + continue + + # TAP test result. + if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/) + { + testno += 1 + setup_result_obj($0) + handle_tap_result() + } + # TAP plan (normal or "SKIP" without explanation). + else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/) + { + # The next two lines will put the number of planned tests in $0. + sub("^1\\.\\.", "") + sub("[^0-9]*$", "") + handle_tap_plan($0, "") + continue + } + # TAP "SKIP" plan, with an explanation. + else if ($0 ~ /^1\.\.0+[ \t]*#/) + { + # The next lines will put the skip explanation in $0, stripping + # any leading and trailing whitespace. This is a little more + # tricky in truth, since we want to also strip a potential leading + # "SKIP" string from the message. + sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "") + sub("[ \t]*$", ""); + handle_tap_plan(0, $0) + } + # "Bail out!" magic. + # Older versions of prove and TAP::Harness (e.g., 3.17) did not + # recognize a "Bail out!" directive when preceded by leading + # whitespace, but more modern versions (e.g., 3.23) do. So we + # emulate the latter, "more modern" behaviour. + else if ($0 ~ /^[ \t]*Bail out!/) + { + bailed_out = 1 + # Get the bailout message (if any), with leading and trailing + # whitespace stripped. The message remains stored in `$0`. + sub("^[ \t]*Bail out![ \t]*", ""); + sub("[ \t]*$", ""); + # Format the error message for the + bailout_message = "Bail out!" + if (length($0)) + bailout_message = bailout_message " " $0 + testsuite_error(bailout_message) + } + # Maybe we have too look for dianogtic comments too. + else if (comments != 0) + { + comment = extract_tap_comment($0); + if (length(comment)) + report("#", comment); + } + } + +## -------- ## +## FINISH ## +## -------- ## + +# A "Bail out!" directive should cause us to ignore any following TAP +# error, as well as a non-zero exit status from the TAP producer. +if (!bailed_out) + { + if (!plan_seen) + { + testsuite_error("missing test plan") + } + else if (planned_tests != testno) + { + bad_amount = testno > planned_tests ? "many" : "few" + testsuite_error(sprintf("too %s tests run (expected %d, got %d)", + bad_amount, planned_tests, testno)) + } + if (!ignore_exit) + { + # Fetch exit status from the last line. + exit_message = get_test_exit_message(nextline) + if (exit_message) + testsuite_error(exit_message) + } + } + +write_test_results() + +exit 0 + +} # End of "BEGIN" block. +' + +# TODO: document that we consume the file descriptor 3 :-( +} 3>"$log_file" + +test $? -eq 0 || fatal "I/O or internal error" + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/tap-test b/tap-test new file mode 100755 index 00000000..481e333e --- /dev/null +++ b/tap-test @@ -0,0 +1,5 @@ +#! /bin/sh + +# run a GTest in tap mode. The test binary is passed as $1 + +$1 -k --tap diff --git a/tests/Makefile.am b/tests/Makefile.am index 995196c3..a8b9d019 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,143 +1,136 @@ -INCLUDES = \ +include $(top_srcdir)/glib-tap.mk + +AM_CPPFLAGS = \ -I$(top_srcdir) \ - -DSRCDIR=\""$(abs_srcdir)"\" \ - -DLIBSOUP_DISABLE_DEPRECATED \ - $(SOUP_MAINTAINER_FLAGS) \ - $(XML_CFLAGS) \ $(GLIB_CFLAGS) LIBS = \ $(top_builddir)/libsoup/libsoup-2.4.la \ - $(LIBGNUTLS_LIBS) \ $(GLIB_LIBS) -noinst_PROGRAMS = \ - chunk-test \ - coding-test \ - connection-test \ - context-test \ - continue-test \ - cookies-test \ - date \ - dns \ - forms-test \ - get \ - header-parsing \ - misc-test \ - ntlm-test \ - redirect-test \ - requester-test \ - simple-httpd \ - simple-proxy \ - sniffing-test \ - ssl-test \ - streaming-test \ - timeout-test \ - tld-test \ - uri-parsing \ - $(CURL_TESTS) \ - $(APACHE_TESTS) \ - $(XMLRPC_TESTS) - -TEST_SRCS = test-utils.c test-utils.h - -auth_test_SOURCES = auth-test.c $(TEST_SRCS) -chunk_test_SOURCES = chunk-test.c $(TEST_SRCS) -coding_test_SOURCES = coding-test.c $(TEST_SRCS) -connection_test_SOURCES = connection-test.c $(TEST_SRCS) -context_test_SOURCES = context-test.c $(TEST_SRCS) -continue_test_SOURCES = continue-test.c $(TEST_SRCS) -cookies_test_SOURCES = cookies-test.c $(TEST_SRCS) -date_SOURCES = date.c $(TEST_SRCS) -dns_SOURCES = dns.c -forms_test_SOURCES = forms-test.c $(TEST_SRCS) -get_SOURCES = get.c -if BUILD_LIBSOUP_GNOME -get_LDADD = $(top_builddir)/libsoup/libsoup-gnome-2.4.la -endif -header_parsing_SOURCES = header-parsing.c $(TEST_SRCS) -misc_test_SOURCES = misc-test.c $(TEST_SRCS) -ntlm_test_SOURCES = ntlm-test.c $(TEST_SRCS) -proxy_test_SOURCES = proxy-test.c $(TEST_SRCS) -pull_api_SOURCES = pull-api.c $(TEST_SRCS) -range_test_SOURCES = range-test.c $(TEST_SRCS) -redirect_test_SOURCES = redirect-test.c $(TEST_SRCS) -requester_test_SOURCES = requester-test.c $(TEST_SRCS) -server_auth_test_SOURCES = server-auth-test.c $(TEST_SRCS) -simple_httpd_SOURCES = simple-httpd.c -simple_proxy_SOURCES = simple-proxy.c -sniffing_test_SOURCES = sniffing-test.c $(TEST_SRCS) -ssl_test_SOURCES = ssl-test.c $(TEST_SRCS) -streaming_test_SOURCES = streaming-test.c $(TEST_SRCS) -timeout_test_SOURCES = timeout-test.c $(TEST_SRCS) -tld_test_SOURCES = tld-test.c $(TEST_SRCS) -uri_parsing_SOURCES = uri-parsing.c $(TEST_SRCS) -xmlrpc_test_SOURCES = xmlrpc-test.c $(TEST_SRCS) -xmlrpc_server_test_SOURCES = xmlrpc-server-test.c $(TEST_SRCS) +test_programs = \ + auth-test \ + cache-test \ + chunk-test \ + chunk-io-test \ + coding-test \ + connection-test \ + context-test \ + continue-test \ + cookies-test \ + date \ + forms-test \ + header-parsing \ + misc-test \ + multipart-test \ + no-ssl-test \ + ntlm-test \ + proxy-test \ + pull-api \ + range-test \ + redirect-test \ + requester-test \ + resource-test \ + session-test \ + server-auth-test \ + server-test \ + sniffing-test \ + socket-test \ + ssl-test \ + streaming-test \ + timeout-test \ + tld-test \ + uri-parsing \ + xmlrpc-server-test \ + xmlrpc-test + +test_extra_programs = \ + ntlm-test-helper \ + $(TESTS) + +test_data = \ + index.txt \ + soup-tests.gresource \ + test-cert.pem \ + test-key.pem \ + xmlrpc-server.php + +noinst_LTLIBRARIES += libtest.la + +libtest_la_SOURCES = \ + test-utils.c \ + test-utils.h + +LDADD = libtest.la if HAVE_APACHE -APACHE_TESTS = auth-test proxy-test pull-api range-test -endif -if HAVE_CURL -CURL_TESTS = forms-test server-auth-test -endif -if HAVE_XMLRPC_EPI_PHP -XMLRPC_TESTS = xmlrpc-test xmlrpc-server-test +if HAVE_APACHE_2_2 +httpd_conf_in = httpd.conf.22.in +else +httpd_conf_in = httpd.conf.24.in endif +httpd.conf: $(httpd_conf_in) + $(AM_V_GEN) sed -e 's,[@]srcdir@,$(srcdir),' \ + -e 's,[@]builddir@,$(builddir),' \ + -e 's,[@]APACHE_MODULE_DIR@,$(APACHE_MODULE_DIR),' \ + -e 's,[@]APACHE_PHP_MODULE_DIR@,$(APACHE_PHP_MODULE_DIR),' \ + -e 's,[@]APACHE_PHP_MODULE@,$(APACHE_PHP_MODULE),' \ + -e 's,[@]IF_HAVE_PHP@,$(IF_HAVE_PHP),' \ + -e 's,[@]APACHE_SSL_MODULE_DIR@,$(APACHE_SSL_MODULE_DIR),' \ + $< > $@ || rm -f $@ -TESTS = \ - chunk-test \ - coding-test \ - connection-test \ - context-test \ - continue-test \ - cookies-test \ - date \ - header-parsing \ - misc-test \ - ntlm-test \ - redirect-test \ - requester-test \ - sniffing-test \ - ssl-test \ - streaming-test \ - timeout-test \ - tld-test \ - uri-parsing \ - $(APACHE_TESTS) \ - $(CURL_TESTS) \ - $(XMLRPC_TESTS) - -RESOURCES = \ - resources/atom.xml \ - resources/home.gif \ - resources/html_binary.html \ - resources/mbox \ - resources/mbox.gz \ - resources/mbox.raw \ - resources/mbox.zlib \ - resources/ps_binary.ps \ - resources/rss20.xml \ - resources/test.html \ - resources/text_binary.txt - -EXTRA_DIST = \ +BUILT_SOURCES += httpd.conf +test_data += \ htdigest \ htpasswd \ - httpd.conf.in \ - index.txt \ - libsoup.supp \ - test-cert.pem \ - test-key.pem \ - xmlrpc-server.php \ + httpd.conf +endif + +RESOURCES = $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --generate-dependencies $(srcdir)/soup-tests.gresource.xml) + +soup-tests.gresource: soup-tests.gresource.xml $(RESOURCES) + $(AM_V_GEN) $(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) $< + +EXTRA_DIST += \ + htdigest \ + htpasswd \ + httpd.conf.22.in \ + httpd.conf.24.in \ + index.txt \ + libsoup.supp \ + soup-tests.gresource.xml \ + test-cert.pem \ + test-key.pem \ + xmlrpc-server.php \ $(RESOURCES) -if MISSING_REGRESSION_TEST_PACKAGES +DISTCLEANFILES += soup-tests.gresource httpd.conf + +TESTS_ENVIRONMENT += SOUP_TESTS_IN_MAKE_CHECK=1 + +check: start-httpd + check-local: check-TESTS - @echo "" - @echo "NOTE: some tests were not run due to missing packages:" $(MISSING_REGRESSION_TEST_PACKAGES) - @echo "" + @$(MAKE) kill-httpd + +.PHONY: start-httpd kill-httpd + +start-httpd: +if HAVE_APACHE_2_2 + @$(APACHE_HTTPD) -d $(abs_srcdir) -c "PidFile `pwd`/httpd.pid" -f `pwd`/httpd.conf -k start; +endif +if HAVE_APACHE_2_4 + @$(APACHE_HTTPD) -d $(abs_srcdir) -c "DefaultRuntimeDir `pwd`" -c "PidFile `pwd`/httpd.pid" -f `pwd`/httpd.conf -k start; endif kill-httpd: - $(APACHE_HTTPD) -d `pwd` -f httpd.conf -k stop +if HAVE_APACHE_2_2 + @if [ -f httpd.pid ]; then \ + $(APACHE_HTTPD) -d $(abs_srcdir) -c "PidFile `pwd`/httpd.pid" -f `pwd`/httpd.conf -k stop; \ + fi +endif +if HAVE_APACHE_2_4 + @if [ -f httpd.pid ]; then \ + $(APACHE_HTTPD) -d $(abs_srcdir) -c "DefaultRuntimeDir `pwd`" -c "PidFile `pwd`/httpd.pid" -f `pwd`/httpd.conf -k stop; \ + fi +endif diff --git a/tests/auth-test.c b/tests/auth-test.c index 85c4d414..8ed5cead 100644 --- a/tests/auth-test.c +++ b/tests/auth-test.c @@ -2,6 +2,7 @@ #include "test-utils.h" +static const char *base_uri; static GMainLoop *loop; typedef struct { @@ -35,10 +36,6 @@ typedef struct { guint final_status; } SoupAuthTest; -/* Will either point to main_tests or relogin_tests - */ -static SoupAuthTest *current_tests; - static SoupAuthTest main_tests[] = { { "No auth available, should fail", "Basic/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED }, @@ -146,7 +143,12 @@ static SoupAuthTest main_tests[] = { "Digest/realm1/not/", "1", FALSE, /* should not be used */ "1", SOUP_STATUS_UNAUTHORIZED }, { "Make sure we've forgotten it", - "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED } + "Digest/realm1/", "", FALSE, "0", SOUP_STATUS_UNAUTHORIZED }, + + { "Fail with URI-embedded password, then use right password in the authenticate signal", + "Basic/realm3/", "43", TRUE, "43", SOUP_STATUS_OK }, + + { NULL } }; static const char *auths[] = { @@ -202,14 +204,12 @@ handler (SoupMessage *msg, gpointer data) if (*expected) { exp = *expected - '0'; - if (auth != exp) { - debug_printf (1, " expected %s!\n", auths[exp]); - errors++; - } + soup_test_assert (auth == exp, + "expected %s", auths[exp]); memmove (expected, expected + 1, strlen (expected)); } else { - debug_printf (1, " expected to be finished\n"); - errors++; + soup_test_assert (*expected, + "expected to be finished"); } } @@ -217,18 +217,18 @@ static void authenticate (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer data) { - int *i = data; + SoupAuthTest *test = data; char *username, *password; char num; - if (!current_tests[*i].provided[0]) + if (!test->provided[0]) return; if (retrying) { - if (!current_tests[*i].provided[1]) + if (!test->provided[1]) return; - num = current_tests[*i].provided[1]; + num = test->provided[1]; } else - num = current_tests[*i].provided[0]; + num = test->provided[0]; username = g_strdup_printf ("user%c", num); password = g_strdup_printf ("realm%c", num); @@ -244,13 +244,10 @@ bug271540_sent (SoupMessage *msg, gpointer data) gboolean *authenticated = data; int auth = identify_auth (msg); - if (!*authenticated && auth) { - debug_printf (1, " using auth on message %d before authenticating!!??\n", n); - errors++; - } else if (*authenticated && !auth) { - debug_printf (1, " sent unauthenticated message %d after authenticating!\n", n); - errors++; - } + soup_test_assert (*authenticated || !auth, + "using auth on message %d before authenticating", n); + soup_test_assert (!*authenticated || auth, + "sent unauthenticated message %d after authenticating", n); } static void @@ -269,8 +266,8 @@ bug271540_authenticate (SoupSession *session, SoupMessage *msg, soup_auth_authenticate (auth, "user1", "realm1"); *authenticated = TRUE; } else { - debug_printf (1, " asked to authenticate message %d after authenticating!\n", n); - errors++; + soup_test_assert (!*authenticated, + "asked to authenticate message %d after authenticating", n); } } @@ -278,13 +275,8 @@ static void bug271540_finished (SoupSession *session, SoupMessage *msg, gpointer data) { int *left = data; - int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "#")); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " got status '%d %s' on message %d!\n", - msg->status_code, msg->reason_phrase, n); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); (*left)--; if (!*left) @@ -292,7 +284,7 @@ bug271540_finished (SoupSession *session, SoupMessage *msg, gpointer data) } static void -do_pipelined_auth_test (const char *base_uri) +do_pipelined_auth_test (void) { SoupSession *session; SoupMessage *msg; @@ -300,7 +292,10 @@ do_pipelined_auth_test (const char *base_uri) char *uri; int i; - debug_printf (1, "Testing pipelined auth (bug 271540):\n"); + g_test_bug ("271540"); + + SOUP_TEST_SKIP_IF_NO_APACHE; + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); authenticated = FALSE; @@ -437,28 +432,21 @@ do_digest_nonce_test (SoupSession *session, &got_401); got_401 = FALSE; soup_session_send_message (session, msg); - if (got_401 != expect_401) { - debug_printf (1, " %s request %s a 401 Unauthorized!\n", nth, - got_401 ? "got" : "did not get"); - errors++; - } - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " %s request got status %d %s!\n", nth, - msg->status_code, msg->reason_phrase); - errors++; - } - if (errors == 0) - debug_printf (1, " %s request succeeded\n", nth); + soup_test_assert (got_401 == expect_401, + "%s request %s a 401 Unauthorized!\n", nth, + got_401 ? "got" : "did not get"); + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + g_object_unref (msg); } static void -do_digest_expiration_test (const char *base_uri) +do_digest_expiration_test (void) { SoupSession *session; char *uri; - debug_printf (1, "\nTesting digest nonce expiration:\n"); + SOUP_TEST_SKIP_IF_NO_APACHE; session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); @@ -530,10 +518,8 @@ async_authenticate_assert_once (SoupSession *session, SoupMessage *msg, debug_printf (2, " async_authenticate_assert_once\n"); - if (*been_here) { - debug_printf (1, " ERROR: async_authenticate_assert_once called twice\n"); - errors++; - } + soup_test_assert (!*been_here, + "async_authenticate_assert_once called twice"); *been_here = TRUE; } @@ -545,10 +531,8 @@ async_authenticate_assert_once_and_stop (SoupSession *session, SoupMessage *msg, debug_printf (2, " async_authenticate_assert_once_and_stop\n"); - if (*been_here) { - debug_printf (1, " ERROR: async_authenticate_assert_once called twice\n"); - errors++; - } + soup_test_assert (!*been_here, + "async_authenticate_assert_once called twice"); *been_here = TRUE; soup_session_pause_message (session, msg); @@ -556,7 +540,7 @@ async_authenticate_assert_once_and_stop (SoupSession *session, SoupMessage *msg, } static void -do_async_auth_test (const char *base_uri) +do_async_auth_good_password_test (void) { SoupSession *session; SoupMessage *msg1, *msg2, *msg3, msg2_bak; @@ -564,15 +548,13 @@ do_async_auth_test (const char *base_uri) char *uri; SoupAuth *auth = NULL; int remaining; - gboolean been_there; - debug_printf (1, "\nTesting async auth:\n"); + SOUP_TEST_SKIP_IF_NO_APACHE; loop = g_main_loop_new (NULL, TRUE); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - remaining = 0; - uri = g_strconcat (base_uri, "Basic/realm1/", NULL); + remaining = 0; msg1 = soup_message_new ("GET", uri); g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1)); @@ -590,13 +572,7 @@ do_async_auth_test (const char *base_uri) g_object_set_data (G_OBJECT (msg2), "id", GINT_TO_POINTER (2)); soup_session_send_message (session, msg2); - if (msg2->status_code == SOUP_STATUS_UNAUTHORIZED) - debug_printf (1, " msg2 failed as expected\n"); - else { - debug_printf (1, " msg2 got wrong status! (%u)\n", - msg2->status_code); - errors++; - } + soup_test_assert_message_status (msg2, SOUP_STATUS_UNAUTHORIZED); /* msg2 should be done at this point; assuming everything is * working correctly, the session won't look at it again; we @@ -627,25 +603,11 @@ do_async_auth_test (const char *base_uri) g_main_loop_run (loop); /* async_finished will quit the loop */ - } else { - debug_printf (1, " msg1 didn't get authenticate signal!\n"); - errors++; - } + } else + soup_test_assert (auth, "msg1 didn't get authenticate signal"); - if (msg1->status_code == SOUP_STATUS_OK) - debug_printf (1, " msg1 succeeded\n"); - else { - debug_printf (1, " msg1 FAILED! (%u %s)\n", - msg1->status_code, msg1->reason_phrase); - errors++; - } - if (msg3->status_code == SOUP_STATUS_OK) - debug_printf (1, " msg3 succeeded\n"); - else { - debug_printf (1, " msg3 FAILED! (%u %s)\n", - msg3->status_code, msg3->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg1, SOUP_STATUS_OK); + soup_test_assert_message_status (msg3, SOUP_STATUS_OK); soup_test_session_abort_unref (session); @@ -654,27 +616,46 @@ do_async_auth_test (const char *base_uri) memcpy (msg2, &msg2_bak, sizeof (SoupMessage)); g_object_unref (msg2); + g_free (uri); + g_main_loop_unref (loop); +} + +static void +do_async_auth_bad_password_test (void) +{ + SoupSession *session; + SoupMessage *msg; + guint auth_id; + char *uri; + SoupAuth *auth = NULL; + int remaining; + gboolean been_there; + /* Test that giving the wrong password doesn't cause multiple * authenticate signals the second time. */ - debug_printf (1, "\nTesting async auth with wrong password (#522601):\n"); + g_test_bug ("522601"); + + SOUP_TEST_SKIP_IF_NO_APACHE; + loop = g_main_loop_new (NULL, TRUE); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + uri = g_strconcat (base_uri, "Basic/realm1/", NULL); remaining = 0; auth = NULL; - msg1 = soup_message_new ("GET", uri); - g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1)); + msg = soup_message_new ("GET", uri); + g_object_set_data (G_OBJECT (msg), "id", GINT_TO_POINTER (1)); auth_id = g_signal_connect (session, "authenticate", G_CALLBACK (async_authenticate), &auth); - g_object_ref (msg1); + g_object_ref (msg); remaining++; - soup_session_queue_message (session, msg1, async_finished, &remaining); + soup_session_queue_message (session, msg, async_finished, &remaining); g_main_loop_run (loop); g_signal_handler_disconnect (session, auth_id); soup_auth_authenticate (auth, "user1", "wrong"); g_object_unref (auth); - soup_session_unpause_message (session, msg1); + soup_session_unpause_message (session, msg); been_there = FALSE; auth_id = g_signal_connect (session, "authenticate", @@ -683,55 +664,71 @@ do_async_auth_test (const char *base_uri) g_main_loop_run (loop); g_signal_handler_disconnect (session, auth_id); - if (!been_there) { - debug_printf (1, " authenticate not emitted?\n"); - errors++; - } + soup_test_assert (been_there, + "authenticate not emitted"); soup_test_session_abort_unref (session); - g_object_unref (msg1); + g_object_unref (msg); + + g_free (uri); + g_main_loop_unref (loop); +} + +static void +do_async_auth_no_password_test (void) +{ + SoupSession *session; + SoupMessage *msg; + guint auth_id; + char *uri; + int remaining; + gboolean been_there; /* Test that giving no password doesn't cause multiple * authenticate signals the second time. */ - debug_printf (1, "\nTesting async auth with no password (#583462):\n"); + g_test_bug ("583462"); + + SOUP_TEST_SKIP_IF_NO_APACHE; + loop = g_main_loop_new (NULL, TRUE); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + uri = g_strconcat (base_uri, "Basic/realm1/", NULL); remaining = 0; /* Send a message that doesn't actually authenticate */ - msg1 = soup_message_new ("GET", uri); - g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (1)); + msg = soup_message_new ("GET", uri); + g_object_set_data (G_OBJECT (msg), "id", GINT_TO_POINTER (1)); auth_id = g_signal_connect (session, "authenticate", G_CALLBACK (async_authenticate), NULL); - g_object_ref (msg1); + g_object_ref (msg); remaining++; - soup_session_queue_message (session, msg1, async_finished, &remaining); + soup_session_queue_message (session, msg, async_finished, &remaining); g_main_loop_run (loop); g_signal_handler_disconnect (session, auth_id); - soup_session_unpause_message (session, msg1); + soup_session_unpause_message (session, msg); g_main_loop_run (loop); - g_object_unref(msg1); + g_object_unref(msg); /* Now send a second message */ - msg1 = soup_message_new ("GET", uri); - g_object_set_data (G_OBJECT (msg1), "id", GINT_TO_POINTER (2)); - g_object_ref (msg1); + msg = soup_message_new ("GET", uri); + g_object_set_data (G_OBJECT (msg), "id", GINT_TO_POINTER (2)); + g_object_ref (msg); been_there = FALSE; auth_id = g_signal_connect (session, "authenticate", G_CALLBACK (async_authenticate_assert_once_and_stop), &been_there); remaining++; - soup_session_queue_message (session, msg1, async_finished, &remaining); + soup_session_queue_message (session, msg, async_finished, &remaining); g_main_loop_run (loop); - soup_session_unpause_message (session, msg1); + soup_session_unpause_message (session, msg); g_main_loop_run (loop); g_signal_handler_disconnect (session, auth_id); soup_test_session_abort_unref (session); - g_object_unref (msg1); + g_object_unref (msg); g_free (uri); g_main_loop_unref (loop); @@ -795,41 +792,27 @@ select_auth_test_one (SoupURI *uri, msg = soup_message_new_from_uri ("GET", uri); soup_session_send_message (session, msg); - if (strcmp (sad.round[0].headers, first_headers) != 0) { - debug_printf (1, " Header order wrong: expected %s, got %s\n", - first_headers, sad.round[0].headers); - errors++; - } - if (strcmp (sad.round[0].response, first_response) != 0) { - debug_printf (1, " Selected auth type wrong: expected %s, got %s\n", - first_response, sad.round[0].response); - errors++; - } - - if (second_headers && !sad.round[1].headers) { - debug_printf (1, " Expected a second round!\n"); - errors++; - } else if (!second_headers && sad.round[1].headers) { - debug_printf (1, " Didn't expect a second round!\n"); - errors++; - } else if (second_headers) { - if (strcmp (sad.round[1].headers, second_headers) != 0) { - debug_printf (1, " Second round header order wrong: expected %s, got %s\n", - second_headers, sad.round[1].headers); - errors++; - } - if (strcmp (sad.round[1].response, second_response) != 0) { - debug_printf (1, " Second round selected auth type wrong: expected %s, got %s\n", - second_response, sad.round[1].response); - errors++; - } + soup_test_assert (strcmp (sad.round[0].headers, first_headers) == 0, + "Header order wrong: expected %s, got %s", + first_headers, sad.round[0].headers); + soup_test_assert (strcmp (sad.round[0].response, first_response) == 0, + "Selected auth type wrong: expected %s, got %s", + first_response, sad.round[0].response); + + soup_test_assert (sad.round[1].headers || !second_headers, + "Expected a second round"); + soup_test_assert (!sad.round[1].headers || second_headers, + "Didn't expect a second round"); + if (second_headers && second_response) { + soup_test_assert (strcmp (sad.round[1].headers, second_headers) == 0, + "Second round header order wrong: expected %s, got %s\n", + second_headers, sad.round[1].headers); + soup_test_assert (strcmp (sad.round[1].response, second_response) == 0, + "Second round selected auth type wrong: expected %s, got %s\n", + second_response, sad.round[1].response); } - if (msg->status_code != final_status) { - debug_printf (1, " Final status wrong: expected %u, got %u\n", - final_status, msg->status_code); - errors++; - } + soup_test_assert_message_status (msg, final_status); g_object_unref (msg); soup_test_session_abort_unref (session); @@ -873,7 +856,7 @@ do_select_auth_test (void) SoupAuthDomain *basic_auth_domain, *digest_auth_domain; SoupURI *uri; - debug_printf (1, "\nTesting selection among multiple auths:\n"); + g_test_bug ("562339"); /* It doesn't seem to be possible to configure Apache to serve * multiple auth types for a single URL. So we have to use @@ -1029,8 +1012,6 @@ do_auth_close_test (void) SoupURI *uri; AuthCloseData acd; - debug_printf (1, "\nTesting auth when server times out connection:\n"); - server = soup_test_server_new (FALSE); soup_server_add_handler (server, NULL, server_callback, NULL, NULL); @@ -1057,18 +1038,130 @@ do_auth_close_test (void) soup_uri_free (uri); soup_session_send_message (acd.session, acd.msg); - if (acd.msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Final status wrong: expected %u, got %u %s\n", - SOUP_STATUS_OK, acd.msg->status_code, - acd.msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (acd.msg, SOUP_STATUS_OK); g_object_unref (acd.msg); soup_test_session_abort_unref (acd.session); soup_test_server_quit_unref (server); } +static gboolean +infinite_cancel (gpointer session) +{ + soup_session_abort (session); + return FALSE; +} + +static void +infinite_authenticate (SoupSession *session, SoupMessage *msg, + SoupAuth *auth, gboolean retrying, gpointer data) +{ + soup_auth_authenticate (auth, "user", "bad"); +} + +static void +do_infinite_auth_test (void) +{ + SoupSession *session; + SoupMessage *msg; + char *uri; + int timeout; + + SOUP_TEST_SKIP_IF_NO_APACHE; + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + g_signal_connect (session, "authenticate", + G_CALLBACK (infinite_authenticate), NULL); + + uri = g_strconcat (base_uri, "Basic/realm1/", NULL); + msg = soup_message_new ("GET", uri); + g_free (uri); + + timeout = g_timeout_add (500, infinite_cancel, session); + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*stuck in infinite loop*"); + soup_session_send_message (session, msg); + g_test_assert_expected_messages (); + + soup_test_assert (msg->status_code != SOUP_STATUS_CANCELLED, + "Got stuck in loop"); + soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED); + + g_source_remove (timeout); + soup_test_session_abort_unref (session); + g_object_unref (msg); +} + +static void +disappear_request_read (SoupServer *server, SoupMessage *msg, + SoupClientContext *context, gpointer user_data) +{ + /* Remove the WWW-Authenticate header if this was a failed attempt */ + if (soup_message_headers_get_one (msg->request_headers, "Authorization") && + msg->status_code == SOUP_STATUS_UNAUTHORIZED) + soup_message_headers_remove (msg->response_headers, "WWW-Authenticate"); +} + +static void +disappear_authenticate (SoupSession *session, SoupMessage *msg, + SoupAuth *auth, gboolean retrying, gpointer data) +{ + int *counter = data; + + (*counter)++; + if (!retrying) + soup_auth_authenticate (auth, "user", "bad"); +} + +static void +do_disappearing_auth_test (void) +{ + SoupServer *server; + SoupAuthDomain *auth_domain; + SoupURI *uri; + SoupMessage *msg; + SoupSession *session; + int counter; + + g_test_bug ("https://bugzilla.redhat.com/show_bug.cgi?id=916224"); + + server = soup_test_server_new (FALSE); + soup_server_add_handler (server, NULL, + server_callback, NULL, NULL); + + uri = soup_uri_new ("http://127.0.0.1/"); + soup_uri_set_port (uri, soup_server_get_port (server)); + + auth_domain = soup_auth_domain_basic_new ( + SOUP_AUTH_DOMAIN_REALM, "auth-test", + SOUP_AUTH_DOMAIN_ADD_PATH, "/", + SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, server_basic_auth_callback, + NULL); + soup_server_add_auth_domain (server, auth_domain); + g_signal_connect (server, "request-read", + G_CALLBACK (disappear_request_read), NULL); + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + + counter = 0; + g_signal_connect (session, "authenticate", + G_CALLBACK (disappear_authenticate), &counter); + + msg = soup_message_new_from_uri ("GET", uri); + soup_session_send_message (session, msg); + + soup_test_assert (counter <= 2, + "Got stuck in loop"); + soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED); + + g_object_unref (msg); + soup_test_session_abort_unref (session); + + g_object_unref (auth_domain); + soup_uri_free (uri); + soup_test_server_quit_unref (server); +} + static SoupAuthTest relogin_tests[] = { { "Auth provided via URL, should succeed", "Basic/realm12/", "1", TRUE, "01", SOUP_STATUS_OK }, @@ -1090,25 +1183,28 @@ static SoupAuthTest relogin_tests[] = { { "Should fail with no auth, fail again with bad password, and give up", "Basic/realm12/", "3", FALSE, "03", SOUP_STATUS_UNAUTHORIZED }, + + { NULL } }; static void -do_batch_tests (const gchar *base_uri_str, gint ntests) +do_batch_tests (gconstpointer data) { + const SoupAuthTest *current_tests = data; SoupSession *session; SoupMessage *msg; char *expected, *uristr; - SoupURI *base_uri; + SoupURI *base; + guint signal; int i; - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - g_signal_connect (session, "authenticate", - G_CALLBACK (authenticate), &i); + SOUP_TEST_SKIP_IF_NO_APACHE; - base_uri = soup_uri_new (base_uri_str); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + base = soup_uri_new (base_uri); - for (i = 0; i < ntests; i++) { - SoupURI *soup_uri = soup_uri_new_with_base (base_uri, current_tests[i].url); + for (i = 0; current_tests[i].url; i++) { + SoupURI *soup_uri = soup_uri_new_with_base (base, current_tests[i].url); debug_printf (1, "Test %d: %s\n", i + 1, current_tests[i].explanation); @@ -1139,30 +1235,24 @@ do_batch_tests (const gchar *base_uri_str, gint ntests) soup_message_add_status_code_handler ( msg, "got_headers", SOUP_STATUS_OK, G_CALLBACK (handler), expected); + + signal = g_signal_connect (session, "authenticate", + G_CALLBACK (authenticate), + (gpointer)¤t_tests[i]); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_UNAUTHORIZED && - msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " %d %s !\n", msg->status_code, - msg->reason_phrase); - errors++; - } - if (*expected) { - debug_printf (1, " expected %d more round(s)\n", - (int)strlen (expected)); - errors++; - } - g_free (expected); + g_signal_handler_disconnect (session, signal); - if (msg->status_code != current_tests[i].final_status) { - debug_printf (1, " expected %d\n", - current_tests[i].final_status); - } + soup_test_assert_message_status (msg, current_tests[i].final_status); + soup_test_assert (!*expected, + "expected %d more round(s)\n", + (int)strlen (expected)); + g_free (expected); debug_printf (1, "\n"); g_object_unref (msg); } - soup_uri_free (base_uri); + soup_uri_free (base); soup_test_session_abort_unref (session); } @@ -1170,31 +1260,27 @@ do_batch_tests (const gchar *base_uri_str, gint ntests) int main (int argc, char **argv) { - const char *base_uri; - int ntests; + int ret; test_init (argc, argv, NULL); apache_init (); base_uri = "http://127.0.0.1:47524/"; - /* Main tests */ - current_tests = main_tests; - ntests = G_N_ELEMENTS (main_tests); - do_batch_tests (base_uri, ntests); - - /* Re-login tests */ - current_tests = relogin_tests; - ntests = G_N_ELEMENTS (relogin_tests); - do_batch_tests (base_uri, ntests); + g_test_add_data_func ("/auth/main-tests", main_tests, do_batch_tests); + g_test_add_data_func ("/auth/relogin-tests", relogin_tests, do_batch_tests); + g_test_add_func ("/auth/pipelined-auth", do_pipelined_auth_test); + g_test_add_func ("/auth/digest-expiration", do_digest_expiration_test); + g_test_add_func ("/auth/async-auth/good-password", do_async_auth_good_password_test); + g_test_add_func ("/auth/async-auth/bad-password", do_async_auth_bad_password_test); + g_test_add_func ("/auth/async-auth/no-password", do_async_auth_no_password_test); + g_test_add_func ("/auth/select-auth", do_select_auth_test); + g_test_add_func ("/auth/auth-close", do_auth_close_test); + g_test_add_func ("/auth/infinite-auth", do_infinite_auth_test); + g_test_add_func ("/auth/disappearing-auth", do_disappearing_auth_test); - /* Other regression tests */ - do_pipelined_auth_test (base_uri); - do_digest_expiration_test (base_uri); - do_async_auth_test (base_uri); - do_select_auth_test (); - do_auth_close_test (); + ret = g_test_run (); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/cache-test.c b/tests/cache-test.c new file mode 100644 index 00000000..3478f377 --- /dev/null +++ b/tests/cache-test.c @@ -0,0 +1,659 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright 2012 Red Hat, Inc. + */ + +#include "test-utils.h" + +static void +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + const char *last_modified, *etag; + const char *header; + guint status = SOUP_STATUS_OK; + + if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_POST) { + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + return; + } + + header = soup_message_headers_get_one (msg->request_headers, + "Test-Set-Expires"); + if (header) { + soup_message_headers_append (msg->response_headers, + "Expires", + header); + } + + header = soup_message_headers_get_one (msg->request_headers, + "Test-Set-Cache-Control"); + if (header) { + soup_message_headers_append (msg->response_headers, + "Cache-Control", + header); + } + + last_modified = soup_message_headers_get_one (msg->request_headers, + "Test-Set-Last-Modified"); + if (last_modified) { + soup_message_headers_append (msg->response_headers, + "Last-Modified", + last_modified); + } + + etag = soup_message_headers_get_one (msg->request_headers, + "Test-Set-ETag"); + if (etag) { + soup_message_headers_append (msg->response_headers, + "ETag", + etag); + } + + + header = soup_message_headers_get_one (msg->request_headers, + "If-Modified-Since"); + if (header && last_modified) { + SoupDate *date; + time_t lastmod, check; + + date = soup_date_new_from_string (last_modified); + lastmod = soup_date_to_time_t (date); + soup_date_free (date); + + date = soup_date_new_from_string (header); + check = soup_date_to_time_t (date); + soup_date_free (date); + + if (lastmod <= check) + status = SOUP_STATUS_NOT_MODIFIED; + } + + header = soup_message_headers_get_one (msg->request_headers, + "If-None-Match"); + if (header && etag) { + if (!strcmp (header, etag)) + status = SOUP_STATUS_NOT_MODIFIED; + } + + header = soup_message_headers_get_one (msg->request_headers, + "Test-Set-My-Header"); + if (header) { + soup_message_headers_append (msg->response_headers, + "My-Header", + header); + } + + if (status == SOUP_STATUS_OK) { + GChecksum *sum; + const char *body; + + sum = g_checksum_new (G_CHECKSUM_SHA256); + g_checksum_update (sum, (guchar *)path, strlen (path)); + if (last_modified) + g_checksum_update (sum, (guchar *)last_modified, strlen (last_modified)); + if (etag) + g_checksum_update (sum, (guchar *)etag, strlen (etag)); + body = g_checksum_get_string (sum); + soup_message_set_response (msg, "text/plain", + SOUP_MEMORY_COPY, + body, strlen (body) + 1); + g_checksum_free (sum); + } + soup_message_set_status (msg, status); +} + +static gboolean +is_network_stream (GInputStream *stream) +{ + while (G_IS_FILTER_INPUT_STREAM (stream)) + stream = G_FILTER_INPUT_STREAM (stream)->base_stream; + + return !G_IS_FILE_INPUT_STREAM (stream); +} + +static char *do_request (SoupSession *session, + SoupURI *base_uri, + const char *method, + const char *path, + SoupMessageHeaders *response_headers, + ...) G_GNUC_NULL_TERMINATED; + +static gboolean last_request_hit_network; +static gboolean last_request_validated; +static guint cancelled_requests; + +static void +copy_headers (const char *name, + const char *value, + gpointer user_data) +{ + SoupMessageHeaders *headers = (SoupMessageHeaders *) user_data; + soup_message_headers_append (headers, name, value); +} + +static char * +do_request (SoupSession *session, + SoupURI *base_uri, + const char *method, + const char *path, + SoupMessageHeaders *response_headers, + ...) +{ + SoupRequestHTTP *req; + SoupMessage *msg; + GInputStream *stream; + SoupURI *uri; + va_list ap; + const char *header, *value; + char buf[256]; + gsize nread; + GError *error = NULL; + + last_request_validated = last_request_hit_network = FALSE; + + uri = soup_uri_new_with_base (base_uri, path); + req = soup_session_request_http_uri (session, method, uri, NULL); + soup_uri_free (uri); + msg = soup_request_http_get_message (req); + + va_start (ap, response_headers); + while ((header = va_arg (ap, const char *))) { + value = va_arg (ap, const char *); + soup_message_headers_append (msg->request_headers, + header, value); + } + va_end (ap); + + stream = soup_test_request_send (SOUP_REQUEST (req), NULL, 0, &error); + if (!stream) { + debug_printf (1, " could not send request: %s\n", + error->message); + g_error_free (error); + g_object_unref (req); + g_object_unref (msg); + return NULL; + } + + if (response_headers) + soup_message_headers_foreach (msg->response_headers, copy_headers, response_headers); + + g_object_unref (msg); + + last_request_hit_network = is_network_stream (stream); + + g_input_stream_read_all (stream, buf, sizeof (buf), &nread, + NULL, &error); + if (error) { + debug_printf (1, " could not read response: %s\n", + error->message); + g_clear_error (&error); + } + soup_test_request_close_stream (SOUP_REQUEST (req), stream, + NULL, &error); + if (error) { + debug_printf (1, " could not close stream: %s\n", + error->message); + g_clear_error (&error); + } + g_object_unref (stream); + g_object_unref (req); + + /* Cache writes are G_PRIORITY_LOW, so they won't have happened yet... */ + soup_cache_flush ((SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE)); + + return nread ? g_memdup (buf, nread) : g_strdup (""); +} + +static void +do_request_with_cancel (SoupSession *session, + SoupURI *base_uri, + const char *method, + const char *path, + SoupTestRequestFlags flags) +{ + SoupRequestHTTP *req; + GInputStream *stream; + SoupURI *uri; + GError *error = NULL; + GCancellable *cancellable; + + last_request_validated = last_request_hit_network = FALSE; + cancelled_requests = 0; + + uri = soup_uri_new_with_base (base_uri, path); + req = soup_session_request_http_uri (session, method, uri, NULL); + soup_uri_free (uri); + cancellable = flags & SOUP_TEST_REQUEST_CANCEL_CANCELLABLE ? g_cancellable_new () : NULL; + stream = soup_test_request_send (SOUP_REQUEST (req), cancellable, flags, &error); + if (stream) { + debug_printf (1, " could not cancel the request\n"); + g_object_unref (stream); + g_object_unref (req); + return; + } + + g_clear_object (&cancellable); + g_clear_object (&stream); + g_clear_object (&req); + + soup_cache_flush ((SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE)); +} + +static void +request_started (SoupSession *session, SoupMessage *msg, + SoupSocket *socket) +{ + if (soup_message_headers_get_one (msg->request_headers, + "If-Modified-Since") || + soup_message_headers_get_one (msg->request_headers, + "If-None-Match")) { + debug_printf (2, " Conditional request for %s\n", + soup_message_get_uri (msg)->path); + last_request_validated = TRUE; + } +} + +static void +request_unqueued (SoupSession *session, SoupMessage *msg, + gpointer data) +{ + if (msg->status_code == SOUP_STATUS_CANCELLED) + cancelled_requests++; +} + +static void +do_basics_test (gconstpointer data) +{ + SoupURI *base_uri = (SoupURI *)data; + SoupSession *session; + SoupCache *cache; + char *cache_dir; + char *body1, *body2, *body3, *body4, *body5, *cmp; + + cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL); + debug_printf (2, " Caching to %s\n", cache_dir); + cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + SOUP_SESSION_ADD_FEATURE, cache, + NULL); + g_signal_connect (session, "request-started", + G_CALLBACK (request_started), NULL); + + debug_printf (2, " Initial requests\n"); + body1 = do_request (session, base_uri, "GET", "/1", NULL, + "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT", + NULL); + body2 = do_request (session, base_uri, "GET", "/2", NULL, + "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT", + NULL); + body3 = do_request (session, base_uri, "GET", "/3", NULL, + "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT", + "Test-Set-Cache-Control", "must-revalidate", + NULL); + body4 = do_request (session, base_uri, "GET", "/4", NULL, + "Test-Set-ETag", "\"abcdefg\"", + "Test-Set-Cache-Control", "must-revalidate", + NULL); + body5 = do_request (session, base_uri, "GET", "/5", NULL, + "Test-Set-Cache-Control", "no-cache", + NULL); + + + /* Resource with future Expires should have been cached */ + debug_printf (1, " Fresh cached resource\n"); + cmp = do_request (session, base_uri, "GET", "/1", NULL, + NULL); + soup_test_assert (!last_request_hit_network, + "Request for /1 not filled from cache"); + g_assert_cmpstr (body1, ==, cmp); + g_free (cmp); + + + /* Resource with long-ago Last-Modified should have been cached */ + debug_printf (1, " Heuristically-fresh cached resource\n"); + cmp = do_request (session, base_uri, "GET", "/2", NULL, + NULL); + soup_test_assert (!last_request_hit_network, + "Request for /2 not filled from cache"); + g_assert_cmpstr (body2, ==, cmp); + g_free (cmp); + + + /* Adding a query string should bypass the cache but not invalidate it */ + debug_printf (1, " Fresh cached resource with a query\n"); + cmp = do_request (session, base_uri, "GET", "/1?attr=value", NULL, + NULL); + soup_test_assert (last_request_hit_network, + "Request for /1?attr=value filled from cache"); + g_free (cmp); + debug_printf (2, " Second request\n"); + cmp = do_request (session, base_uri, "GET", "/1", NULL, + NULL); + soup_test_assert (!last_request_hit_network, + "Second request for /1 not filled from cache"); + g_assert_cmpstr (body1, ==, cmp); + g_free (cmp); + + + /* Last-Modified + must-revalidate causes a conditional request */ + debug_printf (1, " Unchanged must-revalidate resource w/ Last-Modified\n"); + cmp = do_request (session, base_uri, "GET", "/3", NULL, + "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT", + "Test-Set-Cache-Control", "must-revalidate", + NULL); + soup_test_assert (last_request_validated, + "Request for /3 not validated"); + soup_test_assert (!last_request_hit_network, + "Request for /3 not filled from cache"); + g_assert_cmpstr (body3, ==, cmp); + g_free (cmp); + + + /* Validation failure should update cache */ + debug_printf (1, " Changed must-revalidate resource w/ Last-Modified\n"); + cmp = do_request (session, base_uri, "GET", "/3", NULL, + "Test-Set-Last-Modified", "Sat, 02 Jan 2010 00:00:00 GMT", + "Test-Set-Cache-Control", "must-revalidate", + NULL); + soup_test_assert (last_request_validated, + "Request for /3 not validated"); + soup_test_assert (last_request_hit_network, + "Request for /3 filled from cache"); + g_assert_cmpstr (body3, !=, cmp); + g_free (cmp); + + debug_printf (2, " Second request\n"); + cmp = do_request (session, base_uri, "GET", "/3", NULL, + "Test-Set-Last-Modified", "Sat, 02 Jan 2010 00:00:00 GMT", + "Test-Set-Cache-Control", "must-revalidate", + NULL); + soup_test_assert (last_request_validated, + "Second request for /3 not validated"); + soup_test_assert (!last_request_hit_network, + "Second request for /3 not filled from cache"); + g_assert_cmpstr (body3, !=, cmp); + g_free (cmp); + + /* ETag + must-revalidate causes a conditional request */ + debug_printf (1, " Unchanged must-revalidate resource w/ ETag\n"); + cmp = do_request (session, base_uri, "GET", "/4", NULL, + "Test-Set-ETag", "\"abcdefg\"", + NULL); + soup_test_assert (last_request_validated, + "Request for /4 not validated"); + soup_test_assert (!last_request_hit_network, + "Request for /4 not filled from cache"); + g_assert_cmpstr (body4, ==, cmp); + g_free (cmp); + + + /* Cache-Control: no-cache prevents caching */ + debug_printf (1, " Uncacheable resource\n"); + cmp = do_request (session, base_uri, "GET", "/5", NULL, + "Test-Set-Cache-Control", "no-cache", + NULL); + soup_test_assert (last_request_hit_network, + "Request for /5 filled from cache"); + g_assert_cmpstr (body5, ==, cmp); + g_free (cmp); + + + /* PUT to a URI invalidates the cache entry */ + debug_printf (1, " Invalidating and re-requesting a cached resource\n"); + cmp = do_request (session, base_uri, "PUT", "/1", NULL, + NULL); + soup_test_assert (last_request_hit_network, + "PUT filled from cache"); + g_free (cmp); + cmp = do_request (session, base_uri, "GET", "/1", NULL, + NULL); + soup_test_assert (last_request_hit_network, + "PUT failed to invalidate cache entry"); + g_assert_true (last_request_hit_network); + g_free (cmp); + + + soup_test_session_abort_unref (session); + g_object_unref (cache); + + g_free (cache_dir); + g_free (body1); + g_free (body2); + g_free (body3); + g_free (body4); + g_free (body5); +} + +static void +do_cancel_test (gconstpointer data) +{ + SoupURI *base_uri = (SoupURI *)data; + SoupSession *session; + SoupCache *cache; + char *cache_dir; + char *body1, *body2; + guint flags; + + g_test_bug ("692310"); + + cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL); + debug_printf (2, " Caching to %s\n", cache_dir); + cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + SOUP_SESSION_ADD_FEATURE, cache, + NULL); + g_signal_connect (session, "request-unqueued", + G_CALLBACK (request_unqueued), NULL); + + debug_printf (2, " Initial requests\n"); + body1 = do_request (session, base_uri, "GET", "/1", NULL, + "Test-Set-Expires", "Fri, 01 Jan 2100 00:00:00 GMT", + NULL); + body2 = do_request (session, base_uri, "GET", "/2", NULL, + "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT", + "Test-Set-Cache-Control", "must-revalidate", + NULL); + + /* Check that messages are correctly processed on cancellations. */ + debug_printf (1, " Cancel fresh resource with soup_session_message_cancel()\n"); + flags = SOUP_TEST_REQUEST_CANCEL_MESSAGE | SOUP_TEST_REQUEST_CANCEL_IMMEDIATE; + do_request_with_cancel (session, base_uri, "GET", "/1", flags); + g_assert_cmpint (cancelled_requests, ==, 1); + + debug_printf (1, " Cancel fresh resource with g_cancellable_cancel()\n"); + flags = SOUP_TEST_REQUEST_CANCEL_CANCELLABLE | SOUP_TEST_REQUEST_CANCEL_IMMEDIATE; + do_request_with_cancel (session, base_uri, "GET", "/1", flags); + g_assert_cmpint (cancelled_requests, ==, 1); + + soup_test_session_abort_unref (session); + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + SOUP_SESSION_ADD_FEATURE, cache, + NULL); + g_signal_connect (session, "request-unqueued", + G_CALLBACK (request_unqueued), NULL); + + /* Check that messages are correctly processed on cancellations. */ + debug_printf (1, " Cancel a revalidating resource with soup_session_message_cancel()\n"); + flags = SOUP_TEST_REQUEST_CANCEL_MESSAGE | SOUP_TEST_REQUEST_CANCEL_IMMEDIATE; + do_request_with_cancel (session, base_uri, "GET", "/2", flags); + g_assert_cmpint (cancelled_requests, ==, 2); + + debug_printf (1, " Cancel a revalidating resource with g_cancellable_cancel()\n"); + flags = SOUP_TEST_REQUEST_CANCEL_CANCELLABLE | SOUP_TEST_REQUEST_CANCEL_IMMEDIATE; + do_request_with_cancel (session, base_uri, "GET", "/2", flags); + g_assert_cmpint (cancelled_requests, ==, 2); + + soup_test_session_abort_unref (session); + + g_object_unref (cache); + g_free (cache_dir); + g_free (body1); + g_free (body2); +} + +static gboolean +unref_stream (gpointer stream) +{ + g_object_unref (stream); + return FALSE; +} + +static void +base_stream_unreffed (gpointer loop, GObject *ex_base_stream) +{ + g_main_loop_quit (loop); +} + +static void +do_refcounting_test (gconstpointer data) +{ + SoupURI *base_uri = (SoupURI *)data; + SoupSession *session; + SoupCache *cache; + char *cache_dir; + SoupRequestHTTP *req; + GInputStream *stream, *base_stream; + SoupURI *uri; + GError *error = NULL; + guint flags; + GMainLoop *loop; + + g_test_bug ("682527"); + + cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL); + debug_printf (2, " Caching to %s\n", cache_dir); + cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + SOUP_SESSION_ADD_FEATURE, cache, + NULL); + + last_request_validated = last_request_hit_network = FALSE; + cancelled_requests = 0; + + uri = soup_uri_new_with_base (base_uri, "/1"); + req = soup_session_request_http_uri (session, "GET", uri, NULL); + soup_uri_free (uri); + + flags = SOUP_TEST_REQUEST_CANCEL_AFTER_SEND_FINISH | SOUP_TEST_REQUEST_CANCEL_MESSAGE; + stream = soup_test_request_send (SOUP_REQUEST (req), NULL, flags, &error); + if (!stream) { + debug_printf (1, " could not send request: %s\n", + error->message); + g_error_free (error); + g_object_unref (req); + return; + } + g_object_unref (req); + + base_stream = g_filter_input_stream_get_base_stream (G_FILTER_INPUT_STREAM (stream)); + + debug_printf (1, " Checking that the base stream is properly unref'ed\n"); + loop = g_main_loop_new (NULL, FALSE); + g_object_weak_ref (G_OBJECT (base_stream), base_stream_unreffed, loop); + g_idle_add (unref_stream, stream); + g_main_loop_run (loop); + g_main_loop_unref (loop); + + soup_cache_flush ((SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE)); + + soup_test_session_abort_unref (session); + + g_object_unref (cache); + g_free (cache_dir); +} + +static void +do_headers_test (gconstpointer data) +{ + SoupURI *base_uri = (SoupURI *)data; + SoupSession *session; + SoupMessageHeaders *headers; + SoupCache *cache; + char *cache_dir; + char *body1, *cmp; + const char *header_value; + + cache_dir = g_dir_make_tmp ("cache-test-XXXXXX", NULL); + debug_printf (2, " Caching to %s\n", cache_dir); + cache = soup_cache_new (cache_dir, SOUP_CACHE_SINGLE_USER); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + SOUP_SESSION_ADD_FEATURE, cache, + NULL); + + g_signal_connect (session, "request-started", + G_CALLBACK (request_started), NULL); + + debug_printf (2, " Initial requests\n"); + body1 = do_request (session, base_uri, "GET", "/1", NULL, + "Test-Set-Last-Modified", "Fri, 01 Jan 2100 00:00:00 GMT", + "Test-Set-My-Header", "My header value", + NULL); + + /* My-Header new value should be updated in cache */ + debug_printf (2, " Fresh cached resource which updates My-Header\n"); + cmp = do_request (session, base_uri, "GET", "/1", NULL, + "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT", + "Test-Set-My-Header", "My header NEW value", + NULL); + soup_test_assert (last_request_validated, + "Request for /1 not validated"); + soup_test_assert (!last_request_hit_network, + "Request for /1 not filled from cache"); + g_free (cmp); + + /* Check that cache returns the updated header */ + debug_printf (2, " Fresh cached resource with new value for My-Header\n"); + headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE); + cmp = do_request (session, base_uri, "GET", "/1", headers, + "Test-Set-Last-Modified", "Fri, 01 Jan 2010 00:00:00 GMT", + NULL); + soup_test_assert (!last_request_hit_network, + "Request for /1 not filled from cache"); + g_free (cmp); + + header_value = soup_message_headers_get_list (headers, "My-Header"); + g_assert_cmpstr (header_value, ==, "My header NEW value"); + soup_message_headers_free (headers); + + soup_test_session_abort_unref (session); + g_object_unref (cache); + + g_free (cache_dir); + g_free (body1); +} + +int +main (int argc, char **argv) +{ + SoupServer *server; + SoupURI *base_uri; + int ret; + + test_init (argc, argv, NULL); + + server = soup_test_server_new (TRUE); + soup_server_add_handler (server, NULL, server_callback, NULL, NULL); + base_uri = soup_uri_new ("http://127.0.0.1/"); + soup_uri_set_port (base_uri, soup_server_get_port (server)); + + g_test_add_data_func ("/cache/basics", base_uri, do_basics_test); + g_test_add_data_func ("/cache/cancellation", base_uri, do_cancel_test); + g_test_add_data_func ("/cache/refcounting", base_uri, do_refcounting_test); + g_test_add_data_func ("/cache/headers", base_uri, do_headers_test); + + ret = g_test_run (); + + soup_uri_free (base_uri); + soup_test_server_quit_unref (server); + + test_cleanup (); + return ret; +} diff --git a/tests/chunk-io-test.c b/tests/chunk-io-test.c new file mode 100644 index 00000000..1e53eef1 --- /dev/null +++ b/tests/chunk-io-test.c @@ -0,0 +1,610 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright 2013 Red Hat, Inc. + */ + +#include "test-utils.h" + +static void +force_io_streams_init (void) +{ + SoupServer *server; + SoupSession *session; + guint port; + SoupURI *base_uri; + SoupMessage *msg; + + /* Poke libsoup enough to cause SoupBodyInputStream and + * SoupBodyOutputStream to get defined, so we can find them + * via g_type_from_name() later. + */ + + server = soup_test_server_new (TRUE); + port = soup_server_get_port (server); + + base_uri = soup_uri_new ("http://127.0.0.1"); + soup_uri_set_port (base_uri, port); + + session = soup_test_session_new (SOUP_TYPE_SESSION, NULL); + msg = soup_message_new_from_uri ("POST", base_uri); + soup_session_send_message (session, msg); + g_object_unref (msg); + soup_test_session_abort_unref (session); + + soup_uri_free (base_uri); + soup_test_server_quit_unref (server); +} + +typedef struct { + GFilterInputStream grandparent; + + gpointer *soup_filter_input_stream_private; + + gboolean is_readable; +} SlowInputStream; + +typedef struct { + GFilterInputStreamClass grandparent; +} SlowInputStreamClass; + +GType slow_input_stream_get_type (void); +static void slow_pollable_input_stream_init (GPollableInputStreamInterface *pollable_interface, + gpointer interface_data); + +G_DEFINE_TYPE_WITH_CODE (SlowInputStream, slow_input_stream, + g_type_from_name ("SoupFilterInputStream"), + G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_INPUT_STREAM, slow_pollable_input_stream_init); + ) + +static void +slow_input_stream_init (SlowInputStream *sis) +{ +} + +static gssize +slow_input_stream_read (GInputStream *stream, + void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + return g_input_stream_read (G_FILTER_INPUT_STREAM (stream)->base_stream, + buffer, 1, cancellable, error); +} + +static void +slow_input_stream_class_init (SlowInputStreamClass *sisclass) +{ + GInputStreamClass *input_stream_class = G_INPUT_STREAM_CLASS (sisclass); + + input_stream_class->read_fn = slow_input_stream_read; +} + +static gboolean +slow_input_stream_is_readable (GPollableInputStream *stream) +{ + return ((SlowInputStream *)stream)->is_readable; +} + +static gssize +slow_input_stream_read_nonblocking (GPollableInputStream *stream, + void *buffer, + gsize count, + GError **error) +{ + if (((SlowInputStream *)stream)->is_readable) { + ((SlowInputStream *)stream)->is_readable = FALSE; + return slow_input_stream_read (G_INPUT_STREAM (stream), buffer, count, + NULL, error); + } else { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, + "would block"); + return -1; + } +} + +static GSource * +slow_input_stream_create_source (GPollableInputStream *stream, + GCancellable *cancellable) +{ + GSource *base_source, *pollable_source; + + ((SlowInputStream *)stream)->is_readable = TRUE; + base_source = g_timeout_source_new (0); + g_source_set_dummy_callback (base_source); + + pollable_source = g_pollable_source_new (G_OBJECT (stream)); + g_source_add_child_source (pollable_source, base_source); + g_source_unref (base_source); + + return pollable_source; +} + +static void +slow_pollable_input_stream_init (GPollableInputStreamInterface *pollable_interface, + gpointer interface_data) +{ + pollable_interface->is_readable = slow_input_stream_is_readable; + pollable_interface->read_nonblocking = slow_input_stream_read_nonblocking; + pollable_interface->create_source = slow_input_stream_create_source; +} + +typedef struct { + GFilterOutputStream parent; + + gboolean is_writable; +} SlowOutputStream; + +typedef struct { + GFilterOutputStreamClass parent; +} SlowOutputStreamClass; + +GType slow_output_stream_get_type (void); + +static void slow_pollable_output_stream_init (GPollableOutputStreamInterface *pollable_interface, + gpointer interface_data); + +G_DEFINE_TYPE_WITH_CODE (SlowOutputStream, slow_output_stream, + g_type_from_name ("GFilterOutputStream"), + G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM, slow_pollable_output_stream_init); + ) + +static void +slow_output_stream_init (SlowOutputStream *sis) +{ +} + +static gssize +slow_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + return g_output_stream_write (G_FILTER_OUTPUT_STREAM (stream)->base_stream, + buffer, 1, cancellable, error); +} + +static void +slow_output_stream_class_init (SlowOutputStreamClass *sisclass) +{ + GOutputStreamClass *output_stream_class = G_OUTPUT_STREAM_CLASS (sisclass); + + output_stream_class->write_fn = slow_output_stream_write; +} + +static gboolean +slow_output_stream_is_writable (GPollableOutputStream *stream) +{ + return ((SlowOutputStream *)stream)->is_writable; +} + +static gssize +slow_output_stream_write_nonblocking (GPollableOutputStream *stream, + const void *buffer, + gsize count, + GError **error) +{ + if (((SlowOutputStream *)stream)->is_writable) { + ((SlowOutputStream *)stream)->is_writable = FALSE; + return slow_output_stream_write (G_OUTPUT_STREAM (stream), buffer, count, + NULL, error); + } else { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, + "would block"); + return -1; + } +} + +static GSource * +slow_output_stream_create_source (GPollableOutputStream *stream, + GCancellable *cancellable) +{ + GSource *base_source, *pollable_source; + + ((SlowOutputStream *)stream)->is_writable = TRUE; + base_source = g_timeout_source_new (0); + g_source_set_dummy_callback (base_source); + + pollable_source = g_pollable_source_new (G_OBJECT (stream)); + g_source_add_child_source (pollable_source, base_source); + g_source_unref (base_source); + + return pollable_source; +} + +static void +slow_pollable_output_stream_init (GPollableOutputStreamInterface *pollable_interface, + gpointer interface_data) +{ + pollable_interface->is_writable = slow_output_stream_is_writable; + pollable_interface->write_nonblocking = slow_output_stream_write_nonblocking; + pollable_interface->create_source = slow_output_stream_create_source; +} + +typedef struct { + GFilterOutputStream parent; + + gboolean is_broken; +} BreakingOutputStream; + +typedef struct { + GFilterOutputStreamClass parent; +} BreakingOutputStreamClass; + +GType breaking_output_stream_get_type (void); + +static void breaking_pollable_output_stream_init (GPollableOutputStreamInterface *pollable_interface, + gpointer interface_data); + +G_DEFINE_TYPE_WITH_CODE (BreakingOutputStream, breaking_output_stream, + g_type_from_name ("GFilterOutputStream"), + G_IMPLEMENT_INTERFACE (G_TYPE_POLLABLE_OUTPUT_STREAM, breaking_pollable_output_stream_init); + ) + +static void +breaking_output_stream_init (BreakingOutputStream *sis) +{ +} + +static gssize +breaking_output_stream_write (GOutputStream *stream, + const void *buffer, + gsize count, + GCancellable *cancellable, + GError **error) +{ + if (((BreakingOutputStream *)stream)->is_broken) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed"); + return -1; + } + + if (count > 128) { + ((BreakingOutputStream *)stream)->is_broken = TRUE; + count /= 2; + } + return g_output_stream_write (G_FILTER_OUTPUT_STREAM (stream)->base_stream, + buffer, count, cancellable, error); +} + +static void +breaking_output_stream_class_init (BreakingOutputStreamClass *sisclass) +{ + GOutputStreamClass *output_stream_class = G_OUTPUT_STREAM_CLASS (sisclass); + + output_stream_class->write_fn = breaking_output_stream_write; +} + +static gboolean +breaking_output_stream_is_writable (GPollableOutputStream *stream) +{ + return TRUE; +} + +static gssize +breaking_output_stream_write_nonblocking (GPollableOutputStream *stream, + const void *buffer, + gsize count, + GError **error) +{ + if (((BreakingOutputStream *)stream)->is_broken) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, "failed"); + return -1; + } + + if (count > 128) { + ((BreakingOutputStream *)stream)->is_broken = TRUE; + count /= 2; + } + return g_pollable_output_stream_write_nonblocking (G_POLLABLE_OUTPUT_STREAM (G_FILTER_OUTPUT_STREAM (stream)->base_stream), + buffer, count, NULL, error); +} + +static GSource * +breaking_output_stream_create_source (GPollableOutputStream *stream, + GCancellable *cancellable) +{ + GSource *base_source, *pollable_source; + + base_source = g_timeout_source_new (0); + g_source_set_dummy_callback (base_source); + + pollable_source = g_pollable_source_new (G_OBJECT (stream)); + g_source_add_child_source (pollable_source, base_source); + g_source_unref (base_source); + + return pollable_source; +} + +static void +breaking_pollable_output_stream_init (GPollableOutputStreamInterface *pollable_interface, + gpointer interface_data) +{ + pollable_interface->is_writable = breaking_output_stream_is_writable; + pollable_interface->write_nonblocking = breaking_output_stream_write_nonblocking; + pollable_interface->create_source = breaking_output_stream_create_source; +} + +#define CHUNK_SIZE 1024 + +static GString * +chunkify (const char *str, gsize length) +{ + GString *gstr; + int i, size; + + gstr = g_string_new (NULL); + for (i = 0; i < length; i += CHUNK_SIZE) { + size = MIN (CHUNK_SIZE, length - i); + g_string_append_printf (gstr, "%x\r\n", size); + g_string_append_len (gstr, str + i, size); + g_string_append (gstr, "\r\n"); + } + g_string_append (gstr, "0\r\n\r\n"); + + return gstr; +} + +static void +do_io_tests (void) +{ + GInputStream *imem, *islow, *in; + GOutputStream *omem, *oslow, *out; + GMemoryOutputStream *mem; + SoupBuffer *raw_contents; + char *buf; + GString *chunkified; + GError *error = NULL; + gssize nread, nwrote, total; + gssize chunk_length, chunk_total; + + raw_contents = soup_test_get_index (); + chunkified = chunkify (raw_contents->data, raw_contents->length); + + debug_printf (1, " sync read\n"); + + imem = g_memory_input_stream_new_from_data (chunkified->str, chunkified->len, NULL); + islow = g_object_new (slow_input_stream_get_type (), + "base-stream", imem, + "close-base-stream", TRUE, + NULL); + in = g_object_new (g_type_from_name ("SoupBodyInputStream"), + "base-stream", islow, + "close-base-stream", TRUE, + "encoding", SOUP_ENCODING_CHUNKED, + NULL); + g_object_unref (imem); + g_object_unref (islow); + + buf = g_malloc (raw_contents->length); + total = 0; + while (TRUE) { + nread = g_input_stream_read (in, buf + total, + raw_contents->length - total, + NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + if (nread > 0) + total += nread; + else + break; + } + + g_input_stream_close (in, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + g_object_unref (in); + + soup_assert_cmpmem (buf, total, raw_contents->data, raw_contents->length); + g_free (buf); + + debug_printf (1, " async read\n"); + + imem = g_memory_input_stream_new_from_data (chunkified->str, chunkified->len, NULL); + islow = g_object_new (slow_input_stream_get_type (), + "base-stream", imem, + "close-base-stream", TRUE, + NULL); + in = g_object_new (g_type_from_name ("SoupBodyInputStream"), + "base-stream", islow, + "close-base-stream", TRUE, + "encoding", SOUP_ENCODING_CHUNKED, + NULL); + g_object_unref (imem); + g_object_unref (islow); + + buf = g_malloc (raw_contents->length); + total = 0; + while (TRUE) { + nread = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (in), + buf + total, + raw_contents->length - total, + NULL, &error); + if (nread == -1 && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + GSource *source; + + g_clear_error (&error); + source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (in), NULL); + g_source_set_dummy_callback (source); + g_source_attach (source, NULL); + while (!g_pollable_input_stream_is_readable (G_POLLABLE_INPUT_STREAM (in))) + g_main_context_iteration (NULL, TRUE); + g_source_destroy (source); + g_source_unref (source); + continue; + } else if (nread == -1) { + g_assert_no_error (error); + g_clear_error (&error); + break; + } else if (nread == 0) + break; + else + total += nread; + } + + g_input_stream_close (in, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + g_object_unref (in); + + soup_assert_cmpmem (buf, total, raw_contents->data, raw_contents->length); + g_free (buf); + + debug_printf (1, " sync write\n"); + + buf = g_malloc (chunkified->len); + omem = g_memory_output_stream_new (buf, chunkified->len, NULL, NULL); + oslow = g_object_new (slow_output_stream_get_type (), + "base-stream", omem, + "close-base-stream", TRUE, + NULL); + out = g_object_new (g_type_from_name ("SoupBodyOutputStream"), + "base-stream", oslow, + "close-base-stream", TRUE, + "encoding", SOUP_ENCODING_CHUNKED, + NULL); + g_object_unref (omem); + g_object_unref (oslow); + + total = chunk_length = chunk_total = 0; + while (total < raw_contents->length) { + if (chunk_total == chunk_length) { + chunk_length = MIN (CHUNK_SIZE, raw_contents->length - total); + chunk_total = 0; + } + nwrote = g_output_stream_write (out, raw_contents->data + total, + chunk_length - chunk_total, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + if (nwrote > 0) { + total += nwrote; + chunk_total += nwrote; + } else + break; + } + + g_output_stream_close (out, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + + mem = G_MEMORY_OUTPUT_STREAM (omem); + soup_assert_cmpmem (g_memory_output_stream_get_data (mem), + g_memory_output_stream_get_data_size (mem), + chunkified->str, chunkified->len); + + g_object_unref (out); + g_free (buf); + + debug_printf (1, " async write\n"); + + buf = g_malloc (chunkified->len); + omem = g_memory_output_stream_new (buf, chunkified->len, NULL, NULL); + oslow = g_object_new (slow_output_stream_get_type (), + "base-stream", omem, + "close-base-stream", TRUE, + NULL); + out = g_object_new (g_type_from_name ("SoupBodyOutputStream"), + "base-stream", oslow, + "close-base-stream", TRUE, + "encoding", SOUP_ENCODING_CHUNKED, + NULL); + g_object_unref (omem); + g_object_unref (oslow); + + total = chunk_length = chunk_total = 0; + while (total < raw_contents->length) { + if (chunk_total == chunk_length) { + chunk_length = MIN (CHUNK_SIZE, raw_contents->length - total); + chunk_total = 0; + } + nwrote = g_pollable_output_stream_write_nonblocking (G_POLLABLE_OUTPUT_STREAM (out), + raw_contents->data + total, + chunk_length - chunk_total, + NULL, &error); + if (nwrote == -1 && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + GSource *source; + + g_clear_error (&error); + source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (out), NULL); + g_source_set_dummy_callback (source); + g_source_attach (source, NULL); + while (!g_pollable_output_stream_is_writable (G_POLLABLE_OUTPUT_STREAM (out))) + g_main_context_iteration (NULL, TRUE); + g_source_destroy (source); + g_source_unref (source); + continue; + } else if (nwrote == -1) { + g_assert_no_error (error); + g_clear_error (&error); + break; + } else { + total += nwrote; + chunk_total += nwrote; + } + } + + g_output_stream_close (out, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + + mem = G_MEMORY_OUTPUT_STREAM (omem); + soup_assert_cmpmem (g_memory_output_stream_get_data (mem), + g_memory_output_stream_get_data_size (mem), + chunkified->str, chunkified->len); + + g_object_unref (out); + g_free (buf); + + debug_printf (1, " failed write\n"); + /* this succeeds if it doesn't critical */ + + buf = g_malloc (chunkified->len); + omem = g_memory_output_stream_new (buf, chunkified->len, NULL, NULL); + oslow = g_object_new (breaking_output_stream_get_type (), + "base-stream", omem, + "close-base-stream", TRUE, + NULL); + out = g_object_new (g_type_from_name ("SoupBodyOutputStream"), + "base-stream", oslow, + "close-base-stream", TRUE, + "encoding", SOUP_ENCODING_CHUNKED, + NULL); + g_object_unref (omem); + g_object_unref (oslow); + + total = 0; + while (total < raw_contents->length) { + nwrote = g_output_stream_write (out, raw_contents->data + total, + raw_contents->length - total, NULL, NULL); + if (nwrote == -1) + break; + else + total += nwrote; + } + + g_assert_cmpint (total, !=, raw_contents->length); + + g_output_stream_close (out, NULL, NULL); + g_object_unref (out); + + g_free (buf); + + g_string_free (chunkified, TRUE); +} + +int +main (int argc, char **argv) +{ + int ret; + + test_init (argc, argv, NULL); + + force_io_streams_init (); + + g_test_add_func ("/chunk-io", do_io_tests); + + ret = g_test_run (); + + test_cleanup (); + return ret; +} diff --git a/tests/chunk-test.c b/tests/chunk-test.c index cce7127f..864f8620 100644 --- a/tests/chunk-test.c +++ b/tests/chunk-test.c @@ -5,6 +5,9 @@ #include "test-utils.h" +static SoupSession *session; +static SoupURI *base_uri; + typedef struct { SoupSession *session; SoupBuffer *chunks[3]; @@ -19,9 +22,9 @@ write_next_chunk (SoupMessage *msg, gpointer user_data) debug_printf (2, " writing chunk %d\n", ptd->next); - if (ptd->streaming && ptd->next > 0 && ptd->chunks[ptd->next - 1]) { - debug_printf (1, " error: next chunk requested before last one freed!\n"); - errors++; + if (ptd->streaming && ptd->next > 0) { + soup_test_assert (ptd->chunks[ptd->next - 1] == NULL, + "next chunk requested before last one freed"); } if (ptd->next < G_N_ELEMENTS (ptd->chunks)) { @@ -50,8 +53,8 @@ write_next_chunk_streaming_hack (SoupMessage *msg, gpointer user_data) soup_message_body_wrote_chunk (msg->request_body, chunk); soup_buffer_free (chunk); } else { - debug_printf (1, " error: written chunk does not exist!\n"); - errors++; + soup_test_assert (chunk, + "written chunk does not exist"); } write_next_chunk (msg, user_data); } @@ -77,8 +80,8 @@ clear_buffer_ptr (gpointer data) g_free ((char *)(*buffer_ptr)->data); *buffer_ptr = NULL; } else { - debug_printf (2, " chunk is already clear!\n"); - errors++; + soup_test_assert (*buffer_ptr, + "chunk is already clear"); } } @@ -136,26 +139,20 @@ typedef enum { } RequestTestFlags; static void -do_request_test (SoupSession *session, SoupURI *base_uri, RequestTestFlags flags) +do_request_test (gconstpointer data) { - SoupURI *uri = base_uri; + RequestTestFlags flags = GPOINTER_TO_UINT (data); + SoupURI *uri; PutTestData ptd; SoupMessage *msg; const char *client_md5, *server_md5; GChecksum *check; int i, length; - debug_printf (1, "PUT"); - if (flags & HACKY_STREAMING) - debug_printf (1, " w/ hacky streaming"); - else if (flags & PROPER_STREAMING) - debug_printf (1, " w/ proper streaming"); - if (flags & RESTART) { - debug_printf (1, " and restart"); + if (flags & RESTART) + uri = soup_uri_new_with_base (base_uri, "/redirect"); + else uri = soup_uri_copy (base_uri); - soup_uri_set_path (uri, "/redirect"); - } - debug_printf (1, "\n"); ptd.session = session; setup_request_body (&ptd); @@ -199,35 +196,19 @@ do_request_test (SoupSession *session, SoupURI *base_uri, RequestTestFlags flags G_CALLBACK (wrote_body_data), &ptd); soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " message failed: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - - if (msg->request_body->data) { - debug_printf (1, " msg->request_body set!\n"); - errors++; - } - if (msg->request_body->length != length || length != ptd.nwrote) { - debug_printf (1, " sent length mismatch: %d vs %d vs %d\n", - (int)msg->request_body->length, length, ptd.nwrote); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_CREATED); + g_assert_null (msg->request_body->data); + g_assert_cmpint (msg->request_body->length, ==, length); + g_assert_cmpint (length, ==, ptd.nwrote); server_md5 = soup_message_headers_get_one (msg->response_headers, "Content-MD5"); - if (!server_md5 || strcmp (client_md5, server_md5) != 0) { - debug_printf (1, " client/server data mismatch: %s vs %s\n", - client_md5, server_md5 ? server_md5 : "(null)"); - errors++; - } + g_assert_cmpstr (client_md5, ==, server_md5); g_object_unref (msg); g_checksum_free (check); - if (uri != base_uri) - soup_uri_free (uri); + soup_uri_free (uri); } typedef struct { @@ -243,10 +224,8 @@ chunk_allocator (SoupMessage *msg, gsize max_len, gpointer user_data) debug_printf (2, " allocating chunk\n"); - if (gtd->current_chunk) { - debug_printf (1, " error: next chunk allocated before last one freed!\n"); - errors++; - } + soup_test_assert (gtd->current_chunk == NULL, + "error: next chunk allocated before last one freed"); gtd->current_chunk = soup_buffer_new_with_owner (g_malloc (6), 6, >d->current_chunk, clear_buffer_ptr); @@ -270,56 +249,40 @@ got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) } static void -do_response_test (SoupSession *session, SoupURI *base_uri) +do_response_test (void) { GetTestData gtd; SoupMessage *msg; const char *client_md5, *server_md5; - debug_printf (1, "GET\n"); - gtd.current_chunk = NULL; gtd.length = 0; gtd.check = g_checksum_new (G_CHECKSUM_MD5); msg = soup_message_new_from_uri ("GET", base_uri); soup_message_body_set_accumulate (msg->response_body, FALSE); + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; soup_message_set_chunk_allocator (msg, chunk_allocator, >d, NULL); + G_GNUC_END_IGNORE_DEPRECATIONS; g_signal_connect (msg, "got_chunk", G_CALLBACK (got_chunk), >d); soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " message failed: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - - if (msg->response_body->data) { - debug_printf (1, " msg->response_body set!\n"); - errors++; - } - if (soup_message_headers_get_content_length (msg->response_headers) != gtd.length) { - debug_printf (1, " received length mismatch: %d vs %d\n", - (int)soup_message_headers_get_content_length (msg->response_headers), gtd.length); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + g_assert_null (msg->response_body->data); + g_assert_cmpint (soup_message_headers_get_content_length (msg->response_headers), ==, gtd.length); client_md5 = g_checksum_get_string (gtd.check); server_md5 = soup_message_headers_get_one (msg->response_headers, "Content-MD5"); - if (!server_md5 || strcmp (client_md5, server_md5) != 0) { - debug_printf (1, " client/server data mismatch: %s vs %s\n", - client_md5, server_md5 ? server_md5 : "(null)"); - errors++; - } + g_assert_cmpstr (client_md5, ==, server_md5); g_object_unref (msg); g_checksum_free (gtd.check); } /* Make sure TEMPORARY buffers are handled properly with non-accumulating - * message bodies. Part of https://bugs.webkit.org/show_bug.cgi?id=18343 + * message bodies. */ static void @@ -334,24 +297,24 @@ temp_test_wrote_chunk (SoupMessage *msg, gpointer session) * the I/O to stall since soup-message-io will think it's * done, but it hasn't written Content-Length bytes yet. */ - if (!chunk) { - debug_printf (1, " Lost second chunk!\n"); - errors++; - soup_session_abort (session); - } else + if (chunk) soup_buffer_free (chunk); + else { + soup_test_assert (chunk, "Lost second chunk"); + soup_session_abort (session); + } g_signal_handlers_disconnect_by_func (msg, temp_test_wrote_chunk, session); } static void -do_temporary_test (SoupSession *session, SoupURI *base_uri) +do_temporary_test (void) { SoupMessage *msg; char *client_md5; const char *server_md5; - debug_printf (1, "PUT w/ temporary buffers\n"); + g_test_bug ("https://bugs.webkit.org/show_bug.cgi?id=18343"); msg = soup_message_new_from_uri ("PUT", base_uri); soup_message_body_append (msg->request_body, SOUP_MEMORY_TEMPORARY, @@ -366,19 +329,11 @@ do_temporary_test (SoupSession *session, SoupURI *base_uri) G_CALLBACK (temp_test_wrote_chunk), session); soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " message failed: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_CREATED); server_md5 = soup_message_headers_get_one (msg->response_headers, "Content-MD5"); - if (!server_md5 || strcmp (client_md5, server_md5) != 0) { - debug_printf (1, " client/server data mismatch: %s vs %s\n", - client_md5, server_md5 ? server_md5 : "(null)"); - errors++; - } + g_assert_cmpstr (client_md5, ==, server_md5); g_free (client_md5); g_object_unref (msg); @@ -396,24 +351,20 @@ large_wrote_body_data (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) { LargeChunkData *lcd = user_data; - if (memcmp (chunk->data, lcd->buf->data + lcd->offset, chunk->length) != 0) { - debug_printf (1, " chunk data mismatch at %ld\n", (long)lcd->offset); - errors++; - } else - debug_printf (2, " chunk data match at %ld\n", (long)lcd->offset); + soup_assert_cmpmem (chunk->data, chunk->length, + lcd->buf->data + lcd->offset, + chunk->length); lcd->offset += chunk->length; } static void -do_large_chunk_test (SoupSession *session, SoupURI *base_uri) +do_large_chunk_test (void) { SoupMessage *msg; char *buf_data; int i; LargeChunkData lcd; - debug_printf (1, "PUT w/ large chunk\n"); - msg = soup_message_new_from_uri ("PUT", base_uri); buf_data = g_malloc0 (LARGE_CHUNK_SIZE); @@ -428,41 +379,13 @@ do_large_chunk_test (SoupSession *session, SoupURI *base_uri) G_CALLBACK (large_wrote_body_data), &lcd); soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " message failed: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_CREATED); soup_buffer_free (lcd.buf); g_object_unref (msg); } static void -do_chunk_tests (SoupURI *base_uri) -{ - SoupSession *session; - - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - do_request_test (session, base_uri, 0); - debug_printf (2, "\n\n"); - do_request_test (session, base_uri, PROPER_STREAMING); - debug_printf (2, "\n\n"); - do_request_test (session, base_uri, PROPER_STREAMING | RESTART); - debug_printf (2, "\n\n"); - do_request_test (session, base_uri, HACKY_STREAMING); - debug_printf (2, "\n\n"); - do_request_test (session, base_uri, HACKY_STREAMING | RESTART); - debug_printf (2, "\n\n"); - do_response_test (session, base_uri); - debug_printf (2, "\n\n"); - do_temporary_test (session, base_uri); - debug_printf (2, "\n\n"); - do_large_chunk_test (session, base_uri); - soup_test_session_abort_unref (session); -} - -static void server_callback (SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *context, gpointer data) @@ -505,7 +428,7 @@ main (int argc, char **argv) GMainLoop *loop; SoupServer *server; guint port; - SoupURI *base_uri; + int ret; test_init (argc, argv, NULL); @@ -518,12 +441,27 @@ main (int argc, char **argv) base_uri = soup_uri_new ("http://127.0.0.1"); soup_uri_set_port (base_uri, port); - do_chunk_tests (base_uri); + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + + g_test_add_data_func ("/chunks/request/unstreamed", GINT_TO_POINTER (0), do_request_test); + g_test_add_data_func ("/chunks/request/proper-streaming", GINT_TO_POINTER (PROPER_STREAMING), do_request_test); + g_test_add_data_func ("/chunks/request/proper-streaming/restart", GINT_TO_POINTER (PROPER_STREAMING | RESTART), do_request_test); + g_test_add_data_func ("/chunks/request/hacky-streaming", GINT_TO_POINTER (HACKY_STREAMING), do_request_test); + g_test_add_data_func ("/chunks/request/hacky-streaming/restart", GINT_TO_POINTER (HACKY_STREAMING | RESTART), do_request_test); + g_test_add_func ("/chunks/response", do_response_test); + g_test_add_func ("/chunks/temporary", do_temporary_test); + g_test_add_func ("/chunks/large", do_large_chunk_test); + + ret = g_test_run (); + + soup_test_session_abort_unref (session); + soup_uri_free (base_uri); g_main_loop_unref (loop); soup_test_server_quit_unref (server); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/coding-test.c b/tests/coding-test.c index cda4b89e..0445f632 100644 --- a/tests/coding-test.c +++ b/tests/coding-test.c @@ -16,8 +16,7 @@ server_callback (SoupServer *server, SoupMessage *msg, { const char *accept_encoding, *options; GSList *codings; - char *file = NULL, *contents; - gsize length; + SoupBuffer *response = NULL; options = soup_message_headers_get_one (msg->request_headers, "X-Test-Options"); @@ -33,7 +32,7 @@ server_callback (SoupServer *server, SoupMessage *msg, if (codings) { gboolean claim_deflate, claim_gzip; - const char *file_path = NULL, *encoding = NULL; + const char *extension = NULL, *encoding = NULL; claim_deflate = g_slist_find_custom (codings, "deflate", (GCompareFunc)g_ascii_strcasecmp) != NULL; claim_gzip = g_slist_find_custom (codings, "gzip", (GCompareFunc)g_ascii_strcasecmp) != NULL; @@ -41,44 +40,44 @@ server_callback (SoupServer *server, SoupMessage *msg, if (claim_gzip && (!claim_deflate || (!soup_header_contains (options, "prefer-deflate-zlib") && !soup_header_contains (options, "prefer-deflate-raw")))) { - file_path = SRCDIR "/resources%s.gz"; + extension = "gz"; encoding = "gzip"; } else if (claim_deflate) { if (soup_header_contains (options, "prefer-deflate-raw")) { - file_path = SRCDIR "/resources%s.raw"; + extension = "raw"; encoding = "deflate"; } else { - file_path = SRCDIR "/resources%s.zlib"; + extension = "zlib"; encoding = "deflate"; } } - if (file_path && encoding) { - file = g_strdup_printf (file_path, path); - if (g_file_test (file, G_FILE_TEST_EXISTS)) { + if (extension && encoding) { + char *resource; + + resource = g_strdup_printf ("%s.%s", path, extension); + response = soup_test_load_resource (resource, NULL); + + if (response) { soup_message_headers_append (msg->response_headers, "Content-Encoding", encoding); - } else { - g_free (file); - file = NULL; } + g_free (resource); } } soup_header_free_list (codings); - if (!file) - file = g_strdup_printf (SRCDIR "/resources%s", path); - if (!g_file_get_contents (file, &contents, &length, NULL)) { + if (!response) + response = soup_test_load_resource (path, NULL); + if (!response) { /* If path.gz exists but can't be read, we'll send back * the error with "Content-Encoding: gzip" but there's * no body, so, eh. */ - g_free (file); soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND); return; } - g_free (file); if (soup_header_contains (options, "force-encode")) { const gchar *encoding = "gzip"; @@ -106,10 +105,9 @@ server_callback (SoupServer *server, SoupMessage *msg, soup_message_set_status (msg, SOUP_STATUS_OK); soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_CHUNKED); - if (!soup_header_contains (options, "empty")) { - soup_message_body_append (msg->response_body, - SOUP_MEMORY_TAKE, contents, length); - } + if (!soup_header_contains (options, "empty")) + soup_message_body_append_buffer (msg->response_body, response); + soup_buffer_free (response); if (soup_header_contains (options, "trailing-junk")) { soup_message_body_append (msg->response_body, SOUP_MEMORY_COPY, @@ -118,6 +116,20 @@ server_callback (SoupServer *server, SoupMessage *msg, soup_message_body_complete (msg->response_body); } +typedef struct { + SoupSession *session; + SoupMessage *msg; + SoupRequest *req; + SoupBuffer *response; +} CodingTestData; + +typedef enum { + CODING_TEST_DEFAULT = 0, + CODING_TEST_NO_DECODER = (1 << 0), + CODING_TEST_REQUEST_API = (1 << 1), + CODING_TEST_EMPTY = (1 << 2) +} CodingTestType; + typedef enum { NO_CHECK, EXPECT_DECODED, @@ -125,184 +137,189 @@ typedef enum { } MessageContentStatus; static void -check_response (SoupMessage *msg, +check_response (CodingTestData *data, const char *expected_encoding, const char *expected_content_type, - MessageContentStatus status) + MessageContentStatus status, + GByteArray *body) { const char *coding, *type; - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " Unexpected status %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (data->msg, SOUP_STATUS_OK); - coding = soup_message_headers_get_one (msg->response_headers, "Content-Encoding"); - if (expected_encoding) { - if (!coding || g_ascii_strcasecmp (coding, expected_encoding) != 0) { - debug_printf (1, " Unexpected Content-Encoding: %s\n", - coding ? coding : "(none)"); - errors++; - } - } else { - if (coding) { - debug_printf (1, " Unexpected Content-Encoding: %s\n", - coding); - errors++; - } - } + coding = soup_message_headers_get_one (data->msg->response_headers, "Content-Encoding"); + g_assert_cmpstr (coding, ==, expected_encoding); if (status != NO_CHECK) { - if (status == EXPECT_DECODED) { - if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_CONTENT_DECODED)) { - debug_printf (1, " SOUP_MESSAGE_CONTENT_DECODED not set!\n"); - errors++; - } - } else { - if (soup_message_get_flags (msg) & SOUP_MESSAGE_CONTENT_DECODED) { - debug_printf (1, " SOUP_MESSAGE_CONTENT_DECODED set!\n"); - errors++; - } - } + if (status == EXPECT_DECODED) + g_assert_true (soup_message_get_flags (data->msg) & SOUP_MESSAGE_CONTENT_DECODED); + else + g_assert_false (soup_message_get_flags (data->msg) & SOUP_MESSAGE_CONTENT_DECODED); } - type = soup_message_headers_get_one (msg->response_headers, "Content-Type"); - if (!type || g_ascii_strcasecmp (type, expected_content_type) != 0) { - debug_printf (1, " Unexpected Content-Type: %s\n", - type ? type : "(none)"); - errors++; + type = soup_message_headers_get_one (data->msg->response_headers, "Content-Type"); + g_assert_cmpstr (type, ==, expected_content_type); + + if (body) { + soup_assert_cmpmem (body->data, + body->len, + data->response->data, + data->response->length); + } else { + soup_assert_cmpmem (data->msg->response_body->data, + data->msg->response_body->length, + data->response->data, + data->response->length); } } static void -check_msg_bodies (SoupMessage *msg1, - SoupMessage *msg2, - const char *msg1_type, - const char *msg2_type) +setup_coding_test (CodingTestData *data, gconstpointer test_data) { - if (msg1->response_body->length != msg2->response_body->length) { - debug_printf (1, " Message length mismatch: %lu (%s) vs %lu (%s)\n", - (gulong)msg1->response_body->length, - msg1_type, - (gulong)msg2->response_body->length, - msg2_type); - errors++; - } else if (memcmp (msg1->response_body->data, - msg2->response_body->data, - msg1->response_body->length) != 0) { - debug_printf (1, " Message data mismatch (%s/%s)\n", - msg1_type, msg2_type); - errors++; + CodingTestType test_type = GPOINTER_TO_INT (test_data); + SoupMessage *msg; + SoupURI *uri; + + data->session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + NULL); + + uri = soup_uri_new_with_base (base_uri, "/mbox"); + + if (test_type & CODING_TEST_EMPTY) + data->response = soup_buffer_new (SOUP_MEMORY_STATIC, "", 0); + else { + msg = soup_message_new_from_uri ("GET", uri); + soup_session_send_message (data->session, msg); + + data->response = soup_message_body_flatten (msg->response_body); + g_object_unref (msg); } + + if (test_type & CODING_TEST_REQUEST_API) { + SoupRequestHTTP *reqh; + + reqh = soup_session_request_http_uri (data->session, "GET", uri, NULL); + data->req = SOUP_REQUEST (reqh); + data->msg = soup_request_http_get_message (reqh); + } else + data->msg = soup_message_new_from_uri ("GET", uri); + soup_uri_free (uri); + + if (! (test_type & CODING_TEST_NO_DECODER)) + soup_session_add_feature_by_type (data->session, SOUP_TYPE_CONTENT_DECODER); } static void -do_coding_test (void) +teardown_coding_test (CodingTestData *data, gconstpointer test_data) { - SoupSession *session; - SoupMessage *msg, *msgz, *msgj, *msge, *msgzl, *msgzlj, *msgzle, *msgzlr, *msgzlre; - SoupURI *uri; + soup_buffer_free (data->response); - debug_printf (1, "SoupMessage tests\n"); + g_clear_object (&data->req); + g_object_unref (data->msg); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - uri = soup_uri_new_with_base (base_uri, "/mbox"); + soup_test_session_abort_unref (data->session); +} + +static void +do_coding_test_plain (CodingTestData *data, gconstpointer test_data) +{ + soup_session_send_message (data->session, data->msg); + check_response (data, NULL, "text/plain", EXPECT_NOT_DECODED, NULL); +} + +static void +do_coding_test_gzip (CodingTestData *data, gconstpointer test_data) +{ + soup_session_send_message (data->session, data->msg); + check_response (data, "gzip", "text/plain", EXPECT_DECODED, NULL); +} - /* Plain text data, no claim */ - debug_printf (1, " GET /mbox, plain\n"); - msg = soup_message_new_from_uri ("GET", uri); - soup_session_send_message (session, msg); - check_response (msg, NULL, "text/plain", EXPECT_NOT_DECODED); - - /* Plain text data, claim gzip */ - debug_printf (1, " GET /mbox, Accept-Encoding: gzip\n"); - soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER); - msgz = soup_message_new_from_uri ("GET", uri); - soup_session_send_message (session, msgz); - check_response (msgz, "gzip", "text/plain", EXPECT_DECODED); - check_msg_bodies (msg, msgz, "plain", "compressed"); - - /* Plain text data, claim gzip w/ junk */ - debug_printf (1, " GET /mbox, Accept-Encoding: gzip, plus trailing junk\n"); - msgj = soup_message_new_from_uri ("GET", uri); - soup_message_headers_append (msgj->request_headers, +static void +do_coding_test_gzip_with_junk (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("606352"); + g_test_bug ("676477"); + + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "trailing-junk"); - soup_session_send_message (session, msgj); - check_response (msgj, "gzip", "text/plain", EXPECT_DECODED); - check_msg_bodies (msg, msgj, "plain", "compressed w/ junk"); - - /* Plain text data, claim gzip with server error */ - debug_printf (1, " GET /mbox, Accept-Encoding: gzip, with server error\n"); - msge = soup_message_new_from_uri ("GET", uri); - soup_message_headers_append (msge->request_headers, + + soup_session_send_message (data->session, data->msg); + check_response (data, "gzip", "text/plain", EXPECT_DECODED, NULL); +} + +static void +do_coding_test_gzip_bad_server (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("613361"); + + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "force-encode"); - soup_session_send_message (session, msge); - check_response (msge, "gzip", "text/plain", EXPECT_NOT_DECODED); + + soup_session_send_message (data->session, data->msg); /* Failed content-decoding should have left the body untouched * from what the server sent... which happens to be the * uncompressed data. */ - check_msg_bodies (msg, msge, "plain", "mis-encoded"); + check_response (data, "gzip", "text/plain", EXPECT_NOT_DECODED, NULL); +} - /* Plain text data, claim deflate */ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate\n"); - msgzl = soup_message_new_from_uri ("GET", uri); - soup_message_headers_append (msgzl->request_headers, +static void +do_coding_test_deflate (CodingTestData *data, gconstpointer test_data) +{ + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "prefer-deflate-zlib"); - soup_session_send_message (session, msgzl); - check_response (msgzl, "deflate", "text/plain", EXPECT_DECODED); - check_msg_bodies (msg, msgzl, "plain", "compressed"); - - /* Plain text data, claim deflate w/ junk */ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate, plus trailing junk\n"); - msgzlj = soup_message_new_from_uri ("GET", uri); - soup_message_headers_append (msgzlj->request_headers, + soup_session_send_message (data->session, data->msg); + + check_response (data, "deflate", "text/plain", EXPECT_DECODED, NULL); +} + +static void +do_coding_test_deflate_with_junk (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("606352"); + g_test_bug ("676477"); + + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "prefer-deflate-zlib, trailing-junk"); - soup_session_send_message (session, msgzlj); - check_response (msgzlj, "deflate", "text/plain", EXPECT_DECODED); - check_msg_bodies (msg, msgzlj, "plain", "compressed w/ junk"); - - /* Plain text data, claim deflate with server error */ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate, with server error\n"); - msgzle = soup_message_new_from_uri ("GET", uri); - soup_message_headers_append (msgzle->request_headers, + soup_session_send_message (data->session, data->msg); + + check_response (data, "deflate", "text/plain", EXPECT_DECODED, NULL); +} + +static void +do_coding_test_deflate_bad_server (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("613361"); + + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "force-encode, prefer-deflate-zlib"); - soup_session_send_message (session, msgzle); - check_response (msgzle, "deflate", "text/plain", EXPECT_NOT_DECODED); - check_msg_bodies (msg, msgzle, "plain", "mis-encoded"); - - /* Plain text data, claim deflate (no zlib headers)*/ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate (raw data)\n"); - msgzlr = soup_message_new_from_uri ("GET", uri); - soup_message_headers_append (msgzlr->request_headers, + soup_session_send_message (data->session, data->msg); + + check_response (data, "deflate", "text/plain", EXPECT_NOT_DECODED, NULL); +} + +static void +do_coding_test_deflate_raw (CodingTestData *data, gconstpointer test_data) +{ + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "prefer-deflate-raw"); - soup_session_send_message (session, msgzlr); - check_response (msgzlr, "deflate", "text/plain", EXPECT_DECODED); - check_msg_bodies (msg, msgzlr, "plain", "compressed"); - - /* Plain text data, claim deflate with server error */ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate (raw data), with server error\n"); - msgzlre = soup_message_new_from_uri ("GET", uri); - soup_message_headers_append (msgzlre->request_headers, + soup_session_send_message (data->session, data->msg); + + check_response (data, "deflate", "text/plain", EXPECT_DECODED, NULL); +} + +static void +do_coding_test_deflate_raw_bad_server (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("613361"); + + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "force-encode, prefer-deflate-raw"); - soup_session_send_message (session, msgzlre); - check_response (msgzlre, "deflate", "text/plain", EXPECT_NOT_DECODED); - check_msg_bodies (msg, msgzlre, "plain", "mis-encoded"); - - g_object_unref (msg); - g_object_unref (msgzlre); - g_object_unref (msgzlr); - g_object_unref (msgzlj); - g_object_unref (msgzle); - g_object_unref (msgzl); - g_object_unref (msgz); - g_object_unref (msgj); - g_object_unref (msge); - soup_uri_free (uri); + soup_session_send_message (data->session, data->msg); - soup_test_session_abort_unref (session); + check_response (data, "deflate", "text/plain", EXPECT_NOT_DECODED, NULL); } static void @@ -313,38 +330,29 @@ read_finished (GObject *stream, GAsyncResult *result, gpointer user_data) *nread = g_input_stream_read_finish (G_INPUT_STREAM (stream), result, &error); - if (error) { - debug_printf (1, " Error reading: %s\n", - error->message); - g_error_free (error); - errors++; - } + g_assert_no_error (error); + g_clear_error (&error); } -static GByteArray * -do_single_coding_req_test (SoupRequest *req, +static void +do_single_coding_req_test (CodingTestData *data, const char *expected_encoding, const char *expected_content_type, MessageContentStatus status) { GInputStream *stream; - SoupMessage *msg; - GByteArray *data; + GByteArray *body; guchar buf[1024]; gssize nread; GError *error = NULL; - data = g_byte_array_new (); - - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); + body = g_byte_array_new (); - stream = soup_test_request_send_async_as_sync (req, NULL, &error); - if (error) { - debug_printf (1, " Error sending request: %s\n", - error->message); + stream = soup_test_request_send (data->req, NULL, 0, &error); + if (!stream) { + g_assert_no_error (error); g_error_free (error); - errors++; - return data; + return; } do { @@ -356,208 +364,126 @@ do_single_coding_req_test (SoupRequest *req, g_main_context_iteration (NULL, TRUE); if (nread > 0) - g_byte_array_append (data, buf, nread); + g_byte_array_append (body, buf, nread); } while (nread > 0); - soup_test_stream_close_async_as_sync (stream, NULL, &error); - if (error) { - debug_printf (1, " error closing stream: %s\n", - error->message); - g_error_free (error); - errors++; - } + soup_test_request_close_stream (data->req, stream, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + g_object_unref (stream); - check_response (msg, expected_encoding, expected_content_type, status); - g_object_unref (msg); + check_response (data, expected_encoding, expected_content_type, status, body); + g_byte_array_free (body, TRUE); +} - return data; +static void +do_coding_req_test_plain (CodingTestData *data, gconstpointer test_data) +{ + do_single_coding_req_test (data, NULL, "text/plain", EXPECT_NOT_DECODED); } static void -check_req_bodies (GByteArray *body1, - GByteArray *body2, - const char *msg1_type, - const char *msg2_type) +do_coding_req_test_gzip (CodingTestData *data, gconstpointer test_data) { - if (body1->len != body2->len) { - debug_printf (1, " Message length mismatch: %lu (%s) vs %lu (%s)\n", - (gulong)body1->len, msg1_type, - (gulong)body2->len, msg2_type); - errors++; - } else if (memcmp (body1->data, body2->data, body1->len) != 0) { - debug_printf (1, " Message data mismatch (%s/%s)\n", - msg1_type, msg2_type); - errors++; - } + do_single_coding_req_test (data, "gzip", "text/plain", EXPECT_DECODED); } static void -do_coding_req_test (void) +do_coding_req_test_gzip_with_junk (CodingTestData *data, gconstpointer test_data) { - SoupSession *session; - SoupRequester *requester; - SoupRequest *req; - SoupMessage *msg; - SoupURI *uri; - GByteArray *plain, *cmp; + g_test_bug ("606352"); + g_test_bug ("676477"); - debug_printf (1, "\nSoupRequest tests\n"); + soup_message_headers_append (data->msg->request_headers, + "X-Test-Options", "trailing-junk"); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, - NULL); - requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); - uri = soup_uri_new_with_base (base_uri, "/mbox"); + do_single_coding_req_test (data, "gzip", "text/plain", EXPECT_DECODED); +} - /* Plain text data, no claim */ - debug_printf (1, " GET /mbox, plain\n"); - req = soup_requester_request_uri (requester, uri, NULL); - plain = do_single_coding_req_test (req, NULL, "text/plain", EXPECT_NOT_DECODED); - g_object_unref (req); - - /* Plain text data, claim gzip */ - debug_printf (1, " GET /mbox, Accept-Encoding: gzip\n"); - soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_DECODER); - req = soup_requester_request_uri (requester, uri, NULL); - cmp = do_single_coding_req_test (req, "gzip", "text/plain", EXPECT_DECODED); - check_req_bodies (plain, cmp, "plain", "compressed"); - g_byte_array_free (cmp, TRUE); - g_object_unref (req); - - /* Plain text data, claim gzip w/ junk */ - debug_printf (1, " GET /mbox, Accept-Encoding: gzip, plus trailing junk\n"); - req = soup_requester_request_uri (requester, uri, NULL); - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - soup_message_headers_append (msg->request_headers, - "X-Test-Options", "trailing-junk"); - cmp = do_single_coding_req_test (req, "gzip", "text/plain", EXPECT_DECODED); - check_req_bodies (plain, cmp, "plain", "compressed w/ junk"); - g_byte_array_free (cmp, TRUE); - g_object_unref (req); - - /* Plain text data, claim gzip with server error */ - debug_printf (1, " GET /mbox, Accept-Encoding: gzip, with server error\n"); - req = soup_requester_request_uri (requester, uri, NULL); - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - soup_message_headers_append (msg->request_headers, +static void +do_coding_req_test_gzip_bad_server (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("613361"); + + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "force-encode"); - cmp = do_single_coding_req_test (req, "gzip", "text/plain", EXPECT_NOT_DECODED); + do_single_coding_req_test (data, "gzip", "text/plain", EXPECT_NOT_DECODED); +} - /* Failed content-decoding should have left the body untouched - * from what the server sent... which happens to be the - * uncompressed data. - */ - check_req_bodies (plain, cmp, "plain", "mis-encoded"); - g_byte_array_free (cmp, TRUE); - g_object_unref (req); - - /* Plain text data, claim deflate */ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate\n"); - req = soup_requester_request_uri (requester, uri, NULL); - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - soup_message_headers_append (msg->request_headers, +static void +do_coding_req_test_deflate (CodingTestData *data, gconstpointer test_data) +{ + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "prefer-deflate-zlib"); - cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_DECODED); - check_req_bodies (plain, cmp, "plain", "compressed"); - g_byte_array_free (cmp, TRUE); - g_object_unref (req); - - /* Plain text data, claim deflate w/ junk */ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate, plus trailing junk\n"); - req = soup_requester_request_uri (requester, uri, NULL); - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - soup_message_headers_append (msg->request_headers, + do_single_coding_req_test (data, "deflate", "text/plain", EXPECT_DECODED); +} + +static void +do_coding_req_test_deflate_with_junk (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("606352"); + g_test_bug ("676477"); + + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "prefer-deflate-zlib, trailing-junk"); - cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_DECODED); - check_req_bodies (plain, cmp, "plain", "compressed w/ junk"); - g_byte_array_free (cmp, TRUE); - g_object_unref (req); - - /* Plain text data, claim deflate with server error */ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate, with server error\n"); - req = soup_requester_request_uri (requester, uri, NULL); - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - soup_message_headers_append (msg->request_headers, - "X-Test-Options", "force-encode, prefer-deflate-zlib"); - cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_NOT_DECODED); - check_req_bodies (plain, cmp, "plain", "mis-encoded"); - g_byte_array_free (cmp, TRUE); - g_object_unref (req); - - /* Plain text data, claim deflate (no zlib headers)*/ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate (raw data)\n"); - req = soup_requester_request_uri (requester, uri, NULL); - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - soup_message_headers_append (msg->request_headers, - "X-Test-Options", "prefer-deflate-raw"); - cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_DECODED); - check_req_bodies (plain, cmp, "plain", "compressed"); - g_byte_array_free (cmp, TRUE); - g_object_unref (req); - - /* Plain text data, claim deflate with server error */ - debug_printf (1, " GET /mbox, Accept-Encoding: deflate (raw data), with server error\n"); - req = soup_requester_request_uri (requester, uri, NULL); - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - soup_message_headers_append (msg->request_headers, - "X-Test-Options", "force-encode, prefer-deflate-raw"); - cmp = do_single_coding_req_test (req, "deflate", "text/plain", EXPECT_NOT_DECODED); - check_req_bodies (plain, cmp, "plain", "mis-encoded"); - g_byte_array_free (cmp, TRUE); - g_object_unref (req); + do_single_coding_req_test (data, "deflate", "text/plain", EXPECT_DECODED); +} - g_byte_array_free (plain, TRUE); - soup_uri_free (uri); +static void +do_coding_req_test_deflate_bad_server (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("613361"); - soup_test_session_abort_unref (session); + soup_message_headers_append (data->msg->request_headers, + "X-Test-Options", "force-encode, prefer-deflate-zlib"); + do_single_coding_req_test (data, "deflate", "text/plain", EXPECT_NOT_DECODED); } static void -do_coding_empty_test (void) +do_coding_req_test_deflate_raw (CodingTestData *data, gconstpointer test_data) { - SoupSession *session; - SoupMessage *msg; - SoupURI *uri; - SoupRequester *requester; - SoupRequest *req; - GByteArray *body; + soup_message_headers_append (data->msg->request_headers, + "X-Test-Options", "prefer-deflate-raw"); + do_single_coding_req_test (data, "deflate", "text/plain", EXPECT_DECODED); +} - debug_printf (1, "\nEmpty allegedly-encoded body test\n"); +static void +do_coding_req_test_deflate_raw_bad_server (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("613361"); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER, - SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, - NULL); - requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); - uri = soup_uri_new_with_base (base_uri, "/mbox"); + soup_message_headers_append (data->msg->request_headers, + "X-Test-Options", "force-encode, prefer-deflate-raw"); + do_single_coding_req_test (data, "deflate", "text/plain", EXPECT_NOT_DECODED); +} - debug_printf (1, " SoupMessage\n"); - msg = soup_message_new_from_uri ("GET", uri); - soup_message_headers_append (msg->request_headers, - "X-Test-Options", "empty"); - soup_session_send_message (session, msg); - check_response (msg, "gzip", "text/plain", EXPECT_NOT_DECODED); - g_object_unref (msg); - - debug_printf (1, " SoupRequest\n"); - req = soup_requester_request_uri (requester, uri, NULL); - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - soup_message_headers_append (msg->request_headers, +static void +do_coding_msg_empty_test (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("697527"); + + soup_message_headers_append (data->msg->request_headers, "X-Test-Options", "empty"); - g_object_unref (msg); - body = do_single_coding_req_test (req, "gzip", "text/plain", EXPECT_NOT_DECODED); - g_byte_array_free (body, TRUE); - g_object_unref (req); + soup_session_send_message (data->session, data->msg); - soup_test_session_abort_unref (session); + check_response (data, "gzip", "text/plain", EXPECT_NOT_DECODED, NULL); } +static void +do_coding_req_empty_test (CodingTestData *data, gconstpointer test_data) +{ + g_test_bug ("697527"); + + soup_message_headers_append (data->msg->request_headers, + "X-Test-Options", "empty"); + do_single_coding_req_test (data, "gzip", "text/plain", EXPECT_NOT_DECODED); +} int main (int argc, char **argv) { + int ret; + test_init (argc, argv, NULL); server = soup_test_server_new (TRUE); @@ -565,13 +491,74 @@ main (int argc, char **argv) base_uri = soup_uri_new ("http://127.0.0.1/"); soup_uri_set_port (base_uri, soup_server_get_port (server)); - do_coding_test (); - do_coding_req_test (); - do_coding_empty_test (); + g_test_add ("/coding/message/plain", CodingTestData, + GINT_TO_POINTER (CODING_TEST_NO_DECODER), + setup_coding_test, do_coding_test_plain, teardown_coding_test); + g_test_add ("/coding/message/gzip", CodingTestData, + GINT_TO_POINTER (CODING_TEST_DEFAULT), + setup_coding_test, do_coding_test_gzip, teardown_coding_test); + g_test_add ("/coding/message/gzip/with-junk", CodingTestData, + GINT_TO_POINTER (CODING_TEST_DEFAULT), + setup_coding_test, do_coding_test_gzip_with_junk, teardown_coding_test); + g_test_add ("/coding/message/gzip/bad-server", CodingTestData, + GINT_TO_POINTER (CODING_TEST_DEFAULT), + setup_coding_test, do_coding_test_gzip_bad_server, teardown_coding_test); + g_test_add ("/coding/message/deflate", CodingTestData, + GINT_TO_POINTER (CODING_TEST_DEFAULT), + setup_coding_test, do_coding_test_deflate, teardown_coding_test); + g_test_add ("/coding/message/deflate/with-junk", CodingTestData, + GINT_TO_POINTER (CODING_TEST_DEFAULT), + setup_coding_test, do_coding_test_deflate_with_junk, teardown_coding_test); + g_test_add ("/coding/message/deflate/bad-server", CodingTestData, + GINT_TO_POINTER (CODING_TEST_DEFAULT), + setup_coding_test, do_coding_test_deflate_bad_server, teardown_coding_test); + g_test_add ("/coding/message/deflate-raw", CodingTestData, + GINT_TO_POINTER (CODING_TEST_DEFAULT), + setup_coding_test, do_coding_test_deflate_raw, teardown_coding_test); + g_test_add ("/coding/message/deflate-raw/bad-server", CodingTestData, + GINT_TO_POINTER (CODING_TEST_DEFAULT), + setup_coding_test, do_coding_test_deflate_raw_bad_server, teardown_coding_test); + + g_test_add ("/coding/request/plain", CodingTestData, + GINT_TO_POINTER (CODING_TEST_NO_DECODER | CODING_TEST_REQUEST_API), + setup_coding_test, do_coding_req_test_plain, teardown_coding_test); + g_test_add ("/coding/request/gzip", CodingTestData, + GINT_TO_POINTER (CODING_TEST_REQUEST_API), + setup_coding_test, do_coding_req_test_gzip, teardown_coding_test); + g_test_add ("/coding/request/gzip/with-junk", CodingTestData, + GINT_TO_POINTER (CODING_TEST_REQUEST_API), + setup_coding_test, do_coding_req_test_gzip_with_junk, teardown_coding_test); + g_test_add ("/coding/request/gzip/bad-server", CodingTestData, + GINT_TO_POINTER (CODING_TEST_REQUEST_API), + setup_coding_test, do_coding_req_test_gzip_bad_server, teardown_coding_test); + g_test_add ("/coding/request/deflate", CodingTestData, + GINT_TO_POINTER (CODING_TEST_REQUEST_API), + setup_coding_test, do_coding_req_test_deflate, teardown_coding_test); + g_test_add ("/coding/request/deflate/with-junk", CodingTestData, + GINT_TO_POINTER (CODING_TEST_REQUEST_API), + setup_coding_test, do_coding_req_test_deflate_with_junk, teardown_coding_test); + g_test_add ("/coding/request/deflate/bad-server", CodingTestData, + GINT_TO_POINTER (CODING_TEST_REQUEST_API), + setup_coding_test, do_coding_req_test_deflate_bad_server, teardown_coding_test); + g_test_add ("/coding/request/deflate-raw", CodingTestData, + GINT_TO_POINTER (CODING_TEST_REQUEST_API), + setup_coding_test, do_coding_req_test_deflate_raw, teardown_coding_test); + g_test_add ("/coding/request/deflate-raw/bad-server", CodingTestData, + GINT_TO_POINTER (CODING_TEST_REQUEST_API), + setup_coding_test, do_coding_req_test_deflate_raw_bad_server, teardown_coding_test); + + g_test_add ("/coding/message/empty", CodingTestData, + GINT_TO_POINTER (CODING_TEST_EMPTY), + setup_coding_test, do_coding_msg_empty_test, teardown_coding_test); + g_test_add ("/coding/request/empty", CodingTestData, + GINT_TO_POINTER (CODING_TEST_REQUEST_API | CODING_TEST_EMPTY), + setup_coding_test, do_coding_req_empty_test, teardown_coding_test); + + ret = g_test_run (); soup_uri_free (base_uri); soup_test_server_quit_unref (server); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/connection-test.c b/tests/connection-test.c index ddf0c988..8f8c74be 100644 --- a/tests/connection-test.c +++ b/tests/connection-test.c @@ -5,6 +5,8 @@ #include "test-utils.h" +#include <gio/gnetworking.h> + SoupServer *server; SoupURI *base_uri; GMutex server_mutex; @@ -164,7 +166,7 @@ do_content_length_framing_test (void) SoupURI *request_uri; goffset declared_length; - debug_printf (1, "\nInvalid Content-Length framing tests\n"); + g_test_bug ("611481"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); @@ -172,21 +174,14 @@ do_content_length_framing_test (void) request_uri = soup_uri_new_with_base (base_uri, "/content-length/long"); msg = soup_message_new_from_uri ("GET", request_uri); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } else { - declared_length = soup_message_headers_get_content_length (msg->response_headers); - debug_printf (2, " Content-Length: %lu, body: %s\n", - (gulong)declared_length, msg->response_body->data); - if (msg->response_body->length >= declared_length) { - debug_printf (1, " Body length %lu >= declared length %lu\n", - (gulong)msg->response_body->length, - (gulong)declared_length); - errors++; - } - } + + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + + declared_length = soup_message_headers_get_content_length (msg->response_headers); + debug_printf (2, " Content-Length: %lu, body: %s\n", + (gulong)declared_length, msg->response_body->data); + g_assert_cmpint (msg->response_body->length, <, declared_length); + soup_uri_free (request_uri); g_object_unref (msg); @@ -194,21 +189,12 @@ do_content_length_framing_test (void) request_uri = soup_uri_new_with_base (base_uri, "/content-length/noclose"); msg = soup_message_new_from_uri ("GET", request_uri); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } else { - declared_length = soup_message_headers_get_content_length (msg->response_headers); - debug_printf (2, " Content-Length: %lu, body: %s\n", - (gulong)declared_length, msg->response_body->data); - if (msg->response_body->length != declared_length) { - debug_printf (1, " Body length %lu != declared length %lu\n", - (gulong)msg->response_body->length, - (gulong)declared_length); - errors++; - } - } + + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + + declared_length = soup_message_headers_get_content_length (msg->response_headers); + g_assert_cmpint (msg->response_body->length, ==, declared_length); + soup_uri_free (request_uri); g_object_unref (msg); @@ -232,12 +218,11 @@ request_started_socket_collector (SoupSession *session, SoupMessage *msg, * two consecutive sockets. */ sockets[i] = g_object_ref (socket); - return; + break; } } - debug_printf (1, " socket queue overflowed!\n"); - errors++; + soup_test_assert (i < 4, "socket queue overflowed"); } static void @@ -257,14 +242,10 @@ do_timeout_test_for_session (SoupSession *session) msg = soup_message_new_from_uri ("GET", timeout_uri); soup_uri_free (timeout_uri); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + if (sockets[1]) { - debug_printf (1, " Message was retried??\n"); - errors++; + soup_test_assert (sockets[1] == NULL, "Message was retried"); sockets[1] = sockets[2] = sockets[3] = NULL; } g_object_unref (msg); @@ -272,24 +253,16 @@ do_timeout_test_for_session (SoupSession *session) debug_printf (1, " Second message\n"); msg = soup_message_new_from_uri ("GET", base_uri); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - if (sockets[1] != sockets[0]) { - debug_printf (1, " Message was not retried on existing connection\n"); - errors++; - } else if (!sockets[2]) { - debug_printf (1, " Message was not retried after disconnect\n"); - errors++; - } else if (sockets[2] == sockets[1]) { - debug_printf (1, " Message was retried on closed connection??\n"); - errors++; - } else if (sockets[3]) { - debug_printf (1, " Message was retried again??\n"); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + + soup_test_assert (sockets[1] == sockets[0], + "Message was not retried on existing connection"); + soup_test_assert (sockets[2] != NULL, + "Message was not retried after disconnect"); + soup_test_assert (sockets[2] != sockets[1], + "Message was retried on closed connection"); + soup_test_assert (sockets[3] == NULL, + "Message was retried again"); g_object_unref (msg); for (i = 0; sockets[i]; i++) @@ -299,7 +272,6 @@ do_timeout_test_for_session (SoupSession *session) static void do_timeout_req_test_for_session (SoupSession *session) { - SoupRequester *requester; SoupRequest *req; SoupMessage *msg; GInputStream *stream; @@ -308,94 +280,67 @@ do_timeout_req_test_for_session (SoupSession *session) GError *error = NULL; int i; - requester = soup_requester_new (); - soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester)); - g_object_unref (requester); - g_signal_connect (session, "request-started", G_CALLBACK (request_started_socket_collector), &sockets); debug_printf (1, " First request\n"); timeout_uri = soup_uri_new_with_base (base_uri, "/timeout-persistent"); - req = soup_requester_request_uri (requester, timeout_uri, NULL); + req = soup_session_request_uri (session, timeout_uri, NULL); soup_uri_free (timeout_uri); - if (SOUP_IS_SESSION_SYNC (session)) - stream = soup_request_send (req, NULL, &error); - else - stream = soup_test_request_send_async_as_sync (req, NULL, &error); - - if (!stream) { - debug_printf (1, " Unexpected error on send: %s\n", - error->message); - errors++; + stream = soup_test_request_send (req, NULL, 0, &error); + if (error) { + g_assert_no_error (error); g_clear_error (&error); } else { - if (SOUP_IS_SESSION_SYNC (session)) - g_input_stream_close (stream, NULL, &error); - else - soup_test_stream_close_async_as_sync (stream, NULL, &error); + soup_test_request_read_all (req, stream, NULL, &error); + if (error) { + g_assert_no_error (error); + g_clear_error (&error); + } + + soup_test_request_close_stream (req, stream, NULL, &error); if (error) { - debug_printf (1, " Unexpected error on close: %s\n", - error->message); - errors++; + g_assert_no_error (error); g_clear_error (&error); } + g_object_unref (stream); } if (sockets[1]) { - debug_printf (1, " Message was retried??\n"); - errors++; + soup_test_assert (sockets[1] == NULL, "Message was retried"); sockets[1] = sockets[2] = sockets[3] = NULL; } g_object_unref (req); debug_printf (1, " Second request\n"); - req = soup_requester_request_uri (requester, base_uri, NULL); + req = soup_session_request_uri (session, base_uri, NULL); - if (SOUP_IS_SESSION_SYNC (session)) - stream = soup_request_send (req, NULL, &error); - else - stream = soup_test_request_send_async_as_sync (req, NULL, &error); - - if (!stream) { - debug_printf (1, " Unexpected error on send: %s\n", - error->message); - errors++; + stream = soup_test_request_send (req, NULL, 0, &error); + if (error) { + g_assert_no_error (error); g_clear_error (&error); } else { - if (SOUP_IS_SESSION_SYNC (session)) - g_input_stream_close (stream, NULL, &error); - else - soup_test_stream_close_async_as_sync (stream, NULL, &error); + soup_test_request_close_stream (req, stream, NULL, &error); if (error) { - debug_printf (1, " Unexpected error on close: %s\n", - error->message); - errors++; + g_assert_no_error (error); g_clear_error (&error); } + g_object_unref (stream); } msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - if (sockets[1] != sockets[0]) { - debug_printf (1, " Message was not retried on existing connection\n"); - errors++; - } else if (!sockets[2]) { - debug_printf (1, " Message was not retried after disconnect\n"); - errors++; - } else if (sockets[2] == sockets[1]) { - debug_printf (1, " Message was retried on closed connection??\n"); - errors++; - } else if (sockets[3]) { - debug_printf (1, " Message was retried again??\n"); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + + soup_test_assert (sockets[1] == sockets[0], + "Message was not retried on existing connection"); + soup_test_assert (sockets[2] != NULL, + "Message was not retried after disconnect"); + soup_test_assert (sockets[2] != sockets[1], + "Message was retried on closed connection"); + soup_test_assert (sockets[3] == NULL, + "Message was retried again"); g_object_unref (msg); g_object_unref (req); @@ -408,13 +353,14 @@ do_persistent_connection_timeout_test (void) { SoupSession *session; - debug_printf (1, "\nUnexpected timing out of persistent connections\n"); + g_test_bug ("631525"); debug_printf (1, " Async session, message API\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); do_timeout_test_for_session (session); soup_test_session_abort_unref (session); + debug_printf (1, " Async session, request API\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); @@ -426,6 +372,7 @@ do_persistent_connection_timeout_test (void) do_timeout_test_for_session (session); soup_test_session_abort_unref (session); + debug_printf (1, " Sync session, request API\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); do_timeout_req_test_for_session (session); soup_test_session_abort_unref (session); @@ -491,26 +438,18 @@ do_max_conns_test_for_session (SoupSession *session) } g_main_loop_run (max_conns_loop); - if (msgs_done != MAX_CONNS) { - debug_printf (1, " Queued %d connections out of max %d?", - msgs_done, MAX_CONNS); - errors++; - } + g_assert_cmpint (msgs_done, ==, MAX_CONNS); g_signal_handlers_disconnect_by_func (session, max_conns_request_started, NULL); msgs_done = 0; g_idle_add (idle_start_server, NULL); + if (quit_loop_timeout) + g_source_remove (quit_loop_timeout); quit_loop_timeout = g_timeout_add (1000, quit_loop, NULL); g_main_loop_run (max_conns_loop); - for (i = 0; i < TEST_CONNS; i++) { - if (!SOUP_STATUS_IS_SUCCESSFUL (msgs[i]->status_code)) { - debug_printf (1, " Message %d failed? %d %s\n", - i, msgs[i]->status_code, - msgs[i]->reason_phrase ? msgs[i]->reason_phrase : "-"); - errors++; - } - } + for (i = 0; i < TEST_CONNS; i++) + soup_test_assert_message_status (msgs[i], SOUP_STATUS_OK); if (msgs_done != TEST_CONNS) { /* Clean up so we don't get a spurious "Leaked @@ -522,8 +461,10 @@ do_max_conns_test_for_session (SoupSession *session) } g_main_loop_unref (max_conns_loop); - if (quit_loop_timeout) + if (quit_loop_timeout) { g_source_remove (quit_loop_timeout); + quit_loop_timeout = 0; + } for (i = 0; i < TEST_CONNS; i++) g_object_unref (msgs[i]); @@ -534,7 +475,7 @@ do_max_conns_test (void) { SoupSession *session; - debug_printf (1, "\nExceeding max-conns\n"); + g_test_bug ("634422"); debug_printf (1, " Async session\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, @@ -551,8 +492,6 @@ do_max_conns_test (void) soup_test_session_abort_unref (session); } -GMainLoop *loop; - static void np_request_started (SoupSession *session, SoupMessage *msg, SoupSocket *socket, gpointer user_data) @@ -568,10 +507,14 @@ np_request_unqueued (SoupSession *session, SoupMessage *msg, { SoupSocket *socket = *(SoupSocket **)user_data; - if (soup_socket_is_connected (socket)) { - debug_printf (1, " socket is still connected\n"); - errors++; - } + g_assert_false (soup_socket_is_connected (socket)); +} + +static void +np_request_finished (SoupSession *session, SoupMessage *msg, + gpointer user_data) +{ + GMainLoop *loop = user_data; g_main_loop_quit (loop); } @@ -581,6 +524,7 @@ do_non_persistent_test_for_session (SoupSession *session) { SoupMessage *msg; SoupSocket *socket = NULL; + GMainLoop *loop; loop = g_main_loop_new (NULL, FALSE); @@ -594,15 +538,15 @@ do_non_persistent_test_for_session (SoupSession *session) msg = soup_message_new_from_uri ("GET", base_uri); soup_message_headers_append (msg->request_headers, "Connection", "close"); g_object_ref (msg); - soup_session_queue_message (session, msg, NULL, NULL); + soup_session_queue_message (session, msg, + np_request_finished, loop); g_main_loop_run (loop); + g_main_loop_unref (loop); + + soup_test_assert_message_status (msg, SOUP_STATUS_OK); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } g_object_unref (msg); + g_object_unref (socket); } static void @@ -610,7 +554,7 @@ do_non_persistent_connection_test (void) { SoupSession *session; - debug_printf (1, "\nNon-persistent connections are closed immediately\n"); + g_test_bug ("578990"); debug_printf (1, " Async session\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); @@ -637,14 +581,9 @@ do_non_idempotent_test_for_session (SoupSession *session) debug_printf (2, " GET\n"); msg = soup_message_new_from_uri ("GET", base_uri); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); if (sockets[1]) { - debug_printf (1, " Message was retried??\n"); - errors++; + soup_test_assert (sockets[1] == NULL, "Message was retried"); sockets[1] = sockets[2] = sockets[3] = NULL; } g_object_unref (msg); @@ -652,19 +591,12 @@ do_non_idempotent_test_for_session (SoupSession *session) debug_printf (2, " POST\n"); msg = soup_message_new_from_uri ("POST", base_uri); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - if (sockets[1] == sockets[0]) { - debug_printf (1, " Message was sent on existing connection!\n"); - errors++; - } - if (sockets[2]) { - debug_printf (1, " Too many connections used...\n"); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + soup_test_assert (sockets[1] != sockets[0], + "Message was sent on existing connection"); + soup_test_assert (sockets[2] == NULL, + "Too many connections used"); + g_object_unref (msg); for (i = 0; sockets[i]; i++) @@ -676,8 +608,6 @@ do_non_idempotent_connection_test (void) { SoupSession *session; - debug_printf (1, "\nNon-idempotent methods are always sent on new connections\n"); - debug_printf (1, " Async session\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); do_non_idempotent_test_for_session (session); @@ -689,25 +619,254 @@ do_non_idempotent_connection_test (void) soup_test_session_abort_unref (session); } +#define HTTP_SERVER "http://127.0.0.1:47524" +#define HTTPS_SERVER "https://127.0.0.1:47525" +#define HTTP_PROXY "http://127.0.0.1:47526" + +static SoupConnectionState state_transitions[] = { + /* NEW -> */ SOUP_CONNECTION_CONNECTING, + /* CONNECTING -> */ SOUP_CONNECTION_IN_USE, + /* IDLE -> */ SOUP_CONNECTION_DISCONNECTED, + /* IN_USE -> */ SOUP_CONNECTION_IDLE, + + /* REMOTE_DISCONNECTED */ -1, + /* DISCONNECTED */ -1, +}; + +static const char *state_names[] = { + "NEW", "CONNECTING", "IDLE", "IN_USE", + "REMOTE_DISCONNECTED", "DISCONNECTED" +}; + +static void +connection_state_changed (GObject *object, GParamSpec *param, + gpointer user_data) +{ + SoupConnectionState *state = user_data; + SoupConnectionState new_state; + + g_object_get (object, "state", &new_state, NULL); + debug_printf (2, " %s -> %s\n", + state_names[*state], state_names[new_state]); + soup_test_assert (state_transitions[*state] == new_state, + "Unexpected transition: %s -> %s\n", + state_names[*state], state_names[new_state]); + *state = new_state; +} + +static void +connection_created (SoupSession *session, GObject *conn, + gpointer user_data) +{ + SoupConnectionState *state = user_data; + + g_object_get (conn, "state", state, NULL); + g_assert_cmpint (*state, ==, SOUP_CONNECTION_NEW); + + g_signal_connect (conn, "notify::state", + G_CALLBACK (connection_state_changed), + state); +} + +static void +do_one_connection_state_test (SoupSession *session, const char *uri) +{ + SoupMessage *msg; + + msg = soup_message_new ("GET", uri); + soup_session_send_message (session, msg); + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + g_object_unref (msg); + soup_session_abort (session); +} + +static void +do_connection_state_test_for_session (SoupSession *session) +{ + SoupConnectionState state; + SoupURI *proxy_uri; + + g_signal_connect (session, "connection-created", + G_CALLBACK (connection_created), + &state); + + debug_printf (1, " http\n"); + do_one_connection_state_test (session, HTTP_SERVER); + + if (tls_available) { + debug_printf (1, " https\n"); + do_one_connection_state_test (session, HTTPS_SERVER); + } else + debug_printf (1, " https -- SKIPPING\n"); + + proxy_uri = soup_uri_new (HTTP_PROXY); + g_object_set (G_OBJECT (session), + SOUP_SESSION_PROXY_URI, proxy_uri, + NULL); + soup_uri_free (proxy_uri); + + debug_printf (1, " http with proxy\n"); + do_one_connection_state_test (session, HTTP_SERVER); + + if (tls_available) { + debug_printf (1, " https with proxy\n"); + do_one_connection_state_test (session, HTTPS_SERVER); + } else + debug_printf (1, " https with proxy -- SKIPPING\n"); +} + +static void +do_connection_state_test (void) +{ + SoupSession *session; + + SOUP_TEST_SKIP_IF_NO_APACHE; + + debug_printf (1, " Async session\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + do_connection_state_test_for_session (session); + soup_test_session_abort_unref (session); + + debug_printf (1, " Sync session\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); + do_connection_state_test_for_session (session); + soup_test_session_abort_unref (session); +} + + +static const char *event_names[] = { + "RESOLVING", "RESOLVED", "CONNECTING", "CONNECTED", + "PROXY_NEGOTIATING", "PROXY_NEGOTIATED", + "TLS_HANDSHAKING", "TLS_HANDSHAKED", "COMPLETE" +}; + +static const char event_abbrevs[] = { + 'r', 'R', 'c', 'C', 'p', 'P', 't', 'T', 'x', '\0' +}; + +static const char * +event_name_from_abbrev (char abbrev) +{ + int evt; + + for (evt = 0; event_abbrevs[evt]; evt++) { + if (event_abbrevs[evt] == abbrev) + return event_names[evt]; + } + return "???"; +} + +static void +network_event (SoupMessage *msg, GSocketClientEvent event, + GIOStream *connection, gpointer user_data) +{ + const char **events = user_data; + + debug_printf (2, " %s\n", event_name_from_abbrev (**events)); + soup_test_assert (**events == event_abbrevs[event], + "Unexpected event: %s (expected %s)\n", + event_names[event], + event_name_from_abbrev (**events)); + *events = *events + 1; +} + +static void +do_one_connection_event_test (SoupSession *session, const char *uri, + const char *events) +{ + SoupMessage *msg; + + msg = soup_message_new ("GET", uri); + g_signal_connect (msg, "network-event", + G_CALLBACK (network_event), + &events); + soup_session_send_message (session, msg); + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + while (*events) { + soup_test_assert (!*events, + "Expected %s", + event_name_from_abbrev (*events)); + events++; + } + + g_object_unref (msg); + soup_session_abort (session); +} + +static void +do_connection_event_test_for_session (SoupSession *session) +{ + SoupURI *proxy_uri; + + debug_printf (1, " http\n"); + do_one_connection_event_test (session, HTTP_SERVER, "rRcCx"); + + if (tls_available) { + debug_printf (1, " https\n"); + do_one_connection_event_test (session, HTTPS_SERVER, "rRcCtTx"); + } else + debug_printf (1, " https -- SKIPPING\n"); + + proxy_uri = soup_uri_new (HTTP_PROXY); + g_object_set (G_OBJECT (session), + SOUP_SESSION_PROXY_URI, proxy_uri, + NULL); + soup_uri_free (proxy_uri); + + debug_printf (1, " http with proxy\n"); + do_one_connection_event_test (session, HTTP_SERVER, "rRcCx"); + + if (tls_available) { + debug_printf (1, " https with proxy\n"); + do_one_connection_event_test (session, HTTPS_SERVER, "rRcCpPtTx"); + } else + debug_printf (1, " https with proxy -- SKIPPING\n"); +} + +static void +do_connection_event_test (void) +{ + SoupSession *session; + + SOUP_TEST_SKIP_IF_NO_APACHE; + + debug_printf (1, " Async session\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + do_connection_event_test_for_session (session); + soup_test_session_abort_unref (session); + + debug_printf (1, " Sync session\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); + do_connection_event_test_for_session (session); + soup_test_session_abort_unref (session); +} + int main (int argc, char **argv) { + int ret; + test_init (argc, argv, NULL); + apache_init (); server = soup_test_server_new (TRUE); soup_server_add_handler (server, NULL, server_callback, "http", NULL); base_uri = soup_uri_new ("http://127.0.0.1/"); soup_uri_set_port (base_uri, soup_server_get_port (server)); - do_content_length_framing_test (); - do_persistent_connection_timeout_test (); - do_max_conns_test (); - do_non_persistent_connection_test (); - do_non_idempotent_connection_test (); + g_test_add_func ("/connection/content-length-framing", do_content_length_framing_test); + g_test_add_func ("/connection/persistent-connection-timeout", do_persistent_connection_timeout_test); + g_test_add_func ("/connection/max-conns", do_max_conns_test); + g_test_add_func ("/connection/non-persistent", do_non_persistent_connection_test); + g_test_add_func ("/connection/non-idempotent", do_non_idempotent_connection_test); + g_test_add_func ("/connection/state", do_connection_state_test); + g_test_add_func ("/connection/event", do_connection_event_test); + + ret = g_test_run (); soup_uri_free (base_uri); soup_test_server_quit_unref (server); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/context-test.c b/tests/context-test.c index 97cd2c0c..727c63ba 100644 --- a/tests/context-test.c +++ b/tests/context-test.c @@ -84,13 +84,9 @@ static GMutex test1_mutex; static GMainLoop *test1_loop; static void -do_test1 (int n, gboolean use_thread_context) +do_test1 (gconstpointer data) { - debug_printf (1, "\nTest %d: blocking the main thread does not block other thread\n", n); - if (use_thread_context) - debug_printf (1, "(Using g_main_context_push_thread_default())\n"); - else - debug_printf (1, "(Using SOUP_SESSION_ASYNC_CONTEXT)\n"); + gboolean use_thread_context = GPOINTER_TO_INT (data); test1_loop = g_main_loop_new (NULL, FALSE); g_idle_add (idle_start_test1_thread, GINT_TO_POINTER (use_thread_context)); @@ -111,9 +107,8 @@ idle_start_test1_thread (gpointer use_thread_context) if (g_cond_wait_until (&test1_cond, &test1_mutex, time)) g_thread_join (thread); else { - debug_printf (1, " timeout!\n"); + soup_test_assert (FALSE, "timeout"); g_thread_unref (thread); - errors++; } g_mutex_unlock (&test1_mutex); @@ -158,11 +153,7 @@ test1_thread (gpointer use_thread_context) debug_printf (1, " send_message\n"); msg = soup_message_new ("GET", uri); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); g_object_unref (msg); debug_printf (1, " queue_message\n"); @@ -172,11 +163,7 @@ test1_thread (gpointer use_thread_context) soup_session_queue_message (session, msg, test1_finished, loop); g_main_loop_run (loop); g_main_loop_unref (loop); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); g_object_unref (msg); soup_test_session_abort_unref (session); @@ -196,20 +183,15 @@ test1_thread (gpointer use_thread_context) static gboolean idle_test2_fail (gpointer user_data); static void -do_test2 (int n, gboolean use_thread_context) +do_test2 (gconstpointer data) { + gboolean use_thread_context = GPOINTER_TO_INT (data); guint idle; GMainContext *async_context; SoupSession *session; char *uri; SoupMessage *msg; - debug_printf (1, "\nTest %d: a session with its own context is independent of the main loop.\n", n); - if (use_thread_context) - debug_printf (1, "(Using g_main_context_push_thread_default())\n"); - else - debug_printf (1, "(Using SOUP_SESSION_ASYNC_CONTEXT)\n"); - idle = g_idle_add_full (G_PRIORITY_HIGH, idle_test2_fail, NULL, NULL); async_context = g_main_context_new (); @@ -230,11 +212,7 @@ do_test2 (int n, gboolean use_thread_context) debug_printf (1, " send_message\n"); msg = soup_message_new ("GET", uri); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); g_object_unref (msg); soup_test_session_abort_unref (session); @@ -249,8 +227,7 @@ do_test2 (int n, gboolean use_thread_context) static gboolean idle_test2_fail (gpointer user_data) { - debug_printf (1, " idle ran!\n"); - errors++; + soup_test_assert (FALSE, "idle ran"); return FALSE; } @@ -279,15 +256,13 @@ multi_msg_finished (SoupSession *session, SoupMessage *msg, gpointer user_data) } static void -do_multicontext_test (int n) +do_multicontext_test (void) { SoupSession *session; SoupMessage *msg1, *msg2; GMainContext *context1, *context2; GMainLoop *loop1, *loop2; - debug_printf (1, "\nTest %d: Using multiple async contexts\n", n); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); @@ -318,36 +293,26 @@ do_multicontext_test (int n) g_main_loop_run (loop1); g_main_context_pop_thread_default (context1); - if (!g_object_get_data (G_OBJECT (msg1), "started")) { - debug_printf (1, " msg1 not started??\n"); - errors++; - } - if (g_object_get_data (G_OBJECT (msg2), "started")) { - debug_printf (1, " msg2 started while loop1 was running!\n"); - errors++; - } + if (!g_object_get_data (G_OBJECT (msg1), "started")) + soup_test_assert (FALSE, "msg1 not started"); + if (g_object_get_data (G_OBJECT (msg2), "started")) + soup_test_assert (FALSE, "msg2 started while loop1 was running"); g_main_context_push_thread_default (context2); g_main_loop_run (loop2); g_main_context_pop_thread_default (context2); - if (g_object_get_data (G_OBJECT (msg1), "finished")) { - debug_printf (1, " msg1 finished while loop2 was running!\n"); - errors++; - } - if (!g_object_get_data (G_OBJECT (msg2), "finished")) { - debug_printf (1, " msg2 not finished??\n"); - errors++; - } + if (g_object_get_data (G_OBJECT (msg1), "finished")) + soup_test_assert (FALSE, "msg1 finished while loop2 was running"); + if (!g_object_get_data (G_OBJECT (msg2), "finished")) + soup_test_assert (FALSE, "msg2 not finished"); g_main_context_push_thread_default (context1); g_main_loop_run (loop1); g_main_context_pop_thread_default (context1); - if (!g_object_get_data (G_OBJECT (msg1), "finished")) { - debug_printf (1, " msg1 not finished??\n"); - errors++; - } + if (!g_object_get_data (G_OBJECT (msg1), "finished")) + soup_test_assert (FALSE, "msg1 not finished"); g_object_unref (msg1); g_object_unref (msg2); @@ -364,6 +329,7 @@ int main (int argc, char **argv) { SoupServer *server; + int ret; test_init (argc, argv, NULL); @@ -372,15 +338,17 @@ main (int argc, char **argv) base_uri = g_strdup_printf ("http://127.0.0.1:%u/", soup_server_get_port (server)); - do_test1 (1, FALSE); - do_test1 (2, TRUE); - do_test2 (3, FALSE); - do_test2 (4, TRUE); - do_multicontext_test (5); + g_test_add_data_func ("/context/blocking/explicit", GINT_TO_POINTER (FALSE), do_test1); + g_test_add_data_func ("/context/blocking/thread-default", GINT_TO_POINTER (TRUE), do_test1); + g_test_add_data_func ("/context/nested/explicit", GINT_TO_POINTER (FALSE), do_test2); + g_test_add_data_func ("/context/nested/thread-default", GINT_TO_POINTER (TRUE), do_test2); + g_test_add_func ("/context/multiple", do_multicontext_test); + + ret = g_test_run (); g_free (base_uri); soup_test_server_quit_unref (server); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/continue-test.c b/tests/continue-test.c index fa9ef809..b6a5805f 100644 --- a/tests/continue-test.c +++ b/tests/continue-test.c @@ -59,13 +59,6 @@ do_message (const char *path, gboolean long_body, const char *expected_event; char *actual_event; int expected_status, actual_status; - static int count = 1; - - debug_printf (1, "%d. /%s, %s body, %sExpect, %s password\n", - count++, path, - long_body ? "long" : "short", - expect_continue ? "" : "no ", - auth ? "with" : "without"); uri = g_strdup_printf ("http://%s127.0.0.1:%d/%s", auth ? "user:pass@" : "", @@ -106,17 +99,13 @@ do_message (const char *path, gboolean long_body, while ((expected_event = va_arg (ap, const char *))) { if (!events) { - actual_event = g_strdup (""); - debug_printf (1, " Expected '%s', got end of list\n", - expected_event); - errors++; + soup_test_assert (events != NULL, + "Expected '%s', got end of list", + expected_event); + continue; } else { actual_event = events->data; - if (strcmp (expected_event, actual_event) != 0) { - debug_printf (1, " Expected '%s', got '%s'\n", - expected_event, actual_event); - errors++; - } + g_assert_cmpstr (expected_event, ==, actual_event); events = g_slist_delete_link (events, events); } @@ -134,10 +123,10 @@ do_message (const char *path, gboolean long_body, if (expected_status != -1 && actual_status != -1 && expected_status != actual_status) { - debug_printf (1, " Expected status '%s', got '%s'\n", - soup_status_get_phrase (expected_status), - soup_status_get_phrase (actual_status)); - errors++; + soup_test_assert (expected_status == actual_status, + "Expected status '%s', got '%s'", + soup_status_get_phrase (expected_status), + soup_status_get_phrase (actual_status)); } g_free (actual_event); @@ -145,8 +134,8 @@ do_message (const char *path, gboolean long_body, va_end (ap); while (events) { actual_event = events->data; - debug_printf (1, " Expected to be done, got '%s'\n", actual_event); - errors++; + soup_test_assert (events == NULL, + "Expected to be done, got '%s'", actual_event); events = g_slist_delete_link (events, events); if (!strcmp (actual_event, "server-wrote_headers") || @@ -157,7 +146,7 @@ do_message (const char *path, gboolean long_body, } static void -run_tests (void) +do_test_unauth_short_noexpect_nopass (void) { do_message ("unauth", FALSE, FALSE, FALSE, "client-wrote_headers", @@ -171,6 +160,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} + +static void +do_test_unauth_long_noexpect_nopass (void) +{ do_message ("unauth", TRUE, FALSE, FALSE, "client-wrote_headers", "client-wrote_body", @@ -183,6 +177,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} + +static void +do_test_unauth_short_expect_nopass (void) +{ do_message ("unauth", FALSE, TRUE, FALSE, "client-wrote_headers", "server-got_headers", @@ -197,6 +196,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} + +static void +do_test_unauth_long_expect_nopass (void) +{ do_message ("unauth", TRUE, TRUE, FALSE, "client-wrote_headers", "server-got_headers", @@ -207,7 +211,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} +static void +do_test_auth_short_noexpect_nopass (void) +{ do_message ("auth", FALSE, FALSE, FALSE, "client-wrote_headers", "client-wrote_body", @@ -220,6 +228,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} + +static void +do_test_auth_long_noexpect_nopass (void) +{ do_message ("auth", TRUE, FALSE, FALSE, "client-wrote_headers", "client-wrote_body", @@ -232,6 +245,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} + +static void +do_test_auth_short_expect_nopass (void) +{ do_message ("auth", FALSE, TRUE, FALSE, "client-wrote_headers", "server-got_headers", @@ -242,6 +260,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} + +static void +do_test_auth_long_expect_nopass (void) +{ do_message ("auth", TRUE, TRUE, FALSE, "client-wrote_headers", "server-got_headers", @@ -252,7 +275,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} +static void +do_test_auth_short_noexpect_pass (void) +{ do_message ("auth", FALSE, FALSE, TRUE, "client-wrote_headers", "client-wrote_body", @@ -274,6 +301,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} + +static void +do_test_auth_long_noexpect_pass (void) +{ do_message ("auth", TRUE, FALSE, TRUE, "client-wrote_headers", "client-wrote_body", @@ -295,6 +327,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} + +static void +do_test_auth_short_expect_pass (void) +{ do_message ("auth", FALSE, TRUE, TRUE, "client-wrote_headers", "server-got_headers", @@ -316,6 +353,11 @@ run_tests (void) "client-got_body", "client-finished", NULL); +} + +static void +do_test_auth_long_expect_pass (void) +{ do_message ("auth", TRUE, TRUE, TRUE, "client-wrote_headers", "server-got_headers", @@ -434,15 +476,30 @@ int main (int argc, char **argv) { SoupServer *server; + int ret; test_init (argc, argv, NULL); server = setup_server (); port = soup_server_get_port (server); - run_tests (); + g_test_add_func ("/continue/unauth_short_noexpect_nopass", do_test_unauth_short_noexpect_nopass); + g_test_add_func ("/continue/unauth_long_noexpect_nopass", do_test_unauth_long_noexpect_nopass); + g_test_add_func ("/continue/unauth_short_expect_nopass", do_test_unauth_short_expect_nopass); + g_test_add_func ("/continue/unauth_long_expect_nopass", do_test_unauth_long_expect_nopass); + g_test_add_func ("/continue/auth_short_noexpect_nopass", do_test_auth_short_noexpect_nopass); + g_test_add_func ("/continue/auth_long_noexpect_nopass", do_test_auth_long_noexpect_nopass); + g_test_add_func ("/continue/auth_short_expect_nopass", do_test_auth_short_expect_nopass); + g_test_add_func ("/continue/auth_long_expect_nopass", do_test_auth_long_expect_nopass); + g_test_add_func ("/continue/auth_short_noexpect_pass", do_test_auth_short_noexpect_pass); + g_test_add_func ("/continue/auth_long_noexpect_pass", do_test_auth_long_noexpect_pass); + g_test_add_func ("/continue/auth_short_expect_pass", do_test_auth_short_expect_pass); + g_test_add_func ("/continue/auth_long_expect_pass", do_test_auth_long_expect_pass); + + ret = g_test_run (); soup_test_server_quit_unref (server); test_cleanup (); - return errors != 0; + + return ret; } diff --git a/tests/cookies-test.c b/tests/cookies-test.c index 58f8052e..12529d81 100644 --- a/tests/cookies-test.c +++ b/tests/cookies-test.c @@ -55,8 +55,6 @@ do_cookies_accept_policy_test (void) GSList *l, *p; int i; - debug_printf (1, "SoupCookieJarAcceptPolicy test\n"); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR); jar = SOUP_COOKIE_JAR (soup_session_get_feature (session, SOUP_TYPE_COOKIE_JAR)); @@ -83,13 +81,7 @@ do_cookies_accept_policy_test (void) g_object_unref (msg); l = soup_cookie_jar_all_cookies (jar); - if (g_slist_length (l) < validResults[i].n_cookies) { - debug_printf (1, " accepted less cookies than it should have\n"); - errors++; - } else if (g_slist_length (l) > validResults[i].n_cookies) { - debug_printf (1, " accepted more cookies than it should have\n"); - errors++; - } + g_assert_cmpint (g_slist_length (l), ==, validResults[i].n_cookies); for (p = l; p; p = p->next) { soup_cookie_jar_delete_cookie (jar, p->data); @@ -113,7 +105,7 @@ do_cookies_parsing_test (void) SoupCookie *cookie; gboolean got1, got2, got3; - debug_printf (1, "\nSoupCookie parsing test\n"); + g_test_bug ("678753"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); soup_session_add_feature_by_type (session, SOUP_TYPE_COOKIE_JAR); @@ -146,56 +138,28 @@ do_cookies_parsing_test (void) if (!strcmp (soup_cookie_get_name (cookie), "one")) { got1 = TRUE; - if (!soup_cookie_get_http_only (cookie)) { - debug_printf (1, " cookie 1 is not HttpOnly!\n"); - errors++; - } - if (!soup_cookie_get_expires (cookie)) { - debug_printf (1, " cookie 1 did not fully parse!\n"); - errors++; - } + g_assert_true (soup_cookie_get_http_only (cookie)); + g_assert_true (soup_cookie_get_expires (cookie) != NULL); } else if (!strcmp (soup_cookie_get_name (cookie), "two")) { got2 = TRUE; - if (!soup_cookie_get_http_only (cookie)) { - debug_printf (1, " cookie 2 is not HttpOnly!\n"); - errors++; - } - if (!soup_cookie_get_expires (cookie)) { - debug_printf (1, " cookie 3 did not fully parse!\n"); - errors++; - } + g_assert_true (soup_cookie_get_http_only (cookie)); + g_assert_true (soup_cookie_get_expires (cookie) != NULL); } else if (!strcmp (soup_cookie_get_name (cookie), "three")) { got3 = TRUE; - if (!soup_cookie_get_http_only (cookie)) { - debug_printf (1, " cookie 3 is not HttpOnly!\n"); - errors++; - } - if (!soup_cookie_get_expires (cookie)) { - debug_printf (1, " cookie 3 did not fully parse!\n"); - errors++; - } + g_assert_true (soup_cookie_get_http_only (cookie)); + g_assert_true (soup_cookie_get_expires (cookie) != NULL); } else { - debug_printf (1, " got unexpected cookie '%s'\n", - soup_cookie_get_name (cookie)); - errors++; + soup_test_assert (FALSE, "got unexpected cookie '%s'", + soup_cookie_get_name (cookie)); } soup_cookie_free (cookie); } g_slist_free (cookies); - if (!got1) { - debug_printf (1, " didn't get cookie 1\n"); - errors++; - } - if (!got2) { - debug_printf (1, " didn't get cookie 2\n"); - errors++; - } - if (!got3) { - debug_printf (1, " didn't get cookie 3\n"); - errors++; - } + g_assert_true (got1); + g_assert_true (got2); + g_assert_true (got3); soup_test_session_abort_unref (session); } @@ -203,6 +167,8 @@ do_cookies_parsing_test (void) int main (int argc, char **argv) { + int ret; + test_init (argc, argv, NULL); server = soup_test_server_new (TRUE); @@ -212,14 +178,15 @@ main (int argc, char **argv) soup_uri_set_port (first_party_uri, soup_server_get_port (server)); soup_uri_set_port (third_party_uri, soup_server_get_port (server)); - do_cookies_accept_policy_test (); - do_cookies_parsing_test (); + g_test_add_func ("/cookies/accept-policy", do_cookies_accept_policy_test); + g_test_add_func ("/cookies/parsing", do_cookies_parsing_test); + + ret = g_test_run (); soup_uri_free (first_party_uri); soup_uri_free (third_party_uri); soup_test_server_quit_unref (server); test_cleanup (); - - return errors != 0; + return ret; } diff --git a/tests/date.c b/tests/date.c index 54a59f2b..e51fa57f 100644 --- a/tests/date.c +++ b/tests/date.c @@ -5,7 +5,7 @@ #include "test-utils.h" -static gboolean check_ok (const char *strdate, SoupDate *date); +static void check_ok (gconstpointer data); static SoupDate * make_date (const char *strdate) @@ -24,178 +24,226 @@ make_date (const char *strdate) return date; } -static const struct { +static SoupDate * +check_correct_date (const char *strdate) +{ + SoupDate *date; + + date = make_date (strdate); + if (!date) { + g_assert_nonnull (date); + return NULL; + } + + g_assert_cmpint (date->year, ==, 2004); + g_assert_cmpint (date->month, ==, 11); + g_assert_cmpint (date->day, ==, 6); + g_assert_cmpint (date->hour, ==, 8); + g_assert_cmpint (date->minute, ==, 9); + g_assert_cmpint (date->second, ==, 7); + + return date; +} + +typedef struct { SoupDateFormat format; const char *date; -} good_dates[] = { - { SOUP_DATE_HTTP, "Sat, 06 Nov 2004 08:09:07 GMT" }, - { SOUP_DATE_COOKIE, "Sat, 06-Nov-2004 08:09:07 GMT" }, - { SOUP_DATE_RFC2822, "Sat, 6 Nov 2004 08:09:07 -0430" }, - { SOUP_DATE_ISO8601_COMPACT, "20041106T080907" }, - { SOUP_DATE_ISO8601_FULL, "2004-11-06T08:09:07" }, - { SOUP_DATE_ISO8601_XMLRPC, "20041106T08:09:07" } + const char *bugref; +} GoodDate; + +static const GoodDate good_dates[] = { + { SOUP_DATE_HTTP, "Sat, 06 Nov 2004 08:09:07 GMT", NULL }, + { SOUP_DATE_COOKIE, "Sat, 06-Nov-2004 08:09:07 GMT", NULL }, + { SOUP_DATE_RFC2822, "Sat, 6 Nov 2004 08:09:07 -0430", "579055" }, + { SOUP_DATE_ISO8601_COMPACT, "20041106T080907", NULL }, + { SOUP_DATE_ISO8601_FULL, "2004-11-06T08:09:07", NULL }, + { SOUP_DATE_ISO8601_XMLRPC, "20041106T08:09:07", NULL } }; static void -check_good (SoupDateFormat format, const char *strdate) +check_good (gconstpointer data) { + GoodDate *good = (GoodDate *)data; SoupDate *date; char *strdate2; - date = make_date (strdate); - g_assert (date); - strdate2 = soup_date_to_string (date, format); - if (!check_ok (strdate, date)) + if (good->bugref) + g_test_bug (good->bugref); + + date = check_correct_date (good->date); + if (!date) return; - if (strcmp (strdate, strdate2) != 0) { - debug_printf (1, " restringification failed: '%s' -> '%s'\n", - strdate, strdate2); - errors++; - } + strdate2 = soup_date_to_string (date, good->format); + soup_date_free (date); + + soup_test_assert (strcmp (good->date, strdate2) == 0, + "restringification failed: '%s' -> '%s'\n", + good->date, strdate2); g_free (strdate2); } -static const char *ok_dates[] = { +typedef struct { + const char *date; + const char *bugref; +} OkDate; + +static const OkDate ok_dates[] = { /* rfc1123-date, and broken variants */ - "Sat, 06 Nov 2004 08:09:07 GMT", - "Sat, 6 Nov 2004 08:09:07 GMT", - "Sat, 6 Nov 2004 08:09:07 GMT", - "Sat, 06 Nov 2004 08:09:07", - "06 Nov 2004 08:09:07 GMT", - "SAT, 06 NOV 2004 08:09:07 +1000", + { "Sat, 06 Nov 2004 08:09:07 GMT", NULL }, + { "Sat, 6 Nov 2004 08:09:07 GMT", NULL }, + { "Sat, 6 Nov 2004 08:09:07 GMT", NULL }, + { "Sat, 06 Nov 2004 08:09:07", NULL }, + { "06 Nov 2004 08:09:07 GMT", NULL }, + { "SAT, 06 NOV 2004 08:09:07 +1000", "644048" }, /* rfc850-date, and broken variants */ - "Saturday, 06-Nov-04 08:09:07 GMT", - "Saturday, 6-Nov-04 08:09:07 GMT", - "Saturday, 6-Nov-04 08:09:07 GMT", - "Saturday, 06-Nov-104 08:09:07 GMT", - "Saturday, 06-Nov-04 08:09:07", - "06-Nov-04 08:09:07 GMT", + { "Saturday, 06-Nov-04 08:09:07 GMT", NULL }, + { "Saturday, 6-Nov-04 08:09:07 GMT", NULL }, + { "Saturday, 6-Nov-04 08:09:07 GMT", NULL }, + { "Saturday, 06-Nov-104 08:09:07 GMT", NULL }, + { "Saturday, 06-Nov-04 08:09:07", NULL }, + { "06-Nov-04 08:09:07 GMT", NULL }, /* asctime-date, and broken variants */ - "Sat Nov 6 08:09:07 2004", - "Sat Nov 06 08:09:07 2004", - "Sat Nov 6 08:09:07 2004", - "Sat Nov 6 08:09:07 2004 GMT", + { "Sat Nov 6 08:09:07 2004", NULL }, + { "Sat Nov 06 08:09:07 2004", NULL }, + { "Sat Nov 6 08:09:07 2004", NULL }, + { "Sat Nov 6 08:09:07 2004 GMT", NULL }, /* ISO 8601 */ - "2004-11-06T08:09:07Z", - "20041106T08:09:07Z", - "20041106T08:09:07+00:00", - "20041106T080907+00:00", + { "2004-11-06T08:09:07Z", NULL }, + { "20041106T08:09:07Z", NULL }, + { "20041106T08:09:07+00:00", NULL }, + { "20041106T080907+00:00", NULL }, /* Netscape cookie spec date, and broken variants */ - "Sat, 06-Nov-2004 08:09:07 GMT", - "Sat, 6-Nov-2004 08:09:07 GMT", - "Sat, 6-Nov-2004 08:09:07 GMT", - "Sat, 06-Nov-2004 08:09:07", + { "Sat, 06-Nov-2004 08:09:07 GMT", NULL }, + { "Sat, 6-Nov-2004 08:09:07 GMT", NULL }, + { "Sat, 6-Nov-2004 08:09:07 GMT", NULL }, + { "Sat, 06-Nov-2004 08:09:07", NULL }, /* Original version of Netscape cookie spec, and broken variants */ - "Sat, 06-Nov-04 08:09:07 GMT", - "Sat, 6-Nov-04 08:09:07 GMT", - "Sat, 6-Nov-04 08:09:07 GMT", - "Sat, 06-Nov-104 08:09:07 GMT", - "Sat, 06-Nov-04 08:09:07", + { "Sat, 06-Nov-04 08:09:07 GMT", NULL }, + { "Sat, 6-Nov-04 08:09:07 GMT", NULL }, + { "Sat, 6-Nov-04 08:09:07 GMT", NULL }, + { "Sat, 06-Nov-104 08:09:07 GMT", NULL }, + { "Sat, 06-Nov-04 08:09:07", NULL }, /* Netscape cookie spec example syntax, and broken variants */ - "Saturday, 06-Nov-04 08:09:07 GMT", - "Saturday, 6-Nov-04 08:09:07 GMT", - "Saturday, 6-Nov-04 08:09:07 GMT", - "Saturday, 06-Nov-104 08:09:07 GMT", - "Saturday, 06-Nov-2004 08:09:07 GMT", - "Saturday, 6-Nov-2004 08:09:07 GMT", - "Saturday, 6-Nov-2004 08:09:07 GMT", - "Saturday, 06-Nov-04 08:09:07", + { "Saturday, 06-Nov-04 08:09:07 GMT", NULL }, + { "Saturday, 6-Nov-04 08:09:07 GMT", NULL }, + { "Saturday, 6-Nov-04 08:09:07 GMT", NULL }, + { "Saturday, 06-Nov-104 08:09:07 GMT", NULL }, + { "Saturday, 06-Nov-2004 08:09:07 GMT", NULL }, + { "Saturday, 6-Nov-2004 08:09:07 GMT", NULL }, + { "Saturday, 6-Nov-2004 08:09:07 GMT", NULL }, + { "Saturday, 06-Nov-04 08:09:07", NULL }, /* Miscellaneous broken formats seen on the web */ - "Sat 06-Nov-2004 08:9:07", - "Saturday, 06-Nov-04 8:9:07 GMT", - "Sat, 06 Nov 2004 08:09:7 GMT" + { "Sat 06-Nov-2004 08:9:07", NULL }, + { "Saturday, 06-Nov-04 8:9:07 GMT", NULL }, + { "Sat, 06 Nov 2004 08:09:7 GMT", NULL } }; +static void +check_ok (gconstpointer data) +{ + OkDate *ok = (OkDate *)data; + SoupDate *date; + + if (ok->bugref) + g_test_bug (ok->bugref); + + date = check_correct_date (ok->date); + if (!date) + return; + soup_date_free (date); +} + #define TIME_T 1099728547L #define TIME_T_STRING "1099728547" -static gboolean -check_ok (const char *strdate, SoupDate *date) +static void +check_ok_time_t (void) { - debug_printf (2, "%s\n", strdate); + SoupDate *date; - if (date && - date->year == 2004 && date->month == 11 && date->day == 6 && - date->hour == 8 && date->minute == 9 && date->second == 7) { - soup_date_free (date); - return TRUE; - } + date = soup_date_new_from_time_t (TIME_T); - debug_printf (1, " date parsing failed for '%s'.\n", strdate); - if (date) { - debug_printf (1, " got: %d %d %d - %d %d %d\n\n", - date->year, date->month, date->day, - date->hour, date->minute, date->second); - soup_date_free (date); - } - errors++; - return FALSE; + g_assert_cmpint (date->year, ==, 2004); + g_assert_cmpint (date->month, ==, 11); + g_assert_cmpint (date->day, ==, 6); + g_assert_cmpint (date->hour, ==, 8); + g_assert_cmpint (date->minute, ==, 9); + g_assert_cmpint (date->second, ==, 7); } -static const char *bad_dates[] = { +typedef struct { + const char *date; + const char *bugref; +} BadDate; + +static const BadDate bad_dates[] = { /* broken rfc1123-date */ - ", 06 Nov 2004 08:09:07 GMT", - "Sat, Nov 2004 08:09:07 GMT", - "Sat, 06 2004 08:09:07 GMT", - "Sat, 06 Nov 08:09:07 GMT", - "Sat, 06 Nov 2004 :09:07 GMT", - "Sat, 06 Nov 2004 09:07 GMT", - "Sat, 06 Nov 2004 08::07 GMT", - "Sat, 06 Nov 2004 08:09: GMT", + { ", 06 Nov 2004 08:09:07 GMT", NULL }, + { "Sat, Nov 2004 08:09:07 GMT", NULL }, + { "Sat, 06 2004 08:09:07 GMT", NULL }, + { "Sat, 06 Nov 08:09:07 GMT", NULL }, + { "Sat, 06 Nov 2004 :09:07 GMT", NULL }, + { "Sat, 06 Nov 2004 09:07 GMT", NULL }, + { "Sat, 06 Nov 2004 08::07 GMT", NULL }, + { "Sat, 06 Nov 2004 08:09: GMT", NULL }, /* broken rfc850-date */ - ", 06-Nov-04 08:09:07 GMT", - "Saturday, -Nov-04 08:09:07 GMT", - "Saturday, Nov-04 08:09:07 GMT", - "Saturday, 06-04 08:09:07 GMT", - "Saturday, 06--04 08:09:07 GMT", - "Saturday, 06-Nov- 08:09:07 GMT", - "Saturday, 06-Nov 08:09:07 GMT", - "Saturday, 06-Nov-04 :09:07 GMT", - "Saturday, 06-Nov-04 09:07 GMT", - "Saturday, 06-Nov-04 08::07 GMT", - "Saturday, 06-Nov-04 08:09: GMT", + { ", 06-Nov-04 08:09:07 GMT", NULL }, + { "Saturday, -Nov-04 08:09:07 GMT", NULL }, + { "Saturday, Nov-04 08:09:07 GMT", NULL }, + { "Saturday, 06-04 08:09:07 GMT", NULL }, + { "Saturday, 06--04 08:09:07 GMT", NULL }, + { "Saturday, 06-Nov- 08:09:07 GMT", NULL }, + { "Saturday, 06-Nov 08:09:07 GMT", NULL }, + { "Saturday, 06-Nov-04 :09:07 GMT", NULL }, + { "Saturday, 06-Nov-04 09:07 GMT", NULL }, + { "Saturday, 06-Nov-04 08::07 GMT", NULL }, + { "Saturday, 06-Nov-04 08:09: GMT", NULL }, /* broken asctime-date */ - "Nov 6 08:09:07 2004", - "Sat 6 08:09:07 2004", - "Sat Nov 08:09:07 2004", - "Sat Nov 6 :09:07 2004", - "Sat Nov 6 09:07 2004", - "Sat Nov 6 08::07 2004", - "Sat Nov 6 08:09: 2004", - "Sat Nov 6 08:09:07", - "Sat Nov 6 08:09:07 GMT 2004" + { "Nov 6 08:09:07 2004", NULL }, + { "Sat 6 08:09:07 2004", NULL }, + { "Sat Nov 08:09:07 2004", NULL }, + { "Sat Nov 6 :09:07 2004", NULL }, + { "Sat Nov 6 09:07 2004", NULL }, + { "Sat Nov 6 08::07 2004", NULL }, + { "Sat Nov 6 08:09: 2004", NULL }, + { "Sat Nov 6 08:09:07", NULL }, + { "Sat Nov 6 08:09:07 GMT 2004", NULL } }; static void -check_bad (const char *strdate, SoupDate *date) +check_bad (gconstpointer data) { - debug_printf (2, "%s\n", strdate); + BadDate *bad = (BadDate *)data; + SoupDate *date; - if (!date) - return; - errors++; + if (bad->bugref) + g_test_bug (bad->bugref); - debug_printf (1, " date parsing succeeded for '%s'!\n", strdate); - debug_printf (1, " got: %d %d %d - %d %d %d\n\n", - date->year, date->month, date->day, - date->hour, date->minute, date->second); - soup_date_free (date); + date = make_date (bad->date); + soup_test_assert (date == NULL, + "date parsing succeeded for '%s': %d %d %d - %d %d %d", + bad->date, + date->year, date->month, date->day, + date->hour, date->minute, date->second); + g_clear_pointer (&date, soup_date_free); } -static const struct conversion { +typedef struct { const char *source; const char *http, *cookie, *rfc2822, *compact, *full, *xmlrpc; -} conversions[] = { +} DateConversion; + +static const DateConversion conversions[] = { /* SOUP_DATE_HTTP */ { "Sat, 06 Nov 2004 08:09:07 GMT", @@ -288,71 +336,40 @@ static const struct conversion { }; static void -check_conversion (const struct conversion *conv) +check_conversion (gconstpointer data) { + const DateConversion *conv = data; SoupDate *date; char *str; - debug_printf (2, "%s\n", conv->source); date = make_date (conv->source); if (!date) { - debug_printf (1, " date parsing failed for '%s'.\n", conv->source); - errors++; + soup_test_assert (FALSE, "date parsing failed for '%s'.", conv->source); return; } str = soup_date_to_string (date, SOUP_DATE_HTTP); - if (!str || strcmp (str, conv->http) != 0) { - debug_printf (1, " conversion of '%s' to HTTP failed:\n" - " wanted: %s\n got: %s\n", - conv->source, conv->http, str ? str : "(null)"); - errors++; - } + g_assert_cmpstr (str, ==, conv->http); g_free (str); str = soup_date_to_string (date, SOUP_DATE_COOKIE); - if (!str || strcmp (str, conv->cookie) != 0) { - debug_printf (1, " conversion of '%s' to COOKIE failed:\n" - " wanted: %s\n got: %s\n", - conv->source, conv->cookie, str ? str : "(null)"); - errors++; - } + g_assert_cmpstr (str, ==, conv->cookie); g_free (str); str = soup_date_to_string (date, SOUP_DATE_RFC2822); - if (!str || strcmp (str, conv->rfc2822) != 0) { - debug_printf (1, " conversion of '%s' to RFC2822 failed:\n" - " wanted: %s\n got: %s\n", - conv->source, conv->rfc2822, str ? str : "(null)"); - errors++; - } + g_assert_cmpstr (str, ==, conv->rfc2822); g_free (str); str = soup_date_to_string (date, SOUP_DATE_ISO8601_COMPACT); - if (!str || strcmp (str, conv->compact) != 0) { - debug_printf (1, " conversion of '%s' to COMPACT failed:\n" - " wanted: %s\n got: %s\n", - conv->source, conv->compact, str ? str : "(null)"); - errors++; - } + g_assert_cmpstr (str, ==, conv->compact); g_free (str); str = soup_date_to_string (date, SOUP_DATE_ISO8601_FULL); - if (!str || strcmp (str, conv->full) != 0) { - debug_printf (1, " conversion of '%s' to FULL failed:\n" - " wanted: %s\n got: %s\n", - conv->source, conv->full, str ? str : "(null)"); - errors++; - } + g_assert_cmpstr (str, ==, conv->full); g_free (str); str = soup_date_to_string (date, SOUP_DATE_ISO8601_XMLRPC); - if (!str || strcmp (str, conv->xmlrpc) != 0) { - debug_printf (1, " conversion of '%s' to XMLRPC failed:\n" - " wanted: %s\n got: %s\n", - conv->source, conv->xmlrpc, str ? str : "(null)"); - errors++; - } + g_assert_cmpstr (str, ==, conv->xmlrpc); g_free (str); soup_date_free (date); @@ -361,27 +378,38 @@ check_conversion (const struct conversion *conv) int main (int argc, char **argv) { - int i; + int i, ret; + char *path; test_init (argc, argv, NULL); - debug_printf (1, "Good dates:\n"); - for (i = 0; i < G_N_ELEMENTS (good_dates); i++) - check_good (good_dates[i].format, good_dates[i].date); + for (i = 0; i < G_N_ELEMENTS (good_dates); i++) { + path = g_strdup_printf ("/date/good/%s", good_dates[i].date); + g_test_add_data_func (path, &good_dates[i], check_good); + g_free (path); + } + + for (i = 0; i < G_N_ELEMENTS (ok_dates); i++) { + path = g_strdup_printf ("/date/ok/%s", ok_dates[i].date); + g_test_add_data_func (path, &ok_dates[i], check_ok); + g_free (path); + } + g_test_add_func ("/date/ok/" TIME_T_STRING, check_ok_time_t); - debug_printf (1, "\nOK dates:\n"); - for (i = 0; i < G_N_ELEMENTS (ok_dates); i++) - check_ok (ok_dates[i], make_date (ok_dates[i])); - check_ok (TIME_T_STRING, soup_date_new_from_time_t (TIME_T)); + for (i = 0; i < G_N_ELEMENTS (bad_dates); i++) { + path = g_strdup_printf ("/date/bad/%s", bad_dates[i].date); + g_test_add_data_func (path, &bad_dates[i], check_bad); + g_free (path); + } - debug_printf (1, "\nBad dates:\n"); - for (i = 0; i < G_N_ELEMENTS (bad_dates); i++) - check_bad (bad_dates[i], make_date (bad_dates[i])); + for (i = 0; i < G_N_ELEMENTS (conversions); i++) { + path = g_strdup_printf ("/date/conversions/%s", conversions[i].source); + g_test_add_data_func (path, &conversions[i], check_conversion); + g_free (path); + } - debug_printf (1, "\nConversions:\n"); - for (i = 0; i < G_N_ELEMENTS (conversions); i++) - check_conversion (&conversions[i] ); + ret = g_test_run (); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/dns.c b/tests/dns.c deleted file mode 100644 index f5940647..00000000 --- a/tests/dns.c +++ /dev/null @@ -1,62 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ - -#include "test-utils.h" - -static GMainLoop *loop; -static int nlookups = 0; - -static void -resolve_callback (SoupAddress *addr, guint status, gpointer data) -{ - if (status == SOUP_STATUS_OK) { - g_print ("Name: %s\n", soup_address_get_name (addr)); - g_print ("Address: %s\n", soup_address_get_physical (addr)); - } else { - g_print ("Name: %s\n", soup_address_get_name (addr)); - g_print ("Error: %s\n", soup_status_get_phrase (status)); - } - g_print ("\n"); - - g_object_unref (addr); - - nlookups--; - if (nlookups == 0) - g_main_loop_quit (loop); -} - -static void -usage (void) -{ - g_printerr ("Usage: dns hostname ...\n"); - exit (1); -} - -int -main (int argc, char **argv) -{ - SoupAddress *addr; - int i; - - if (argc < 2) - usage (); - - g_type_init (); - - for (i = 1; i < argc; i++) { - addr = soup_address_new (argv[i], 0); - if (!addr) { - g_printerr ("Could not parse address %s\n", argv[1]); - exit (1); - } - - soup_address_resolve_async (addr, NULL, NULL, - resolve_callback, NULL); - nlookups++; - } - - loop = g_main_loop_new (NULL, TRUE); - g_main_loop_run (loop); - g_main_loop_unref (loop); - - return 0; -} diff --git a/tests/forms-test.c b/tests/forms-test.c index 1d254b12..3915b019 100644 --- a/tests/forms-test.c +++ b/tests/forms-test.c @@ -41,6 +41,7 @@ do_hello_test (int n, gboolean extra, const char *uri) GPtrArray *args; char *title_arg = NULL, *name_arg = NULL; char *str_stdout = NULL; + GError *error = NULL; debug_printf (1, "%2d. '%s' '%s'%s: ", n * 2 + (extra ? 2 : 1), tests[n].title ? tests[n].title : "(null)", @@ -49,6 +50,8 @@ do_hello_test (int n, gboolean extra, const char *uri) args = g_ptr_array_new (); g_ptr_array_add (args, "curl"); + g_ptr_array_add (args, "--noproxy"); + g_ptr_array_add (args, "*"); g_ptr_array_add (args, "-G"); if (tests[n].title) { title_arg = soup_form_encode ("title", tests[n].title, NULL); @@ -70,20 +73,12 @@ do_hello_test (int n, gboolean extra, const char *uri) if (g_spawn_sync (NULL, (char **)args->pdata, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, - &str_stdout, NULL, NULL, NULL)) { - if (str_stdout && !strcmp (str_stdout, tests[n].result)) - debug_printf (1, "OK!\n"); - else { - debug_printf (1, "WRONG!\n"); - debug_printf (1, " expected '%s', got '%s'\n", - tests[n].result, - str_stdout ? str_stdout : "(error)"); - errors++; - } + &str_stdout, NULL, NULL, &error)) { + g_assert_cmpstr (str_stdout, ==, tests[n].result); g_free (str_stdout); } else { - debug_printf (1, "ERROR!\n"); - errors++; + g_assert_no_error (error); + g_error_free (error); } g_ptr_array_free (args, TRUE); g_free (title_arg); @@ -91,30 +86,75 @@ do_hello_test (int n, gboolean extra, const char *uri) } static void -do_hello_tests (const char *uri) +do_hello_tests (gconstpointer uri) { int n; - debug_printf (1, "Hello tests (GET, application/x-www-form-urlencoded)\n"); +#ifndef HAVE_CURL + g_test_skip ("/usr/bin/curl is not available"); + return; +#endif + for (n = 0; n < G_N_ELEMENTS (tests); n++) { do_hello_test (n, FALSE, uri); do_hello_test (n, TRUE, uri); } } +#define MD5_TEST_FILE (g_test_get_filename (G_TEST_DIST, "index.txt", NULL)) +#define MD5_TEST_FILE_BASENAME "index.txt" +#define MD5_TEST_FILE_MIME_TYPE "text/plain" + +static char * +get_md5_data (char **contents, gsize *length) +{ + char *my_contents, *md5; + gsize my_length; + GError *error = NULL; + + if (!g_file_get_contents (MD5_TEST_FILE, &my_contents, &my_length, &error)) { + g_assert_no_error (error); + g_error_free (error); + return NULL; + } + + md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, my_contents, my_length); + + if (contents) + *contents = my_contents; + else + g_free (my_contents); + if (length) + *length = my_length; + + return md5; +} + static void -do_md5_test_curl (const char *uri, const char *file, const char *md5) +do_md5_test_curl (gconstpointer data) { + const char *uri = data; + char *md5; GPtrArray *args; char *file_arg, *str_stdout; + GError *error = NULL; - debug_printf (1, " via curl: "); +#ifndef HAVE_CURL + g_test_skip ("/usr/bin/curl is not available"); + return; +#endif + + md5 = get_md5_data (NULL, NULL); + if (!md5) + return; args = g_ptr_array_new (); g_ptr_array_add (args, "curl"); + g_ptr_array_add (args, "--noproxy"); + g_ptr_array_add (args, "*"); g_ptr_array_add (args, "-L"); g_ptr_array_add (args, "-F"); - file_arg = g_strdup_printf ("file=@%s", file); + file_arg = g_strdup_printf ("file=@%s", MD5_TEST_FILE); g_ptr_array_add (args, file_arg); g_ptr_array_add (args, "-F"); g_ptr_array_add (args, "fmt=txt"); @@ -125,37 +165,34 @@ do_md5_test_curl (const char *uri, const char *file, const char *md5) G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL, &str_stdout, NULL, NULL, NULL)) { - if (str_stdout && !strcmp (str_stdout, md5)) - debug_printf (1, "OK!\n"); - else { - debug_printf (1, "WRONG!\n"); - debug_printf (1, " expected '%s', got '%s'\n", - md5, str_stdout ? str_stdout : "(error)"); - errors++; - } + g_assert_cmpstr (str_stdout, ==, md5); g_free (str_stdout); } else { - debug_printf (1, "ERROR!\n"); - errors++; + g_assert_no_error (error); + g_error_free (error); } g_ptr_array_free (args, TRUE); g_free (file_arg); -} -#define MD5_TEST_FILE SRCDIR "/resources/home.gif" -#define MD5_TEST_FILE_BASENAME "home.gif" -#define MD5_TEST_FILE_MIME_TYPE "image/gif" + g_free (md5); +} static void -do_md5_test_libsoup (const char *uri, const char *contents, - gsize length, const char *md5) +do_md5_test_libsoup (gconstpointer data) { + const char *uri = data; + char *contents, *md5; + gsize length; SoupMultipart *multipart; SoupBuffer *buffer; SoupMessage *msg; SoupSession *session; - debug_printf (1, " via libsoup: "); + g_test_bug ("601640"); + + md5 = get_md5_data (&contents, &length); + if (!md5) + return; multipart = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART); buffer = soup_buffer_new (SOUP_MEMORY_COPY, contents, length); @@ -172,47 +209,16 @@ do_md5_test_libsoup (const char *uri, const char *contents, session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, "ERROR: Unexpected status %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } else if (strcmp (msg->response_body->data, md5) != 0) { - debug_printf (1, "ERROR: Incorrect response: expected '%s' got '%s'\n", - md5, msg->response_body->data); - errors++; - } else - debug_printf (1, "OK!\n"); + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + g_assert_cmpstr (msg->response_body->data, ==, md5); g_object_unref (msg); soup_test_session_abort_unref (session); -} - -static void -do_md5_tests (const char *uri) -{ - char *contents, *md5; - gsize length; - GError *error = NULL; - - debug_printf (1, "\nMD5 tests (POST, multipart/form-data)\n"); - - if (!g_file_get_contents (MD5_TEST_FILE, &contents, &length, &error)) { - debug_printf (1, " ERROR: Could not read " MD5_TEST_FILE ": %s\n", error->message); - g_error_free (error); - errors++; - return; - } - - md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, contents, length); - - do_md5_test_curl (uri, MD5_TEST_FILE, md5); - do_md5_test_libsoup (uri, contents, length, md5); g_free (contents); g_free (md5); } - static void do_form_decode_test (void) { @@ -220,7 +226,10 @@ do_form_decode_test (void) const gchar *value; gchar *tmp; - debug_printf (1, "\nDecode tests\n"); +#ifndef HAVE_CURL + g_test_skip ("/usr/bin/curl is not available"); + return; +#endif /* Test that the code handles multiple values with the same key. */ table = soup_form_decode ("foo=first&foo=second&foo=third"); @@ -232,11 +241,7 @@ do_form_decode_test (void) tmp = g_strdup ("other"); value = g_hash_table_lookup (table, "foo"); - if (g_strcmp0 (value, "third") != 0) { - debug_printf (1, " ERROR: expected '%s', got '%s'\n", - "third", value ? value : "(null)"); - errors++; - } + g_assert_cmpstr (value, ==, "third"); g_free (tmp); g_hash_table_destroy (table); @@ -417,6 +422,7 @@ main (int argc, char **argv) SoupServer *server; guint port; char *uri_str; + int ret = 0; test_init (argc, argv, no_test_entry); @@ -431,14 +437,16 @@ main (int argc, char **argv) if (run_tests) { uri_str = g_strdup_printf ("http://127.0.0.1:%u/hello", port); - do_hello_tests (uri_str); - g_free (uri_str); + g_test_add_data_func_full ("/forms/hello", uri_str, do_hello_tests, g_free); uri_str = g_strdup_printf ("http://127.0.0.1:%u/md5", port); - do_md5_tests (uri_str); + g_test_add_data_func_full ("/forms/md5/curl", g_strdup (uri_str), do_md5_test_curl, g_free); + g_test_add_data_func_full ("/forms/md5/libsoup", g_strdup (uri_str), do_md5_test_libsoup, g_free); g_free (uri_str); - do_form_decode_test (); + g_test_add_func ("/forms/decode", do_form_decode_test); + + ret = g_test_run (); } else { g_print ("Listening on port %d\n", port); g_main_loop_run (loop); @@ -449,5 +457,5 @@ main (int argc, char **argv) soup_test_server_quit_unref (server); if (run_tests) test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/get.c b/tests/get.c deleted file mode 100644 index 1d867155..00000000 --- a/tests/get.c +++ /dev/null @@ -1,189 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* - * Copyright (C) 2001-2003, Ximian, Inc. - */ - -#include "test-utils.h" -#include <stdio.h> - -static SoupSession *session; -static GMainLoop *loop; -static gboolean debug = FALSE, quiet = FALSE; -static const char *method; - -static void -get_url (const char *url) -{ - const char *name; - SoupMessage *msg; - const char *header; - - msg = soup_message_new (method, url); - soup_message_set_flags (msg, SOUP_MESSAGE_NO_REDIRECT); - - soup_session_send_message (session, msg); - - name = soup_message_get_uri (msg)->path; - - if (debug) { - SoupMessageHeadersIter iter; - const char *hname, *value; - char *path = soup_uri_to_string (soup_message_get_uri (msg), TRUE); - - g_print ("%s %s HTTP/1.%d\n", method, path, - soup_message_get_http_version (msg)); - soup_message_headers_iter_init (&iter, msg->request_headers); - while (soup_message_headers_iter_next (&iter, &hname, &value)) - g_print ("%s: %s\r\n", hname, value); - g_print ("\n"); - - g_print ("HTTP/1.%d %d %s\n", - soup_message_get_http_version (msg), - msg->status_code, msg->reason_phrase); - - soup_message_headers_iter_init (&iter, msg->response_headers); - while (soup_message_headers_iter_next (&iter, &hname, &value)) - g_print ("%s: %s\r\n", hname, value); - g_print ("\n"); - } else if (msg->status_code == SOUP_STATUS_SSL_FAILED) { - GTlsCertificateFlags flags; - - if (soup_message_get_https_status (msg, NULL, &flags)) - g_print ("%s: %d %s (0x%x)\n", name, msg->status_code, msg->reason_phrase, flags); - else - g_print ("%s: %d %s (no handshake status)\n", name, msg->status_code, msg->reason_phrase); - } else if (!quiet || SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) - g_print ("%s: %d %s\n", name, msg->status_code, msg->reason_phrase); - - if (SOUP_STATUS_IS_REDIRECTION (msg->status_code)) { - header = soup_message_headers_get_one (msg->response_headers, - "Location"); - if (header) { - SoupURI *uri; - char *uri_string; - - if (!debug && !quiet) - g_print (" -> %s\n", header); - - uri = soup_uri_new_with_base (soup_message_get_uri (msg), header); - uri_string = soup_uri_to_string (uri, FALSE); - get_url (uri_string); - g_free (uri_string); - soup_uri_free (uri); - } - } else if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - fwrite (msg->response_body->data, 1, - msg->response_body->length, stdout); - } -} - -static void -usage (void) -{ - g_printerr ("Usage: get [-c CAfile] [-p proxy URL] [-h] [-d] URL\n"); - exit (1); -} - -int -main (int argc, char **argv) -{ - const char *cafile = NULL, *url; - SoupURI *proxy = NULL, *parsed; - gboolean synchronous = FALSE, ntlm = FALSE; - int opt; - - g_type_init (); - - method = SOUP_METHOD_GET; - - while ((opt = getopt (argc, argv, "c:dhnp:qs")) != -1) { - switch (opt) { - case 'c': - cafile = optarg; - break; - - case 'd': - debug = TRUE; - break; - - case 'h': - method = SOUP_METHOD_HEAD; - debug = TRUE; - break; - - case 'n': - ntlm = TRUE; - break; - - case 'p': - proxy = soup_uri_new (optarg); - if (!proxy) { - g_printerr ("Could not parse %s as URI\n", - optarg); - exit (1); - } - break; - - case 'q': - quiet = TRUE; - break; - - case 's': - synchronous = TRUE; - break; - - case '?': - usage (); - break; - } - } - argc -= optind; - argv += optind; - - if (argc != 1) - usage (); - url = argv[0]; - parsed = soup_uri_new (url); - if (!parsed) { - g_printerr ("Could not parse '%s' as a URL\n", url); - exit (1); - } - soup_uri_free (parsed); - - if (synchronous) { - session = soup_session_sync_new_with_options ( - SOUP_SESSION_SSL_CA_FILE, cafile, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR, - SOUP_SESSION_USER_AGENT, "get ", - SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE, - SOUP_SESSION_USE_NTLM, ntlm, - NULL); - } else { - session = soup_session_async_new_with_options ( - SOUP_SESSION_SSL_CA_FILE, cafile, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_CONTENT_DECODER, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_COOKIE_JAR, - SOUP_SESSION_USER_AGENT, "get ", - SOUP_SESSION_ACCEPT_LANGUAGE_AUTO, TRUE, - SOUP_SESSION_USE_NTLM, ntlm, - NULL); - } - - if (proxy) { - g_object_set (G_OBJECT (session), - SOUP_SESSION_PROXY_URI, proxy, - NULL); - } else - soup_session_add_feature_by_type (session, SOUP_TYPE_PROXY_RESOLVER_DEFAULT); - - if (!synchronous) - loop = g_main_loop_new (NULL, TRUE); - - get_url (url); - - if (!synchronous) - g_main_loop_unref (loop); - - return 0; -} diff --git a/tests/header-parsing.c b/tests/header-parsing.c index e57d7d83..fdc7885d 100644 --- a/tests/header-parsing.c +++ b/tests/header-parsing.c @@ -8,6 +8,7 @@ typedef struct { static struct RequestTest { const char *description; + const char *bugref; const char *request; int length; guint status; @@ -19,14 +20,14 @@ static struct RequestTest { /*** VALID REQUESTS ***/ /**********************/ - { "HTTP 1.0 request with no headers", + { "HTTP 1.0 request with no headers", NULL, "GET / HTTP/1.0\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_0, { { NULL } } }, - { "Req w/ 1 header", + { "Req w/ 1 header", NULL, "GET / HTTP/1.1\r\nHost: example.com\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -35,7 +36,7 @@ static struct RequestTest { } }, - { "Req w/ 1 header, no leading whitespace", + { "Req w/ 1 header, no leading whitespace", NULL, "GET / HTTP/1.1\r\nHost:example.com\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -44,7 +45,7 @@ static struct RequestTest { } }, - { "Req w/ 1 header including trailing whitespace", + { "Req w/ 1 header including trailing whitespace", NULL, "GET / HTTP/1.1\r\nHost: example.com \r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -53,7 +54,7 @@ static struct RequestTest { } }, - { "Req w/ 1 header, wrapped", + { "Req w/ 1 header, wrapped", NULL, "GET / HTTP/1.1\r\nFoo: bar\r\n baz\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -62,7 +63,7 @@ static struct RequestTest { } }, - { "Req w/ 1 header, wrapped with additional whitespace", + { "Req w/ 1 header, wrapped with additional whitespace", NULL, "GET / HTTP/1.1\r\nFoo: bar \r\n baz\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -71,7 +72,7 @@ static struct RequestTest { } }, - { "Req w/ 1 header, wrapped with tab", + { "Req w/ 1 header, wrapped with tab", NULL, "GET / HTTP/1.1\r\nFoo: bar\r\n\tbaz\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -80,7 +81,7 @@ static struct RequestTest { } }, - { "Req w/ 1 header, wrapped before value", + { "Req w/ 1 header, wrapped before value", NULL, "GET / HTTP/1.1\r\nFoo:\r\n bar baz\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -89,7 +90,7 @@ static struct RequestTest { } }, - { "Req w/ 1 header with empty value", + { "Req w/ 1 header with empty value", NULL, "GET / HTTP/1.1\r\nHost:\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -98,7 +99,7 @@ static struct RequestTest { } }, - { "Req w/ 2 headers", + { "Req w/ 2 headers", NULL, "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -108,7 +109,7 @@ static struct RequestTest { } }, - { "Req w/ 3 headers", + { "Req w/ 3 headers", NULL, "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\nBlah: blah\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -119,7 +120,7 @@ static struct RequestTest { } }, - { "Req w/ 3 headers, 1st wrapped", + { "Req w/ 3 headers, 1st wrapped", NULL, "GET / HTTP/1.1\r\nFoo: bar\r\n baz\r\nConnection: close\r\nBlah: blah\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -130,7 +131,7 @@ static struct RequestTest { } }, - { "Req w/ 3 headers, 2nd wrapped", + { "Req w/ 3 headers, 2nd wrapped", NULL, "GET / HTTP/1.1\r\nConnection: close\r\nBlah: blah\r\nFoo: bar\r\n baz\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -141,7 +142,7 @@ static struct RequestTest { } }, - { "Req w/ 3 headers, 3rd wrapped", + { "Req w/ 3 headers, 3rd wrapped", NULL, "GET / HTTP/1.1\r\nConnection: close\r\nBlah: blah\r\nFoo: bar\r\n baz\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -152,7 +153,7 @@ static struct RequestTest { } }, - { "Req w/ same header multiple times", + { "Req w/ same header multiple times", NULL, "GET / HTTP/1.1\r\nFoo: bar\r\nFoo: baz\r\nFoo: quux\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -161,7 +162,7 @@ static struct RequestTest { } }, - { "Connection header on HTTP/1.0 message", + { "Connection header on HTTP/1.0 message", NULL, "GET / HTTP/1.0\r\nFoo: bar\r\nConnection: Bar, Quux\r\nBar: baz\r\nQuux: foo\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_0, @@ -171,14 +172,14 @@ static struct RequestTest { } }, - { "GET with full URI", + { "GET with full URI", "667637", "GET http://example.com HTTP/1.1\r\n", -1, SOUP_STATUS_OK, "GET", "http://example.com", SOUP_HTTP_1_1, { { NULL } } }, - { "GET with full URI in upper-case", + { "GET with full URI in upper-case", "667637", "GET HTTP://example.com HTTP/1.1\r\n", -1, SOUP_STATUS_OK, "GET", "HTTP://example.com", SOUP_HTTP_1_1, @@ -188,7 +189,7 @@ static struct RequestTest { /* It's better for this to be passed through: this means a SoupServer * could implement ftp-over-http proxying, for instance */ - { "GET with full URI of unrecognised scheme", + { "GET with full URI of unrecognised scheme", "667637", "GET AbOuT: HTTP/1.1\r\n", -1, SOUP_STATUS_OK, "GET", "AbOuT:", SOUP_HTTP_1_1, @@ -201,7 +202,7 @@ static struct RequestTest { /* RFC 2616 section 4.1 says we SHOULD accept this */ - { "Spurious leading CRLF", + { "Spurious leading CRLF", NULL, "\r\nGET / HTTP/1.1\r\nHost: example.com\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -212,7 +213,7 @@ static struct RequestTest { /* RFC 2616 section 3.1 says we MUST accept this */ - { "HTTP/01.01 request", + { "HTTP/01.01 request", NULL, "GET / HTTP/01.01\r\nHost: example.com\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -223,7 +224,7 @@ static struct RequestTest { /* RFC 2616 section 19.3 says we SHOULD accept these */ - { "LF instead of CRLF after header", + { "LF instead of CRLF after header", NULL, "GET / HTTP/1.1\r\nHost: example.com\nConnection: close\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -233,7 +234,7 @@ static struct RequestTest { } }, - { "LF instead of CRLF after Request-Line", + { "LF instead of CRLF after Request-Line", NULL, "GET / HTTP/1.1\nHost: example.com\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -242,7 +243,7 @@ static struct RequestTest { } }, - { "Mixed CRLF/LF", + { "Mixed CRLF/LF", "666316", "GET / HTTP/1.1\r\na: b\r\nc: d\ne: f\r\ng: h\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -254,7 +255,7 @@ static struct RequestTest { } }, - { "Req w/ incorrect whitespace in Request-Line", + { "Req w/ incorrect whitespace in Request-Line", NULL, "GET /\tHTTP/1.1\r\nHost: example.com\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -263,7 +264,7 @@ static struct RequestTest { } }, - { "Req w/ incorrect whitespace after Request-Line", + { "Req w/ incorrect whitespace after Request-Line", "475169", "GET / HTTP/1.1 \r\nHost: example.com\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -274,10 +275,9 @@ static struct RequestTest { /* If the request/status line is parseable, then we * just ignore any invalid-looking headers after that. - * (qv bug 579318). */ - { "Req w/ mangled header", + { "Req w/ mangled header", "579318", "GET / HTTP/1.1\r\nHost: example.com\r\nFoo one\r\nBar: two\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -287,7 +287,7 @@ static struct RequestTest { } }, - { "First header line is continuation", + { "First header line is continuation", "666316", "GET / HTTP/1.1\r\n b\r\nHost: example.com\r\nc: d\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -297,7 +297,7 @@ static struct RequestTest { } }, - { "Zero-length header name", + { "Zero-length header name", "666316", "GET / HTTP/1.1\r\na: b\r\n: example.com\r\nc: d\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -307,7 +307,7 @@ static struct RequestTest { } }, - { "CR in header name", + { "CR in header name", "666316", "GET / HTTP/1.1\r\na: b\r\na\rb: cd\r\nx\r: y\r\n\rz: w\r\nc: d\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -317,7 +317,7 @@ static struct RequestTest { } }, - { "CR in header value", + { "CR in header value", "666316", "GET / HTTP/1.1\r\na: b\r\nHost: example\rcom\r\np: \rq\r\ns: t\r\r\nc: d\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -330,7 +330,7 @@ static struct RequestTest { } }, - { "Tab in header name", + { "Tab in header name", "666316", "GET / HTTP/1.1\r\na: b\r\na\tb: cd\r\nx\t: y\r\np: q\r\n\tz: w\r\nc: d\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -345,7 +345,7 @@ static struct RequestTest { } }, - { "Tab in header value", + { "Tab in header value", "666316", "GET / HTTP/1.1\r\na: b\r\nab: c\td\r\nx: \ty\r\nz: w\t\r\nc: d\r\n", -1, SOUP_STATUS_OK, "GET", "/", SOUP_HTTP_1_1, @@ -362,84 +362,84 @@ static struct RequestTest { /*** INVALID REQUESTS ***/ /************************/ - { "HTTP 0.9 request; not supported", + { "HTTP 0.9 request; not supported", NULL, "GET /\r\n", -1, SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "HTTP 1.2 request (no such thing)", + { "HTTP 1.2 request (no such thing)", NULL, "GET / HTTP/1.2\r\n", -1, SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED, NULL, NULL, -1, { { NULL } } }, - { "HTTP 2000 request (no such thing)", + { "HTTP 2000 request (no such thing)", NULL, "GET / HTTP/2000.0\r\n", -1, SOUP_STATUS_HTTP_VERSION_NOT_SUPPORTED, NULL, NULL, -1, { { NULL } } }, - { "Non-HTTP request", + { "Non-HTTP request", NULL, "GET / SOUP/1.1\r\nHost: example.com\r\n", -1, SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "Junk after Request-Line", + { "Junk after Request-Line", NULL, "GET / HTTP/1.1 blah\r\nHost: example.com\r\n", -1, SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "NUL in Method", + { "NUL in Method", NULL, "G\x00T / HTTP/1.1\r\nHost: example.com\r\n", 37, SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "NUL at beginning of Method", + { "NUL at beginning of Method", "666316", "\x00 / HTTP/1.1\r\nHost: example.com\r\n", 35, SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "NUL in Path", + { "NUL in Path", NULL, "GET /\x00 HTTP/1.1\r\nHost: example.com\r\n", 38, SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "NUL in header name", + { "NUL in header name", "666316", "GET / HTTP/1.1\r\n\x00: silly\r\n", 37, SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "NUL in header value", + { "NUL in header value", NULL, "GET / HTTP/1.1\r\nHost: example\x00com\r\n", 37, SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "No terminating CRLF", + { "No terminating CRLF", NULL, "GET / HTTP/1.1\r\nHost: example.com", -1, SOUP_STATUS_BAD_REQUEST, NULL, NULL, -1, { { NULL } } }, - { "Unrecognized expectation", + { "Unrecognized expectation", NULL, "GET / HTTP/1.1\r\nHost: example.com\r\nExpect: the-impossible\r\n", -1, SOUP_STATUS_EXPECTATION_FAILED, NULL, NULL, -1, @@ -450,36 +450,37 @@ static const int num_reqtests = G_N_ELEMENTS (reqtests); static struct ResponseTest { const char *description; + const char *bugref; const char *response; int length; SoupHTTPVersion version; guint status_code; const char *reason_phrase; - Header headers[4]; + Header headers[10]; } resptests[] = { /***********************/ /*** VALID RESPONSES ***/ /***********************/ - { "HTTP 1.0 response w/ no headers", + { "HTTP 1.0 response w/ no headers", NULL, "HTTP/1.0 200 ok\r\n", -1, SOUP_HTTP_1_0, SOUP_STATUS_OK, "ok", { { NULL } } }, - { "HTTP 1.1 response w/ no headers", + { "HTTP 1.1 response w/ no headers", NULL, "HTTP/1.1 200 ok\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { NULL } } }, - { "Response w/ multi-word Reason-Phrase", + { "Response w/ multi-word Reason-Phrase", NULL, "HTTP/1.1 400 bad request\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_BAD_REQUEST, "bad request", { { NULL } } }, - { "Response w/ 1 header", + { "Response w/ 1 header", NULL, "HTTP/1.1 200 ok\r\nFoo: bar\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { "Foo", "bar" }, @@ -487,7 +488,7 @@ static struct ResponseTest { } }, - { "Response w/ 2 headers", + { "Response w/ 2 headers", NULL, "HTTP/1.1 200 ok\r\nFoo: bar\r\nBaz: quux\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { "Foo", "bar" }, @@ -496,7 +497,7 @@ static struct ResponseTest { } }, - { "Response w/ same header multiple times", + { "Response w/ same header multiple times", NULL, "HTTP/1.1 200 ok\r\nFoo: bar\r\nFoo: baz\r\nFoo: quux\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { "Foo", "bar, baz, quux" }, @@ -504,7 +505,7 @@ static struct ResponseTest { } }, - { "Response w/ no reason phrase", + { "Response w/ no reason phrase", NULL, "HTTP/1.1 200 \r\nFoo: bar\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "", { { "Foo", "bar" }, @@ -512,7 +513,7 @@ static struct ResponseTest { } }, - { "Connection header on HTTP/1.0 message", + { "Connection header on HTTP/1.0 message", NULL, "HTTP/1.0 200 ok\r\nFoo: bar\r\nConnection: Bar\r\nBar: quux\r\n", -1, SOUP_HTTP_1_0, SOUP_STATUS_OK, "ok", { { "Foo", "bar" }, @@ -521,13 +522,26 @@ static struct ResponseTest { } }, + /* Tests from Cockpit */ + + { "Response w/ 3 headers, check case-insensitivity", "722341", + "HTTP/1.0 200 ok\r\nHeader1: value3\r\nHeader2: field\r\nHead3: Another \r\n", -1, + SOUP_HTTP_1_0, SOUP_STATUS_OK, "ok", + { { "header1", "value3" }, + { "Header2", "field" }, + { "hEAD3", "Another" }, + { "Something else", NULL }, + { NULL } + } + }, + /*****************************/ /*** RECOVERABLE RESPONSES ***/ /*****************************/ /* RFC 2616 section 3.1 says we MUST accept this */ - { "HTTP/01.01 response", + { "HTTP/01.01 response", NULL, "HTTP/01.01 200 ok\r\nFoo: bar\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { "Foo", "bar" }, @@ -537,7 +551,7 @@ static struct ResponseTest { /* RFC 2616 section 19.3 says we SHOULD accept these */ - { "Response w/ LF instead of CRLF after Status-Line", + { "Response w/ LF instead of CRLF after Status-Line", NULL, "HTTP/1.1 200 ok\nFoo: bar\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { "Foo", "bar" }, @@ -545,7 +559,7 @@ static struct ResponseTest { } }, - { "Response w/ incorrect spacing in Status-Line", + { "Response w/ incorrect spacing in Status-Line", NULL, "HTTP/1.1 200\tok\r\nFoo: bar\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { "Foo", "bar" }, @@ -553,7 +567,7 @@ static struct ResponseTest { } }, - { "Response w/ no reason phrase or preceding SP", + { "Response w/ no reason phrase or preceding SP", NULL, "HTTP/1.1 200\r\nFoo: bar\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "", { { "Foo", "bar" }, @@ -561,7 +575,7 @@ static struct ResponseTest { } }, - { "Response w/ no whitespace after status code", + { "Response w/ no whitespace after status code", NULL, "HTTP/1.1 200ok\r\nFoo: bar\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { "Foo", "bar" }, @@ -570,7 +584,7 @@ static struct ResponseTest { }, /* Shoutcast support */ - { "Shoutcast server not-quite-HTTP", + { "Shoutcast server not-quite-HTTP", "502325", "ICY 200 OK\r\nFoo: bar\r\n", -1, SOUP_HTTP_1_0, SOUP_STATUS_OK, "OK", { { "Foo", "bar" }, @@ -578,8 +592,7 @@ static struct ResponseTest { } }, - /* qv bug 579318, do_bad_header_tests() below */ - { "Response w/ mangled header", + { "Response w/ mangled header", "579318", "HTTP/1.1 200 ok\r\nFoo: one\r\nBar two:2\r\nBaz: three\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { "Foo", "one" }, @@ -588,8 +601,7 @@ static struct ResponseTest { } }, - /* qv bug 602863 */ - { "HTTP 1.1 response with leading line break", + { "HTTP 1.1 response with leading line break", "602863", "\nHTTP/1.1 200 ok\r\nFoo: bar\r\n", -1, SOUP_HTTP_1_1, SOUP_STATUS_OK, "ok", { { "Foo", "bar" }, @@ -600,79 +612,79 @@ static struct ResponseTest { /*** INVALID RESPONSES ***/ /*************************/ - { "Invalid HTTP version", + { "Invalid HTTP version", NULL, "HTTP/1.2 200 OK\r\nFoo: bar\r\n", -1, -1, 0, NULL, { { NULL } } }, - { "Non-HTTP response", + { "Non-HTTP response", NULL, "SOUP/1.1 200 OK\r\nFoo: bar\r\n", -1, -1, 0, NULL, { { NULL } } }, - { "Non-numeric status code", + { "Non-numeric status code", NULL, "HTTP/1.1 XXX OK\r\nFoo: bar\r\n", -1, -1, 0, NULL, { { NULL } } }, - { "No status code", + { "No status code", NULL, "HTTP/1.1 OK\r\nFoo: bar\r\n", -1, -1, 0, NULL, { { NULL } } }, - { "One-digit status code", + { "One-digit status code", NULL, "HTTP/1.1 2 OK\r\nFoo: bar\r\n", -1, -1, 0, NULL, { { NULL } } }, - { "Two-digit status code", + { "Two-digit status code", NULL, "HTTP/1.1 20 OK\r\nFoo: bar\r\n", -1, -1, 0, NULL, { { NULL } } }, - { "Four-digit status code", + { "Four-digit status code", NULL, "HTTP/1.1 2000 OK\r\nFoo: bar\r\n", -1, -1, 0, NULL, { { NULL } } }, - { "Status code < 100", + { "Status code < 100", NULL, "HTTP/1.1 001 OK\r\nFoo: bar\r\n", -1, -1, 0, NULL, { { NULL } } }, - { "Status code > 599", + { "Status code > 599", NULL, "HTTP/1.1 600 OK\r\nFoo: bar\r\n", -1, -1, 0, NULL, { { NULL } } }, - { "NUL at start", + { "NUL at start", "666316", "\x00HTTP/1.1 200 OK\r\nFoo: bar\r\n", 28, -1, 0, NULL, { { NULL } } }, - { "NUL in Reason Phrase", + { "NUL in Reason Phrase", NULL, "HTTP/1.1 200 O\x00K\r\nFoo: bar\r\n", 28, -1, 0, NULL, { { NULL } } }, - { "NUL in header name", + { "NUL in header name", NULL, "HTTP/1.1 200 OK\r\nF\x00oo: bar\r\n", 28, -1, 0, NULL, { { NULL } } }, - { "NUL in header value", + { "NUL in header value", NULL, "HTTP/1.1 200 OK\r\nFoo: b\x00ar\r\n", 28, -1, 0, NULL, { { NULL } } @@ -704,18 +716,11 @@ static struct QValueTest { static const int num_qvaluetests = G_N_ELEMENTS (qvaluetests); static void -print_header (const char *name, const char *value, gpointer data) -{ - debug_printf (1, " '%s': '%s'\n", name, value); -} - -static gboolean check_headers (Header *headers, SoupMessageHeaders *hdrs) { GSList *header_names, *h; SoupMessageHeadersIter iter; const char *name, *value; - gboolean ok = TRUE; int i; header_names = NULL; @@ -727,36 +732,33 @@ check_headers (Header *headers, SoupMessageHeaders *hdrs) } for (i = 0, h = header_names; headers[i].name && h; i++, h = h->next) { - if (strcmp (h->data, headers[i].name) != 0) { - ok = FALSE; - break; - } + g_assert (g_ascii_strcasecmp (h->data, headers[i].name) == 0); + value = soup_message_headers_get_list (hdrs, headers[i].name); - if (strcmp (value, headers[i].value) != 0) { - ok = FALSE; - break; - } + g_assert_cmpstr (value, ==, headers[i].value); + } + /* If we have remaining fields to check, they should return NULL */ + for (; headers[i].name; i++) { + value = soup_message_headers_get_list (hdrs, headers[i].name); + g_assert_null (value); } - if (headers[i].name || h) - ok = FALSE; + g_assert_null (headers[i].name); + g_assert_null (h); + g_slist_free (header_names); - return ok; } static void do_request_tests (void) { - int i, len, h; + int i, len; char *method, *path; SoupHTTPVersion version; SoupMessageHeaders *headers; guint status; - debug_printf (1, "Request tests\n"); for (i = 0; i < num_reqtests; i++) { - gboolean ok = TRUE; - - debug_printf (1, "%2d. %s (%s): ", i + 1, reqtests[i].description, + debug_printf (1, "%2d. %s (%s)\n", i + 1, reqtests[i].description, soup_status_get_phrase (reqtests[i].status)); headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST); @@ -769,71 +771,32 @@ do_request_tests (void) status = soup_headers_parse_request (reqtests[i].request, len, headers, &method, &path, &version); + g_assert_cmpint (status, ==, reqtests[i].status); if (SOUP_STATUS_IS_SUCCESSFUL (status)) { - if ((reqtests[i].method && strcmp (reqtests[i].method, method) != 0) || !reqtests[i].method) - ok = FALSE; - if ((reqtests[i].path && strcmp (reqtests[i].path, path) != 0) || !reqtests[i].path) - ok = FALSE; - if (reqtests[i].version != version) - ok = FALSE; - - if (!check_headers (reqtests[i].headers, headers)) - ok = FALSE; - } else { - if (status != reqtests[i].status) - ok = FALSE; - } + g_assert_cmpstr (method, ==, reqtests[i].method); + g_assert_cmpstr (path, ==, reqtests[i].path); + g_assert_cmpint (version, ==, reqtests[i].version); - if (ok) - debug_printf (1, "OK!\n"); - else { - debug_printf (1, "BAD!\n"); - errors++; - if (reqtests[i].method) { - debug_printf (1, " expected: '%s' '%s' 'HTTP/1.%d'\n", - reqtests[i].method, - reqtests[i].path, - reqtests[i].version); - for (h = 0; reqtests[i].headers[h].name; h++) { - debug_printf (1, " '%s': '%s'\n", - reqtests[i].headers[h].name, - reqtests[i].headers[h].value); - } - } else { - debug_printf (1, " expected: %s\n", - soup_status_get_phrase (reqtests[i].status)); - } - if (method) { - debug_printf (1, " got: '%s' '%s' 'HTTP/1.%d'\n", - method, path, version); - soup_message_headers_foreach (headers, print_header, NULL); - } else { - debug_printf (1, " got: %s\n", - soup_status_get_phrase (status)); - } + check_headers (reqtests[i].headers, headers); } g_free (method); g_free (path); soup_message_headers_free (headers); } - debug_printf (1, "\n"); } static void do_response_tests (void) { - int i, len, h; + int i, len; guint status_code; char *reason_phrase; SoupHTTPVersion version; SoupMessageHeaders *headers; - debug_printf (1, "Response tests\n"); for (i = 0; i < num_resptests; i++) { - gboolean ok = TRUE; - - debug_printf (1, "%2d. %s (%s): ", i + 1, resptests[i].description, + debug_printf (1, "%2d. %s (%s)\n", i + 1, resptests[i].description, resptests[i].reason_phrase ? "should parse" : "should NOT parse"); headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE); @@ -846,49 +809,17 @@ do_response_tests (void) if (soup_headers_parse_response (resptests[i].response, len, headers, &version, &status_code, &reason_phrase)) { - if (resptests[i].version != version) - ok = FALSE; - if (resptests[i].status_code != status_code) - ok = FALSE; - if ((resptests[i].reason_phrase && strcmp (resptests[i].reason_phrase, reason_phrase) != 0) || !resptests[i].reason_phrase) - ok = FALSE; - - if (!check_headers (resptests[i].headers, headers)) - ok = FALSE; - } else { - if (resptests[i].reason_phrase) - ok = FALSE; - } + g_assert_cmpint (version, ==, resptests[i].version); + g_assert_cmpint (status_code, ==, resptests[i].status_code); + g_assert_cmpstr (reason_phrase, ==, resptests[i].reason_phrase); - if (ok) - debug_printf (1, "OK!\n"); - else { - debug_printf (1, "BAD!\n"); - errors++; - if (resptests[i].reason_phrase) { - debug_printf (1, " expected: 'HTTP/1.%d' '%03d' '%s'\n", - resptests[i].version, - resptests[i].status_code, - resptests[i].reason_phrase); - for (h = 0; resptests[i].headers[h].name; h++) { - debug_printf (1, " '%s': '%s'\n", - resptests[i].headers[h].name, - resptests[i].headers[h].value); - } - } else - debug_printf (1, " expected: parse error\n"); - if (reason_phrase) { - debug_printf (1, " got: 'HTTP/1.%d' '%03d' '%s'\n", - version, status_code, reason_phrase); - soup_message_headers_foreach (headers, print_header, NULL); - } else - debug_printf (1, " got: parse error\n"); - } + check_headers (resptests[i].headers, headers); + } else + g_assert_null (resptests[i].reason_phrase); g_free (reason_phrase); soup_message_headers_free (headers); } - debug_printf (1, "\n"); } static void @@ -896,9 +827,7 @@ do_qvalue_tests (void) { int i, j; GSList *acceptable, *unacceptable, *iter; - gboolean wrong; - debug_printf (1, "qvalue tests\n"); for (i = 0; i < num_qvaluetests; i++) { debug_printf (1, "%2d. %s:\n", i + 1, qvaluetests[i].header_value); @@ -907,48 +836,26 @@ do_qvalue_tests (void) &unacceptable); debug_printf (1, " acceptable: "); - wrong = FALSE; if (acceptable) { for (iter = acceptable, j = 0; iter; iter = iter->next, j++) { debug_printf (1, "%s ", (char *)iter->data); - if (!qvaluetests[i].acceptable[j] || - strcmp (iter->data, qvaluetests[i].acceptable[j]) != 0) - wrong = TRUE; + g_assert_cmpstr (iter->data, ==, qvaluetests[i].acceptable[j]); } debug_printf (1, "\n"); soup_header_free_list (acceptable); } else debug_printf (1, "(none)\n"); - if (wrong) { - debug_printf (1, " WRONG! expected: "); - for (j = 0; qvaluetests[i].acceptable[j]; j++) - debug_printf (1, "%s ", qvaluetests[i].acceptable[j]); - debug_printf (1, "\n"); - errors++; - } debug_printf (1, " unacceptable: "); - wrong = FALSE; if (unacceptable) { for (iter = unacceptable, j = 0; iter; iter = iter->next, j++) { debug_printf (1, "%s ", (char *)iter->data); - if (!qvaluetests[i].unacceptable[j] || - strcmp (iter->data, qvaluetests[i].unacceptable[j]) != 0) - wrong = TRUE; + g_assert_cmpstr (iter->data, ==, qvaluetests[i].unacceptable[j]); } debug_printf (1, "\n"); soup_header_free_list (unacceptable); } else debug_printf (1, "(none)\n"); - if (wrong) { - debug_printf (1, " WRONG! expected: "); - for (j = 0; qvaluetests[i].unacceptable[j]; j++) - debug_printf (1, "%s ", qvaluetests[i].unacceptable[j]); - debug_printf (1, "\n"); - errors++; - } - - debug_printf (1, "\n"); } } @@ -972,8 +879,6 @@ do_content_disposition_tests (void) SoupMultipart *multipart; SoupMessageBody *body; - debug_printf (1, "Content-Disposition tests\n"); - hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART); params = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (params, "filename", RFC5987_TEST_FILENAME); @@ -981,13 +886,7 @@ do_content_disposition_tests (void) g_hash_table_destroy (params); header = soup_message_headers_get_one (hdrs, "Content-Disposition"); - if (!strcmp (header, RFC5987_TEST_HEADER_ENCODED)) - debug_printf (1, " encoded OK\n"); - else { - debug_printf (1, " encoding FAILED!\n expected: %s\n got: %s\n", - RFC5987_TEST_HEADER_ENCODED, header); - errors++; - } + g_assert_cmpstr (header, ==, RFC5987_TEST_HEADER_ENCODED); /* UTF-8 decoding */ soup_message_headers_clear (hdrs); @@ -996,22 +895,13 @@ do_content_disposition_tests (void) if (!soup_message_headers_get_content_disposition (hdrs, &disposition, ¶ms)) { - debug_printf (1, " UTF-8 decoding FAILED!\n could not parse\n"); - errors++; + soup_test_assert (FALSE, "UTF-8 decoding FAILED"); return; } g_free (disposition); filename = g_hash_table_lookup (params, "filename"); - if (!filename) { - debug_printf (1, " UTF-8 decoding FAILED!\n could not find filename\n"); - errors++; - } else if (strcmp (filename, RFC5987_TEST_FILENAME) != 0) { - debug_printf (1, " UTF-8 decoding FAILED!\n expected: %s\n got: %s\n", - RFC5987_TEST_FILENAME, filename); - errors++; - } else - debug_printf (1, " UTF-8 decoded OK\n"); + g_assert_cmpstr (filename, ==, RFC5987_TEST_FILENAME); g_hash_table_destroy (params); /* ISO-8859-1 decoding */ @@ -1021,22 +911,13 @@ do_content_disposition_tests (void) if (!soup_message_headers_get_content_disposition (hdrs, &disposition, ¶ms)) { - debug_printf (1, " iso-8859-1 decoding FAILED!\n could not parse\n"); - errors++; + soup_test_assert (FALSE, "iso-8859-1 decoding FAILED"); return; } g_free (disposition); filename = g_hash_table_lookup (params, "filename"); - if (!filename) { - debug_printf (1, " iso-8859-1 decoding FAILED!\n could not find filename\n"); - errors++; - } else if (strcmp (filename, RFC5987_TEST_FILENAME) != 0) { - debug_printf (1, " iso-8859-1 decoding FAILED!\n expected: %s\n got: %s\n", - RFC5987_TEST_FILENAME, filename); - errors++; - } else - debug_printf (1, " iso-8859-1 decoded OK\n"); + g_assert_cmpstr (filename, ==, RFC5987_TEST_FILENAME); g_hash_table_destroy (params); /* Fallback */ @@ -1046,27 +927,19 @@ do_content_disposition_tests (void) if (!soup_message_headers_get_content_disposition (hdrs, &disposition, ¶ms)) { - debug_printf (1, " fallback decoding FAILED!\n could not parse\n"); - errors++; + soup_test_assert (FALSE, "fallback decoding FAILED"); return; } g_free (disposition); filename = g_hash_table_lookup (params, "filename"); - if (!filename) { - debug_printf (1, " fallback decoding FAILED!\n could not find filename\n"); - errors++; - } else if (strcmp (filename, RFC5987_TEST_FALLBACK_FILENAME) != 0) { - debug_printf (1, " fallback decoding FAILED!\n expected: %s\n got: %s\n", - RFC5987_TEST_FALLBACK_FILENAME, filename); - errors++; - } else - debug_printf (1, " fallback decoded OK\n"); + g_assert_cmpstr (filename, ==, RFC5987_TEST_FALLBACK_FILENAME); g_hash_table_destroy (params); soup_message_headers_free (hdrs); - /* Ensure that soup-multipart always quotes filename (bug 641280) */ + /* Ensure that soup-multipart always quotes filename */ + g_test_bug ("641280"); multipart = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART); buffer = soup_buffer_new (SOUP_MEMORY_STATIC, "foo", 3); soup_multipart_append_form_file (multipart, "test", "token", @@ -1082,15 +955,9 @@ do_content_disposition_tests (void) buffer = soup_message_body_flatten (body); soup_message_body_free (body); - if (strstr (buffer->data, "filename=\"token\"")) - debug_printf (1, " SoupMultipart encoded filename correctly\n"); - else { - debug_printf (1, " SoupMultipart encoded filename incorrectly!\n"); - errors++; - } - soup_buffer_free (buffer); + g_assert_true (strstr (buffer->data, "filename=\"token\"")); - debug_printf (1, "\n"); + soup_buffer_free (buffer); } #define CONTENT_TYPE_TEST_MIME_TYPE "text/plain" @@ -1107,7 +974,7 @@ do_content_type_tests (void) GHashTable *params; const char *header, *mime_type; - debug_printf (1, "Content-Type tests\n"); + g_test_bug ("576760"); hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART); params = g_hash_table_new (g_str_hash, g_str_equal); @@ -1117,13 +984,7 @@ do_content_type_tests (void) g_hash_table_destroy (params); header = soup_message_headers_get_one (hdrs, "Content-Type"); - if (!strcmp (header, CONTENT_TYPE_TEST_HEADER)) - debug_printf (1, " encoded OK\n"); - else { - debug_printf (1, " encoding FAILED!\n expected: %s\n got: %s\n", - CONTENT_TYPE_TEST_HEADER, header); - errors++; - } + g_assert_cmpstr (header, ==, CONTENT_TYPE_TEST_HEADER); soup_message_headers_clear (hdrs); soup_message_headers_append (hdrs, "Content-Type", @@ -1133,38 +994,20 @@ do_content_type_tests (void) CONTENT_TYPE_TEST_MIME_TYPE); mime_type = soup_message_headers_get_content_type (hdrs, ¶ms); - if (!mime_type) { - debug_printf (1, " decoding FAILED!\n could not parse\n"); - errors++; - } - - if (mime_type && strcmp (mime_type, CONTENT_TYPE_TEST_MIME_TYPE) != 0) { - debug_printf (1, " decoding FAILED!\n bad returned MIME type: %s\n", - mime_type); - errors++; - } else if (params && g_hash_table_size (params) != 0) { - debug_printf (1, " decoding FAILED!\n params contained %d params (should be 0)\n", - g_hash_table_size (params)); - errors++; - } else - debug_printf (1, " decoded OK\n"); - + g_assert_cmpstr (mime_type, ==, CONTENT_TYPE_TEST_MIME_TYPE); + g_assert_cmpint (g_hash_table_size (params), ==, 0); if (params) g_hash_table_destroy (params); + g_test_bug ("577630"); + soup_message_headers_clear (hdrs); soup_message_headers_append (hdrs, "Content-Type", CONTENT_TYPE_BAD_HEADER); mime_type = soup_message_headers_get_content_type (hdrs, ¶ms); - if (mime_type) { - debug_printf (1, " Bad content rejection FAILED!\n"); - errors++; - } else - debug_printf (1, " Bad content rejection OK\n"); + g_assert_null (mime_type); soup_message_headers_free (hdrs); - - debug_printf (1, "\n"); } struct { @@ -1185,7 +1028,7 @@ do_append_param_tests (void) GString *params; int i; - debug_printf (1, "soup_header_g_string_append_param() tests\n"); + g_test_bug ("577728"); params = g_string_new (NULL); for (i = 0; i < G_N_ELEMENTS (test_params); i++) { @@ -1195,15 +1038,8 @@ do_append_param_tests (void) test_params[i].name, test_params[i].value); } - if (strcmp (params->str, TEST_PARAMS_RESULT) != 0) { - debug_printf (1, " FAILED!\n expected: %s\n got: %s\n", - TEST_PARAMS_RESULT, params->str); - errors++; - } else - debug_printf (1, " OK\n"); + g_assert_cmpstr (params->str, ==, TEST_PARAMS_RESULT); g_string_free (params, TRUE); - - debug_printf (1, "\n"); } static const struct { @@ -1226,19 +1062,15 @@ do_bad_header_tests (void) SoupMessageHeaders *hdrs; int i; - debug_printf (1, "bad header rejection tests\n"); - hdrs = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART); for (i = 0; i < G_N_ELEMENTS (bad_headers); i++) { debug_printf (1, " %s\n", bad_headers[i].description); - expect_warning = TRUE; + + g_test_expect_message ("libsoup", G_LOG_LEVEL_CRITICAL, + "*soup_message_headers_append*assertion*failed*"); soup_message_headers_append (hdrs, bad_headers[i].name, bad_headers[i].value); - if (expect_warning) { - expect_warning = FALSE; - debug_printf (1, " FAILED: soup_message_headers_append() did not reject it\n"); - errors++; - } + g_test_assert_expected_messages (); } soup_message_headers_free (hdrs); } @@ -1246,16 +1078,20 @@ do_bad_header_tests (void) int main (int argc, char **argv) { + int ret; + test_init (argc, argv, NULL); - do_request_tests (); - do_response_tests (); - do_qvalue_tests (); - do_content_disposition_tests (); - do_content_type_tests (); - do_append_param_tests (); - do_bad_header_tests (); + g_test_add_func ("/header-parsing/request", do_request_tests); + g_test_add_func ("/header-parsing/response", do_response_tests); + g_test_add_func ("/header-parsing/qvalue", do_qvalue_tests); + g_test_add_func ("/header-parsing/content-disposition", do_content_disposition_tests); + g_test_add_func ("/header-parsing/content-type", do_content_type_tests); + g_test_add_func ("/header-parsing/append-param", do_append_param_tests); + g_test_add_func ("/header-parsing/bad", do_bad_header_tests); + + ret = g_test_run (); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/httpd.conf.in b/tests/httpd.conf.22.in index de0b75fa..b912ca36 100644 --- a/tests/httpd.conf.in +++ b/tests/httpd.conf.22.in @@ -3,8 +3,7 @@ ServerName 127.0.0.1 Listen 127.0.0.1:47524 -PidFile @builddir@/httpd.pid -DocumentRoot @srcdir@ +DocumentRoot . # The tests shut down apache with "graceful-stop", because that makes # it close its listening socket right away. But it seems to sometimes @@ -83,7 +82,7 @@ Listen 127.0.0.1:47527 AuthType Basic AuthName realm1 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require valid-user </Proxy> @@ -94,7 +93,7 @@ Listen 127.0.0.1:47527 AuthType Basic AuthName realm1 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require valid-user </Proxy> @@ -124,7 +123,7 @@ Listen 127.0.0.1:47528 AuthType Basic AuthName realm1 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require user no-such-user </Proxy> @@ -135,7 +134,7 @@ Listen 127.0.0.1:47528 AuthType Basic AuthName realm1 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require user no-such-user </Proxy> @@ -154,89 +153,89 @@ Listen 127.0.0.1:47528 <VirtualHost 127.0.0.1:47525> SSLEngine on - SSLCertificateFile @srcdir@/test-cert.pem - SSLCertificateKeyFile @srcdir@/test-key.pem + SSLCertificateFile ./test-cert.pem + SSLCertificateKeyFile ./test-key.pem </VirtualHost> </IfModule> # Basic auth tests -Alias /Basic/realm1/realm2/realm1 @srcdir@ -Alias /Basic/realm1/realm2 @srcdir@ -Alias /Basic/realm1/subdir @srcdir@ -Alias /Basic/realm1/not @srcdir@ -Alias /Basic/realm1 @srcdir@ -Alias /Basic/realm12/subdir @srcdir@ -Alias /Basic/realm12 @srcdir@ -Alias /Basic/realm2 @srcdir@ -Alias /Basic/realm3 @srcdir@ -Alias /Basic @srcdir@ +Alias /Basic/realm1/realm2/realm1 . +Alias /Basic/realm1/realm2 . +Alias /Basic/realm1/subdir . +Alias /Basic/realm1/not . +Alias /Basic/realm1 . +Alias /Basic/realm12/subdir . +Alias /Basic/realm12 . +Alias /Basic/realm2 . +Alias /Basic/realm3 . +Alias /Basic . <Location /Basic/realm1> AuthType Basic AuthName realm1 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require user user1 </Location> <Location /Basic/realm1/not> AuthType Basic AuthName realm1 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require user user2 </Location> <Location /Basic/realm12> AuthType Basic AuthName realm12 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require user user1 user2 </Location> <Location /Basic/realm1/realm2> AuthType Basic AuthName realm2 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require user user2 </Location> <Location /Basic/realm1/realm2/realm1> AuthType Basic AuthName realm1 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require user user1 </Location> <Location /Basic/realm2> AuthType Basic AuthName realm2 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require user user2 </Location> <Location /Basic/realm3> AuthType Basic AuthName realm3 - AuthUserFile @srcdir@/htpasswd + AuthUserFile ./htpasswd Require user user3 </Location> # Digest auth tests -Alias /Digest/realm1/realm2/realm1 @srcdir@ -Alias /Digest/realm1/realm2 @srcdir@ -Alias /Digest/realm1/subdir @srcdir@ -Alias /Digest/realm1/expire @srcdir@ -Alias /Digest/realm1/not @srcdir@ -Alias /Digest/realm1 @srcdir@ -Alias /Digest/realm2 @srcdir@ -Alias /Digest/realm3 @srcdir@ -Alias /Digest @srcdir@ +Alias /Digest/realm1/realm2/realm1 . +Alias /Digest/realm1/realm2 . +Alias /Digest/realm1/subdir . +Alias /Digest/realm1/expire . +Alias /Digest/realm1/not . +Alias /Digest/realm1 . +Alias /Digest/realm2 . +Alias /Digest/realm3 . +Alias /Digest . <Location /Digest/realm1> AuthType Digest AuthName realm1 - AuthUserFile @srcdir@/htdigest + AuthUserFile ./htdigest AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1 Require valid-user </Location> @@ -244,7 +243,7 @@ Alias /Digest @srcdir@ <Location /Digest/realm1/expire> AuthType Digest AuthName realm1 - AuthUserFile @srcdir@/htdigest + AuthUserFile ./htdigest AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1 AuthDigestNonceLifetime 2 Require valid-user @@ -253,7 +252,7 @@ Alias /Digest @srcdir@ <Location /Digest/realm1/not> AuthType Digest AuthName realm1 - AuthUserFile @srcdir@/htdigest + AuthUserFile ./htdigest AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1 Require user user2 </Location> @@ -261,7 +260,7 @@ Alias /Digest @srcdir@ <Location /Digest/realm1/realm2> AuthType Digest AuthName realm2 - AuthUserFile @srcdir@/htdigest + AuthUserFile ./htdigest AuthDigestDomain /Digest/realm2 /Digest/realm1/realm2 Require valid-user </Location> @@ -269,7 +268,7 @@ Alias /Digest @srcdir@ <Location /Digest/realm1/realm2/realm1> AuthType Digest AuthName realm1 - AuthUserFile @srcdir@/htdigest + AuthUserFile ./htdigest AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1 Require valid-user </Location> @@ -277,7 +276,7 @@ Alias /Digest @srcdir@ <Location /Digest/realm2> AuthType Digest AuthName realm2 - AuthUserFile @srcdir@/htdigest + AuthUserFile ./htdigest AuthDigestDomain /Digest/realm2 /Digest/realm1/realm2 Require valid-user </Location> @@ -285,7 +284,7 @@ Alias /Digest @srcdir@ <Location /Digest/realm3> AuthType Digest AuthName realm3 - AuthUserFile @srcdir@/htdigest + AuthUserFile ./htdigest AuthDigestDomain /Digest/realm3 Require valid-user # test RFC2069-style Digest diff --git a/tests/httpd.conf.24.in b/tests/httpd.conf.24.in new file mode 100644 index 00000000..850b8393 --- /dev/null +++ b/tests/httpd.conf.24.in @@ -0,0 +1,276 @@ +# http.conf used for testing auth-test + +ServerName 127.0.0.1 +Listen 127.0.0.1:47524 + +DocumentRoot . + +# The tests shut down apache with "graceful-stop", because that makes +# it close its listening socket right away. But it seems to sometimes +# result in apache never fully exiting. This fixes that. +GracefulShutdownTimeout 1 + +# Change this to "./error.log" if it's failing and you don't know why +ErrorLog /dev/null + +LoadModule mpm_prefork_module @APACHE_MODULE_DIR@/mod_mpm_prefork.so +LoadModule alias_module @APACHE_MODULE_DIR@/mod_alias.so +LoadModule auth_basic_module @APACHE_MODULE_DIR@/mod_auth_basic.so +LoadModule auth_digest_module @APACHE_MODULE_DIR@/mod_auth_digest.so +LoadModule authn_core_module @APACHE_MODULE_DIR@/mod_authn_core.so +LoadModule authn_file_module @APACHE_MODULE_DIR@/mod_authn_file.so +LoadModule authz_core_module @APACHE_MODULE_DIR@/mod_authz_core.so +LoadModule authz_host_module @APACHE_MODULE_DIR@/mod_authz_host.so +LoadModule authz_user_module @APACHE_MODULE_DIR@/mod_authz_user.so +LoadModule dir_module @APACHE_MODULE_DIR@/mod_dir.so +LoadModule mime_module @APACHE_MODULE_DIR@/mod_mime.so +@IF_HAVE_PHP@LoadModule php5_module @APACHE_PHP_MODULE_DIR@/@APACHE_PHP_MODULE@ +LoadModule proxy_module @APACHE_MODULE_DIR@/mod_proxy.so +LoadModule proxy_http_module @APACHE_MODULE_DIR@/mod_proxy_http.so +LoadModule proxy_connect_module @APACHE_MODULE_DIR@/mod_proxy_connect.so +LoadModule ssl_module @APACHE_SSL_MODULE_DIR@/mod_ssl.so +LoadModule unixd_module @APACHE_SSL_MODULE_DIR@/mod_unixd.so + +DirectoryIndex index.txt +TypesConfig /dev/null +AddType application/x-httpd-php .php +Redirect permanent /redirected /index.txt + +# Proxy #1: unauthenticated +Listen 127.0.0.1:47526 +<VirtualHost 127.0.0.1:47526> + ProxyRequests On + AllowCONNECT 47525 + + # Deny proxying by default + <Proxy *> + Require all denied + </Proxy> + + # Allow local http connections + <Proxy http://127.0.0.1*> + Require all granted + </Proxy> + + # Allow CONNECT to local https port + <Proxy 127.0.0.1:47525> + Require all granted + </Proxy> + + # Deny non-proxy requests + <Directory /> + Require all denied + </Directory> +</VirtualHost> + +# Proxy #2: authenticated +Listen 127.0.0.1:47527 +<VirtualHost 127.0.0.1:47527> + ProxyRequests On + AllowCONNECT 47525 + + # Deny proxying by default + <Proxy *> + Require all denied + </Proxy> + + # Allow local http connections with authentication + <Proxy http://127.0.0.1:47524*> + AuthType Basic + AuthName realm1 + AuthUserFile ./htpasswd + Require valid-user + </Proxy> + + # Allow CONNECT to local https port with authentication + <Proxy 127.0.0.1:47525> + AuthType Basic + AuthName realm1 + AuthUserFile ./htpasswd + Require valid-user + </Proxy> + + # Fail non-proxy requests + <Directory /> + Require all denied + </Directory> +</VirtualHost> + +# Proxy #3: unauthenticatable-to +Listen 127.0.0.1:47528 +<VirtualHost 127.0.0.1:47528> + ProxyRequests On + AllowCONNECT 47525 + + # Deny proxying by default + <Proxy *> + Require all denied + </Proxy> + + # Allow local http connections with authentication + <Proxy http://127.0.0.1:47524*> + AuthType Basic + AuthName realm1 + AuthUserFile ./htpasswd + Require user no-such-user + </Proxy> + + # Allow CONNECT to local https port with authentication + <Proxy 127.0.0.1:47525> + AuthType Basic + AuthName realm1 + AuthUserFile ./htpasswd + Require user no-such-user + </Proxy> + + # Fail non-proxy requests + <Directory /> + Require all denied + </Directory> +</VirtualHost> + + +# SSL setup +<IfModule mod_ssl.c> + Listen 127.0.0.1:47525 + + <VirtualHost 127.0.0.1:47525> + SSLEngine on + + SSLCertificateFile ./test-cert.pem + SSLCertificateKeyFile ./test-key.pem + + </VirtualHost> +</IfModule> + + +# Basic auth tests +Alias /Basic/realm1/realm2/realm1 . +Alias /Basic/realm1/realm2 . +Alias /Basic/realm1/subdir . +Alias /Basic/realm1/not . +Alias /Basic/realm1 . +Alias /Basic/realm12/subdir . +Alias /Basic/realm12 . +Alias /Basic/realm2 . +Alias /Basic/realm3 . +Alias /Basic . + +<Location /Basic/realm1> + AuthType Basic + AuthName realm1 + AuthUserFile ./htpasswd + Require user user1 +</Location> + +<Location /Basic/realm1/not> + AuthType Basic + AuthName realm1 + AuthUserFile ./htpasswd + Require user user2 +</Location> + +<Location /Basic/realm12> + AuthType Basic + AuthName realm12 + AuthUserFile ./htpasswd + Require user user1 user2 +</Location> + +<Location /Basic/realm1/realm2> + AuthType Basic + AuthName realm2 + AuthUserFile ./htpasswd + Require user user2 +</Location> + +<Location /Basic/realm1/realm2/realm1> + AuthType Basic + AuthName realm1 + AuthUserFile ./htpasswd + Require user user1 +</Location> + +<Location /Basic/realm2> + AuthType Basic + AuthName realm2 + AuthUserFile ./htpasswd + Require user user2 +</Location> + +<Location /Basic/realm3> + AuthType Basic + AuthName realm3 + AuthUserFile ./htpasswd + Require user user3 +</Location> + +# Digest auth tests +Alias /Digest/realm1/realm2/realm1 . +Alias /Digest/realm1/realm2 . +Alias /Digest/realm1/subdir . +Alias /Digest/realm1/expire . +Alias /Digest/realm1/not . +Alias /Digest/realm1 . +Alias /Digest/realm2 . +Alias /Digest/realm3 . +Alias /Digest . + +<Location /Digest/realm1> + AuthType Digest + AuthName realm1 + AuthUserFile ./htdigest + AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1 + Require valid-user +</Location> + +<Location /Digest/realm1/expire> + AuthType Digest + AuthName realm1 + AuthUserFile ./htdigest + AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1 + AuthDigestNonceLifetime 2 + Require valid-user +</Location> + +<Location /Digest/realm1/not> + AuthType Digest + AuthName realm1 + AuthUserFile ./htdigest + AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1 + Require user user2 +</Location> + +<Location /Digest/realm1/realm2> + AuthType Digest + AuthName realm2 + AuthUserFile ./htdigest + AuthDigestDomain /Digest/realm2 /Digest/realm1/realm2 + Require valid-user +</Location> + +<Location /Digest/realm1/realm2/realm1> + AuthType Digest + AuthName realm1 + AuthUserFile ./htdigest + AuthDigestDomain /Digest/realm1 /Digest/realm1/realm2/realm1 + Require valid-user +</Location> + +<Location /Digest/realm2> + AuthType Digest + AuthName realm2 + AuthUserFile ./htdigest + AuthDigestDomain /Digest/realm2 /Digest/realm1/realm2 + Require valid-user +</Location> + +<Location /Digest/realm3> + AuthType Digest + AuthName realm3 + AuthUserFile ./htdigest + AuthDigestDomain /Digest/realm3 + Require valid-user + # test RFC2069-style Digest + AuthDigestQop none +</Location> diff --git a/tests/libsoup.supp b/tests/libsoup.supp index 24c6516a..ae8bda38 100644 --- a/tests/libsoup.supp +++ b/tests/libsoup.supp @@ -321,7 +321,7 @@ ... fun:_dl_allocate_tls ... - fun:g_thread_create_posix_impl + fun:g_system_thread_new } { glib/filenamecharsets @@ -342,12 +342,78 @@ fun:g_tls_backend_gnutls_store_session } { + glib/gtlssessioncache_client + Memcheck:Leak + ... + fun:gnutls_session_get_data2 + fun:g_tls_client_connection_gnutls_finish_handshake +} +{ + glib/gtlssessioncache_client2 + Memcheck:Leak + ... + fun:g_bytes_new_with_free_func + fun:g_tls_client_connection_gnutls_finish_handshake +} +{ + glib/gtlssessioncache_client3 + Memcheck:Leak + ... + fun:g_bytes_new_take + fun:g_tls_client_connection_gnutls_constructed +} +{ + glib/gtlssessioncache_client4 + Memcheck:Leak + ... + fun:g_strdup_printf + fun:g_tls_client_connection_gnutls_constructed +} +{ + glib/gtlssessioncache_server + Memcheck:Leak + ... + fun:g_tls_server_connection_gnutls_db_store +} +{ glib/cached_poll_array Memcheck:Leak ... fun:g_malloc_n fun:g_main_context_iterate } +{ + glib/rand + Memcheck:Leak + ... + fun:g_rand_new + fun:g_random_int_range +} +{ + glib/g_cancellable_push_current + Memcheck:Leak + ... + fun:g_cancellable_push_current +} +{ + glib/slice_thread_local + Memcheck:Leak + ... + fun:thread_memory_from_self +} +{ + glib/gobjectinit + Memcheck:Leak + ... + fun:gobject_init_ctor +} +{ + glib/gtask threadpool + Memcheck:Leak + ... + fun:g_thread_pool_new + fun:g_task_thread_pool_init +} # probably inlines the aggressive memcpy/memcmp { @@ -421,6 +487,12 @@ ... fun:intern_header_name } +{ + libsoup/tlds + Memcheck:Leak + ... + fun:soup_tld_ensure_rules_hash_table +} # fixme? diff --git a/tests/misc-test.c b/tests/misc-test.c index 605fa216..00559a03 100644 --- a/tests/misc-test.c +++ b/tests/misc-test.c @@ -32,16 +32,6 @@ server_callback (SoupServer *server, SoupMessage *msg, SoupURI *uri = soup_message_get_uri (msg); const char *server_protocol = data; - soup_message_headers_append (msg->response_headers, - "X-Handled-By", "server_callback"); - - if (!strcmp (path, "*")) { - debug_printf (1, " default server_callback got request for '*'!\n"); - errors++; - soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); - return; - } - if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_POST) { soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); return; @@ -99,32 +89,8 @@ server_callback (SoupServer *server, SoupMessage *msg, } } -static void -server_star_callback (SoupServer *server, SoupMessage *msg, - const char *path, GHashTable *query, - SoupClientContext *context, gpointer data) -{ - soup_message_headers_append (msg->response_headers, - "X-Handled-By", "star_callback"); - - if (strcmp (path, "*") != 0) { - debug_printf (1, " server_star_callback got request for '%s'!\n", path); - errors++; - soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); - return; - } - - if (msg->method != SOUP_METHOD_OPTIONS) { - soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED); - return; - } - - soup_message_set_status (msg, SOUP_STATUS_OK); -} - /* Host header handling: client must be able to override the default * value, server must be able to recognize different Host values. - * #539803. */ static void do_host_test (void) @@ -132,7 +98,7 @@ do_host_test (void) SoupSession *session; SoupMessage *one, *two; - debug_printf (1, "Host handling\n"); + g_test_bug ("539803"); session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); @@ -145,42 +111,24 @@ do_host_test (void) soup_test_session_abort_unref (session); - if (!SOUP_STATUS_IS_SUCCESSFUL (one->status_code)) { - debug_printf (1, " Message 1 failed: %d %s\n", - one->status_code, one->reason_phrase); - errors++; - } else if (strcmp (one->response_body->data, "index") != 0) { - debug_printf (1, " Unexpected response to message 1: '%s'\n", - one->response_body->data); - errors++; - } + soup_test_assert_message_status (one, SOUP_STATUS_OK); + g_assert_cmpstr (one->response_body->data, ==, "index"); g_object_unref (one); - if (!SOUP_STATUS_IS_SUCCESSFUL (two->status_code)) { - debug_printf (1, " Message 2 failed: %d %s\n", - two->status_code, two->reason_phrase); - errors++; - } else if (strcmp (two->response_body->data, "foo-index") != 0) { - debug_printf (1, " Unexpected response to message 2: '%s'\n", - two->response_body->data); - errors++; - } + soup_test_assert_message_status (two, SOUP_STATUS_OK); + g_assert_cmpstr (two->response_body->data, ==, "foo-index"); g_object_unref (two); } /* Dropping the application's ref on the session from a callback * should not cause the session to be freed at an incorrect time. - * (This test will crash if it fails.) #533473 + * (This test will crash if it fails.) */ static void cu_one_completed (SoupSession *session, SoupMessage *msg, gpointer loop) { debug_printf (2, " Message 1 completed\n"); - if (msg->status_code != SOUP_STATUS_CANT_CONNECT) { - debug_printf (1, " Unexpected status on Message 1: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_CANT_CONNECT); g_object_unref (session); } @@ -195,11 +143,7 @@ static void cu_two_completed (SoupSession *session, SoupMessage *msg, gpointer loop) { debug_printf (2, " Message 2 completed\n"); - if (msg->status_code != SOUP_STATUS_CANT_CONNECT) { - debug_printf (1, " Unexpected status on Message 2: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_CANT_CONNECT); g_idle_add (cu_idle_quit, loop); } @@ -213,7 +157,7 @@ do_callback_unref_test (void) GMainLoop *loop; char *bad_uri; - debug_printf (1, "\nCallback unref handling (msg api)\n"); + g_test_bug ("533473"); /* Get a guaranteed-bad URI */ addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT); @@ -243,22 +187,19 @@ do_callback_unref_test (void) g_main_loop_run (loop); g_main_loop_unref (loop); + g_assert_null (session); if (session) { g_object_remove_weak_pointer (G_OBJECT (session), (gpointer *)&session); - debug_printf (1, " Session not destroyed?\n"); - errors++; g_object_unref (session); } + g_assert_null (one); if (one) { g_object_remove_weak_pointer (G_OBJECT (one), (gpointer *)&one); - debug_printf (1, " Message 1 not destroyed?\n"); - errors++; g_object_unref (one); } + g_assert_null (two); if (two) { g_object_remove_weak_pointer (G_OBJECT (two), (gpointer *)&two); - debug_printf (1, " Message 2 not destroyed?\n"); - errors++; g_object_unref (two); } @@ -272,14 +213,8 @@ cur_one_completed (GObject *source, GAsyncResult *result, gpointer session) GError *error = NULL; debug_printf (2, " Request 1 completed\n"); - if (soup_request_send_finish (one, result, &error)) { - debug_printf (1, " Request 1 succeeded?\n"); - errors++; - } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT)) { - debug_printf (1, " Unexpected error on Request 1: %s\n", - error->message); - errors++; - } + soup_request_send_finish (one, result, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED); g_clear_error (&error); g_object_unref (session); @@ -299,14 +234,8 @@ cur_two_completed (GObject *source, GAsyncResult *result, gpointer loop) GError *error = NULL; debug_printf (2, " Request 2 completed\n"); - if (soup_request_send_finish (two, result, &error)) { - debug_printf (1, " Request 2 succeeded?\n"); - errors++; - } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANT_CONNECT)) { - debug_printf (1, " Unexpected error on Request 2: %s\n", - error->message); - errors++; - } + soup_request_send_finish (two, result, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED); g_clear_error (&error); g_idle_add (cur_idle_quit, loop); @@ -318,13 +247,10 @@ do_callback_unref_req_test (void) SoupServer *bad_server; SoupAddress *addr; SoupSession *session; - SoupRequester *requester; SoupRequest *one, *two; GMainLoop *loop; char *bad_uri; - debug_printf (1, "\nCallback unref handling (request api)\n"); - /* Get a guaranteed-bad URI */ addr = soup_address_new ("127.0.0.1", SOUP_ADDRESS_ANY_PORT); soup_address_resolve_sync (addr, NULL); @@ -337,18 +263,15 @@ do_callback_unref_req_test (void) g_object_unref (bad_server); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); g_object_add_weak_pointer (G_OBJECT (session), (gpointer *)&session); - requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); - loop = g_main_loop_new (NULL, TRUE); - one = soup_requester_request (requester, bad_uri, NULL); + one = soup_session_request (session, bad_uri, NULL); g_object_add_weak_pointer (G_OBJECT (one), (gpointer *)&one); - two = soup_requester_request (requester, bad_uri, NULL); + two = soup_session_request (session, bad_uri, NULL); g_object_add_weak_pointer (G_OBJECT (two), (gpointer *)&two); g_free (bad_uri); @@ -360,22 +283,19 @@ do_callback_unref_req_test (void) g_main_loop_run (loop); g_main_loop_unref (loop); + g_assert_null (session); if (session) { g_object_remove_weak_pointer (G_OBJECT (session), (gpointer *)&session); - debug_printf (1, " Session not destroyed?\n"); - errors++; g_object_unref (session); } + g_assert_null (one); if (one) { g_object_remove_weak_pointer (G_OBJECT (one), (gpointer *)&one); - debug_printf (1, " Request 1 not destroyed?\n"); - errors++; g_object_unref (one); } + g_assert_null (two); if (two) { g_object_remove_weak_pointer (G_OBJECT (two), (gpointer *)&two); - debug_printf (1, " Request 2 not destroyed?\n"); - errors++; g_object_unref (two); } @@ -384,20 +304,19 @@ do_callback_unref_req_test (void) /* SoupSession should clean up all signal handlers on a message after * it is finished, allowing the message to be reused if desired. - * #559054 */ static void ensure_no_signal_handlers (SoupMessage *msg, guint *signal_ids, guint n_signal_ids) { int i; + guint id; for (i = 0; i < n_signal_ids; i++) { - if (g_signal_handler_find (msg, G_SIGNAL_MATCH_ID, signal_ids[i], - 0, NULL, NULL, NULL)) { - debug_printf (1, " Message has handler for '%s'\n", - g_signal_name (signal_ids[i])); - errors++; - } + id = g_signal_handler_find (msg, G_SIGNAL_MATCH_ID, signal_ids[i], + 0, NULL, NULL, NULL); + soup_test_assert (id == 0, + "message has handler for '%s'", + g_signal_name (signal_ids[i])); } } @@ -420,7 +339,7 @@ do_msg_reuse_test (void) SoupURI *uri; guint *signal_ids, n_signal_ids; - debug_printf (1, "\nSoupMessage reuse\n"); + g_test_bug ("559054"); signal_ids = g_signal_list_ids (SOUP_TYPE_MESSAGE, &n_signal_ids); @@ -438,10 +357,7 @@ do_msg_reuse_test (void) soup_message_set_uri (msg, uri); soup_uri_free (uri); soup_session_send_message (session, msg); - if (!soup_uri_equal (soup_message_get_uri (msg), base_uri)) { - debug_printf (1, " Message did not get redirected!\n"); - errors++; - } + g_assert_true (soup_uri_equal (soup_message_get_uri (msg), base_uri)); ensure_no_signal_handlers (msg, signal_ids, n_signal_ids); debug_printf (1, " Auth message\n"); @@ -449,10 +365,7 @@ do_msg_reuse_test (void) soup_message_set_uri (msg, uri); soup_uri_free (uri); soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " Message did not get authenticated!\n"); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); ensure_no_signal_handlers (msg, signal_ids, n_signal_ids); /* One last try to make sure the auth stuff got cleaned up */ @@ -466,79 +379,12 @@ do_msg_reuse_test (void) g_free (signal_ids); } -/* Server handlers for "*" work but are separate from handlers for - * all other URIs. #590751 - */ -static void -do_star_test (void) -{ - SoupSession *session; - SoupMessage *msg; - SoupURI *star_uri; - const char *handled_by; - - debug_printf (1, "\nOPTIONS *\n"); - - session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); - star_uri = soup_uri_copy (base_uri); - soup_uri_set_path (star_uri, "*"); - - debug_printf (1, " Testing with no handler\n"); - msg = soup_message_new_from_uri ("OPTIONS", star_uri); - soup_session_send_message (session, msg); - - if (msg->status_code != SOUP_STATUS_NOT_FOUND) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - handled_by = soup_message_headers_get_one (msg->response_headers, - "X-Handled-By"); - if (handled_by) { - /* Should have been rejected by SoupServer directly */ - debug_printf (1, " Message reached handler '%s'\n", - handled_by); - errors++; - } - g_object_unref (msg); - - soup_server_add_handler (server, "*", server_star_callback, NULL, NULL); - - debug_printf (1, " Testing with handler\n"); - msg = soup_message_new_from_uri ("OPTIONS", star_uri); - soup_session_send_message (session, msg); - - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - handled_by = soup_message_headers_get_one (msg->response_headers, - "X-Handled-By"); - if (!handled_by) { - debug_printf (1, " Message did not reach handler!\n"); - errors++; - } else if (strcmp (handled_by, "star_callback") != 0) { - debug_printf (1, " Message reached incorrect handler '%s'\n", - handled_by); - errors++; - } - g_object_unref (msg); - - soup_test_session_abort_unref (session); - soup_uri_free (star_uri); -} - -/* Handle unexpectedly-early aborts. #596074, #618641 */ +/* Handle unexpectedly-early aborts. */ static void ea_msg_completed_one (SoupSession *session, SoupMessage *msg, gpointer loop) { debug_printf (2, " Message 1 completed\n"); - if (msg->status_code != SOUP_STATUS_CANCELLED) { - debug_printf (1, " Unexpected status on Message 1: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_CANCELLED); g_main_loop_quit (loop); } @@ -585,7 +431,8 @@ do_early_abort_test (void) GMainContext *context; GMainLoop *loop; - debug_printf (1, "\nAbort with pending connection (msg api)\n"); + g_test_bug ("596074"); + g_test_bug ("618641"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); msg = soup_message_new_from_uri ("GET", base_uri); @@ -609,11 +456,7 @@ do_early_abort_test (void) soup_session_send_message (session, msg); debug_printf (2, " Message 2 completed\n"); - if (msg->status_code != SOUP_STATUS_CANCELLED) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_CANCELLED); g_object_unref (msg); while (g_main_context_pending (context)) @@ -621,6 +464,8 @@ do_early_abort_test (void) soup_test_session_abort_unref (session); + g_test_bug ("668098"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); msg = soup_message_new_from_uri ("GET", base_uri); @@ -629,11 +474,7 @@ do_early_abort_test (void) soup_session_send_message (session, msg); debug_printf (2, " Message 3 completed\n"); - if (msg->status_code != SOUP_STATUS_CANCELLED) { - debug_printf (1, " Unexpected response: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_CANCELLED); g_object_unref (msg); while (g_main_context_pending (context)) @@ -648,14 +489,8 @@ ear_one_completed (GObject *source, GAsyncResult *result, gpointer user_data) GError *error = NULL; debug_printf (2, " Request 1 completed\n"); - if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) { - debug_printf (1, " Request 1 succeeded?\n"); - errors++; - } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) { - debug_printf (1, " Unexpected error on Request 1: %s\n", - error->message); - errors++; - } + soup_request_send_finish (SOUP_REQUEST (source), result, &error); + g_assert_error (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED); g_clear_error (&error); } @@ -665,14 +500,8 @@ ear_two_completed (GObject *source, GAsyncResult *result, gpointer loop) GError *error = NULL; debug_printf (2, " Request 2 completed\n"); - if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) { - debug_printf (1, " Request 2 succeeded?\n"); - errors++; - } else if (!g_error_matches (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED)) { - debug_printf (1, " Unexpected error on Request 2: %s\n", - error->message); - errors++; - } + soup_request_send_finish (SOUP_REQUEST (source), result, &error); + g_assert_error (error, SOUP_HTTP_ERROR, SOUP_STATUS_CANCELLED); g_clear_error (&error); g_main_loop_quit (loop); @@ -684,14 +513,8 @@ ear_three_completed (GObject *source, GAsyncResult *result, gpointer loop) GError *error = NULL; debug_printf (2, " Request 3 completed\n"); - if (soup_request_send_finish (SOUP_REQUEST (source), result, &error)) { - debug_printf (1, " Request 3 succeeded?\n"); - errors++; - } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - debug_printf (1, " Unexpected error on Request 3: %s\n", - error->message); - errors++; - } + soup_request_send_finish (SOUP_REQUEST (source), result, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&error); g_main_loop_quit (loop); @@ -708,20 +531,15 @@ static void do_early_abort_req_test (void) { SoupSession *session; - SoupRequester *requester; SoupRequest *req; GMainContext *context; GMainLoop *loop; GCancellable *cancellable; - debug_printf (1, "\nAbort with pending connection (request api)\n"); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); - requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); - req = soup_requester_request_uri (requester, base_uri, NULL); + req = soup_session_request_uri (session, base_uri, NULL); context = g_main_context_default (); loop = g_main_loop_new (context, TRUE); @@ -735,11 +553,9 @@ do_early_abort_req_test (void) soup_test_session_abort_unref (session); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); - requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); - req = soup_requester_request_uri (requester, base_uri, NULL); + req = soup_session_request_uri (session, base_uri, NULL); g_signal_connect (session, "connection-created", G_CALLBACK (ea_connection_created), NULL); @@ -753,11 +569,9 @@ do_early_abort_req_test (void) soup_test_session_abort_unref (session); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); - requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); - req = soup_requester_request_uri (requester, base_uri, NULL); + req = soup_session_request_uri (session, base_uri, NULL); cancellable = g_cancellable_new (); g_signal_connect (session, "request-started", @@ -790,21 +604,10 @@ do_one_accept_language_test (const char *language, const char *expected_header) soup_session_send_message (session, msg); soup_test_session_abort_unref (session); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " Message failed? %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); val = soup_message_headers_get_list (msg->request_headers, "Accept-Language"); - if (!val) { - debug_printf (1, " No Accept-Language set!\n"); - errors++; - } else if (strcmp (val, expected_header) != 0) { - debug_printf (1, " Wrong Accept-Language: expected '%s', got '%s'\n", - expected_header, val); - errors++; - } + g_assert_cmpstr (val, ==, expected_header); g_object_unref (msg); } @@ -814,7 +617,7 @@ do_accept_language_test (void) { const char *orig_language; - debug_printf (1, "\nAutomatic Accept-Language processing\n"); + g_test_bug ("602547"); orig_language = g_getenv ("LANGUAGE"); do_one_accept_language_test ("C", "en"); @@ -887,11 +690,7 @@ do_cancel_while_reading_test_for_session (SoupSession *session) while (!done) g_main_context_iteration (NULL, TRUE); - if (msg->status_code != SOUP_STATUS_CANCELLED) { - debug_printf (1, " FAILED: %d %s (expected Cancelled)\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_CANCELLED); g_object_unref (msg); if (thread) @@ -903,7 +702,8 @@ do_cancel_while_reading_test (void) { SoupSession *session; - debug_printf (1, "\nCancelling message while reading response (msg api)\n"); + g_test_bug ("637741"); + g_test_bug ("676038"); debug_printf (1, " Async session\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); @@ -916,23 +716,9 @@ do_cancel_while_reading_test (void) soup_test_session_abort_unref (session); } -static gboolean -cancel_request_timeout (gpointer cancellable) -{ - g_cancellable_cancel (cancellable); - return FALSE; -} - -static gpointer -cancel_request_thread (gpointer cancellable) -{ - g_usleep (100000); /* .1s */ - g_cancellable_cancel (cancellable); - return NULL; -} - static void -do_cancel_while_reading_req_test_for_session (SoupRequester *requester) +do_cancel_while_reading_req_test_for_session (SoupSession *session, + guint flags) { SoupRequest *req; SoupURI *uri; @@ -940,30 +726,12 @@ do_cancel_while_reading_req_test_for_session (SoupRequester *requester) GError *error = NULL; uri = soup_uri_new_with_base (base_uri, "/slow"); - req = soup_requester_request_uri (requester, uri, NULL); + req = soup_session_request_uri (session, uri, NULL); soup_uri_free (uri); cancellable = g_cancellable_new (); - - if (SOUP_IS_SESSION_ASYNC (soup_request_get_session (req))) { - g_timeout_add (100, cancel_request_timeout, cancellable); - soup_test_request_send_async_as_sync (req, cancellable, &error); - } else { - GThread *thread; - - thread = g_thread_new ("cancel_request_thread", cancel_request_thread, cancellable); - soup_request_send (req, cancellable, &error); - g_thread_unref (thread); - } - - if (!error) { - debug_printf (1, " Request succeeded?\n"); - errors++; - } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - debug_printf (1, " Unexpected error: %s\n", - error->message); - errors++; - } + soup_test_request_send (req, cancellable, flags, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); g_clear_error (&error); g_object_unref (req); @@ -971,169 +739,128 @@ do_cancel_while_reading_req_test_for_session (SoupRequester *requester) } static void -do_cancel_while_reading_req_test (void) +do_cancel_while_reading_immediate_req_test (void) { SoupSession *session; - SoupRequester *requester; + guint flags; - debug_printf (1, "\nCancelling message while reading response (request api)\n"); + g_test_bug ("692310"); + + flags = SOUP_TEST_REQUEST_CANCEL_CANCELLABLE | SOUP_TEST_REQUEST_CANCEL_IMMEDIATE; debug_printf (1, " Async session\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); - requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); - do_cancel_while_reading_req_test_for_session (requester); + do_cancel_while_reading_req_test_for_session (session, flags); soup_test_session_abort_unref (session); debug_printf (1, " Sync session\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, NULL); - requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); - do_cancel_while_reading_req_test_for_session (requester); + do_cancel_while_reading_req_test_for_session (session, flags); soup_test_session_abort_unref (session); } static void -do_aliases_test_for_session (SoupSession *session, - const char *redirect_protocol) +do_cancel_while_reading_delayed_req_test (void) { - SoupMessage *msg; - SoupURI *uri; - const char *redirected_protocol; - - uri = soup_uri_new_with_base (base_uri, "/alias-redirect"); - msg = soup_message_new_from_uri ("GET", uri); - if (redirect_protocol) - soup_message_headers_append (msg->request_headers, "X-Redirect-Protocol", redirect_protocol); - soup_uri_free (uri); - soup_session_send_message (session, msg); + SoupSession *session; + guint flags; - redirected_protocol = soup_message_headers_get_one (msg->response_headers, "X-Redirected-Protocol"); + flags = SOUP_TEST_REQUEST_CANCEL_CANCELLABLE | SOUP_TEST_REQUEST_CANCEL_SOON; - if (g_strcmp0 (redirect_protocol, redirected_protocol)) { - debug_printf (1, " redirect went to %s, should have gone to %s!\n", - redirected_protocol ? redirected_protocol : "(none)", - redirect_protocol ? redirect_protocol : "(none)"); - errors++; - } else if (redirect_protocol && !SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " msg failed? (%d %s)\n", - msg->status_code, msg->reason_phrase); - errors++; - } else if (!redirect_protocol && SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " msg succeeded? (%d %s)\n", - msg->status_code, msg->reason_phrase); - errors++; - } + debug_printf (1, " Async session\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + NULL); + do_cancel_while_reading_req_test_for_session (session, flags); + soup_test_session_abort_unref (session); - g_object_unref (msg); + debug_printf (1, " Sync session\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, + NULL); + do_cancel_while_reading_req_test_for_session (session, flags); + soup_test_session_abort_unref (session); } static void -do_aliases_test (void) +do_cancel_while_reading_preemptive_req_test (void) { SoupSession *session; - char *aliases[] = { "foo", NULL }; + guint flags; - debug_printf (1, "\nhttp-aliases / https-aliases\n"); + g_test_bug ("637039"); - debug_printf (1, " Default behavior\n"); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - do_aliases_test_for_session (session, "http"); - soup_test_session_abort_unref (session); + flags = SOUP_TEST_REQUEST_CANCEL_CANCELLABLE | SOUP_TEST_REQUEST_CANCEL_PREEMPTIVE; - debug_printf (1, " foo-means-https\n"); + debug_printf (1, " Async session\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_HTTPS_ALIASES, aliases, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); - do_aliases_test_for_session (session, "https"); + do_cancel_while_reading_req_test_for_session (session, flags); soup_test_session_abort_unref (session); - debug_printf (1, " foo-means-nothing\n"); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_HTTP_ALIASES, NULL, + debug_printf (1, " Sync session\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); - do_aliases_test_for_session (session, NULL); + do_cancel_while_reading_req_test_for_session (session, flags); soup_test_session_abort_unref (session); } static void -do_dot_dot_test (void) +do_aliases_test_for_session (SoupSession *session, + const char *redirect_protocol) { - SoupSession *session; SoupMessage *msg; SoupURI *uri; + const char *redirected_protocol; - debug_printf (1, "\n'..' smuggling test\n"); - - session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); - - uri = soup_uri_new_with_base (base_uri, "/..%2ftest"); + uri = soup_uri_new_with_base (base_uri, "/alias-redirect"); msg = soup_message_new_from_uri ("GET", uri); + if (redirect_protocol) + soup_message_headers_append (msg->request_headers, "X-Redirect-Protocol", redirect_protocol); soup_uri_free (uri); - soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_BAD_REQUEST) { - debug_printf (1, " FAILED: %d %s (expected Bad Request)\n", - msg->status_code, msg->reason_phrase); - errors++; - } - g_object_unref (msg); + redirected_protocol = soup_message_headers_get_one (msg->response_headers, "X-Redirected-Protocol"); - soup_test_session_abort_unref (session); + g_assert_cmpstr (redirect_protocol, ==, redirected_protocol); + if (redirect_protocol) + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + else + soup_test_assert_message_status (msg, SOUP_STATUS_FOUND); + + g_object_unref (msg); } static void -do_ipv6_test (void) +do_aliases_test (void) { - SoupServer *ipv6_server; - SoupURI *ipv6_uri; - SoupAddress *ipv6_addr; SoupSession *session; - SoupMessage *msg; - - debug_printf (1, "\nIPv6 server test\n"); - - ipv6_addr = soup_address_new ("::1", SOUP_ADDRESS_ANY_PORT); - soup_address_resolve_sync (ipv6_addr, NULL); - ipv6_server = soup_server_new (SOUP_SERVER_INTERFACE, ipv6_addr, - NULL); - g_object_unref (ipv6_addr); - soup_server_add_handler (ipv6_server, NULL, server_callback, NULL, NULL); - soup_server_run_async (ipv6_server); - - ipv6_uri = soup_uri_new ("http://[::1]/"); - soup_uri_set_port (ipv6_uri, soup_server_get_port (ipv6_server)); + char *aliases[] = { "foo", NULL }; + debug_printf (1, " Default behavior\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + do_aliases_test_for_session (session, "http"); + soup_test_session_abort_unref (session); - debug_printf (1, " HTTP/1.1\n"); - msg = soup_message_new_from_uri ("GET", ipv6_uri); - soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " request failed: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - g_object_unref (msg); - - debug_printf (1, " HTTP/1.0\n"); - msg = soup_message_new_from_uri ("GET", ipv6_uri); - soup_message_set_http_version (msg, SOUP_HTTP_1_0); - soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " request failed: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - g_object_unref (msg); + if (tls_available) { + debug_printf (1, " foo-means-https\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_HTTPS_ALIASES, aliases, + NULL); + do_aliases_test_for_session (session, "https"); + soup_test_session_abort_unref (session); + } else + debug_printf (1, " foo-means-https -- SKIPPING\n"); - soup_uri_free (ipv6_uri); + debug_printf (1, " foo-means-nothing\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_HTTP_ALIASES, NULL, + NULL); + do_aliases_test_for_session (session, NULL); soup_test_session_abort_unref (session); - soup_test_server_quit_unref (ipv6_server); } static void @@ -1143,7 +870,7 @@ do_idle_on_dispose_test (void) SoupMessage *msg; GMainContext *async_context; - debug_printf (1, "\nTesting SoupSessionAsync dispose behavior\n"); + g_test_bug ("667364"); async_context = g_main_context_new (); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, @@ -1159,10 +886,8 @@ do_idle_on_dispose_test (void) g_object_run_dispose (G_OBJECT (session)); - if (g_main_context_iteration (async_context, FALSE)) { - debug_printf (1, " idle was queued!\n"); - errors++; - } + if (g_main_context_iteration (async_context, FALSE)) + soup_test_assert (FALSE, "idle was queued"); g_object_unref (session); g_main_context_unref (async_context); @@ -1175,7 +900,7 @@ do_pause_abort_test (void) SoupMessage *msg; gpointer ptr; - debug_printf (1, "\nTesting paused messages don't get leaked on abort\n"); + g_test_bug ("673905"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); @@ -1186,16 +911,14 @@ do_pause_abort_test (void) g_object_add_weak_pointer (G_OBJECT (msg), &ptr); soup_test_session_abort_unref (session); - if (ptr) { - debug_printf (1, " msg was leaked\n"); - errors++; - } + g_assert_null (ptr); } int main (int argc, char **argv) { SoupAuthDomain *auth_domain; + int ret; test_init (argc, argv, NULL); @@ -1212,32 +935,37 @@ main (int argc, char **argv) soup_server_add_auth_domain (server, auth_domain); g_object_unref (auth_domain); - ssl_server = soup_test_server_new_ssl (TRUE); - soup_server_add_handler (ssl_server, NULL, server_callback, "https", NULL); - ssl_base_uri = soup_uri_new ("https://127.0.0.1/"); - soup_uri_set_port (ssl_base_uri, soup_server_get_port (ssl_server)); - - do_host_test (); - do_callback_unref_test (); - do_callback_unref_req_test (); - do_msg_reuse_test (); - do_star_test (); - do_early_abort_test (); - do_early_abort_req_test (); - do_accept_language_test (); - do_cancel_while_reading_test (); - do_cancel_while_reading_req_test (); - do_aliases_test (); - do_dot_dot_test (); - do_ipv6_test (); - do_idle_on_dispose_test (); - do_pause_abort_test (); + if (tls_available) { + ssl_server = soup_test_server_new_ssl (TRUE); + soup_server_add_handler (ssl_server, NULL, server_callback, "https", NULL); + ssl_base_uri = soup_uri_new ("https://127.0.0.1/"); + soup_uri_set_port (ssl_base_uri, soup_server_get_port (ssl_server)); + } + + g_test_add_func ("/misc/host", do_host_test); + g_test_add_func ("/misc/callback-unref/msg", do_callback_unref_test); + g_test_add_func ("/misc/callback-unref/req", do_callback_unref_req_test); + g_test_add_func ("/misc/msg-reuse", do_msg_reuse_test); + g_test_add_func ("/misc/early-abort/msg", do_early_abort_test); + g_test_add_func ("/misc/early-abort/req", do_early_abort_req_test); + g_test_add_func ("/misc/accept-language", do_accept_language_test); + g_test_add_func ("/misc/cancel-while-reading/msg", do_cancel_while_reading_test); + g_test_add_func ("/misc/cancel-while-reading/req/immediate", do_cancel_while_reading_immediate_req_test); + g_test_add_func ("/misc/cancel-while-reading/req/delayed", do_cancel_while_reading_delayed_req_test); + g_test_add_func ("/misc/cancel-while-reading/req/preemptive", do_cancel_while_reading_preemptive_req_test); + g_test_add_func ("/misc/aliases", do_aliases_test); + g_test_add_func ("/misc/idle-on-dispose", do_idle_on_dispose_test); + g_test_add_func ("/misc/pause-abort", do_pause_abort_test); + + ret = g_test_run (); soup_uri_free (base_uri); - soup_uri_free (ssl_base_uri); soup_test_server_quit_unref (server); - soup_test_server_quit_unref (ssl_server); - test_cleanup (); - return errors != 0; + if (tls_available) { + soup_uri_free (ssl_base_uri); + soup_test_server_quit_unref (ssl_server); + } + + return ret; } diff --git a/tests/multipart-test.c b/tests/multipart-test.c new file mode 100644 index 00000000..e057412e --- /dev/null +++ b/tests/multipart-test.c @@ -0,0 +1,521 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2011 Collabora Ltd. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "test-utils.h" + +#define READ_BUFFER_SIZE 8192 + +typedef enum { + NO_MULTIPART, + SYNC_MULTIPART, + ASYNC_MULTIPART, + ASYNC_MULTIPART_SMALL_READS +} MultipartMode; + +char *buffer; +SoupSession *session; +char *base_uri_string; +SoupURI *base_uri; +SoupMultipartInputStream *multipart; +unsigned passes; + + +/* This payload contains 4 different responses. + * + * First, a text/html response with a Content-Length (31); + * Second, a response lacking Content-Type with Content-Length (11); + * Third, a text/css response with no Content-Length; + * Fourth, same as the third, but with different content; + */ +const char *payload = \ + "--cut-here\r\n" \ + "Content-Type: text/html\n" + "Content-Length: 30\r\n" \ + "\r\n" \ + "<html><body>Hey!</body></html>" \ + "\r\n--cut-here\r\n" \ + "Content-Length: 10\r\n" \ + "\r\n" \ + "soup rocks" \ + "\r\n--cut-here\r\n" \ + "Content-Type: text/css\r\n" \ + "\r\n" \ + ".soup { before: rocks; }" \ + "\r\n--cut-here\n" /* Tests boundary ending in a single \n. */ \ + "Content-Type: text/css\r\n" \ + "\r\n" \ + "#soup { background-color: black; }" \ + "\r\n--cut-here--"; + +static void +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + if (msg->method != SOUP_METHOD_GET) { + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + return; + } + + soup_message_set_status (msg, SOUP_STATUS_OK); + + soup_message_headers_append (msg->response_headers, + "Content-Type", "multipart/x-mixed-replace; boundary=cut-here"); + + soup_message_body_append (msg->response_body, + SOUP_MEMORY_STATIC, + payload, + strlen (payload)); + + soup_message_body_complete (msg->response_body); +} + +static void +content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, int *sniffed_count) +{ + *sniffed_count = *sniffed_count + 1; + debug_printf (2, " content-sniffed -> %s\n", content_type); +} + +static void +check_is_next (gboolean is_next) +{ + soup_test_assert (is_next, + "expected a header, but there are no more headers"); +} + +static void +got_headers (SoupMessage *msg, int *headers_count) +{ + SoupMessageHeadersIter iter; + gboolean is_next; + const char* name, *value; + + *headers_count = *headers_count + 1; + + soup_message_headers_iter_init (&iter, msg->response_headers); + + is_next = soup_message_headers_iter_next (&iter, &name, &value); + check_is_next (is_next); + + if (g_str_equal (name, "Date")) { + is_next = soup_message_headers_iter_next (&iter, &name, &value); + check_is_next (is_next); + } + + g_assert_cmpstr (name, ==, "Content-Type"); + g_assert_cmpstr (value, ==, "multipart/x-mixed-replace; boundary=cut-here"); +} + +static void +read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data) +{ + GMainLoop *loop = (GMainLoop*)data; + GInputStream *stream = G_INPUT_STREAM (source); + GError *error = NULL; + gssize bytes_read; + + bytes_read = g_input_stream_read_finish (stream, asyncResult, &error); + g_assert_no_error (error); + if (error) { + g_object_unref (stream); + g_main_loop_quit (loop); + return; + } + + if (!bytes_read) { + g_input_stream_close (stream, NULL, &error); + g_assert_no_error (error); + g_object_unref (stream); + g_main_loop_quit (loop); + return; + } + + g_input_stream_read_async (stream, buffer, READ_BUFFER_SIZE, + G_PRIORITY_DEFAULT, NULL, + read_cb, data); +} + +static void +no_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data) +{ + GMainLoop *loop = (GMainLoop*)data; + SoupRequest *request = SOUP_REQUEST (source); + GError *error = NULL; + GInputStream* in; + + in = soup_request_send_finish (request, res, &error); + g_assert_no_error (error); + if (error) { + g_main_loop_quit (loop); + return; + } + + g_input_stream_read_async (in, buffer, READ_BUFFER_SIZE, + G_PRIORITY_DEFAULT, NULL, + read_cb, data); +} + +static void +multipart_close_part_cb (GObject *source, GAsyncResult *res, gpointer data) +{ + GInputStream *in = G_INPUT_STREAM (source); + GError *error = NULL; + + g_input_stream_close_finish (in, res, &error); + g_assert_no_error (error); +} + +static void multipart_next_part_cb (GObject *source, + GAsyncResult *res, + gpointer data); + +static void +check_read (gsize nread, unsigned passes) +{ + switch (passes) { + case 0: + g_assert_cmpint (nread, ==, 30); + break; + case 1: + g_assert_cmpint (nread, ==, 10); + break; + case 2: + g_assert_cmpint (nread, ==, 24); + break; + case 3: + g_assert_cmpint (nread, ==, 34); + break; + default: + soup_test_assert (FALSE, "unexpected read of size: %d", (int)nread); + break; + } +} + +static void +multipart_read_cb (GObject *source, GAsyncResult *asyncResult, gpointer data) +{ + GMainLoop *loop = (GMainLoop*)data; + GInputStream *in = G_INPUT_STREAM (source); + GError *error = NULL; + static gssize bytes_read_for_part = 0; + gssize bytes_read; + + bytes_read = g_input_stream_read_finish (in, asyncResult, &error); + g_assert_no_error (error); + if (error) { + g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL, + multipart_close_part_cb, NULL); + g_object_unref (in); + + g_main_loop_quit (loop); + return; + } + + /* Read 0 bytes - try to start reading another part. */ + if (!bytes_read) { + check_read (bytes_read_for_part, passes); + bytes_read_for_part = 0; + passes++; + + g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL, + multipart_close_part_cb, NULL); + g_object_unref (in); + + soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL, + multipart_next_part_cb, data); + return; + } + + bytes_read_for_part += bytes_read; + g_input_stream_read_async (in, buffer, READ_BUFFER_SIZE, + G_PRIORITY_DEFAULT, NULL, + multipart_read_cb, data); +} + +static void +check_headers (SoupMultipartInputStream* multipart, unsigned passes) +{ + SoupMessageHeaders *headers; + SoupMessageHeadersIter iter; + gboolean is_next; + const char *name, *value; + + headers = soup_multipart_input_stream_get_headers (multipart); + soup_message_headers_iter_init (&iter, headers); + + switch (passes) { + case 0: + is_next = soup_message_headers_iter_next (&iter, &name, &value); + check_is_next (is_next); + + g_assert_cmpstr (name, ==, "Content-Type"); + g_assert_cmpstr (value, ==, "text/html"); + + is_next = soup_message_headers_iter_next (&iter, &name, &value); + check_is_next (is_next); + + g_assert_cmpstr (name, ==, "Content-Length"); + g_assert_cmpstr (value, ==, "30"); + + break; + case 1: + is_next = soup_message_headers_iter_next (&iter, &name, &value); + check_is_next (is_next); + + g_assert_cmpstr (name, ==, "Content-Length"); + g_assert_cmpstr (value, ==, "10"); + + break; + case 2: + case 3: + is_next = soup_message_headers_iter_next (&iter, &name, &value); + check_is_next (is_next); + + g_assert_cmpstr (name, ==, "Content-Type"); + g_assert_cmpstr (value, ==, "text/css"); + + break; + default: + soup_test_assert (FALSE, "unexpected part received"); + break; + } +} + +static void +multipart_next_part_cb (GObject *source, GAsyncResult *res, gpointer data) +{ + GMainLoop *loop = (GMainLoop*)data; + GError *error = NULL; + GInputStream *in; + gsize read_size = READ_BUFFER_SIZE; + + g_assert (SOUP_MULTIPART_INPUT_STREAM (source) == multipart); + + in = soup_multipart_input_stream_next_part_finish (multipart, res, &error); + g_assert_no_error (error); + if (error) { + g_clear_error (&error); + g_object_unref (multipart); + g_main_loop_quit (loop); + return; + } + + if (!in) { + g_assert_cmpint (passes, ==, 4); + g_object_unref (multipart); + g_main_loop_quit (loop); + return; + } + + check_headers (multipart, passes); + + if (g_object_get_data (G_OBJECT (multipart), "multipart-small-reads")) + read_size = 4; + + g_input_stream_read_async (in, buffer, read_size, + G_PRIORITY_DEFAULT, NULL, + multipart_read_cb, data); +} + +static void +multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data) +{ + GMainLoop *loop = (GMainLoop*)data; + SoupRequest *request = SOUP_REQUEST (source); + GError *error = NULL; + GInputStream *in; + SoupMessage *message; + + in = soup_request_send_finish (request, res, &error); + g_assert_no_error (error); + if (error) { + g_main_loop_quit (loop); + return; + } + + message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request)); + multipart = soup_multipart_input_stream_new (message, in); + g_object_unref (message); + g_object_unref (in); + + if (g_object_get_data (source, "multipart-small-reads")) + g_object_set_data (G_OBJECT (multipart), "multipart-small-reads", GINT_TO_POINTER(1)); + + soup_multipart_input_stream_next_part_async (multipart, G_PRIORITY_DEFAULT, NULL, + multipart_next_part_cb, data); +} + +static void +sync_multipart_handling_cb (GObject *source, GAsyncResult *res, gpointer data) +{ + GMainLoop *loop = (GMainLoop*)data; + SoupRequest *request = SOUP_REQUEST (source); + GError *error = NULL; + GInputStream *in; + SoupMessage *message; + char buffer[READ_BUFFER_SIZE]; + gsize bytes_read; + + in = soup_request_send_finish (request, res, &error); + g_assert_no_error (error); + if (error) { + g_main_loop_quit (loop); + return; + } + + message = soup_request_http_get_message (SOUP_REQUEST_HTTP (request)); + multipart = soup_multipart_input_stream_new (message, in); + g_object_unref (message); + g_object_unref (in); + + while (TRUE) { + in = soup_multipart_input_stream_next_part (multipart, NULL, &error); + g_assert_no_error (error); + if (error) { + g_clear_error (&error); + break; + } + + if (!in) + break; + + check_headers (multipart, passes); + + g_input_stream_read_all (in, (void*)buffer, sizeof (buffer), &bytes_read, NULL, &error); + g_assert_no_error (error); + if (error) { + g_clear_error (&error); + g_object_unref (in); + break; + } + + check_read (bytes_read, passes); + + passes++; + g_object_unref (in); + } + + g_assert_cmpint (passes, ==, 4); + + g_main_loop_quit (loop); + g_object_unref (multipart); +} + +static void +test_multipart (gconstpointer data) +{ + int headers_expected = 1, sniffed_expected = 1; + MultipartMode multipart_mode = GPOINTER_TO_INT (data); + SoupRequest* request; + SoupMessage *msg; + GMainLoop *loop; + int headers_count = 0; + int sniffed_count = 0; + GHashTable *params; + const char *content_type; + gboolean message_is_multipart = FALSE; + GError* error = NULL; + + request = soup_session_request (session, base_uri_string, &error); + g_assert_no_error (error); + if (error) + return; + + msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request)); + + /* This is used to track the number of parts. */ + passes = 0; + + /* Force the server to close the connection. */ + soup_message_headers_append (msg->request_headers, + "Connection", "close"); + + g_signal_connect (msg, "got_headers", + G_CALLBACK (got_headers), &headers_count); + + g_signal_connect (msg, "content-sniffed", + G_CALLBACK (content_sniffed), &sniffed_count); + + loop = g_main_loop_new (NULL, TRUE); + + if (multipart_mode == ASYNC_MULTIPART) + soup_request_send_async (request, NULL, multipart_handling_cb, loop); + else if (multipart_mode == ASYNC_MULTIPART_SMALL_READS) { + g_object_set_data (G_OBJECT (request), "multipart-small-reads", GINT_TO_POINTER(1)); + soup_request_send_async (request, NULL, multipart_handling_cb, loop); + } else if (multipart_mode == SYNC_MULTIPART) + soup_request_send_async (request, NULL, sync_multipart_handling_cb, loop); + else + soup_request_send_async (request, NULL, no_multipart_handling_cb, loop); + + g_main_loop_run (loop); + + content_type = soup_message_headers_get_content_type (msg->response_headers, ¶ms); + + if (content_type && + g_str_has_prefix (content_type, "multipart/") && + g_hash_table_lookup (params, "boundary")) { + message_is_multipart = TRUE; + } + g_clear_pointer (¶ms, g_hash_table_unref); + + g_assert_true (message_is_multipart); + g_assert_cmpint (headers_count, ==, headers_expected); + g_assert_cmpint (sniffed_count, ==, sniffed_expected); + + g_object_unref (msg); + g_object_unref (request); + g_main_loop_unref (loop); +} + +int +main (int argc, char **argv) +{ + SoupServer *server; + int ret; + + test_init (argc, argv, NULL); + + buffer = g_malloc (READ_BUFFER_SIZE); + + server = soup_test_server_new (FALSE); + soup_server_add_handler (server, NULL, server_callback, NULL, NULL); + base_uri = soup_uri_new ("http://127.0.0.1"); + soup_uri_set_port (base_uri, soup_server_get_port (server)); + base_uri_string = soup_uri_to_string (base_uri, FALSE); + + /* FIXME: I had to raise the number of connections allowed here, otherwise I + * was hitting the limit, which indicates some connections are not dying. + */ + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + "use-thread-context", TRUE, + "max-conns", 20, + "max-conns-per-host", 20, + NULL); + soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER); + + g_test_add_data_func ("/multipart/no", GINT_TO_POINTER (NO_MULTIPART), test_multipart); + g_test_add_data_func ("/multipart/sync", GINT_TO_POINTER (SYNC_MULTIPART), test_multipart); + g_test_add_data_func ("/multipart/async", GINT_TO_POINTER (ASYNC_MULTIPART), test_multipart); + g_test_add_data_func ("/multipart/async-small-reads", GINT_TO_POINTER (ASYNC_MULTIPART_SMALL_READS), test_multipart); + + ret = g_test_run (); + + soup_uri_free (base_uri); + g_free (base_uri_string); + g_free (buffer); + + soup_test_session_abort_unref (session); + soup_test_server_quit_unref (server); + + test_cleanup (); + return ret; +} diff --git a/tests/no-ssl-test.c b/tests/no-ssl-test.c new file mode 100644 index 00000000..82532c74 --- /dev/null +++ b/tests/no-ssl-test.c @@ -0,0 +1,151 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +#include "test-utils.h" + +static void +do_ssl_test_for_session (SoupSession *session, const char *uri) +{ + SoupMessage *msg; + GTlsCertificate *cert = NULL; + GTlsCertificateFlags flags; + gboolean is_https; + + msg = soup_message_new ("GET", uri); + soup_session_send_message (session, msg); + soup_test_assert_message_status (msg, SOUP_STATUS_SSL_FAILED); + + is_https = soup_message_get_https_status (msg, &cert, &flags); + soup_test_assert (!is_https, "get_http_status() returned TRUE? (flags %x)", flags); + + g_assert_null (cert); + g_assert_false (soup_message_get_flags (msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED); + + g_object_unref (msg); +} + +static void +do_ssl_tests (gconstpointer uri) +{ + SoupSession *session; + + g_test_bug ("700518"); + + debug_printf (1, " plain\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION, NULL); + do_ssl_test_for_session (session, uri); + soup_test_session_abort_unref (session); + + debug_printf (1, " async\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + do_ssl_test_for_session (session, uri); + soup_test_session_abort_unref (session); + + debug_printf (1, " sync\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); + do_ssl_test_for_session (session, uri); + soup_test_session_abort_unref (session); +} + +static void +do_session_property_tests (void) +{ + gboolean use_system; + GTlsDatabase *tlsdb; + char *ca_file; + SoupSession *session; + + g_test_bug ("700518"); + + session = soup_session_async_new (); + + g_object_get (G_OBJECT (session), + "ssl-use-system-ca-file", &use_system, + "tls-database", &tlsdb, + "ssl-ca-file", &ca_file, + NULL); + soup_test_assert (!use_system, "ssl-use-system-ca-file defaults to TRUE"); + soup_test_assert (tlsdb == NULL, "tls-database set by default"); + soup_test_assert (ca_file == NULL, "ca-file set by default"); + + g_object_set (G_OBJECT (session), + "ssl-use-system-ca-file", TRUE, + NULL); + g_object_get (G_OBJECT (session), + "ssl-ca-file", &ca_file, + NULL); + soup_test_assert (ca_file == NULL, "setting ssl-use-system-ca-file set ssl-ca-file"); + + g_object_set (G_OBJECT (session), + "ssl-ca-file", + g_test_get_filename (G_TEST_DIST, "test-cert.pem", NULL), + NULL); + g_object_get (G_OBJECT (session), + "ssl-use-system-ca-file", &use_system, + "tls-database", &tlsdb, + "ssl-ca-file", &ca_file, + NULL); + soup_test_assert (ca_file == NULL, "setting ssl-ca-file did not fail"); + soup_test_assert (!use_system, "setting ssl-ca-file set ssl-use-system-ca-file"); + soup_test_assert (tlsdb == NULL, "setting ssl-ca-file set tls-database"); + + g_object_set (G_OBJECT (session), + "tls-database", NULL, + NULL); + g_object_get (G_OBJECT (session), + "ssl-use-system-ca-file", &use_system, + "tls-database", &tlsdb, + "ssl-ca-file", &ca_file, + NULL); + soup_test_assert (tlsdb == NULL, "setting tls-database NULL failed"); + soup_test_assert (!use_system, "setting tls-database NULL set ssl-use-system-ca-file"); + soup_test_assert (ca_file == NULL, "setting tls-database NULL set ssl-ca-file"); + + soup_test_session_abort_unref (session); +} + +static void +server_handler (SoupServer *server, + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *client, + gpointer user_data) +{ + soup_message_set_status (msg, SOUP_STATUS_OK); + soup_message_set_response (msg, "text/plain", + SOUP_MEMORY_STATIC, + "ok\r\n", 4); +} + +int +main (int argc, char **argv) +{ + SoupServer *server; + char *uri; + int ret; + + /* Force this test to use the dummy TLS backend */ + g_setenv ("GIO_USE_TLS", "dummy", TRUE); + + test_init (argc, argv, NULL); + + /* Make a non-SSL server and pretend that it's ssl, which is fine + * since we won't ever actually talk to it anyway. We don't + * currently test that failing to construct an SSL server works. + */ + server = soup_test_server_new (TRUE); + soup_server_add_handler (server, NULL, server_handler, NULL, NULL); + uri = g_strdup_printf ("https://127.0.0.1:%u/", + soup_server_get_port (server)); + + g_test_add_func ("/no-ssl/session-properties", do_session_property_tests); + g_test_add_data_func ("/no-ssl/request-error", uri, do_ssl_tests); + + ret = g_test_run (); + + g_free (uri); + soup_test_server_quit_unref (server); + + test_cleanup (); + return ret; +} diff --git a/tests/ntlm-test-helper.c b/tests/ntlm-test-helper.c new file mode 100644 index 00000000..f2450cae --- /dev/null +++ b/tests/ntlm-test-helper.c @@ -0,0 +1,103 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright 2012 Red Hat, Inc. + */ + +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <glib.h> +#include "libsoup/soup.h" + +const char *helper_protocol, *username, *domain; +gboolean use_cached_creds; + +static GOptionEntry entries[] = { + { "helper-protocol", 0, 0, + G_OPTION_ARG_STRING, &helper_protocol, + NULL, NULL }, + { "use-cached-creds", 0, 0, + G_OPTION_ARG_NONE, &use_cached_creds, + NULL, NULL }, + { "username", 0, 0, + G_OPTION_ARG_STRING, &username, + NULL, NULL }, + { "domain", 0, 0, + G_OPTION_ARG_STRING, &domain, + NULL, NULL }, + { NULL } +}; + +int +main (int argc, char **argv) +{ + GOptionContext *opts; + char buf[256], *header; + SoupMessage *msg; + SoupAuth *auth; + + /* Don't recurse */ + g_setenv ("SOUP_NTLM_AUTH_DEBUG", "", TRUE); + + setlocale (LC_ALL, ""); + + opts = g_option_context_new (NULL); + g_option_context_add_main_entries (opts, entries, NULL); + if (!g_option_context_parse (opts, &argc, &argv, NULL)) { + g_printerr ("Bad arguments\n"); + exit (1); + } + g_option_context_free (opts); + + if (!username || !use_cached_creds || !helper_protocol || + !g_str_equal (helper_protocol, "ntlmssp-client-1")) { + g_printerr ("Wrong arguments; this program is only intended for use by ntlm-test\n"); + exit (1); + } + + msg = soup_message_new ("GET", "http://localhost/"); + auth = NULL; + + while (fgets (buf, sizeof (buf), stdin)) { + if (strchr (buf, '\n')) + *strchr (buf, '\n') = '\0'; + if (!strcmp (buf, "YR")) { + if (g_getenv ("SOUP_NTLM_AUTH_DEBUG_NOCREDS")) { + g_print ("PW\n"); + continue; + } + + g_clear_object (&auth); + auth = g_object_new (SOUP_TYPE_AUTH_NTLM, NULL); + header = soup_auth_get_authorization (auth, msg); + g_print ("YR %s\n", header + 5); + g_free (header); + } else if (g_str_has_prefix (buf, "TT ")) { + header = g_strdup_printf ("NTLM %s\n", buf + 3); + if (!soup_auth_update (auth, msg, header)) { + g_printerr ("Bad challenge\n"); + exit (1); + } + g_free (header); + + soup_auth_authenticate (auth, username, "password"); + header = soup_auth_get_authorization (auth, msg); + if (!header) { + g_printerr ("Internal authentication failure\n"); + exit (1); + } + g_print ("KK %s\n", header + 5); + g_free (header); + } else { + g_printerr ("Unexpected command\n"); + exit (1); + } + } + + g_object_unref (msg); + g_clear_object (&auth); + + return 0; +} diff --git a/tests/ntlm-test.c b/tests/ntlm-test.c index 00222e87..24a0f2e4 100644 --- a/tests/ntlm-test.c +++ b/tests/ntlm-test.c @@ -11,6 +11,8 @@ #include "test-utils.h" +static SoupURI *uri; + typedef enum { NTLM_UNAUTHENTICATED, NTLM_RECEIVED_REQUEST, @@ -19,12 +21,16 @@ typedef enum { NTLM_AUTHENTICATED_BOB } NTLMServerState; +static const char *state_name[] = { + "unauth", "recv", "sent", "alice", "bob" +}; + #define NTLM_REQUEST_START "TlRMTVNTUAABAAAA" #define NTLM_RESPONSE_START "TlRMTVNTUAADAAAA" #define NTLM_CHALLENGE "TlRMTVNTUAACAAAADAAMADAAAAABAoEAASNFZ4mrze8AAAAAAAAAAGIAYgA8AAAARABPAE0AQQBJAE4AAgAMAEQATwBNAEEASQBOAAEADABTAEUAUgBWAEUAUgAEABQAZABvAG0AYQBpAG4ALgBjAG8AbQADACIAcwBlAHIAdgBlAHIALgBkAG8AbQBhAGkAbgAuAGMAbwBtAAAAAAA=" -#define NTLM_RESPONSE_USER(response) ((response)[102] == 'E' ? NTLM_AUTHENTICATED_ALICE : NTLM_AUTHENTICATED_BOB) +#define NTLM_RESPONSE_USER(response) ((response)[86] == 'E' ? NTLM_AUTHENTICATED_ALICE : ((response)[86] == 'I' ? NTLM_AUTHENTICATED_BOB : NTLM_UNAUTHENTICATED)) static void clear_state (gpointer connections, GObject *ex_connection) @@ -134,16 +140,22 @@ server_callback (SoupServer *server, SoupMessage *msg, } } + debug_printf (2, " (S:%s)", state_name[state]); g_hash_table_insert (connections, socket, GINT_TO_POINTER (state)); g_object_weak_ref (G_OBJECT (socket), clear_state, connections); } +static gboolean authenticated_ntlm = FALSE; + static void authenticate (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer user) { - if (!retrying) + if (!retrying) { soup_auth_authenticate (auth, user, "password"); + if (g_str_equal (soup_auth_get_scheme_name (auth), "NTLM")) + authenticated_ntlm = TRUE; + } } typedef struct { @@ -238,82 +250,65 @@ do_message (SoupSession *session, SoupURI *base_uri, const char *path, if (state.got_ntlm_prompt) { debug_printf (1, " NTLM_PROMPT"); - if (!get_ntlm_prompt) { + if (!get_ntlm_prompt) debug_printf (1, "???"); - errors++; - } - } else if (get_ntlm_prompt) { + } else if (get_ntlm_prompt) debug_printf (1, " no-ntlm-prompt???"); - errors++; - } if (state.got_basic_prompt) { debug_printf (1, " BASIC_PROMPT"); - if (!get_basic_prompt) { + if (!get_basic_prompt) debug_printf (1, "???"); - errors++; - } - } else if (get_basic_prompt) { + } else if (get_basic_prompt) debug_printf (1, " no-basic-prompt???"); - errors++; - } if (state.sent_ntlm_request) { debug_printf (1, " REQUEST"); - if (!do_ntlm) { + if (!do_ntlm) debug_printf (1, "???"); - errors++; - } - } else if (do_ntlm) { + } else if (do_ntlm) debug_printf (1, " no-request???"); - errors++; - } if (state.got_ntlm_challenge) { debug_printf (1, " CHALLENGE"); - if (!do_ntlm) { + if (!do_ntlm) debug_printf (1, "???"); - errors++; - } - } else if (do_ntlm) { + } else if (do_ntlm) debug_printf (1, " no-challenge???"); - errors++; - } if (state.sent_ntlm_response) { debug_printf (1, " NTLM_RESPONSE"); - if (!do_ntlm) { + if (!do_ntlm) debug_printf (1, "???"); - errors++; - } - } else if (do_ntlm) { + } else if (do_ntlm) debug_printf (1, " no-ntlm-response???"); - errors++; - } if (state.sent_basic_response) { debug_printf (1, " BASIC_RESPONSE"); - if (!do_basic) { + if (!do_basic) debug_printf (1, "???"); - errors++; - } - } else if (do_basic) { + } else if (do_basic) debug_printf (1, " no-basic-response???"); - errors++; - } debug_printf (1, " -> %s", msg->reason_phrase); - if (msg->status_code != status_code) { + if (msg->status_code != status_code) debug_printf (1, "???"); - errors++; - } debug_printf (1, "\n"); + g_assert_true (state.got_ntlm_prompt == get_ntlm_prompt); + g_assert_true (state.got_basic_prompt == get_basic_prompt); + g_assert_true (state.sent_ntlm_request == do_ntlm); + g_assert_true (state.got_ntlm_challenge == do_ntlm); + g_assert_true (state.sent_ntlm_response == do_ntlm); + g_assert_true (state.sent_basic_response == do_basic); + soup_test_assert_message_status (msg, status_code); + g_object_unref (msg); } static void -do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user) +do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, + const char *user, gboolean use_builtin_ntlm) { SoupSession *session; gboolean alice = !g_strcmp0 (user, "alice"); @@ -322,13 +317,23 @@ do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user) gboolean bob_via_ntlm = use_ntlm && bob; gboolean alice_via_basic = !use_ntlm && alice; - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - if (use_ntlm) - soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM); + session = soup_test_session_new (SOUP_TYPE_SESSION, NULL); if (user) { g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), (char *)user); + if (use_ntlm && !use_builtin_ntlm) + g_setenv ("NTLMUSER", user, TRUE); + } + if (use_ntlm) { + SoupAuthManager *auth_manager; + SoupAuth *ntlm; + + soup_session_add_feature_by_type (session, SOUP_TYPE_AUTH_NTLM); + auth_manager = SOUP_AUTH_MANAGER (soup_session_get_feature (session, SOUP_TYPE_AUTH_MANAGER)); + ntlm = g_object_new (SOUP_TYPE_AUTH_NTLM, NULL); + soup_auth_manager_use_auth (auth_manager, base_uri, ntlm); + g_object_unref (ntlm); } /* 1. Server doesn't request auth, so both get_ntlm_prompt and @@ -336,11 +341,17 @@ do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user) * if we're using NTLM we'll try that even without the server * asking. */ + authenticated_ntlm = FALSE; do_message (session, base_uri, "/noauth", FALSE, use_ntlm, FALSE, FALSE, SOUP_STATUS_OK); + soup_test_assert (authenticated_ntlm == (use_ntlm && use_builtin_ntlm), + "%s built-in NTLM support, but authenticate signal %s emitted\n", + use_builtin_ntlm ? "Using" : "Not using", + authenticated_ntlm ? "was" : "wasn't"); + /* 2. Server requires auth as Alice, so it will request that * if we didn't already authenticate the connection to her in * the previous step. If we authenticated as Bob in the @@ -421,46 +432,190 @@ do_ntlm_round (SoupURI *base_uri, gboolean use_ntlm, const char *user) soup_test_session_abort_unref (session); } +typedef enum { + BUILTIN, + WINBIND, + FALLBACK +} NtlmType; + +typedef struct { + const char *name, *user; + gboolean conn_uses_ntlm; + NtlmType ntlm_type; +} NtlmTest; + +static const NtlmTest ntlm_tests[] = { + { "/ntlm/builtin/none", NULL, FALSE, BUILTIN }, + { "/ntlm/builtin/alice", "alice", TRUE, BUILTIN }, + { "/ntlm/builtin/bob", "bob", TRUE, BUILTIN }, + { "/ntlm/builtin/basic", "alice", FALSE, BUILTIN }, + + { "/ntlm/winbind/none", NULL, FALSE, WINBIND }, + { "/ntlm/winbind/alice", "alice", TRUE, WINBIND }, + { "/ntlm/winbind/bob", "bob", TRUE, WINBIND }, + { "/ntlm/winbind/basic", "alice", FALSE, WINBIND }, + + { "/ntlm/fallback/none", NULL, FALSE, FALLBACK }, + { "/ntlm/fallback/alice", "alice", TRUE, FALLBACK }, + { "/ntlm/fallback/bob", "bob", TRUE, FALLBACK }, + { "/ntlm/fallback/basic", "alice", FALSE, FALLBACK } +}; + static void -do_ntlm_tests (SoupURI *base_uri) +do_ntlm_test (gconstpointer data) { - debug_printf (1, "Round 1: Non-NTLM Connection, no auth\n"); - do_ntlm_round (base_uri, FALSE, NULL); - debug_printf (1, "Round 2: NTLM Connection, user=alice\n"); - do_ntlm_round (base_uri, TRUE, "alice"); - debug_printf (1, "Round 3: NTLM Connection, user=bob\n"); - do_ntlm_round (base_uri, TRUE, "bob"); - debug_printf (1, "Round 4: Non-NTLM Connection, user=alice\n"); - do_ntlm_round (base_uri, FALSE, "alice"); + const NtlmTest *test = data; + gboolean use_builtin_ntlm = TRUE; + + switch (test->ntlm_type) { + case BUILTIN: + /* Built-in NTLM auth support. (We set SOUP_NTLM_AUTH_DEBUG to + * an empty string to ensure that the built-in support is + * being used, even if /usr/bin/ntlm_auth is available.) + */ + g_setenv ("SOUP_NTLM_AUTH_DEBUG", "", TRUE); + break; + + case WINBIND: +#ifndef USE_NTLM_AUTH + g_test_skip ("/usr/bin/ntlm_auth is not available"); + return; +#endif + + /* Samba winbind /usr/bin/ntlm_auth helper support (via a + * helper program that emulates its interface). + */ + g_setenv ("SOUP_NTLM_AUTH_DEBUG", + g_test_get_filename (G_TEST_BUILT, "ntlm-test-helper", NULL), + TRUE); + g_unsetenv ("SOUP_NTLM_AUTH_DEBUG_NOCREDS"); + use_builtin_ntlm = FALSE; + break; + + case FALLBACK: +#ifndef USE_NTLM_AUTH + g_test_skip ("/usr/bin/ntlm_auth is not available"); + return; +#endif + + /* Support for when ntlm_auth is installed, but the user has + * no cached credentials (and thus we have to fall back to + * libsoup's built-in NTLM support). + */ + g_setenv ("SOUP_NTLM_AUTH_DEBUG", + g_test_get_filename (G_TEST_BUILT, "ntlm-test-helper", NULL), + TRUE); + g_setenv ("SOUP_NTLM_AUTH_DEBUG_NOCREDS", "1", TRUE); + break; + } + + do_ntlm_round (uri, test->conn_uses_ntlm, test->user, use_builtin_ntlm); +} + +static void +retry_test_authenticate (SoupSession *session, SoupMessage *msg, + SoupAuth *auth, gboolean retrying, + gpointer user_data) +{ + gboolean *retried = user_data; + + if (!retrying) { + /* server_callback doesn't actually verify the password, + * only the username. So we pass an incorrect username + * rather than an incorrect password. + */ + soup_auth_authenticate (auth, "wrong", "password"); + } else if (!*retried) { + soup_auth_authenticate (auth, "alice", "password"); + *retried = TRUE; + } +} + +static void +do_retrying_test (gconstpointer data) +{ + SoupURI *base_uri = (SoupURI *)data; + SoupSession *session; + SoupMessage *msg; + SoupURI *uri; + gboolean retried = FALSE; + + g_test_bug ("693222"); + + g_setenv ("SOUP_NTLM_AUTH_DEBUG", "", TRUE); + + debug_printf (1, " /alice\n"); + + session = soup_test_session_new (SOUP_TYPE_SESSION, + SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_NTLM, + NULL); + g_signal_connect (session, "authenticate", + G_CALLBACK (retry_test_authenticate), &retried); + + uri = soup_uri_new_with_base (base_uri, "/alice"); + msg = soup_message_new_from_uri ("GET", uri); + soup_uri_free (uri); + + soup_session_send_message (session, msg); + + g_assert_true (retried); + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + + g_object_unref (msg); + + soup_test_session_abort_unref (session); + + debug_printf (1, " /bob\n"); + + session = soup_test_session_new (SOUP_TYPE_SESSION, + SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_AUTH_NTLM, + NULL); + g_signal_connect (session, "authenticate", + G_CALLBACK (retry_test_authenticate), &retried); + retried = FALSE; + + uri = soup_uri_new_with_base (base_uri, "/bob"); + msg = soup_message_new_from_uri ("GET", uri); + soup_uri_free (uri); + + soup_session_send_message (session, msg); + + g_assert_true (retried); + soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED); + + g_object_unref (msg); + + soup_test_session_abort_unref (session); } int main (int argc, char **argv) { - GMainLoop *loop; SoupServer *server; GHashTable *connections; - SoupURI *uri; + int i, ret; test_init (argc, argv, NULL); - server = soup_test_server_new (FALSE); + server = soup_test_server_new (TRUE); connections = g_hash_table_new (NULL, NULL); soup_server_add_handler (server, NULL, server_callback, connections, NULL); - loop = g_main_loop_new (NULL, TRUE); - uri = soup_uri_new ("http://127.0.0.1/"); soup_uri_set_port (uri, soup_server_get_port (server)); - do_ntlm_tests (uri); - soup_uri_free (uri); - g_main_loop_unref (loop); + for (i = 0; i < G_N_ELEMENTS (ntlm_tests); i++) + g_test_add_data_func (ntlm_tests[i].name, &ntlm_tests[i], do_ntlm_test); + g_test_add_data_func ("/ntlm/retry", uri, do_retrying_test); + + ret = g_test_run (); + + soup_uri_free (uri); soup_test_server_quit_unref (server); test_cleanup (); g_hash_table_destroy (connections); - return errors != 0; + return ret; } diff --git a/tests/proxy-test.c b/tests/proxy-test.c index 85aac9f5..4b6679b3 100644 --- a/tests/proxy-test.c +++ b/tests/proxy-test.c @@ -6,16 +6,18 @@ typedef struct { const char *explanation; const char *url; const guint final_status; + const char *bugref; } SoupProxyTest; static SoupProxyTest tests[] = { - { "GET -> 200", "", SOUP_STATUS_OK }, - { "GET -> 404", "/not-found", SOUP_STATUS_NOT_FOUND }, - { "GET -> 401 -> 200", "/Basic/realm1/", SOUP_STATUS_OK }, - { "GET -> 401 -> 401", "/Basic/realm2/", SOUP_STATUS_UNAUTHORIZED }, - { "GET -> 403", "http://no-such-hostname.xx/", SOUP_STATUS_FORBIDDEN }, + { "GET -> 200", "", SOUP_STATUS_OK, NULL }, + { "GET -> 404", "/not-found", SOUP_STATUS_NOT_FOUND, NULL }, + { "GET -> 401 -> 200", "/Basic/realm1/", SOUP_STATUS_OK, NULL }, + { "GET -> 401 -> 401", "/Basic/realm2/", SOUP_STATUS_UNAUTHORIZED, NULL }, + { "GET -> 403", "http://no-such-hostname.xx/", SOUP_STATUS_FORBIDDEN, "577532" }, + { "GET -> 200 (unproxied)", "http://localhost:47524/", SOUP_STATUS_OK, "700472" }, }; -static int ntests = sizeof (tests) / sizeof (tests[0]); +static const int ntests = sizeof (tests) / sizeof (tests[0]); #define HTTP_SERVER "http://127.0.0.1:47524" #define HTTPS_SERVER "https://127.0.0.1:47525" @@ -35,25 +37,23 @@ static const char *proxy_names[] = { "authenticated proxy", "unauthenticatable-to proxy" }; +static GProxyResolver *proxy_resolvers[3]; +static const char *ignore_hosts[] = { "localhost", NULL }; static void authenticate (SoupSession *session, SoupMessage *msg, SoupAuth *auth, gboolean retrying, gpointer data) { if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) { - if (soup_auth_is_for_proxy (auth)) { - debug_printf (1, " got proxy auth object for 401!\n"); - errors++; - } + soup_test_assert (!soup_auth_is_for_proxy (auth), + "got proxy auth object for 401"); } else if (msg->status_code == SOUP_STATUS_PROXY_UNAUTHORIZED) { - if (!soup_auth_is_for_proxy (auth)) { - debug_printf (1, " got regular auth object for 407!\n"); - errors++; - } + soup_test_assert (soup_auth_is_for_proxy (auth), + "got regular auth object for 407"); } else { - debug_printf (1, " got authenticate signal with status %d\n", - msg->status_code); - errors++; + soup_test_assert (FALSE, + "got authenticate signal with status %d\n", + msg->status_code); } if (!retrying) @@ -80,28 +80,29 @@ test_url (const char *url, int proxy, guint expected, gboolean sync, gboolean close) { SoupSession *session; - SoupURI *proxy_uri; SoupMessage *msg; + gboolean noproxy = !!strstr (url, "localhost"); if (!tls_available && g_str_has_prefix (url, "https:")) return; debug_printf (1, " GET %s via %s%s\n", url, proxy_names[proxy], close ? " (with Connection: close)" : ""); - if (proxy == UNAUTH_PROXY && expected != SOUP_STATUS_FORBIDDEN) + if (proxy == UNAUTH_PROXY && expected != SOUP_STATUS_FORBIDDEN && !noproxy) expected = SOUP_STATUS_PROXY_UNAUTHORIZED; /* We create a new session for each request to ensure that * connections/auth aren't cached between tests. */ - proxy_uri = soup_uri_new (proxies[proxy]); session = soup_test_session_new (sync ? SOUP_TYPE_SESSION_SYNC : SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_PROXY_URI, proxy_uri, + SOUP_SESSION_PROXY_RESOLVER, proxy_resolvers[proxy], + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + SOUP_SESSION_SSL_STRICT, FALSE, NULL); - soup_uri_free (proxy_uri); g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), NULL); if (close) { + /* FIXME g_test_bug ("611663") */ g_signal_connect (session, "request-started", G_CALLBACK (set_close_on_connect), NULL); } @@ -115,10 +116,7 @@ test_url (const char *url, int proxy, guint expected, soup_session_send_message (session, msg); debug_printf (1, " %d %s\n", msg->status_code, msg->reason_phrase); - if (msg->status_code != expected) { - debug_printf (1, " EXPECTED %d!\n", expected); - errors++; - } + soup_test_assert_message_status (msg, expected); g_object_unref (msg); soup_test_session_abort_unref (session); @@ -129,73 +127,54 @@ test_url_new_api (const char *url, int proxy, guint expected, gboolean sync, gboolean close) { SoupSession *session; - SoupURI *proxy_uri; SoupMessage *msg; - SoupRequester *requester; SoupRequest *request; GInputStream *stream; GError *error = NULL; + gboolean noproxy = !!strstr (url, "localhost"); + /* FIXME g_test_skip() FIXME g_test_bug ("675865") */ if (!tls_available && g_str_has_prefix (url, "https:")) return; - debug_printf (1, " GET (requester API) %s via %s%s\n", url, proxy_names[proxy], + debug_printf (1, " GET (request API) %s via %s%s\n", url, proxy_names[proxy], close ? " (with Connection: close)" : ""); - if (proxy == UNAUTH_PROXY && expected != SOUP_STATUS_FORBIDDEN) + if (proxy == UNAUTH_PROXY && expected != SOUP_STATUS_FORBIDDEN && !noproxy) expected = SOUP_STATUS_PROXY_UNAUTHORIZED; /* We create a new session for each request to ensure that * connections/auth aren't cached between tests. */ - proxy_uri = soup_uri_new (proxies[proxy]); session = soup_test_session_new (sync ? SOUP_TYPE_SESSION_SYNC : SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, - SOUP_SESSION_PROXY_URI, proxy_uri, + SOUP_SESSION_PROXY_RESOLVER, proxy_resolvers[proxy], + SOUP_SESSION_SSL_STRICT, FALSE, NULL); - soup_uri_free (proxy_uri); g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), NULL); if (close) { + /* FIXME g_test_bug ("611663") */ g_signal_connect (session, "request-started", G_CALLBACK (set_close_on_connect), NULL); } - requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); - request = soup_requester_request (requester, url, NULL); + request = soup_session_request (session, url, NULL); msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request)); - if (sync) - stream = soup_request_send (request, NULL, &error); - else - stream = soup_test_request_send_async_as_sync (request, NULL, &error); - - if (!stream) { - debug_printf (1, " Unexpected error on Request: %s\n", - error->message); - errors++; - g_clear_error (&error); - } + stream = soup_test_request_send (request, NULL, 0, &error); + g_assert_no_error (error); + g_clear_error (&error); if (stream) { - if (sync) - g_input_stream_close (stream, NULL, NULL); - else - soup_test_stream_close_async_as_sync (stream, NULL, NULL); - if (error) { - debug_printf (1, " Unexpected error on close: %s\n", - error->message); - errors++; - g_clear_error (&error); - } + soup_test_request_close_stream (request, stream, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + g_object_unref (stream); } debug_printf (1, " %d %s\n", msg->status_code, msg->reason_phrase); - if (msg->status_code != expected) { - debug_printf (1, " EXPECTED %d!\n", expected); - errors++; - } + soup_test_assert_message_status (msg, expected); g_object_unref (msg); g_object_unref (request); @@ -204,42 +183,70 @@ test_url_new_api (const char *url, int proxy, guint expected, } static void -run_test (int i, gboolean sync) +do_proxy_test (SoupProxyTest *test, gboolean sync) { char *http_url, *https_url; - debug_printf (1, "Test %d: %s (%s)\n", i + 1, tests[i].explanation, - sync ? "sync" : "async"); + if (test->bugref) + g_test_bug (test->bugref); + + if (!strncmp (test->url, "http", 4)) { + SoupURI *uri; + guint port; - if (!strncmp (tests[i].url, "http", 4)) { - http_url = g_strdup (tests[i].url); - https_url = g_strdup_printf ("https%s", tests[i].url + 4); + http_url = g_strdup (test->url); + + uri = soup_uri_new (test->url); + port = uri->port; + soup_uri_set_scheme (uri, "https"); + if (port) + soup_uri_set_port (uri, port + 1); + https_url = soup_uri_to_string (uri, FALSE); + soup_uri_free (uri); } else { - http_url = g_strconcat (HTTP_SERVER, tests[i].url, NULL); - https_url = g_strconcat (HTTPS_SERVER, tests[i].url, NULL); + http_url = g_strconcat (HTTP_SERVER, test->url, NULL); + https_url = g_strconcat (HTTPS_SERVER, test->url, NULL); } - test_url (http_url, SIMPLE_PROXY, tests[i].final_status, sync, FALSE); - test_url_new_api (http_url, SIMPLE_PROXY, tests[i].final_status, sync, FALSE); - test_url (https_url, SIMPLE_PROXY, tests[i].final_status, sync, FALSE); - test_url_new_api (https_url, SIMPLE_PROXY, tests[i].final_status, sync, FALSE); + test_url (http_url, SIMPLE_PROXY, test->final_status, sync, FALSE); + test_url_new_api (http_url, SIMPLE_PROXY, test->final_status, sync, FALSE); + test_url (https_url, SIMPLE_PROXY, test->final_status, sync, FALSE); + test_url_new_api (https_url, SIMPLE_PROXY, test->final_status, sync, FALSE); - test_url (http_url, AUTH_PROXY, tests[i].final_status, sync, FALSE); - test_url_new_api (http_url, AUTH_PROXY, tests[i].final_status, sync, FALSE); - test_url (https_url, AUTH_PROXY, tests[i].final_status, sync, FALSE); - test_url_new_api (https_url, AUTH_PROXY, tests[i].final_status, sync, FALSE); - test_url (https_url, AUTH_PROXY, tests[i].final_status, sync, TRUE); - test_url_new_api (https_url, AUTH_PROXY, tests[i].final_status, sync, TRUE); + test_url (http_url, AUTH_PROXY, test->final_status, sync, FALSE); + test_url_new_api (http_url, AUTH_PROXY, test->final_status, sync, FALSE); + test_url (https_url, AUTH_PROXY, test->final_status, sync, FALSE); + test_url_new_api (https_url, AUTH_PROXY, test->final_status, sync, FALSE); + test_url (https_url, AUTH_PROXY, test->final_status, sync, TRUE); + test_url_new_api (https_url, AUTH_PROXY, test->final_status, sync, TRUE); - test_url (http_url, UNAUTH_PROXY, tests[i].final_status, sync, FALSE); - test_url_new_api (http_url, UNAUTH_PROXY, tests[i].final_status, sync, FALSE); - test_url (https_url, UNAUTH_PROXY, tests[i].final_status, sync, FALSE); - test_url_new_api (https_url, UNAUTH_PROXY, tests[i].final_status, sync, FALSE); + test_url (http_url, UNAUTH_PROXY, test->final_status, sync, FALSE); + test_url_new_api (http_url, UNAUTH_PROXY, test->final_status, sync, FALSE); + test_url (https_url, UNAUTH_PROXY, test->final_status, sync, FALSE); + test_url_new_api (https_url, UNAUTH_PROXY, test->final_status, sync, FALSE); g_free (http_url); g_free (https_url); +} + +static void +do_async_proxy_test (gconstpointer data) +{ + SoupProxyTest *test = (SoupProxyTest *)data; + + SOUP_TEST_SKIP_IF_NO_APACHE; - debug_printf (1, "\n"); + do_proxy_test (test, FALSE); +} + +static void +do_sync_proxy_test (gconstpointer data) +{ + SoupProxyTest *test = (SoupProxyTest *)data; + + SOUP_TEST_SKIP_IF_NO_APACHE; + + do_proxy_test (test, TRUE); } static void @@ -253,13 +260,14 @@ server_callback (SoupServer *server, SoupMessage *msg, } static void -do_proxy_fragment_test (SoupURI *base_uri) +do_proxy_fragment_test (gconstpointer data) { + SoupURI *base_uri = (SoupURI *)data; SoupSession *session; SoupURI *proxy_uri, *req_uri; SoupMessage *msg; - debug_printf (1, "\nTesting request with fragment via proxy\n"); + SOUP_TEST_SKIP_IF_NO_APACHE; proxy_uri = soup_uri_new (proxies[SIMPLE_PROXY]); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, @@ -272,11 +280,7 @@ do_proxy_fragment_test (SoupURI *base_uri) soup_uri_free (req_uri); soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " unexpected status %d %s!\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); g_object_unref (msg); soup_test_session_abort_unref (session); @@ -289,7 +293,10 @@ do_proxy_redirect_test (void) SoupURI *proxy_uri, *req_uri, *new_uri; SoupMessage *msg; - debug_printf (1, "\nTesting redirection through proxy\n"); + g_test_bug ("631368"); + + SOUP_TEST_SKIP_IF_NO_APACHE; + SOUP_TEST_SKIP_IF_NO_TLS; proxy_uri = soup_uri_new (proxies[SIMPLE_PROXY]); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, @@ -305,17 +312,11 @@ do_proxy_redirect_test (void) soup_session_send_message (session, msg); new_uri = soup_message_get_uri (msg); - if (!strcmp (req_uri->path, new_uri->path)) { - debug_printf (1, " message was not redirected!\n"); - errors++; - } + soup_test_assert (strcmp (req_uri->path, new_uri->path) != 0, + "message was not redirected"); soup_uri_free (req_uri); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " unexpected status %d %s!\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); g_object_unref (msg); soup_test_session_abort_unref (session); @@ -326,14 +327,15 @@ main (int argc, char **argv) { SoupServer *server; SoupURI *base_uri; - int i; + char *path; + int i, ret; test_init (argc, argv, NULL); apache_init (); - for (i = 0; i < ntests; i++) { - run_test (i, FALSE); - run_test (i, TRUE); + for (i = 0; i < 3; i++) { + proxy_resolvers[i] = + g_simple_proxy_resolver_new (proxies[i], (char **) ignore_hosts); } server = soup_test_server_new (TRUE); @@ -341,12 +343,25 @@ main (int argc, char **argv) base_uri = soup_uri_new ("http://127.0.0.1/"); soup_uri_set_port (base_uri, soup_server_get_port (server)); - do_proxy_fragment_test (base_uri); - do_proxy_redirect_test (); + for (i = 0; i < ntests; i++) { + path = g_strdup_printf ("/proxy/async/%s", tests[i].explanation); + g_test_add_data_func (path, &tests[i], do_async_proxy_test); + g_free (path); + } + for (i = 0; i < ntests; i++) { + path = g_strdup_printf ("/proxy/sync/%s", tests[i].explanation); + g_test_add_data_func (path, &tests[i], do_sync_proxy_test); + g_free (path); + } + + g_test_add_data_func ("/proxy/fragment", base_uri, do_proxy_fragment_test); + g_test_add_func ("/proxy/redirect", do_proxy_redirect_test); + + ret = g_test_run (); soup_uri_free (base_uri); soup_test_server_quit_unref (server); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/pull-api.c b/tests/pull-api.c index 512d1768..2915b9e4 100644 --- a/tests/pull-api.c +++ b/tests/pull-api.c @@ -12,6 +12,7 @@ authenticate (SoupSession *session, SoupMessage *msg, soup_auth_authenticate (auth, "user2", "realm2"); } +#if HAVE_APACHE static void get_correct_response (const char *uri) { @@ -32,6 +33,7 @@ get_correct_response (const char *uri) g_object_unref (msg); soup_test_session_abort_unref (session); } +#endif /* Pull API version 1: fully-async. More like a "poke" API. Rather * than having SoupMessage emit "got_chunk" signals whenever it wants, @@ -161,9 +163,7 @@ fully_async_got_headers (SoupMessage *msg, gpointer user_data) */ return; } else if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; + soup_test_assert_message_status (msg, SOUP_STATUS_OK); return; } @@ -192,18 +192,10 @@ fully_async_got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) * test program, that means comparing it against * correct_response to make sure that we got the right data. */ - if (ad->read_so_far + chunk->length > correct_response->length) { - debug_printf (1, " read too far! (%lu > %lu)\n", - (unsigned long) (ad->read_so_far + chunk->length), - (unsigned long) correct_response->length); - errors++; - } else if (memcmp (chunk->data, - correct_response->data + ad->read_so_far, - chunk->length) != 0) { - debug_printf (1, " data mismatch in block starting at %lu\n", - (unsigned long) ad->read_so_far); - errors++; - } + g_assert_cmpint (ad->read_so_far + chunk->length, <=, correct_response->length); + soup_assert_cmpmem (chunk->data, chunk->length, + correct_response->data + ad->read_so_far, + chunk->length); ad->read_so_far += chunk->length; /* Now pause I/O, and prepare to read another chunk later. @@ -224,11 +216,7 @@ fully_async_finished (SoupSession *session, SoupMessage *msg, { FullyAsyncData *ad = user_data; - if (msg->status_code != ad->expected_status) { - debug_printf (1, " unexpected final status: %d %s !\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, ad->expected_status); if (ad->timeout != 0) g_source_remove (ad->timeout); @@ -240,6 +228,45 @@ fully_async_finished (SoupSession *session, SoupMessage *msg, g_main_loop_quit (ad->loop); } +static void +do_fast_async_test (gconstpointer data) +{ + const char *base_uri = data; + SoupSession *session; + + SOUP_TEST_SKIP_IF_NO_APACHE; + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + g_signal_connect (session, "authenticate", + G_CALLBACK (authenticate), NULL); + do_fully_async_test (session, base_uri, "/", + TRUE, SOUP_STATUS_OK); + do_fully_async_test (session, base_uri, "/Basic/realm1/", + TRUE, SOUP_STATUS_UNAUTHORIZED); + do_fully_async_test (session, base_uri, "/Basic/realm2/", + TRUE, SOUP_STATUS_OK); + soup_test_session_abort_unref (session); +} + +static void +do_slow_async_test (gconstpointer data) +{ + const char *base_uri = data; + SoupSession *session; + + SOUP_TEST_SKIP_IF_NO_APACHE; + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + g_signal_connect (session, "authenticate", + G_CALLBACK (authenticate), NULL); + do_fully_async_test (session, base_uri, "/", + FALSE, SOUP_STATUS_OK); + do_fully_async_test (session, base_uri, "/Basic/realm1/", + FALSE, SOUP_STATUS_UNAUTHORIZED); + do_fully_async_test (session, base_uri, "/Basic/realm2/", + FALSE, SOUP_STATUS_OK); + soup_test_session_abort_unref (session); +} /* Pull API version 2: synchronous pull API via async I/O. */ @@ -284,14 +311,12 @@ do_synchronously_async_test (SoupSession *session, /* Send the message, get back headers */ sync_async_send (session, msg); - if (sync_async_is_finished (msg) && - expected_status == SOUP_STATUS_OK) { - debug_printf (1, " finished without reading response!\n"); - errors++; - } else if (!sync_async_is_finished (msg) && - expected_status != SOUP_STATUS_OK) { - debug_printf (1, " request failed to fail!\n"); - errors++; + if (expected_status == SOUP_STATUS_OK) { + soup_test_assert (!sync_async_is_finished (msg), + "finished without reading response"); + } else { + soup_test_assert (sync_async_is_finished (msg), + "request failed to fail"); } /* Now we're ready to read the response body (though we could @@ -303,32 +328,19 @@ do_synchronously_async_test (SoupSession *session, (unsigned long) read_so_far, (unsigned long) read_so_far + chunk->length); - if (read_so_far + chunk->length > correct_response->length) { - debug_printf (1, " read too far! (%lu > %lu)\n", - (unsigned long) read_so_far + chunk->length, - (unsigned long) correct_response->length); - errors++; - } else if (memcmp (chunk->data, - correct_response->data + read_so_far, - chunk->length) != 0) { - debug_printf (1, " data mismatch in block starting at %lu\n", - (unsigned long) read_so_far); - errors++; - } + g_assert_cmpint (read_so_far + chunk->length, <=, correct_response->length); + soup_assert_cmpmem (chunk->data, chunk->length, + correct_response->data + read_so_far, + chunk->length); + read_so_far += chunk->length; soup_buffer_free (chunk); } - if (!sync_async_is_finished (msg) || - (msg->status_code == SOUP_STATUS_OK && - read_so_far != correct_response->length)) { - debug_printf (1, " loop ended before message was fully read!\n"); - errors++; - } else if (msg->status_code != expected_status) { - debug_printf (1, " unexpected final status: %d %s !\n", - msg->status_code, msg->reason_phrase); - errors++; - } + g_assert_true (sync_async_is_finished (msg)); + soup_test_assert_message_status (msg, expected_status); + if (msg->status_code == SOUP_STATUS_OK) + g_assert_cmpint (read_so_far, ==, correct_response->length); sync_async_cleanup (msg); g_object_unref (msg); @@ -393,9 +405,7 @@ sync_async_got_headers (SoupMessage *msg, gpointer user_data) */ return; } else if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " unexpected status: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; + soup_test_assert_message_status (msg, SOUP_STATUS_OK); return; } @@ -473,44 +483,14 @@ sync_async_cleanup (SoupMessage *msg) g_free (ad); } - -int -main (int argc, char **argv) +static void +do_sync_async_test (gconstpointer data) { + const char *base_uri = data; SoupSession *session; - const char *base_uri; - test_init (argc, argv, NULL); - apache_init (); - - base_uri = "http://127.0.0.1:47524/"; - get_correct_response (base_uri); - - debug_printf (1, "\nFully async, fast requests\n"); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - g_signal_connect (session, "authenticate", - G_CALLBACK (authenticate), NULL); - do_fully_async_test (session, base_uri, "/", - TRUE, SOUP_STATUS_OK); - do_fully_async_test (session, base_uri, "/Basic/realm1/", - TRUE, SOUP_STATUS_UNAUTHORIZED); - do_fully_async_test (session, base_uri, "/Basic/realm2/", - TRUE, SOUP_STATUS_OK); - soup_test_session_abort_unref (session); - - debug_printf (1, "\nFully async, slow requests\n"); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - g_signal_connect (session, "authenticate", - G_CALLBACK (authenticate), NULL); - do_fully_async_test (session, base_uri, "/", - FALSE, SOUP_STATUS_OK); - do_fully_async_test (session, base_uri, "/Basic/realm1/", - FALSE, SOUP_STATUS_UNAUTHORIZED); - do_fully_async_test (session, base_uri, "/Basic/realm2/", - FALSE, SOUP_STATUS_OK); - soup_test_session_abort_unref (session); + SOUP_TEST_SKIP_IF_NO_APACHE; - debug_printf (1, "\nSynchronously async\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); g_signal_connect (session, "authenticate", G_CALLBACK (authenticate), NULL); @@ -521,9 +501,33 @@ main (int argc, char **argv) do_synchronously_async_test (session, base_uri, "/Basic/realm2/", SOUP_STATUS_OK); soup_test_session_abort_unref (session); +} + + +int +main (int argc, char **argv) +{ + const char *base_uri; + int ret; + + test_init (argc, argv, NULL); + apache_init (); + + base_uri = "http://127.0.0.1:47524/"; +#if HAVE_APACHE + get_correct_response (base_uri); +#endif + + g_test_add_data_func ("/pull-api/async/fast", base_uri, do_fast_async_test); + g_test_add_data_func ("/pull-api/async/slow", base_uri, do_slow_async_test); + g_test_add_data_func ("/pull-api/sync-async", base_uri, do_sync_async_test); + + ret = g_test_run (); +#if HAVE_APACHE soup_buffer_free (correct_response); +#endif test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/range-test.c b/tests/range-test.c index 98f56450..c23ba462 100644 --- a/tests/range-test.c +++ b/tests/range-test.c @@ -7,23 +7,6 @@ int total_length; char *test_response; static void -get_full_response (void) -{ - char *contents; - gsize length; - GError *error = NULL; - - if (!g_file_get_contents (SRCDIR "/index.txt", &contents, &length, &error)) { - g_printerr ("Could not read index.txt: %s\n", - error->message); - exit (1); - } - - full_response = soup_buffer_new (SOUP_MEMORY_TAKE, contents, length); - debug_printf (1, "Total response length is %d\n\n", (int)length); -} - -static void check_part (SoupMessageHeaders *headers, const char *body, gsize body_len, gboolean check_start_end, int expected_start, int expected_end) { @@ -33,41 +16,39 @@ check_part (SoupMessageHeaders *headers, const char *body, gsize body_len, soup_message_headers_get_one (headers, "Content-Range")); if (!soup_message_headers_get_content_range (headers, &start, &end, &total_length)) { - debug_printf (1, " Could not find/parse Content-Range\n"); - errors++; + soup_test_assert (FALSE, "Could not find/parse Content-Range"); return; } if (total_length != full_response->length && total_length != -1) { - debug_printf (1, " Unexpected total length %" G_GINT64_FORMAT " in response\n", - total_length); - errors++; + soup_test_assert (FALSE, + "Unexpected total length %" G_GINT64_FORMAT " in response\n", + total_length); return; } if (check_start_end) { if ((expected_start >= 0 && start != expected_start) || (expected_start < 0 && start != full_response->length + expected_start)) { - debug_printf (1, " Unexpected range start %" G_GINT64_FORMAT " in response\n", - start); - errors++; + soup_test_assert (FALSE, + "Unexpected range start %" G_GINT64_FORMAT " in response\n", + start); return; } if ((expected_end >= 0 && end != expected_end) || (expected_end < 0 && end != full_response->length - 1)) { - debug_printf (1, " Unexpected range end %" G_GINT64_FORMAT " in response\n", - end); - errors++; + soup_test_assert (FALSE, + "Unexpected range end %" G_GINT64_FORMAT " in response\n", + end); return; } } if (end - start + 1 != body_len) { - debug_printf (1, " Range length (%d) does not match body length (%d)\n", - (int)(end - start) + 1, - (int)body_len); - errors++; + soup_test_assert (FALSE, "Range length (%d) does not match body length (%d)\n", + (int)(end - start) + 1, + (int)body_len); return; } @@ -76,7 +57,7 @@ check_part (SoupMessageHeaders *headers, const char *body, gsize body_len, static void do_single_range (SoupSession *session, SoupMessage *msg, - int start, int end) + int start, int end, gboolean succeed) { const char *content_type; @@ -85,22 +66,26 @@ do_single_range (SoupSession *session, SoupMessage *msg, soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) { - debug_printf (1, " Unexpected status %d %s\n", - msg->status_code, msg->reason_phrase); + if (!succeed) { + soup_test_assert_message_status (msg, SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE); + if (msg->status_code != SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE) { + const char *content_range; + + content_range = soup_message_headers_get_one (msg->response_headers, + "Content-Range"); + if (content_range) + debug_printf (1, " Content-Range: %s\n", content_range); + } + g_object_unref (msg); - errors++; return; } + soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT); + content_type = soup_message_headers_get_content_type ( msg->response_headers, NULL); - if (content_type && !strcmp (content_type, "multipart/byteranges")) { - debug_printf (1, " Response body should not have been multipart/byteranges\n"); - g_object_unref (msg); - errors++; - return; - } + g_assert_cmpstr (content_type, !=, "multipart/byteranges"); check_part (msg->response_headers, msg->response_body->data, msg->response_body->length, TRUE, start, end); @@ -109,13 +94,13 @@ do_single_range (SoupSession *session, SoupMessage *msg, static void request_single_range (SoupSession *session, const char *uri, - int start, int end) + int start, int end, gboolean succeed) { SoupMessage *msg; msg = soup_message_new ("GET", uri); soup_message_headers_set_range (msg->request_headers, start, end); - do_single_range (session, msg, start, end); + do_single_range (session, msg, start, end, succeed); } static void @@ -131,38 +116,21 @@ do_multi_range (SoupSession *session, SoupMessage *msg, soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) { - debug_printf (1, " Unexpected status %d %s\n", - msg->status_code, msg->reason_phrase); - g_object_unref (msg); - errors++; - return; - } + soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT); content_type = soup_message_headers_get_content_type (msg->response_headers, NULL); - if (!content_type || strcmp (content_type, "multipart/byteranges") != 0) { - debug_printf (1, " Response Content-Type (%s) was not multipart/byteranges\n", - content_type); - g_object_unref (msg); - errors++; - return; - } + g_assert_cmpstr (content_type, ==, "multipart/byteranges"); multipart = soup_multipart_new_from_message (msg->response_headers, msg->response_body); if (!multipart) { - debug_printf (1, " Could not parse multipart\n"); + soup_test_assert (FALSE, "Could not parse multipart"); g_object_unref (msg); - errors++; return; } length = soup_multipart_get_length (multipart); - if (length != expected_return_ranges) { - debug_printf (1, " Expected %d ranges, got %d\n", - expected_return_ranges, length); - errors++; - } + g_assert_cmpint (length, ==, expected_return_ranges); for (i = 0; i < length; i++) { SoupMessageHeaders *headers; @@ -196,7 +164,8 @@ request_double_range (SoupSession *session, const char *uri, if (expected_return_ranges == 1) { do_single_range (session, msg, MIN (first_start, second_start), - MAX (first_end, second_end)); + MAX (first_end, second_end), + TRUE); } else do_multi_range (session, msg, expected_return_ranges); } @@ -223,13 +192,36 @@ request_triple_range (SoupSession *session, const char *uri, if (expected_return_ranges == 1) { do_single_range (session, msg, MIN (first_start, MIN (second_start, third_start)), - MAX (first_end, MAX (second_end, third_end))); + MAX (first_end, MAX (second_end, third_end)), + TRUE); } else do_multi_range (session, msg, expected_return_ranges); } static void -do_range_test (SoupSession *session, const char *uri, gboolean expect_coalesce) +request_semi_invalid_range (SoupSession *session, const char *uri, + int first_good_start, int first_good_end, + int bad_start, int bad_end, + int second_good_start, int second_good_end) +{ + SoupMessage *msg; + SoupRange ranges[3]; + + msg = soup_message_new ("GET", uri); + ranges[0].start = first_good_start; + ranges[0].end = first_good_end; + ranges[1].start = bad_start; + ranges[1].end = bad_end; + ranges[2].start = second_good_start; + ranges[2].end = second_good_end; + soup_message_headers_set_ranges (msg->request_headers, ranges, 3); + + do_multi_range (session, msg, 2); +} + +static void +do_range_test (SoupSession *session, const char *uri, + gboolean expect_coalesce, gboolean expect_partial_coalesce) { int twelfths = full_response->length / 12; @@ -255,7 +247,8 @@ do_range_test (SoupSession *session, const char *uri, gboolean expect_coalesce) /* A: 0, simple request */ debug_printf (1, "Requesting %d-%d\n", 0 * twelfths, 1 * twelfths); request_single_range (session, uri, - 0 * twelfths, 1 * twelfths); + 0 * twelfths, 1 * twelfths, + TRUE); /* B: 11, end-relative request. These two are mostly redundant * in terms of data coverage, but they may still catch @@ -263,10 +256,12 @@ do_range_test (SoupSession *session, const char *uri, gboolean expect_coalesce) */ debug_printf (1, "Requesting %d-\n", 11 * twelfths); request_single_range (session, uri, - 11 * twelfths, -1); + 11 * twelfths, -1, + TRUE); debug_printf (1, "Requesting -%d\n", 1 * twelfths); request_single_range (session, uri, - -1 * twelfths, -1); + -1 * twelfths, -1, + TRUE); /* C: 2 and 5 */ debug_printf (1, "Requesting %d-%d,%d-%d\n", @@ -309,12 +304,43 @@ do_range_test (SoupSession *session, const char *uri, gboolean expect_coalesce) 9 * twelfths, 10 * twelfths + 5, 4 * twelfths, 5 * twelfths, 10 * twelfths - 5, 11 * twelfths, - expect_coalesce ? 2 : 3); + expect_partial_coalesce ? 2 : 3); - if (memcmp (full_response->data, test_response, full_response->length) != 0) { - debug_printf (1, "\nfull_response and test_response don't match\n"); - errors++; - } + soup_assert_cmpmem (full_response->data, full_response->length, + test_response, full_response->length); + + debug_printf (1, "Requesting (invalid) %d-%d\n", + (int) full_response->length + 1, + (int) full_response->length + 100); + request_single_range (session, uri, + full_response->length + 1, full_response->length + 100, + FALSE); + + debug_printf (1, "Requesting (semi-invalid) 1-10,%d-%d,20-30\n", + (int) full_response->length + 1, + (int) full_response->length + 100); + request_semi_invalid_range (session, uri, + 1, 10, + full_response->length + 1, full_response->length + 100, + 20, 30); +} + +static void +do_apache_range_test (void) +{ + SoupSession *session; + + SOUP_TEST_SKIP_IF_NO_APACHE; + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + +#if HAVE_APACHE_2_2 + do_range_test (session, "http://127.0.0.1:47524/", FALSE, FALSE); +#else + do_range_test (session, "http://127.0.0.1:47524/", TRUE, FALSE); +#endif + + soup_test_session_abort_unref (session); } static void @@ -330,38 +356,44 @@ server_handler (SoupServer *server, full_response); } -int -main (int argc, char **argv) +static void +do_libsoup_range_test (void) { SoupSession *session; SoupServer *server; char *base_uri; - test_init (argc, argv, NULL); - apache_init (); - - get_full_response (); - test_response = g_malloc0 (full_response->length); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - debug_printf (1, "1. Testing against apache\n"); - do_range_test (session, "http://127.0.0.1:47524/", FALSE); - - debug_printf (1, "\n2. Testing against SoupServer\n"); server = soup_test_server_new (FALSE); soup_server_add_handler (server, NULL, server_handler, NULL, NULL); base_uri = g_strdup_printf ("http://127.0.0.1:%u/", soup_server_get_port (server)); - do_range_test (session, base_uri, TRUE); + do_range_test (session, base_uri, TRUE, TRUE); g_free (base_uri); soup_test_server_quit_unref (server); soup_test_session_abort_unref (session); +} + +int +main (int argc, char **argv) +{ + int ret; + + test_init (argc, argv, NULL); + apache_init (); + + full_response = soup_test_get_index (); + test_response = g_malloc0 (full_response->length); + + g_test_add_func ("/ranges/apache", do_apache_range_test); + g_test_add_func ("/ranges/libsoup", do_libsoup_range_test); + + ret = g_test_run (); - soup_buffer_free (full_response); g_free (test_response); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/redirect-test.c b/tests/redirect-test.c index 59a2f077..ad8dabaa 100644 --- a/tests/redirect-test.c +++ b/tests/redirect-test.c @@ -5,7 +5,9 @@ #include "test-utils.h" +SoupURI *base_uri; char *server2_uri; +SoupSession *async_session, *sync_session; typedef struct { const char *method; @@ -14,104 +16,106 @@ typedef struct { gboolean repeat; } TestRequest; -static struct { +typedef struct { TestRequest requests[3]; guint final_status; - guint request_api_final_status; -} tests[] = { + const char *bugref; +} TestCase; + +static TestCase tests[] = { /* A redirecty response to a GET or HEAD should cause a redirect */ { { { "GET", "/301", 301 }, { "GET", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, NULL }, { { { "GET", "/302", 302 }, { "GET", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, NULL }, { { { "GET", "/303", 303 }, { "GET", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, NULL }, { { { "GET", "/307", 307 }, { "GET", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, NULL }, { { { "HEAD", "/301", 301 }, { "HEAD", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, "551190" }, { { { "HEAD", "/302", 302 }, { "HEAD", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, "551190" }, /* 303 is a nonsensical response to HEAD, but some sites do * it anyway. :-/ */ { { { "HEAD", "/303", 303 }, { "HEAD", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, "600830" }, { { { "HEAD", "/307", 307 }, { "HEAD", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, "551190" }, /* A non-redirecty response to a GET or HEAD should not */ { { { "GET", "/300", 300 }, - { NULL } }, 300 }, + { NULL } }, 300, NULL }, { { { "GET", "/304", 304 }, - { NULL } }, 304 }, + { NULL } }, 304, NULL }, { { { "GET", "/305", 305 }, - { NULL } }, 305 }, + { NULL } }, 305, NULL }, { { { "GET", "/306", 306 }, - { NULL } }, 306 }, + { NULL } }, 306, NULL }, { { { "GET", "/308", 308 }, - { NULL } }, 308 }, + { NULL } }, 308, NULL }, { { { "HEAD", "/300", 300 }, - { NULL } }, 300 }, + { NULL } }, 300, "551190" }, { { { "HEAD", "/304", 304 }, - { NULL } }, 304 }, + { NULL } }, 304, "551190" }, { { { "HEAD", "/305", 305 }, - { NULL } }, 305 }, + { NULL } }, 305, "551190" }, { { { "HEAD", "/306", 306 }, - { NULL } }, 306 }, + { NULL } }, 306, "551190" }, { { { "HEAD", "/308", 308 }, - { NULL } }, 308 }, + { NULL } }, 308, "551190" }, /* Test double-redirect */ { { { "GET", "/301/302", 301 }, { "GET", "/302", 302 }, - { "GET", "/", 200 } }, 200 }, + { "GET", "/", 200 } }, 200, NULL }, { { { "HEAD", "/301/302", 301 }, { "HEAD", "/302", 302 }, - { "HEAD", "/", 200 } }, 200 }, + { "HEAD", "/", 200 } }, 200, "551190" }, /* POST should only automatically redirect on 301, 302 and 303 */ { { { "POST", "/301", 301 }, { "GET", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, "586692" }, { { { "POST", "/302", 302 }, { "GET", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, NULL }, { { { "POST", "/303", 303 }, { "GET", "/", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, NULL }, { { { "POST", "/307", 307 }, - { NULL } }, 307 }, + { NULL } }, 307, NULL }, /* Test behavior with recoverably-bad Location header */ { { { "GET", "/bad", 302 }, { "GET", "/bad%20with%20spaces", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, "566530" }, /* Test behavior with irrecoverably-bad Location header */ { { { "GET", "/bad-no-host", 302 }, - { NULL } }, SOUP_STATUS_MALFORMED, 302 }, + { NULL } }, SOUP_STATUS_MALFORMED, "528882" }, /* Test infinite redirection */ { { { "GET", "/bad-recursive", 302, TRUE }, - { NULL } }, SOUP_STATUS_TOO_MANY_REDIRECTS }, + { NULL } }, SOUP_STATUS_TOO_MANY_REDIRECTS, "604383" }, /* Test redirection to a different server */ { { { "GET", "/server2", 302 }, { "GET", "/on-server2", 200 }, - { NULL } }, 200 }, + { NULL } }, 200, NULL }, }; static const int n_tests = G_N_ELEMENTS (tests); @@ -131,11 +135,7 @@ got_headers (SoupMessage *msg, gpointer user_data) if (!(*treq)->method) return; - if (msg->status_code != (*treq)->status_code) { - debug_printf (1, " - Expected %d !\n", - (*treq)->status_code); - errors++; - } + soup_test_assert_message_status (msg, (*treq)->status_code); } static void @@ -149,35 +149,25 @@ restarted (SoupMessage *msg, gpointer user_data) if ((*treq)->method && !(*treq)->repeat) (*treq)++; - if (!(*treq)->method) { - debug_printf (1, " - Expected to be done!\n"); - errors++; - return; - } + soup_test_assert ((*treq)->method, + "Expected to be done"); - if (strcmp (msg->method, (*treq)->method) != 0) { - debug_printf (1, " - Expected %s !\n", (*treq)->method); - errors++; - } - if (strcmp (uri->path, (*treq)->path) != 0) { - debug_printf (1, " - Expected %s !\n", (*treq)->path); - errors++; - } + g_assert_cmpstr (msg->method, ==, (*treq)->method); + g_assert_cmpstr (uri->path, ==, (*treq)->path); } static void -do_message_api_test (SoupSession *session, SoupURI *base_uri, int n) +do_message_api_test (SoupSession *session, TestCase *test) { SoupURI *uri; SoupMessage *msg; TestRequest *treq; - debug_printf (1, "%2d. %s %s\n", n + 1, - tests[n].requests[0].method, - tests[n].requests[0].path); + if (test->bugref) + g_test_bug (test->bugref); - uri = soup_uri_new_with_base (base_uri, tests[n].requests[0].path); - msg = soup_message_new_from_uri (tests[n].requests[0].method, uri); + uri = soup_uri_new_with_base (base_uri, test->requests[0].path); + msg = soup_message_new_from_uri (test->requests[0].method, uri); soup_uri_free (uri); if (msg->method == SOUP_METHOD_POST) { @@ -187,7 +177,7 @@ do_message_api_test (SoupSession *session, SoupURI *base_uri, int n) strlen ("post body")); } - treq = &tests[n].requests[0]; + treq = &test->requests[0]; g_signal_connect (msg, "got_headers", G_CALLBACK (got_headers), &treq); g_signal_connect (msg, "restarted", @@ -195,53 +185,36 @@ do_message_api_test (SoupSession *session, SoupURI *base_uri, int n) soup_session_send_message (session, msg); - if (msg->status_code != tests[n].final_status) { - debug_printf (1, " - Expected final status of %d, got %d !\n", - tests[n].final_status, msg->status_code); - errors++; - } + soup_test_assert_message_status (msg, test->final_status); g_object_unref (msg); - debug_printf (2, "\n"); } static void -do_request_api_test (SoupSession *session, SoupURI *base_uri, int n) +do_request_api_test (SoupSession *session, TestCase *test) { - SoupRequester *requester = (SoupRequester *)soup_session_get_feature (session, SOUP_TYPE_REQUESTER); SoupURI *uri; - SoupRequest *req; + SoupRequestHTTP *reqh; SoupMessage *msg; TestRequest *treq; GInputStream *stream; GError *error = NULL; - guint final_status; - - debug_printf (1, "%2d. %s %s\n", n + 1, - tests[n].requests[0].method, - tests[n].requests[0].path); - final_status = tests[n].request_api_final_status; - if (!final_status) - final_status = tests[n].final_status; + if (test->bugref) + g_test_bug (test->bugref); - uri = soup_uri_new_with_base (base_uri, tests[n].requests[0].path); - req = soup_requester_request_uri (requester, uri, &error); + uri = soup_uri_new_with_base (base_uri, test->requests[0].path); + reqh = soup_session_request_http_uri (session, + test->requests[0].method, + uri, &error); soup_uri_free (uri); - if (!req) { - debug_printf (1, " could not create request: %s\n", - error->message); + g_assert_no_error (error); + if (error) { g_error_free (error); - errors++; - debug_printf (2, "\n"); return; } - msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); - g_object_set (G_OBJECT (msg), - SOUP_MESSAGE_METHOD, tests[n].requests[0].method, - NULL); - + msg = soup_request_http_get_message (reqh); if (msg->method == SOUP_METHOD_POST) { soup_message_set_request (msg, "text/plain", SOUP_MEMORY_STATIC, @@ -249,208 +222,75 @@ do_request_api_test (SoupSession *session, SoupURI *base_uri, int n) strlen ("post body")); } - treq = &tests[n].requests[0]; + treq = &test->requests[0]; g_signal_connect (msg, "got_headers", G_CALLBACK (got_headers), &treq); g_signal_connect (msg, "restarted", G_CALLBACK (restarted), &treq); - if (SOUP_IS_SESSION_SYNC (session)) - stream = soup_request_send (req, NULL, &error); - else - stream = soup_test_request_send_async_as_sync (req, NULL, &error); - - if (SOUP_STATUS_IS_TRANSPORT_ERROR (final_status)) { - if (stream) { - debug_printf (1, " expected failure (%s) but succeeded", - soup_status_get_phrase (final_status)); - errors++; - g_object_unref (stream); - } - if (error->domain != SOUP_HTTP_ERROR || - error->code != final_status) { - debug_printf (1, " expected '%s' but got '%s'", - soup_status_get_phrase (final_status), - error->message); - errors++; - } + stream = soup_test_request_send (SOUP_REQUEST (reqh), NULL, 0, &error); - g_error_free (error); - g_object_unref (req); - debug_printf (2, "\n"); - return; - } else if (!stream) { - debug_printf (1, " could not send request: %s\n", - error->message); - g_error_free (error); - g_object_unref (req); - errors++; - debug_printf (2, "\n"); + if (SOUP_STATUS_IS_TRANSPORT_ERROR (test->final_status) && + test->final_status != SOUP_STATUS_MALFORMED) { + g_assert_error (error, SOUP_HTTP_ERROR, test->final_status); + g_clear_error (&error); + + g_assert_null (stream); + g_clear_object (&stream); + + g_object_unref (msg); + g_object_unref (reqh); return; } - if (SOUP_IS_SESSION_SYNC (session)) - g_input_stream_close (stream, NULL, &error); - else - soup_test_stream_close_async_as_sync (stream, NULL, &error); + g_assert_no_error (error); if (error) { - debug_printf (1, " could not close stream: %s\n", - error->message); g_error_free (error); - errors++; + g_object_unref (msg); + g_object_unref (reqh); + return; } - if (msg->status_code != final_status) { - debug_printf (1, " - Expected final status of %d, got %d !\n", - final_status, msg->status_code); - errors++; - } + soup_test_request_read_all (SOUP_REQUEST (reqh), stream, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); - g_object_unref (req); - debug_printf (2, "\n"); -} + soup_test_request_close_stream (SOUP_REQUEST (reqh), stream, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + g_object_unref (stream); -static void -do_redirect_tests (SoupURI *base_uri) -{ - SoupSession *session; - int n; - - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, - SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, - NULL); - debug_printf (1, "Async session, SoupMessage\n"); - for (n = 0; n < n_tests; n++) - do_message_api_test (session, base_uri, n); - debug_printf (1, "\nAsync session, SoupRequest\n"); - for (n = 0; n < n_tests; n++) - do_request_api_test (session, base_uri, n); - soup_test_session_abort_unref (session); - - session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, - SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_REQUESTER, - NULL); - debug_printf (1, "\nSync session, SoupMessage\n"); - for (n = 0; n < n_tests; n++) - do_message_api_test (session, base_uri, n); - debug_printf (1, "\nSync session, SoupRequest\n"); - for (n = 0; n < n_tests; n++) - do_request_api_test (session, base_uri, n); - soup_test_session_abort_unref (session); -} - -typedef struct { - SoupSession *session; - SoupMessage *msg1, *msg2; - SoupURI *uri1, *uri2; - SoupSocket *sock1, *sock2; -} ConnectionTestData; + if (test->final_status == SOUP_STATUS_MALFORMED) + g_assert_cmpint (msg->status_code, ==, test->requests[0].status_code); + else + g_assert_cmpint (msg->status_code, ==, test->final_status); -static void -msg2_finished (SoupSession *session, SoupMessage *msg2, gpointer user_data) -{ - if (!SOUP_STATUS_IS_SUCCESSFUL (msg2->status_code)) { - debug_printf (1, " msg2 failed: %d %s\n", - msg2->status_code, msg2->reason_phrase); - errors++; - } + g_object_unref (msg); + g_object_unref (reqh); } static void -unpause_msg1 (SoupMessage *msg2, gpointer user_data) +do_async_msg_api_test (gconstpointer test) { - ConnectionTestData *data = user_data; - - if (!data->sock1) { - debug_printf (1, " msg1 has no connection?\n"); - errors++; - } else if (!data->sock2) { - debug_printf (1, " msg2 has no connection?\n"); - errors++; - } else if (data->sock1 == data->sock2) { - debug_printf (1, " Both messages sharing the same connection\n"); - errors++; - } - - soup_session_unpause_message (data->session, data->msg1); -} - -static gboolean -msg1_just_restarted (gpointer user_data) -{ - ConnectionTestData *data = user_data; - - soup_session_pause_message (data->session, data->msg1); - - data->msg2 = soup_message_new_from_uri ("GET", data->uri2); - - g_signal_connect (data->msg2, "got_body", - G_CALLBACK (unpause_msg1), data); - - soup_session_queue_message (data->session, data->msg2, msg2_finished, data); - return FALSE; + do_message_api_test (async_session, (TestCase *)test); } static void -msg1_about_to_restart (SoupMessage *msg1, gpointer user_data) +do_async_req_api_test (gconstpointer test) { - ConnectionTestData *data = user_data; - - /* Do nothing when loading the redirected-to resource */ - if (!SOUP_STATUS_IS_REDIRECTION (data->msg1->status_code)) - return; - - /* We have to pause msg1 after the I/O finishes, but before - * the queue runs again. - */ - g_idle_add_full (G_PRIORITY_HIGH, msg1_just_restarted, data, NULL); + do_request_api_test (async_session, (TestCase *)test); } static void -request_started (SoupSession *session, SoupMessage *msg, - SoupSocket *socket, gpointer user_data) +do_sync_msg_api_test (gconstpointer test) { - ConnectionTestData *data = user_data; - - if (msg == data->msg1) - data->sock1 = socket; - else if (msg == data->msg2) - data->sock2 = socket; - else - g_warn_if_reached (); + do_message_api_test (sync_session, (TestCase *)test); } static void -do_connection_test (SoupURI *base_uri) +do_sync_req_api_test (gconstpointer test) { - ConnectionTestData data; - - debug_printf (1, "\nConnection reuse\n"); - memset (&data, 0, sizeof (data)); - - data.session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - g_signal_connect (data.session, "request-started", - G_CALLBACK (request_started), &data); - - data.uri1 = soup_uri_new_with_base (base_uri, "/301"); - data.uri2 = soup_uri_new_with_base (base_uri, "/"); - data.msg1 = soup_message_new_from_uri ("GET", data.uri1); - - g_signal_connect (data.msg1, "got-body", - G_CALLBACK (msg1_about_to_restart), &data); - soup_session_send_message (data.session, data.msg1); - - if (!SOUP_STATUS_IS_SUCCESSFUL (data.msg1->status_code)) { - debug_printf (1, " msg1 failed: %d %s\n", - data.msg1->status_code, data.msg1->reason_phrase); - errors++; - } - g_object_unref (data.msg1); - soup_uri_free (data.uri1); - soup_uri_free (data.uri2); - - soup_test_session_abort_unref (data.session); + do_request_api_test (sync_session, (TestCase *)test); } static void @@ -550,29 +390,23 @@ server2_callback (SoupServer *server, SoupMessage *msg, soup_message_set_status (msg, SOUP_STATUS_OK); } -static gboolean run_tests = TRUE; - -static GOptionEntry no_test_entry[] = { - { "no-tests", 'n', G_OPTION_FLAG_REVERSE, - G_OPTION_ARG_NONE, &run_tests, - "Don't run tests, just run the test server", NULL }, - { NULL } -}; - int main (int argc, char **argv) { GMainLoop *loop; SoupServer *server, *server2; guint port; - SoupURI *base_uri; + char *path; + int n, ret; - test_init (argc, argv, no_test_entry); + test_init (argc, argv, NULL); server = soup_test_server_new (TRUE); soup_server_add_handler (server, NULL, server_callback, NULL, NULL); port = soup_server_get_port (server); + base_uri = soup_uri_new ("http://127.0.0.1"); + soup_uri_set_port (base_uri, port); server2 = soup_test_server_new (TRUE); soup_server_add_handler (server2, NULL, @@ -582,23 +416,47 @@ main (int argc, char **argv) loop = g_main_loop_new (NULL, TRUE); - if (run_tests) { - base_uri = soup_uri_new ("http://127.0.0.1"); - soup_uri_set_port (base_uri, port); - do_redirect_tests (base_uri); - do_connection_test (base_uri); - soup_uri_free (base_uri); - } else { - g_print ("Listening on port %d\n", port); - g_main_loop_run (loop); + async_session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + NULL); + sync_session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); + + for (n = 0; n < n_tests; n++) { + path = g_strdup_printf ("/redirect/async/msg/%d-%s-%d", n + , tests[n].requests[0].method, + tests[n].requests[0].status_code); + g_test_add_data_func (path, &tests[n], do_async_msg_api_test); + g_free (path); + + path = g_strdup_printf ("/redirect/async/req/%d-%s-%d", n, + tests[n].requests[0].method, + tests[n].requests[0].status_code); + g_test_add_data_func (path, &tests[n], do_async_req_api_test); + g_free (path); + + path = g_strdup_printf ("/redirect/sync/msg/%d-%s-%d", n, + tests[n].requests[0].method, + tests[n].requests[0].status_code); + g_test_add_data_func (path, &tests[n], do_sync_msg_api_test); + g_free (path); + + path = g_strdup_printf ("/redirect/sync/req/%d-%s-%d", n, + tests[n].requests[0].method, + tests[n].requests[0].status_code); + g_test_add_data_func (path, &tests[n], do_sync_req_api_test); + g_free (path); } + ret = g_test_run (); + g_main_loop_unref (loop); - g_free (server2_uri); + soup_uri_free (base_uri); soup_test_server_quit_unref (server); + g_free (server2_uri); soup_test_server_quit_unref (server2); - if (run_tests) - test_cleanup (); - return errors != 0; + soup_test_session_abort_unref (async_session); + soup_test_session_abort_unref (sync_session); + + return ret; } diff --git a/tests/requester-test.c b/tests/requester-test.c index b4e86ea6..39b30bd5 100644 --- a/tests/requester-test.c +++ b/tests/requester-test.c @@ -3,6 +3,9 @@ * Copyright (C) 2011 Red Hat, Inc. */ +/* Kill SoupRequester-related deprecation warnings */ +#define SOUP_VERSION_MIN_REQUIRED SOUP_VERSION_2_40 + #include "test-utils.h" SoupServer *server; @@ -14,24 +17,21 @@ SoupBuffer *response, *auth_response; #define REDIRECT_HTML_BODY "<html><body>Try again</body></html>\r\n" #define AUTH_HTML_BODY "<html><body>Unauthorized</body></html>\r\n" -static void -get_index (void) +static gboolean +slow_finish_message (gpointer msg) { - char *contents; - gsize length; - GError *error = NULL; - - if (!g_file_get_contents (SRCDIR "/index.txt", &contents, &length, &error)) { - g_printerr ("Could not read index.txt: %s\n", - error->message); - exit (1); - } + SoupServer *server = g_object_get_data (G_OBJECT (msg), "server"); - response = soup_buffer_new (SOUP_MEMORY_TAKE, contents, length); + soup_server_unpause_message (server, msg); + return FALSE; +} - auth_response = soup_buffer_new (SOUP_MEMORY_STATIC, - AUTH_HTML_BODY, - strlen (AUTH_HTML_BODY)); +static void +slow_pause_message (SoupMessage *msg, gpointer server) +{ + soup_server_pause_message (server, msg); + soup_add_timeout (soup_server_get_async_context (server), + 1000, slow_finish_message, msg); } static void @@ -67,6 +67,10 @@ server_callback (SoupServer *server, SoupMessage *msg, } else if (strcmp (path, "/non-persistent") == 0) { soup_message_headers_append (msg->response_headers, "Connection", "close"); + } else if (!strcmp (path, "/slow")) { + g_object_set_data (G_OBJECT (msg), "server", server); + g_signal_connect (msg, "wrote-headers", + G_CALLBACK (slow_pause_message), server); } soup_message_set_status (msg, SOUP_STATUS_OK); @@ -99,11 +103,8 @@ stream_closed (GObject *source, GAsyncResult *res, gpointer user_data) GInputStream *stream = G_INPUT_STREAM (source); GError *error = NULL; - if (!g_input_stream_close_finish (stream, res, &error)) { - debug_printf (1, " close failed: %s\n", error->message); - g_error_free (error); - errors++; - } + g_input_stream_close_finish (stream, res, &error); + g_assert_no_error (error); g_main_loop_quit (loop); g_object_unref (stream); } @@ -119,9 +120,8 @@ test_read_ready (GObject *source, GAsyncResult *res, gpointer user_data) nread = g_input_stream_read_finish (stream, res, &error); if (nread == -1) { - debug_printf (1, " read_async failed: %s\n", error->message); + g_assert_no_error (error); g_error_free (error); - errors++; g_input_stream_close (stream, NULL, NULL); g_object_unref (stream); g_main_loop_quit (loop); @@ -150,28 +150,18 @@ auth_test_sent (GObject *source, GAsyncResult *res, gpointer user_data) stream = soup_request_send_finish (SOUP_REQUEST (source), res, &error); if (!stream) { - debug_printf (1, " send_async failed: %s\n", error->message); - errors++; + g_assert_no_error (error); + g_clear_error (&error); g_main_loop_quit (loop); return; } msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (source)); - if (msg->status_code != SOUP_STATUS_UNAUTHORIZED) { - debug_printf (1, " GET failed: %d %s\n", msg->status_code, - msg->reason_phrase); - errors++; - g_main_loop_quit (loop); - return; - } + soup_test_assert_message_status (msg, SOUP_STATUS_UNAUTHORIZED); g_object_unref (msg); content_type = soup_request_get_content_type (SOUP_REQUEST (source)); - if (g_strcmp0 (content_type, "text/html") != 0) { - debug_printf (1, " failed to sniff Content-Type: got %s\n", - content_type ? content_type : "(NULL)"); - errors++; - } + g_assert_cmpstr (content_type, ==, "text/html"); g_input_stream_read_async (stream, buf, sizeof (buf), G_PRIORITY_DEFAULT, NULL, @@ -188,22 +178,13 @@ test_sent (GObject *source, GAsyncResult *res, gpointer user_data) stream = soup_request_send_finish (SOUP_REQUEST (source), res, &error); if (data->cancel) { - if (stream) { - debug_printf (1, " send_async succeeded??\n"); - errors++; - g_input_stream_close (stream, NULL, NULL); - g_object_unref (stream); - } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - debug_printf (1, " send_async failed with wrong error: %s\n", error->message); - errors++; - g_clear_error (&error); - } + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_clear_error (&error); g_main_loop_quit (loop); return; } else { + g_assert_no_error (error); if (!stream) { - debug_printf (1, " send_async failed: %s\n", error->message); - errors++; g_main_loop_quit (loop); g_clear_error (&error); return; @@ -211,11 +192,7 @@ test_sent (GObject *source, GAsyncResult *res, gpointer user_data) } content_type = soup_request_get_content_type (SOUP_REQUEST (source)); - if (g_strcmp0 (content_type, "text/plain") != 0) { - debug_printf (1, " failed to sniff Content-Type: got %s\n", - content_type ? content_type : "(NULL)"); - errors++; - } + g_assert_cmpstr (content_type, ==, "text/plain"); g_input_stream_read_async (stream, buf, sizeof (buf), G_PRIORITY_DEFAULT, NULL, @@ -234,6 +211,7 @@ request_started (SoupSession *session, SoupMessage *msg, { SoupSocket **save_socket = user_data; + g_clear_object (save_socket); *save_socket = g_object_ref (socket); } @@ -250,11 +228,17 @@ do_async_test (SoupSession *session, SoupURI *uri, SoupMessage *msg; RequestData data; - requester = SOUP_REQUESTER (soup_session_get_feature (session, SOUP_TYPE_REQUESTER)); + if (SOUP_IS_SESSION_ASYNC (session)) + requester = SOUP_REQUESTER (soup_session_get_feature (session, SOUP_TYPE_REQUESTER)); + else + requester = NULL; data.body = g_string_new (NULL); data.cancel = cancel; - request = soup_requester_request_uri (requester, uri, NULL); + if (requester) + request = soup_requester_request_uri (requester, uri, NULL); + else + request = soup_session_request_uri (session, uri, NULL); msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request)); if (cancel) { @@ -275,43 +259,20 @@ do_async_test (SoupSession *session, SoupURI *uri, g_signal_handler_disconnect (session, started_id); - if (msg->status_code != expected_status) { - debug_printf (1, " GET failed: %d %s (expected %d)\n", - msg->status_code, msg->reason_phrase, - expected_status); - g_object_unref (msg); - errors++; - return; - } + soup_test_assert_message_status (msg, expected_status); g_object_unref (msg); - if (!expected_response) { - if (data.body->len) { - debug_printf (1, " body length mismatch: expected 0, got %d\n", - (int)data.body->len); - errors++; - } - } else if (data.body->len != expected_response->length) { - debug_printf (1, " body length mismatch: expected %d, got %d\n", - (int)expected_response->length, (int)data.body->len); - errors++; - } else if (memcmp (data.body->str, expected_response->data, - expected_response->length) != 0) { - debug_printf (1, " body data mismatch\n"); - errors++; - } + if (expected_response) { + soup_assert_cmpmem (data.body->str, data.body->len, + expected_response->data, expected_response->length); + } else + g_assert_cmpint (data.body->len, ==, 0); + + if (persistent) + g_assert_true (soup_socket_is_connected (socket)); + else + g_assert_false (soup_socket_is_connected (socket)); - if (persistent) { - if (!soup_socket_is_connected (socket)) { - debug_printf (1, " socket not still connected!\n"); - errors++; - } - } else { - if (soup_socket_is_connected (socket)) { - debug_printf (1, " socket still connected!\n"); - errors++; - } - } g_object_unref (socket); g_string_free (data.body, TRUE); @@ -323,19 +284,21 @@ do_test_for_thread_and_context (SoupSession *session, const char *base_uri) SoupRequester *requester; SoupURI *uri; - requester = soup_requester_new (); - soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester)); - g_object_unref (requester); + if (SOUP_IS_SESSION_ASYNC (session)) { + requester = soup_requester_new (); + soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester)); + g_object_unref (requester); + } soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER); - debug_printf (1, " basic test\n"); + debug_printf (1, " basic test\n"); uri = soup_uri_new (base_uri); do_async_test (session, uri, test_sent, SOUP_STATUS_OK, response, TRUE, FALSE); soup_uri_free (uri); - debug_printf (1, " chunked test\n"); + debug_printf (1, " chunked test\n"); uri = soup_uri_new (base_uri); soup_uri_set_path (uri, "/chunked"); do_async_test (session, uri, test_sent, @@ -343,7 +306,7 @@ do_test_for_thread_and_context (SoupSession *session, const char *base_uri) TRUE, FALSE); soup_uri_free (uri); - debug_printf (1, " auth test\n"); + debug_printf (1, " auth test\n"); uri = soup_uri_new (base_uri); soup_uri_set_path (uri, "/auth"); do_async_test (session, uri, auth_test_sent, @@ -351,7 +314,7 @@ do_test_for_thread_and_context (SoupSession *session, const char *base_uri) TRUE, FALSE); soup_uri_free (uri); - debug_printf (1, " non-persistent test\n"); + debug_printf (1, " non-persistent test\n"); uri = soup_uri_new (base_uri); soup_uri_set_path (uri, "/non-persistent"); do_async_test (session, uri, test_sent, @@ -359,7 +322,7 @@ do_test_for_thread_and_context (SoupSession *session, const char *base_uri) FALSE, FALSE); soup_uri_free (uri); - debug_printf (1, " cancellation test\n"); + debug_printf (1, " cancellation test\n"); uri = soup_uri_new (base_uri); soup_uri_set_path (uri, "/"); do_async_test (session, uri, test_sent, @@ -369,11 +332,23 @@ do_test_for_thread_and_context (SoupSession *session, const char *base_uri) } static void -do_simple_test (const char *uri) +do_simple_plain_test (gconstpointer uri) +{ + SoupSession *session; + + g_test_bug ("653707"); + + session = soup_test_session_new (SOUP_TYPE_SESSION, NULL); + do_test_for_thread_and_context (session, uri); + soup_test_session_abort_unref (session); +} + +static void +do_simple_async_test (gconstpointer uri) { SoupSession *session; - debug_printf (1, "Simple streaming test\n"); + g_test_bug ("653707"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, @@ -382,16 +357,18 @@ do_simple_test (const char *uri) soup_test_session_abort_unref (session); } -static gpointer -do_test_with_context (const char *uri) +static void +do_test_with_context_and_type (const char *uri, gboolean plain_session) { GMainContext *async_context; SoupSession *session; + g_test_bug ("653707"); + async_context = g_main_context_new (); g_main_context_push_thread_default (async_context); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + session = soup_test_session_new (plain_session ? SOUP_TYPE_SESSION : SOUP_TYPE_SESSION_ASYNC, SOUP_SESSION_ASYNC_CONTEXT, async_context, SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); @@ -401,25 +378,52 @@ do_test_with_context (const char *uri) g_main_context_pop_thread_default (async_context); g_main_context_unref (async_context); - return NULL; } static void -do_context_test (const char *uri) +do_async_test_with_context (gconstpointer uri) +{ + do_test_with_context_and_type (uri, FALSE); +} + +static void +do_plain_test_with_context (gconstpointer uri) +{ + do_test_with_context_and_type (uri, TRUE); +} + +static gpointer +async_test_thread (gpointer uri) { - debug_printf (1, "Streaming with a non-default-context\n"); - do_test_with_context (uri); + do_test_with_context_and_type (uri, TRUE); + return NULL; +} + +static gpointer +plain_test_thread (gpointer uri) +{ + do_test_with_context_and_type (uri, FALSE); + return NULL; } static void -do_thread_test (const char *uri) +do_async_test_in_thread (gconstpointer uri) { GThread *thread; - debug_printf (1, "Streaming in another thread\n"); + thread = g_thread_new ("do_async_test_in_thread", + async_test_thread, + (gpointer)uri); + g_thread_join (thread); +} - thread = g_thread_new ("do_test_with_context", - (GThreadFunc)do_test_with_context, +static void +do_plain_test_in_thread (gconstpointer uri) +{ + GThread *thread; + + thread = g_thread_new ("do_plain_test_in_thread", + plain_test_thread, (gpointer)uri); g_thread_join (thread); } @@ -451,173 +455,381 @@ do_sync_request (SoupSession *session, SoupRequest *request, in = soup_request_send (request, NULL, &error); g_signal_handler_disconnect (session, started_id); if (cancel) { - if (in) { - debug_printf (1, " send succeeded??\n"); - errors++; - g_input_stream_close (in, NULL, NULL); - g_object_unref (in); - } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - debug_printf (1, " send failed with wrong error: %s\n", error->message); - errors++; - g_clear_error (&error); - } + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_clear_error (&error); g_object_unref (msg); + g_object_unref (socket); return; } else if (!in) { - debug_printf (1, " soup_request_send failed: %s\n", - error->message); - g_object_unref (msg); + g_assert_no_error (error); g_clear_error (&error); - errors++; - return; - } - - if (msg->status_code != expected_status) { - debug_printf (1, " GET failed: %d %s\n", msg->status_code, - msg->reason_phrase); g_object_unref (msg); - g_object_unref (in); - errors++; + g_object_unref (socket); return; } + + soup_test_assert_message_status (msg, expected_status); g_object_unref (msg); body = g_string_new (NULL); do { nread = g_input_stream_read (in, buf, sizeof (buf), NULL, &error); + g_assert_no_error (error); if (nread == -1) { - debug_printf (1, " g_input_stream_read failed: %s\n", - error->message); g_clear_error (&error); - errors++; break; } g_string_append_len (body, buf, nread); } while (nread > 0); - if (!g_input_stream_close (in, NULL, &error)) { - debug_printf (1, " g_input_stream_close failed: %s\n", - error->message); - g_clear_error (&error); - errors++; - } + g_input_stream_close (in, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); g_object_unref (in); - if (!expected_response) { - if (body->len) { - debug_printf (1, " body length mismatch: expected 0, got %d\n", - (int)body->len); - errors++; - } - } else if (body->len != expected_response->length) { - debug_printf (1, " body length mismatch: expected %d, got %d\n", - (int)expected_response->length, (int)body->len); - errors++; - } else if (memcmp (body->str, expected_response->data, body->len) != 0) { - debug_printf (1, " body data mismatch\n"); - errors++; - } + if (expected_response) { + soup_assert_cmpmem (body->str, body->len, + expected_response->data, expected_response->length); + } else + g_assert_cmpint (body->len, ==, 0); - if (persistent) { - if (!soup_socket_is_connected (socket)) { - debug_printf (1, " socket not still connected!\n"); - errors++; - } - } else { - if (soup_socket_is_connected (socket)) { - debug_printf (1, " socket still connected!\n"); - errors++; - } - } + if (persistent) + g_assert_true (soup_socket_is_connected (socket)); + else + g_assert_false (soup_socket_is_connected (socket)); g_object_unref (socket); g_string_free (body, TRUE); } static void -do_sync_test (const char *uri_string) +do_sync_tests_for_session (SoupSession *session, const char *uri_string) { - SoupSession *session; SoupRequester *requester; SoupRequest *request; SoupURI *uri; - debug_printf (1, "Sync streaming\n"); - - session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); - requester = soup_requester_new (); - soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester)); - g_object_unref (requester); + requester = SOUP_REQUESTER (soup_session_get_feature (session, SOUP_TYPE_REQUESTER)); uri = soup_uri_new (uri_string); - debug_printf (1, " basic test\n"); - request = soup_requester_request_uri (requester, uri, NULL); + debug_printf (1, " basic test\n"); + if (requester) + request = soup_requester_request_uri (requester, uri, NULL); + else + request = soup_session_request_uri (session, uri, NULL); do_sync_request (session, request, SOUP_STATUS_OK, response, TRUE, FALSE); g_object_unref (request); - debug_printf (1, " chunked test\n"); + debug_printf (1, " chunked test\n"); soup_uri_set_path (uri, "/chunked"); - request = soup_requester_request_uri (requester, uri, NULL); + if (requester) + request = soup_requester_request_uri (requester, uri, NULL); + else + request = soup_session_request_uri (session, uri, NULL); do_sync_request (session, request, SOUP_STATUS_OK, response, TRUE, FALSE); g_object_unref (request); - debug_printf (1, " auth test\n"); + debug_printf (1, " auth test\n"); soup_uri_set_path (uri, "/auth"); - request = soup_requester_request_uri (requester, uri, NULL); + if (requester) + request = soup_requester_request_uri (requester, uri, NULL); + else + request = soup_session_request_uri (session, uri, NULL); do_sync_request (session, request, SOUP_STATUS_UNAUTHORIZED, auth_response, TRUE, FALSE); g_object_unref (request); - debug_printf (1, " non-persistent test\n"); + debug_printf (1, " non-persistent test\n"); soup_uri_set_path (uri, "/non-persistent"); - request = soup_requester_request_uri (requester, uri, NULL); + if (requester) + request = soup_requester_request_uri (requester, uri, NULL); + else + request = soup_session_request_uri (session, uri, NULL); do_sync_request (session, request, SOUP_STATUS_OK, response, FALSE, FALSE); g_object_unref (request); - debug_printf (1, " cancel test\n"); + debug_printf (1, " cancel test\n"); soup_uri_set_path (uri, "/"); - request = soup_requester_request_uri (requester, uri, NULL); + if (requester) + request = soup_requester_request_uri (requester, uri, NULL); + else + request = soup_session_request_uri (session, uri, NULL); do_sync_request (session, request, SOUP_STATUS_FORBIDDEN, NULL, TRUE, TRUE); g_object_unref (request); + soup_uri_free (uri); +} + +static void +do_plain_sync_test (gconstpointer uri) +{ + SoupSession *session; + + session = soup_test_session_new (SOUP_TYPE_SESSION, NULL); + do_sync_tests_for_session (session, uri); soup_test_session_abort_unref (session); +} + +static void +do_sync_sync_test (gconstpointer uri) +{ + SoupSession *session; + SoupRequester *requester; + + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); + requester = soup_requester_new (); + soup_session_add_feature (session, SOUP_SESSION_FEATURE (requester)); + g_object_unref (requester); + do_sync_tests_for_session (session, uri); + soup_test_session_abort_unref (session); +} + +static void +do_null_char_request (SoupSession *session, const char *encoded_data, + const char *expected_data, int expected_len) +{ + GError *error = NULL; + GInputStream *stream; + SoupRequest *request; + SoupURI *uri; + char *uri_string, buf[256]; + gsize nread; + + uri_string = g_strdup_printf ("data:text/html,%s", encoded_data); + uri = soup_uri_new (uri_string); + g_free (uri_string); + + request = soup_session_request_uri (session, uri, NULL); + stream = soup_test_request_send (request, NULL, 0, &error); + g_assert_no_error (error); + if (error) { + g_error_free (error); + g_object_unref (request); + soup_uri_free (uri); + return; + } + + g_input_stream_read_all (stream, buf, sizeof (buf), &nread, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + + soup_test_request_close_stream (request, stream, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + + soup_assert_cmpmem (buf, nread, expected_data, expected_len); + + g_object_unref (stream); + g_object_unref (request); soup_uri_free (uri); } +static void +do_null_char_test_for_session (SoupSession *session) +{ + static struct { + const char *encoded_data; + const char *expected_data; + int expected_len; + } test_cases[] = { + { "%3Cscript%3Ea%3D'%00'%3C%2Fscript%3E", "<script>a='\0'</script>", 22 }, + { "%00%3Cscript%3Ea%3D42%3C%2Fscript%3E", "\0<script>a=42</script>", 22 }, + { "%3Cscript%3E%00%3Cbr%2F%3E%3C%2Fscript%3E%00", "<script>\0<br/></script>\0", 24 }, + }; + static int num_test_cases = G_N_ELEMENTS(test_cases); + int i; + + for (i = 0; i < num_test_cases; i++) { + do_null_char_request (session, test_cases[i].encoded_data, + test_cases[i].expected_data, test_cases[i].expected_len); + } +} + +static void +do_plain_null_char_test (void) +{ + SoupSession *session; + + session = soup_test_session_new (SOUP_TYPE_SESSION, NULL); + do_null_char_test_for_session (session); + soup_test_session_abort_unref (session); +} + +static void +do_async_null_char_test (void) +{ + SoupSession *session; + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + NULL); + do_null_char_test_for_session (session); + soup_test_session_abort_unref (session); +} + +static void +close_test_msg_finished (SoupMessage *msg, + gpointer user_data) +{ + gboolean *finished = user_data; + + *finished = TRUE; +} + +static void +do_close_test_for_session (SoupSession *session, + SoupURI *uri) +{ + GError *error = NULL; + GInputStream *stream; + SoupRequest *request; + guint64 start, end; + GCancellable *cancellable; + SoupMessage *msg; + gboolean finished = FALSE; + + debug_printf (1, " normal close\n"); + + request = soup_session_request_uri (session, uri, NULL); + stream = soup_test_request_send (request, NULL, 0, &error); + g_assert_no_error (error); + if (error) { + g_error_free (error); + g_object_unref (request); + return; + } + + start = g_get_monotonic_time (); + soup_test_request_close_stream (request, stream, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + end = g_get_monotonic_time (); + + g_assert_cmpint (end - start, <=, 500000); + + g_object_unref (stream); + g_object_unref (request); + + + debug_printf (1, " error close\n"); + + request = soup_session_request_uri (session, uri, NULL); + msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (request)); + g_signal_connect (msg, "finished", G_CALLBACK (close_test_msg_finished), &finished); + g_object_unref (msg); + + stream = soup_test_request_send (request, NULL, 0, &error); + g_assert_no_error (error); + if (error) { + g_error_free (error); + g_object_unref (request); + return; + } + + cancellable = g_cancellable_new (); + g_cancellable_cancel (cancellable); + soup_test_request_close_stream (request, stream, cancellable, &error); + if (error) + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_clear_error (&error); + + g_assert_true (finished); + + g_object_unref (stream); + g_object_unref (request); +} + +static void +do_async_close_test (gconstpointer uri) +{ + SoupSession *session; + SoupURI *slow_uri; + + g_test_bug ("695652"); + g_test_bug ("711260"); + + slow_uri = soup_uri_new (uri); + soup_uri_set_path (slow_uri, "/slow"); + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + NULL); + do_close_test_for_session (session, slow_uri); + soup_test_session_abort_unref (session); + + soup_uri_free (slow_uri); +} + +static void +do_sync_close_test (gconstpointer uri) +{ + SoupSession *session; + SoupURI *slow_uri; + + g_test_bug ("695652"); + g_test_bug ("711260"); + + slow_uri = soup_uri_new (uri); + soup_uri_set_path (slow_uri, "/slow"); + + debug_printf (1, " SoupSessionSync\n"); + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + NULL); + do_close_test_for_session (session, slow_uri); + soup_test_session_abort_unref (session); + + soup_uri_free (slow_uri); +} + int main (int argc, char **argv) { char *uri; + int ret; test_init (argc, argv, NULL); - get_index (); + + response = soup_test_get_index (); + auth_response = soup_buffer_new (SOUP_MEMORY_STATIC, + AUTH_HTML_BODY, + strlen (AUTH_HTML_BODY)); server = soup_test_server_new (TRUE); soup_server_add_handler (server, NULL, server_callback, NULL, NULL); uri = g_strdup_printf ("http://127.0.0.1:%u/foo", soup_server_get_port (server)); - do_simple_test (uri); - do_thread_test (uri); - do_context_test (uri); - do_sync_test (uri); + g_test_add_data_func ("/requester/simple/SoupSession", uri, do_simple_plain_test); + g_test_add_data_func ("/requester/simple/SoupSessionAsync", uri, do_simple_async_test); + g_test_add_data_func ("/requester/threaded/SoupSession", uri, do_plain_test_in_thread); + g_test_add_data_func ("/requester/threaded/SoupSessionAsync", uri, do_async_test_in_thread); + g_test_add_data_func ("/requester/context/SoupSession", uri, do_plain_test_with_context); + g_test_add_data_func ("/requester/context/SoupSessionAsync", uri, do_async_test_with_context); + g_test_add_data_func ("/requester/sync/SoupSession", uri, do_plain_sync_test); + g_test_add_data_func ("/requester/sync/SoupSessionSync", uri, do_sync_sync_test); + g_test_add_func ("/requester/null-char/SoupSession", do_plain_null_char_test); + g_test_add_func ("/requester/null-char/SoupSessionAsync", do_async_null_char_test); + g_test_add_data_func ("/requester/close/SoupSessionAsync", uri, do_async_close_test); + g_test_add_data_func ("/requester/close/SoupSessionSync", uri, do_sync_close_test); + + ret = g_test_run (); g_free (uri); - soup_buffer_free (response); soup_buffer_free (auth_response); soup_test_server_quit_unref (server); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/resource-test.c b/tests/resource-test.c new file mode 100644 index 00000000..6fcb899e --- /dev/null +++ b/tests/resource-test.c @@ -0,0 +1,220 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012 Igalia S.L. + */ + +#include "test-utils.h" + +SoupBuffer *index_buffer; + +typedef struct { + GString *body; + char buffer[1024]; + GMainLoop *loop; +} AsyncRequestData; + +static void +stream_closed (GObject *source, GAsyncResult *result, gpointer user_data) +{ + GInputStream *in = G_INPUT_STREAM (source); + AsyncRequestData *data = user_data; + GError *error = NULL; + + g_input_stream_close_finish (in, result, &error); + g_assert_no_error (error); + g_main_loop_quit (data->loop); + g_object_unref (in); +} + +static void +test_read_ready (GObject *source, GAsyncResult *result, gpointer user_data) +{ + GInputStream *in = G_INPUT_STREAM (source); + AsyncRequestData *data = user_data; + gssize nread; + GError *error = NULL; + + nread = g_input_stream_read_finish (in, result, &error); + if (nread == -1) { + g_assert_no_error (error); + g_clear_error (&error); + g_input_stream_close (in, NULL, NULL); + g_object_unref (in); + return; + } else if (nread == 0) { + g_input_stream_close_async (in, G_PRIORITY_DEFAULT, NULL, + stream_closed, data); + return; + } + + g_string_append_len (data->body, data->buffer, nread); + g_input_stream_read_async (in, data->buffer, sizeof (data->buffer), + G_PRIORITY_DEFAULT, NULL, + test_read_ready, data); +} + +static void +async_request_sent (GObject *source, GAsyncResult *result, gpointer user_data) +{ + GInputStream *in; + AsyncRequestData *data = user_data; + GError *error = NULL; + + in = soup_request_send_finish (SOUP_REQUEST (source), result, &error); + if (!in) { + g_assert_no_error (error); + g_clear_error (&error); + return; + } + + g_input_stream_read_async (in, data->buffer, sizeof (data->buffer), + G_PRIORITY_DEFAULT, NULL, + test_read_ready, data); +} + +static void +do_async_request (SoupRequest *request) +{ + AsyncRequestData data; + + data.body = g_string_new (NULL); + soup_request_send_async (request, NULL, async_request_sent, &data); + + data.loop = g_main_loop_new (soup_session_get_async_context (soup_request_get_session (request)), TRUE); + g_main_loop_run (data.loop); + g_main_loop_unref (data.loop); + + soup_assert_cmpmem (data.body->str, data.body->len, + index_buffer->data, index_buffer->length); + g_string_free (data.body, TRUE); +} + +static void +do_sync_request (SoupRequest *request) +{ + GInputStream *in; + GString *body; + char buffer[1024]; + gssize nread; + GError *error = NULL; + + in = soup_request_send (request, NULL, &error); + if (!in) { + g_assert_no_error (error); + g_clear_error (&error); + return; + } + + body = g_string_new (NULL); + do { + nread = g_input_stream_read (in, buffer, sizeof (buffer), + NULL, &error); + if (nread == -1) { + g_assert_no_error (error); + g_clear_error (&error); + break; + } + g_string_append_len (body, buffer, nread); + } while (nread > 0); + + g_input_stream_close (in, NULL, &error); + g_assert_no_error (error); + g_clear_error (&error); + g_object_unref (in); + + soup_assert_cmpmem (body->str, body->len, index_buffer->data, index_buffer->length); + g_string_free (body, TRUE); +} + +static void +do_request (const char *uri_string, gconstpointer type) +{ + SoupSession *session; + SoupRequest *request; + GError *error = NULL; + + session = soup_test_session_new (GPOINTER_TO_SIZE (type), + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + NULL); + + request = soup_session_request (session, uri_string, &error); + g_assert_no_error (error); + + if (SOUP_IS_SESSION_ASYNC (session)) + do_async_request (request); + else + do_sync_request (request); + + g_object_unref (request); + soup_test_session_abort_unref (session); +} + +static void +do_request_file_test (gconstpointer type) +{ + GFile *index; + char *uri_string; + + index = g_file_new_for_path (g_test_get_filename (G_TEST_DIST, "index.txt", NULL)); + uri_string = g_file_get_uri (index); + g_object_unref (index); + + do_request (uri_string, type); + g_free (uri_string); +} + +static void +do_request_data_test (gconstpointer type) +{ + gchar *base64; + char *uri_string; + + base64 = g_base64_encode ((const guchar *)index_buffer->data, index_buffer->length); + uri_string = g_strdup_printf ("data:text/plain;charset=utf8;base64,%s", base64); + g_free (base64); + + do_request (uri_string, type); + g_free (uri_string); +} + +static void +do_request_gresource_test (gconstpointer type) +{ + do_request ("resource:///org/gnome/libsoup/tests/index.txt", type); +} + +int +main (int argc, char **argv) +{ + int ret; + + test_init (argc, argv, NULL); + + index_buffer = soup_test_get_index (); + soup_test_register_resources (); + + g_test_add_data_func ("/resource/sync/file", + GSIZE_TO_POINTER (SOUP_TYPE_SESSION_SYNC), + do_request_file_test); + g_test_add_data_func ("/resource/sync/data", + GSIZE_TO_POINTER (SOUP_TYPE_SESSION_SYNC), + do_request_data_test); + g_test_add_data_func ("/resource/sync/gresource", + GSIZE_TO_POINTER (SOUP_TYPE_SESSION_SYNC), + do_request_gresource_test); + + g_test_add_data_func ("/resource/async/file", + GSIZE_TO_POINTER (SOUP_TYPE_SESSION_ASYNC), + do_request_file_test); + g_test_add_data_func ("/resource/async/data", + GSIZE_TO_POINTER (SOUP_TYPE_SESSION_ASYNC), + do_request_data_test); + g_test_add_data_func ("/resource/async/gresource", + GSIZE_TO_POINTER (SOUP_TYPE_SESSION_ASYNC), + do_request_gresource_test); + + ret = g_test_run (); + + test_cleanup (); + return ret; +} diff --git a/tests/resources/feed.rdf b/tests/resources/feed.rdf new file mode 100644 index 00000000..f3d9e276 --- /dev/null +++ b/tests/resources/feed.rdf @@ -0,0 +1,32 @@ +<?xml version="1.0"?> + +<!-- RDF Site Summary (RSS) 1.0 + http://groups.yahoo.com/group/rss-dev/files/specification.html + Section 5.3 + --> + +<rdf:RDF + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns="http://purl.org/rss/1.0/"> + + <channel rdf:about="http://www.xml.com/xml/news.rss"> + <title>XML.com</title> + <link>http://xml.com/pub</link> + <description> + XML.com features a rich mix of information and services + for the XML community. + </description> + + <image rdf:resource="http://xml.com/universal/images/xml_tiny.gif" /> + + <items> + <rdf:Seq> + <rdf:li resource="http://xml.com/pub/2000/08/09/xslt/xslt.html" /> + <rdf:li resource="http://xml.com/pub/2000/08/09/rdfdb/index.html" /> + </rdf:Seq> + </items> + + <textinput rdf:resource="http://search.xml.com" /> + </channel> + +</rdf:RDF> diff --git a/tests/resources/home.jpg b/tests/resources/home.jpg Binary files differnew file mode 100644 index 00000000..ac1f3bbc --- /dev/null +++ b/tests/resources/home.jpg diff --git a/tests/resources/home.png b/tests/resources/home.png Binary files differnew file mode 100644 index 00000000..0bb82bac --- /dev/null +++ b/tests/resources/home.png diff --git a/tests/resources/html_binary.html b/tests/resources/html_binary.html index 9200dd42..d443048c 100644 --- a/tests/resources/html_binary.html +++ b/tests/resources/html_binary.html @@ -1 +1 @@ -<HTML +<HTML diff --git a/tests/resources/leading_space.html b/tests/resources/leading_space.html new file mode 100644 index 00000000..a640d653 --- /dev/null +++ b/tests/resources/leading_space.html @@ -0,0 +1,12 @@ + + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title></title> +</head> +<body> +<h1>GNOME!</h1> +</body> +</html> diff --git a/tests/resources/test.aiff b/tests/resources/test.aiff Binary files differnew file mode 100644 index 00000000..9a1ecbb2 --- /dev/null +++ b/tests/resources/test.aiff diff --git a/tests/resources/test.mp4 b/tests/resources/test.mp4 Binary files differnew file mode 100644 index 00000000..d278c8ad --- /dev/null +++ b/tests/resources/test.mp4 diff --git a/tests/resources/test.ogg b/tests/resources/test.ogg Binary files differnew file mode 100644 index 00000000..e8f49ac3 --- /dev/null +++ b/tests/resources/test.ogg diff --git a/tests/resources/test.wav b/tests/resources/test.wav Binary files differnew file mode 100644 index 00000000..11660b29 --- /dev/null +++ b/tests/resources/test.wav diff --git a/tests/resources/test.webm b/tests/resources/test.webm Binary files differnew file mode 100644 index 00000000..7e53d0b4 --- /dev/null +++ b/tests/resources/test.webm diff --git a/tests/resources/text.txt b/tests/resources/text.txt new file mode 100644 index 00000000..ff7066f6 --- /dev/null +++ b/tests/resources/text.txt @@ -0,0 +1 @@ +This is just text. diff --git a/tests/resources/tux.webp b/tests/resources/tux.webp Binary files differnew file mode 100644 index 00000000..8764f066 --- /dev/null +++ b/tests/resources/tux.webp diff --git a/tests/server-auth-test.c b/tests/server-auth-test.c index 757e065a..f386f526 100644 --- a/tests/server-auth-test.c +++ b/tests/server-auth-test.c @@ -5,6 +5,8 @@ #include "test-utils.h" +static SoupURI *base_uri; + static struct { gboolean client_sent_basic, client_sent_digest; gboolean server_requested_basic, server_requested_digest; @@ -21,7 +23,7 @@ curl_exited (GPid pid, int status, gpointer data) } static void -do_test (int n, SoupURI *base_uri, const char *path, +do_test (SoupURI *base_uri, const char *path, gboolean good_user, gboolean good_password, gboolean offer_basic, gboolean offer_digest, gboolean client_sends_basic, gboolean client_sends_digest, @@ -34,18 +36,14 @@ do_test (int n, SoupURI *base_uri, const char *path, GPid pid; gboolean done; - debug_printf (1, "%2d. %s, %soffer Basic, %soffer Digest, %s user, %s password\n", - n, path, offer_basic ? "" : "don't ", - offer_digest ? "" : "don't ", - good_user ? "good" : "bad", - good_password ? "good" : "bad"); - uri = soup_uri_new_with_base (base_uri, path); uri_str = soup_uri_to_string (uri, FALSE); soup_uri_free (uri); args = g_ptr_array_new (); g_ptr_array_add (args, "curl"); + g_ptr_array_add (args, "--noproxy"); + g_ptr_array_add (args, "*"); g_ptr_array_add (args, "-f"); g_ptr_array_add (args, "-s"); if (offer_basic || offer_digest) { @@ -86,135 +84,103 @@ do_test (int n, SoupURI *base_uri, const char *path, g_ptr_array_free (args, TRUE); g_free (uri_str); - if (server_requests_basic != test_data.server_requested_basic) { - errors++; - if (test_data.server_requested_basic) - debug_printf (1, " Server sent WWW-Authenticate: Basic, but shouldn't have!\n"); - else - debug_printf (1, " Server didn't send WWW-Authenticate: Basic, but should have!\n"); - } - if (server_requests_digest != test_data.server_requested_digest) { - errors++; - if (test_data.server_requested_digest) - debug_printf (1, " Server sent WWW-Authenticate: Digest, but shouldn't have!\n"); - else - debug_printf (1, " Server didn't send WWW-Authenticate: Digest, but should have!\n"); - } - if (client_sends_basic != test_data.client_sent_basic) { - errors++; - if (test_data.client_sent_basic) - debug_printf (1, " Client sent Authorization: Basic, but shouldn't have!\n"); - else - debug_printf (1, " Client didn't send Authorization: Basic, but should have!\n"); - } - if (client_sends_digest != test_data.client_sent_digest) { - errors++; - if (test_data.client_sent_digest) - debug_printf (1, " Client sent Authorization: Digest, but shouldn't have!\n"); - else - debug_printf (1, " Client didn't send Authorization: Digest, but should have!\n"); - } - if (success && !test_data.succeeded) { - errors++; - debug_printf (1, " Should have succeeded, but didn't!\n"); - } else if (!success && test_data.succeeded) { - errors++; - debug_printf (1, " Should not have succeeded, but did!\n"); - } + g_assert_cmpint (server_requests_basic, ==, test_data.server_requested_basic); + g_assert_cmpint (server_requests_digest, ==, test_data.server_requested_digest); + g_assert_cmpint (client_sends_basic, ==, test_data.client_sent_basic); + g_assert_cmpint (client_sends_digest, ==, test_data.client_sent_digest); + + g_assert_cmpint (success, ==, test_data.succeeded); } +#define TEST_USES_BASIC(t) (((t) & 1) == 1) +#define TEST_USES_DIGEST(t) (((t) & 2) == 2) +#define TEST_GOOD_USER(t) (((t) & 4) == 4) +#define TEST_GOOD_PASSWORD(t) (((t) & 8) == 8) + +#define TEST_GOOD_AUTH(t) (TEST_GOOD_USER (t) && TEST_GOOD_PASSWORD (t)) +#define TEST_PREEMPTIVE_BASIC(t) (TEST_USES_BASIC (t) && !TEST_USES_DIGEST (t)) + static void -do_auth_tests (SoupURI *base_uri) +do_server_auth_test (gconstpointer data) { - int i, n = 1; - gboolean use_basic, use_digest, good_user, good_password; - gboolean preemptive_basic, good_auth; - - for (i = 0; i < 16; i++) { - use_basic = (i & 1) == 1; - use_digest = (i & 2) == 2; - good_user = (i & 4) == 4; - good_password = (i & 8) == 8; - - good_auth = good_user && good_password; - - /* Curl will preemptively send Basic if it's told to - * use Basic but not Digest. - */ - preemptive_basic = use_basic && !use_digest; - - /* 1. No auth required. The server will ignore the - * Authorization headers completely, and the request - * will always succeed. - */ - do_test (n++, base_uri, "/foo", - good_user, good_password, - /* request */ - use_basic, use_digest, - /* expected from client */ - preemptive_basic, FALSE, - /* expected from server */ - FALSE, FALSE, - /* success? */ - TRUE); - - /* 2. Basic auth required. The server will send - * "WWW-Authenticate: Basic" if the client fails to - * send an Authorization: Basic on the first request, - * or if it sends a bad password. - */ - do_test (n++, base_uri, "/Basic/foo", - good_user, good_password, - /* request */ - use_basic, use_digest, - /* expected from client */ - use_basic, FALSE, - /* expected from server */ - !preemptive_basic || !good_auth, FALSE, - /* success? */ - use_basic && good_auth); - - /* 3. Digest auth required. Simpler than the basic - * case because the client can't send Digest auth - * premptively. - */ - do_test (n++, base_uri, "/Digest/foo", - good_user, good_password, - /* request */ - use_basic, use_digest, - /* expected from client */ - preemptive_basic, use_digest, - /* expected from server */ - FALSE, TRUE, - /* success? */ - use_digest && good_auth); - - /* 4. Any auth required. */ - do_test (n++, base_uri, "/Any/foo", - good_user, good_password, - /* request */ - use_basic, use_digest, - /* expected from client */ - preemptive_basic, use_digest, - /* expected from server */ - !preemptive_basic || !good_auth, !preemptive_basic || !good_auth, - /* success? */ - (use_basic || use_digest) && good_auth); - - /* 5. No auth required again. (Makes sure that - * SOUP_AUTH_DOMAIN_REMOVE_PATH works.) - */ - do_test (n++, base_uri, "/Any/Not/foo", - good_user, good_password, - /* request */ - use_basic, use_digest, - /* expected from client */ - preemptive_basic, FALSE, - /* expected from server */ - FALSE, FALSE, - /* success? */ - TRUE); - } + int i = GPOINTER_TO_INT (data); + +#ifndef HAVE_CURL + g_test_skip ("/usr/bin/curl is not available"); + return; +#endif + + /* 1. No auth required. The server will ignore the + * Authorization headers completely, and the request + * will always succeed. + */ + do_test (base_uri, "/foo", + TEST_GOOD_USER (i), TEST_GOOD_PASSWORD (i), + /* request */ + TEST_USES_BASIC (i), TEST_USES_DIGEST (i), + /* expected from client */ + TEST_PREEMPTIVE_BASIC (i), FALSE, + /* expected from server */ + FALSE, FALSE, + /* success? */ + TRUE); + + /* 2. Basic auth required. The server will send + * "WWW-Authenticate: Basic" if the client fails to + * send an Authorization: Basic on the first request, + * or if it sends a bad password. + */ + do_test (base_uri, "/Basic/foo", + TEST_GOOD_USER (i), TEST_GOOD_PASSWORD (i), + /* request */ + TEST_USES_BASIC (i), TEST_USES_DIGEST (i), + /* expected from client */ + TEST_USES_BASIC (i), FALSE, + /* expected from server */ + !TEST_PREEMPTIVE_BASIC (i) || !TEST_GOOD_AUTH (i), FALSE, + /* success? */ + TEST_USES_BASIC (i) && TEST_GOOD_AUTH (i)); + + /* 3. Digest auth required. Simpler than the basic + * case because the client can't send Digest auth + * premptively. + */ + do_test (base_uri, "/Digest/foo", + TEST_GOOD_USER (i), TEST_GOOD_PASSWORD (i), + /* request */ + TEST_USES_BASIC (i), TEST_USES_DIGEST (i), + /* expected from client */ + TEST_PREEMPTIVE_BASIC (i), TEST_USES_DIGEST (i), + /* expected from server */ + FALSE, TRUE, + /* success? */ + TEST_USES_DIGEST (i) && TEST_GOOD_AUTH (i)); + + /* 4. Any auth required. */ + do_test (base_uri, "/Any/foo", + TEST_GOOD_USER (i), TEST_GOOD_PASSWORD (i), + /* request */ + TEST_USES_BASIC (i), TEST_USES_DIGEST (i), + /* expected from client */ + TEST_PREEMPTIVE_BASIC (i), TEST_USES_DIGEST (i), + /* expected from server */ + !TEST_PREEMPTIVE_BASIC (i) || !TEST_GOOD_AUTH (i), !TEST_PREEMPTIVE_BASIC (i) || !TEST_GOOD_AUTH (i), + /* success? */ + (TEST_USES_BASIC (i) || TEST_USES_DIGEST (i)) && TEST_GOOD_AUTH (i)); + + /* 5. No auth required again. (Makes sure that + * SOUP_AUTH_DOMAIN_REMOVE_PATH works.) + */ + do_test (base_uri, "/Any/Not/foo", + TEST_GOOD_USER (i), TEST_GOOD_PASSWORD (i), + /* request */ + TEST_USES_BASIC (i), TEST_USES_DIGEST (i), + /* expected from client */ + TEST_PREEMPTIVE_BASIC (i), FALSE, + /* expected from server */ + FALSE, FALSE, + /* success? */ + TRUE); } static gboolean @@ -311,8 +277,8 @@ main (int argc, char **argv) { GMainLoop *loop; SoupServer *server; - SoupURI *uri; SoupAuthDomain *auth_domain; + int ret; test_init (argc, argv, no_test_entry); @@ -345,13 +311,45 @@ main (int argc, char **argv) loop = g_main_loop_new (NULL, TRUE); if (run_tests) { - uri = soup_uri_new ("http://127.0.0.1"); - soup_uri_set_port (uri, soup_server_get_port (server)); - do_auth_tests (uri); - soup_uri_free (uri); + int i; + + base_uri = soup_uri_new ("http://127.0.0.1"); + soup_uri_set_port (base_uri, soup_server_get_port (server)); + + for (i = 0; i < 16; i++) { + char *path; + const char *authtypes; + + if (!TEST_GOOD_USER (i) && !TEST_GOOD_PASSWORD (i)) + continue; + if (TEST_USES_BASIC (i)) { + if (TEST_USES_DIGEST (i)) + authtypes = "basic+digest"; + else + authtypes = "basic"; + } else { + if (TEST_USES_DIGEST (i)) + authtypes = "digest"; + else + authtypes = "none"; + } + + path = g_strdup_printf ("/server-auth/%s/%s-user%c%s-password", + authtypes, + TEST_GOOD_USER (i) ? "good" : "bad", + TEST_GOOD_USER (i) ? '/' : '\0', + TEST_GOOD_PASSWORD (i) ? "good" : "bad"); + g_test_add_data_func (path, GINT_TO_POINTER (i), do_server_auth_test); + g_free (path); + } + + ret = g_test_run (); + + soup_uri_free (base_uri); } else { g_print ("Listening on port %d\n", soup_server_get_port (server)); g_main_loop_run (loop); + ret = 0; } g_main_loop_unref (loop); @@ -359,5 +357,5 @@ main (int argc, char **argv) if (run_tests) test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/server-test.c b/tests/server-test.c new file mode 100644 index 00000000..0c980908 --- /dev/null +++ b/tests/server-test.c @@ -0,0 +1,329 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright 2007-2012 Red Hat, Inc. + */ + +#include "test-utils.h" + +SoupServer *server, *ssl_server; +SoupURI *base_uri, *ssl_base_uri; + +static void +server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + soup_message_headers_append (msg->response_headers, + "X-Handled-By", "server_callback"); + + if (!strcmp (path, "*")) { + soup_test_assert (FALSE, "default server_callback got request for '*'"); + soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + return; + } + + if (msg->method != SOUP_METHOD_GET && msg->method != SOUP_METHOD_POST) { + soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED); + return; + } + + soup_message_set_status (msg, SOUP_STATUS_OK); + soup_message_set_response (msg, "text/plain", + SOUP_MEMORY_STATIC, "index", 5); +} + +static void +server_star_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + soup_message_headers_append (msg->response_headers, + "X-Handled-By", "star_callback"); + + if (strcmp (path, "*") != 0) { + soup_test_assert (FALSE, "server_star_callback got request for '%s'", path); + soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR); + return; + } + + if (msg->method != SOUP_METHOD_OPTIONS) { + soup_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED); + return; + } + + soup_message_set_status (msg, SOUP_STATUS_OK); +} + +/* Server handlers for "*" work but are separate from handlers for + * all other URIs. #590751 + */ +static void +do_star_test (void) +{ + SoupSession *session; + SoupMessage *msg; + SoupURI *star_uri; + const char *handled_by; + + g_test_bug ("590751"); + + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); + star_uri = soup_uri_copy (base_uri); + soup_uri_set_path (star_uri, "*"); + + debug_printf (1, " Testing with no handler\n"); + msg = soup_message_new_from_uri ("OPTIONS", star_uri); + soup_session_send_message (session, msg); + + soup_test_assert_message_status (msg, SOUP_STATUS_NOT_FOUND); + handled_by = soup_message_headers_get_one (msg->response_headers, + "X-Handled-By"); + g_assert_cmpstr (handled_by, ==, NULL); + g_object_unref (msg); + + soup_server_add_handler (server, "*", server_star_callback, NULL, NULL); + + debug_printf (1, " Testing with handler\n"); + msg = soup_message_new_from_uri ("OPTIONS", star_uri); + soup_session_send_message (session, msg); + + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + handled_by = soup_message_headers_get_one (msg->response_headers, + "X-Handled-By"); + g_assert_cmpstr (handled_by, ==, "star_callback"); + g_object_unref (msg); + + soup_test_session_abort_unref (session); + soup_uri_free (star_uri); +} + +static void +do_one_server_aliases_test (SoupURI *uri, + const char *alias, + gboolean succeed) +{ + GSocketClient *client; + GSocketConnectable *addr; + GSocketConnection *conn; + GInputStream *in; + GOutputStream *out; + GError *error = NULL; + GString *req; + static char buf[1024]; + + debug_printf (1, " %s via %s\n", alias, uri->scheme); + + /* There's no way to make libsoup's client side send an absolute + * URI (to a non-proxy server), so we have to fake this. + */ + + client = g_socket_client_new (); + if (uri->scheme == SOUP_URI_SCHEME_HTTPS) { + g_socket_client_set_tls (client, TRUE); + g_socket_client_set_tls_validation_flags (client, 0); + } + addr = g_network_address_new (uri->host, uri->port); + + conn = g_socket_client_connect (client, addr, NULL, &error); + g_object_unref (addr); + g_object_unref (client); + if (!conn) { + g_assert_no_error (error); + g_error_free (error); + return; + } + + in = g_io_stream_get_input_stream (G_IO_STREAM (conn)); + out = g_io_stream_get_output_stream (G_IO_STREAM (conn)); + + req = g_string_new (NULL); + g_string_append_printf (req, "GET %s://%s:%d HTTP/1.1\r\n", + alias, uri->host, uri->port); + g_string_append_printf (req, "Host: %s:%d\r\n", + uri->host, uri->port); + g_string_append (req, "Connection: close\r\n\r\n"); + + if (!g_output_stream_write_all (out, req->str, req->len, NULL, NULL, &error)) { + g_assert_no_error (error); + g_error_free (error); + g_object_unref (conn); + g_string_free (req, TRUE); + return; + } + g_string_free (req, TRUE); + + if (!g_input_stream_read_all (in, buf, sizeof (buf), NULL, NULL, &error)) { + g_assert_no_error (error); + g_error_free (error); + g_object_unref (conn); + return; + } + + if (succeed) + g_assert_true (g_str_has_prefix (buf, "HTTP/1.1 200 ")); + else + g_assert_true (g_str_has_prefix (buf, "HTTP/1.1 400 ")); + + g_io_stream_close (G_IO_STREAM (conn), NULL, NULL); + g_object_unref (conn); +} + +static void +do_server_aliases_test (void) +{ + char *http_good[] = { "http", "dav", NULL }; + char *http_bad[] = { "https", "davs", "fred", NULL }; + char *https_good[] = { "https", "davs", NULL }; + char *https_bad[] = { "http", "dav", "fred", NULL }; + int i; + + g_test_bug ("703694"); + + for (i = 0; http_good[i]; i++) + do_one_server_aliases_test (base_uri, http_good[i], TRUE); + for (i = 0; http_bad[i]; i++) + do_one_server_aliases_test (base_uri, http_bad[i], FALSE); + + if (tls_available) { + for (i = 0; https_good[i]; i++) + do_one_server_aliases_test (ssl_base_uri, https_good[i], TRUE); + for (i = 0; https_bad[i]; i++) + do_one_server_aliases_test (ssl_base_uri, https_bad[i], FALSE); + } +} + +static void +do_dot_dot_test (void) +{ + SoupSession *session; + SoupMessage *msg; + SoupURI *uri; + + g_test_bug ("667635"); + + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); + + uri = soup_uri_new_with_base (base_uri, "/..%2ftest"); + msg = soup_message_new_from_uri ("GET", uri); + soup_uri_free (uri); + + soup_session_send_message (session, msg); + soup_test_assert_message_status (msg, SOUP_STATUS_BAD_REQUEST); + g_object_unref (msg); + + soup_test_session_abort_unref (session); +} + +static void +ipv6_server_callback (SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *context, gpointer data) +{ + const char *host; + char expected_host[128]; + + g_snprintf (expected_host, sizeof (expected_host), + "[::1]:%d", soup_server_get_port (server)); + + host = soup_message_headers_get_one (msg->request_headers, "Host"); + g_assert_cmpstr (host, ==, expected_host); + + if (g_test_failed ()) + soup_message_set_status (msg, SOUP_STATUS_BAD_REQUEST); + else + soup_message_set_status (msg, SOUP_STATUS_OK); +} + +static void +do_ipv6_test (void) +{ + SoupServer *ipv6_server; + SoupURI *ipv6_uri; + SoupAddress *ipv6_addr; + SoupSession *session; + SoupMessage *msg; + + g_test_bug ("666399"); + + ipv6_addr = soup_address_new ("::1", SOUP_ADDRESS_ANY_PORT); + soup_address_resolve_sync (ipv6_addr, NULL); + ipv6_server = soup_server_new (SOUP_SERVER_INTERFACE, ipv6_addr, + NULL); + g_object_unref (ipv6_addr); + if (!ipv6_server) { + debug_printf (1, " skipping due to lack of IPv6 support\n"); + return; + } + + soup_server_add_handler (ipv6_server, NULL, ipv6_server_callback, NULL, NULL); + soup_server_run_async (ipv6_server); + + ipv6_uri = soup_uri_new ("http://[::1]/"); + soup_uri_set_port (ipv6_uri, soup_server_get_port (ipv6_server)); + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + + debug_printf (1, " HTTP/1.1\n"); + msg = soup_message_new_from_uri ("GET", ipv6_uri); + soup_session_send_message (session, msg); + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + g_object_unref (msg); + + debug_printf (1, " HTTP/1.0\n"); + msg = soup_message_new_from_uri ("GET", ipv6_uri); + soup_message_set_http_version (msg, SOUP_HTTP_1_0); + soup_session_send_message (session, msg); + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + g_object_unref (msg); + + soup_uri_free (ipv6_uri); + soup_test_session_abort_unref (session); + soup_test_server_quit_unref (ipv6_server); +} + +int +main (int argc, char **argv) +{ + char *http_aliases[] = { "dav", NULL }; + char *https_aliases[] = { "davs", NULL }; + int ret; + + test_init (argc, argv, NULL); + + server = soup_test_server_new (TRUE); + soup_server_add_handler (server, NULL, server_callback, NULL, NULL); + base_uri = soup_uri_new ("http://127.0.0.1/"); + soup_uri_set_port (base_uri, soup_server_get_port (server)); + + g_object_set (G_OBJECT (server), + SOUP_SERVER_HTTP_ALIASES, http_aliases, + NULL); + + if (tls_available) { + ssl_server = soup_test_server_new_ssl (TRUE); + soup_server_add_handler (ssl_server, NULL, server_callback, NULL, NULL); + ssl_base_uri = soup_uri_new ("https://127.0.0.1/"); + soup_uri_set_port (ssl_base_uri, soup_server_get_port (ssl_server)); + g_object_set (G_OBJECT (ssl_server), + SOUP_SERVER_HTTPS_ALIASES, https_aliases, + NULL); + } + + g_test_add_func ("/server/OPTIONS *", do_star_test); + g_test_add_func ("/server/aliases", do_server_aliases_test); + g_test_add_func ("/server/..-in-path", do_dot_dot_test); + g_test_add_func ("/server/ipv6", do_ipv6_test); + + ret = g_test_run (); + + soup_uri_free (base_uri); + soup_test_server_quit_unref (server); + + if (tls_available) { + soup_uri_free (ssl_base_uri); + soup_test_server_quit_unref (ssl_server); + } + + test_cleanup (); + return ret; +} diff --git a/tests/session-test.c b/tests/session-test.c new file mode 100644 index 00000000..15072058 --- /dev/null +++ b/tests/session-test.c @@ -0,0 +1,396 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +#include "test-utils.h" + +static gboolean server_processed_message; +static gboolean timeout; +static GMainLoop *loop; +static SoupMessagePriority expected_priorities[3]; + +static gboolean +timeout_cb (gpointer user_data) +{ + gboolean *timeout = user_data; + + *timeout = TRUE; + return FALSE; +} + +static void +server_handler (SoupServer *server, + SoupMessage *msg, + const char *path, + GHashTable *query, + SoupClientContext *client, + gpointer user_data) +{ + if (!strcmp (path, "/request-timeout")) { + GMainContext *context = soup_server_get_async_context (server); + GSource *timer; + + timer = g_timeout_source_new (100); + g_source_set_callback (timer, timeout_cb, &timeout, NULL); + g_source_attach (timer, context); + g_source_unref (timer); + } else + server_processed_message = TRUE; + + soup_message_set_status (msg, SOUP_STATUS_OK); + soup_message_set_response (msg, "text/plain", + SOUP_MEMORY_STATIC, + "ok\r\n", 4); +} + +static void +finished_cb (SoupSession *session, SoupMessage *msg, gpointer user_data) +{ + gboolean *finished = user_data; + + *finished = TRUE; +} + +static void +cancel_message_cb (SoupMessage *msg, gpointer session) +{ + soup_session_cancel_message (session, msg, SOUP_STATUS_CANCELLED); + g_main_loop_quit (loop); +} + +static void +do_test_for_session (SoupSession *session, + const char *uri, + gboolean queue_is_async, + gboolean send_is_blocking, + gboolean cancel_is_immediate) +{ + SoupMessage *msg; + gboolean finished, local_timeout; + guint timeout_id; + char *timeout_uri; + + debug_printf (1, " queue_message\n"); + debug_printf (2, " requesting timeout\n"); + timeout_uri = g_strdup_printf ("%s/request-timeout", uri); + msg = soup_message_new ("GET", timeout_uri); + g_free (timeout_uri); + soup_session_send_message (session, msg); + g_object_unref (msg); + + msg = soup_message_new ("GET", uri); + server_processed_message = timeout = finished = FALSE; + soup_session_queue_message (session, msg, finished_cb, &finished); + while (!timeout) + g_usleep (100); + debug_printf (2, " got timeout\n"); + + if (queue_is_async) { + g_assert_false (server_processed_message); + debug_printf (2, " waiting for finished\n"); + while (!finished) + g_main_context_iteration (NULL, TRUE); + g_assert_true (server_processed_message); + } else { + g_assert_true (server_processed_message); + g_assert_false (finished); + debug_printf (2, " waiting for finished\n"); + while (!finished) + g_main_context_iteration (NULL, TRUE); + } + + debug_printf (1, " send_message\n"); + msg = soup_message_new ("GET", uri); + server_processed_message = local_timeout = FALSE; + timeout_id = g_idle_add_full (G_PRIORITY_HIGH, timeout_cb, &local_timeout, NULL); + soup_session_send_message (session, msg); + + g_assert_true (server_processed_message); + + if (send_is_blocking) { + soup_test_assert (!local_timeout, + "send_message ran main loop"); + } else { + soup_test_assert (local_timeout, + "send_message didn't run main loop"); + } + + if (!local_timeout) + g_source_remove (timeout_id); + + if (!queue_is_async) + return; + + debug_printf (1, " cancel_message\n"); + msg = soup_message_new ("GET", uri); + g_object_ref (msg); + finished = FALSE; + soup_session_queue_message (session, msg, finished_cb, &finished); + g_signal_connect (msg, "wrote-headers", + G_CALLBACK (cancel_message_cb), session); + + loop = g_main_loop_new (NULL, FALSE); + g_main_loop_run (loop); + + if (cancel_is_immediate) + g_assert_true (finished); + else + g_assert_false (finished); + + if (!finished) { + debug_printf (2, " waiting for finished\n"); + while (!finished) + g_main_context_iteration (NULL, TRUE); + } + + soup_test_assert_message_status (msg, SOUP_STATUS_CANCELLED); + g_object_unref (msg); +} + +static void +do_plain_tests (gconstpointer uri) +{ + SoupSession *session; + + session = soup_test_session_new (SOUP_TYPE_SESSION, NULL); + do_test_for_session (session, uri, TRUE, TRUE, FALSE); + soup_test_session_abort_unref (session); +} + +static void +do_async_tests (gconstpointer uri) +{ + SoupSession *session; + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + do_test_for_session (session, uri, TRUE, FALSE, TRUE); + soup_test_session_abort_unref (session); +} + +static void +do_sync_tests (gconstpointer uri) +{ + SoupSession *session; + + session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); + do_test_for_session (session, uri, FALSE, TRUE, FALSE); + soup_test_session_abort_unref (session); +} + +static void +priority_test_finished_cb (SoupSession *session, SoupMessage *msg, gpointer user_data) +{ + guint *finished_count = user_data; + SoupMessagePriority priority = soup_message_get_priority (msg); + + debug_printf (1, " received message %d with priority %d\n", + *finished_count, priority); + + soup_test_assert (priority == expected_priorities[*finished_count], + "message %d should have priority %d (%d found)", + *finished_count, expected_priorities[*finished_count], priority); + + (*finished_count)++; +} + +static void +do_priority_tests (gconstpointer data) +{ + const char *uri = data; + SoupSession *session; + int i, finished_count = 0; + SoupMessagePriority priorities[] = + { SOUP_MESSAGE_PRIORITY_LOW, + SOUP_MESSAGE_PRIORITY_HIGH, + SOUP_MESSAGE_PRIORITY_NORMAL }; + + g_test_bug ("696277"); + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); + g_object_set (session, "max-conns", 1, NULL); + + expected_priorities[0] = SOUP_MESSAGE_PRIORITY_HIGH; + expected_priorities[1] = SOUP_MESSAGE_PRIORITY_NORMAL; + expected_priorities[2] = SOUP_MESSAGE_PRIORITY_LOW; + + for (i = 0; i < 3; i++) { + char *msg_uri; + SoupMessage *msg; + + msg_uri = g_strdup_printf ("%s/%d", uri, i); + msg = soup_message_new ("GET", uri); + g_free (msg_uri); + + soup_message_set_priority (msg, priorities[i]); + soup_session_queue_message (session, msg, priority_test_finished_cb, &finished_count); + } + + debug_printf (2, " waiting for finished\n"); + while (finished_count != 3) + g_main_context_iteration (NULL, TRUE); + + soup_test_session_abort_unref (session); +} + +static void +test_session_properties (const char *name, + SoupSession *session, + GProxyResolver *expected_proxy_resolver, + GTlsDatabase *expected_tls_database) +{ + GProxyResolver *proxy_resolver = NULL; + GTlsDatabase *tlsdb = NULL; + + g_object_get (G_OBJECT (session), + SOUP_SESSION_PROXY_RESOLVER, &proxy_resolver, + SOUP_SESSION_TLS_DATABASE, &tlsdb, + NULL); + + soup_test_assert (proxy_resolver == expected_proxy_resolver, + "%s has %s proxy resolver", + name, proxy_resolver ? (expected_proxy_resolver ? "wrong" : "a") : "no"); + soup_test_assert (tlsdb == expected_tls_database, + "%s has %s TLS database", + name, tlsdb ? (expected_tls_database ? "wrong" : "a") : "no"); + + g_clear_object (&proxy_resolver); + g_clear_object (&tlsdb); +} + +static void +do_property_tests (void) +{ + SoupSession *session; + GProxyResolver *proxy_resolver, *default_proxy_resolver; + GTlsDatabase *tlsdb, *default_tlsdb; + SoupURI *uri; + + g_test_bug ("708696"); + + default_proxy_resolver = g_proxy_resolver_get_default (); + default_tlsdb = g_tls_backend_get_default_database (g_tls_backend_get_default ()); + + /* NOTE: We intentionally do not use soup_test_session_new() here */ + + session = g_object_new (SOUP_TYPE_SESSION, + NULL); + test_session_properties ("Base plain session", session, + default_proxy_resolver, default_tlsdb); + g_object_unref (session); + + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_PROXY_RESOLVER, NULL, + NULL); + test_session_properties ("Session with NULL :proxy-resolver", session, + NULL, default_tlsdb); + g_object_unref (session); + + proxy_resolver = g_simple_proxy_resolver_new (NULL, NULL); + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_PROXY_RESOLVER, proxy_resolver, + NULL); + test_session_properties ("Session with non-NULL :proxy-resolver", session, + proxy_resolver, default_tlsdb); + g_object_unref (proxy_resolver); + g_object_unref (session); + + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_PROXY_URI, NULL, + NULL); + test_session_properties ("Session with NULL :proxy-uri", session, + NULL, default_tlsdb); + g_object_unref (session); + + uri = soup_uri_new ("http://example.com/"); + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_PROXY_URI, uri, + NULL); + g_object_get (G_OBJECT (session), + SOUP_SESSION_PROXY_RESOLVER, &proxy_resolver, + NULL); + test_session_properties ("Session with non-NULL :proxy-uri", session, + proxy_resolver, default_tlsdb); + g_assert_cmpstr (G_OBJECT_TYPE_NAME (proxy_resolver), ==, "GSimpleProxyResolver"); + g_object_unref (proxy_resolver); + g_object_unref (session); + soup_uri_free (uri); + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_REMOVE_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_URI_RESOLVER, + NULL); + test_session_properties ("Session with removed proxy resolver feature", session, + NULL, default_tlsdb); + g_object_unref (session); + G_GNUC_END_IGNORE_DEPRECATIONS; + + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_TLS_DATABASE, NULL, + NULL); + test_session_properties ("Session with NULL :tls-database", session, + default_proxy_resolver, NULL); + g_object_unref (session); + + /* g_tls_file_database_new() will fail with the dummy backend, + * so we can only do this test if we have a real TLS backend. + */ + if (tls_available) { + GError *error = NULL; + + tlsdb = g_tls_file_database_new (g_test_get_filename (G_TEST_DIST, + "test-cert.pem", + NULL), &error); + g_assert_no_error (error); + + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_TLS_DATABASE, tlsdb, + NULL); + test_session_properties ("Session with non-NULL :tls-database", session, + default_proxy_resolver, tlsdb); + g_object_unref (tlsdb); + g_object_unref (session); + } + + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, FALSE, + NULL); + test_session_properties ("Session with :ssl-use-system-ca-file FALSE", session, + default_proxy_resolver, NULL); + g_object_unref (session); + + session = g_object_new (SOUP_TYPE_SESSION, + SOUP_SESSION_SSL_USE_SYSTEM_CA_FILE, TRUE, + NULL); + test_session_properties ("Session with :ssl-use-system-ca-file TRUE", session, + default_proxy_resolver, default_tlsdb); + g_object_unref (session); +} + +int +main (int argc, char **argv) +{ + SoupServer *server; + char *uri, *timeout_uri; + int ret; + + test_init (argc, argv, NULL); + + server = soup_test_server_new (TRUE); + soup_server_add_handler (server, NULL, server_handler, NULL, NULL); + uri = g_strdup_printf ("http://127.0.0.1:%u", + soup_server_get_port (server)); + timeout_uri = g_strdup_printf ("%s/request-timeout", uri); + + g_test_add_data_func ("/session/SoupSession", uri, do_plain_tests); + g_test_add_data_func ("/session/SoupSessionAsync", uri, do_async_tests); + g_test_add_data_func ("/session/SoupSessionSync", uri, do_sync_tests); + g_test_add_data_func ("/session/priority", uri, do_priority_tests); + g_test_add_func ("/session/property", do_property_tests); + + ret = g_test_run (); + + g_free (uri); + g_free (timeout_uri); + soup_test_server_quit_unref (server); + + test_cleanup (); + return ret; +} diff --git a/tests/sniffing-test.c b/tests/sniffing-test.c index dd4eb4aa..32fad9fc 100644 --- a/tests/sniffing-test.c +++ b/tests/sniffing-test.c @@ -16,8 +16,8 @@ server_callback (SoupServer *server, SoupMessage *msg, { GError *error = NULL; char *query_key; - char *contents; - gsize length = 0, offset; + SoupBuffer *response = NULL; + gsize offset; gboolean empty_response = FALSE; if (msg->method != SOUP_METHOD_GET) { @@ -40,41 +40,35 @@ server_callback (SoupServer *server, SoupMessage *msg, } if (!strcmp (path, "/mbox")) { - if (empty_response) { - contents = g_strdup (""); - length = 0; - } else { - g_file_get_contents (SRCDIR "/resources/mbox", - &contents, &length, - &error); - } - - if (error) { - g_error ("%s", error->message); - g_error_free (error); - exit (1); + if (!empty_response) { + response = soup_test_load_resource ("mbox", &error); + g_assert_no_error (error); } soup_message_headers_append (msg->response_headers, "Content-Type", "text/plain"); } - if (g_str_has_prefix (path, "/text_or_binary/")) { + if (g_str_has_prefix (path, "/nosniff/")) { char *base_name = g_path_get_basename (path); - char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name); - - g_file_get_contents (file_name, - &contents, &length, - &error); + response = soup_test_load_resource (base_name, &error); + g_assert_no_error (error); g_free (base_name); - g_free (file_name); - if (error) { - g_error ("%s", error->message); - g_error_free (error); - exit (1); - } + soup_message_headers_append (msg->response_headers, + "X-Content-Type-Options", "nosniff"); + + soup_message_headers_append (msg->response_headers, + "Content-Type", "no/sniffing-allowed"); + } + + if (g_str_has_prefix (path, "/text_or_binary/") || g_str_has_prefix (path, "/apache_bug/")) { + char *base_name = g_path_get_basename (path); + + response = soup_test_load_resource (base_name, &error); + g_assert_no_error (error); + g_free (base_name); soup_message_headers_append (msg->response_headers, "Content-Type", "text/plain"); @@ -82,20 +76,10 @@ server_callback (SoupServer *server, SoupMessage *msg, if (g_str_has_prefix (path, "/unknown/")) { char *base_name = g_path_get_basename (path); - char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name); - - g_file_get_contents (file_name, - &contents, &length, - &error); + response = soup_test_load_resource (base_name, &error); + g_assert_no_error (error); g_free (base_name); - g_free (file_name); - - if (error) { - g_error ("%s", error->message); - g_error_free (error); - exit (1); - } soup_message_headers_append (msg->response_headers, "Content-Type", "UNKNOWN/unknown"); @@ -106,20 +90,10 @@ server_callback (SoupServer *server, SoupMessage *msg, char *ptr; char *base_name = g_path_get_basename (path); - char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name); - - g_file_get_contents (file_name, - &contents, &length, - &error); + response = soup_test_load_resource (base_name, &error); + g_assert_no_error (error); g_free (base_name); - g_free (file_name); - - if (error) { - g_error ("%s", error->message); - g_error_free (error); - exit (1); - } /* Hack to allow passing type in the URI */ ptr = g_strrstr (components[2], "_"); @@ -132,20 +106,10 @@ server_callback (SoupServer *server, SoupMessage *msg, if (g_str_has_prefix (path, "/multiple_headers/")) { char *base_name = g_path_get_basename (path); - char *file_name = g_strdup_printf (SRCDIR "/resources/%s", base_name); - - g_file_get_contents (file_name, - &contents, &length, - &error); + response = soup_test_load_resource (base_name, &error); + g_assert_no_error (error); g_free (base_name); - g_free (file_name); - - if (error) { - g_error ("%s", error->message); - g_error_free (error); - exit (1); - } soup_message_headers_append (msg->response_headers, "Content-Type", "text/xml"); @@ -153,15 +117,18 @@ server_callback (SoupServer *server, SoupMessage *msg, "Content-Type", "text/plain"); } - for (offset = 0; offset < length; offset += 500) { - soup_message_body_append (msg->response_body, - SOUP_MEMORY_COPY, - contents + offset, - MIN(500, length - offset)); + if (response) { + for (offset = 0; offset < response->length; offset += 500) { + soup_message_body_append (msg->response_body, + SOUP_MEMORY_COPY, + response->data + offset, + MIN (500, response->length - offset)); + } + + soup_buffer_free (response); } - soup_message_body_complete (msg->response_body); - g_free (contents); + soup_message_body_complete (msg->response_body); } static gboolean @@ -181,10 +148,8 @@ content_sniffed (SoupMessage *msg, char *content_type, GHashTable *params, gpoin debug_printf (2, " content-sniffed -> %s\n", content_type); - if (g_object_get_data (G_OBJECT (msg), "got-chunk")) { - debug_printf (1, " got-chunk got emitted before content-sniffed\n"); - errors++; - } + soup_test_assert (g_object_get_data (G_OBJECT (msg), "got-chunk") == NULL, + "got-chunk got emitted before content-sniffed"); g_object_set_data (G_OBJECT (msg), "content-sniffed", GINT_TO_POINTER (TRUE)); @@ -202,10 +167,8 @@ got_headers (SoupMessage *msg, gpointer data) debug_printf (2, " got-headers\n"); - if (g_object_get_data (G_OBJECT (msg), "content-sniffed")) { - debug_printf (1, " content-sniffed got emitted before got-headers\n"); - errors++; - } + soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") == NULL, + "content-sniffed got emitted before got-headers"); g_object_set_data (G_OBJECT (msg), "got-headers", GINT_TO_POINTER (TRUE)); @@ -233,13 +196,6 @@ got_chunk (SoupMessage *msg, SoupBuffer *chunk, gpointer data) } static void -finished (SoupSession *session, SoupMessage *msg, gpointer data) -{ - GMainLoop *loop = (GMainLoop*)data; - g_main_loop_quit (loop); -} - -static void do_signals_test (gboolean should_content_sniff, gboolean should_pause, gboolean should_accumulate, @@ -248,9 +204,7 @@ do_signals_test (gboolean should_content_sniff, { SoupURI *uri = soup_uri_new_with_base (base_uri, "/mbox"); SoupMessage *msg = soup_message_new_from_uri ("GET", uri); - GMainLoop *loop = g_main_loop_new (NULL, TRUE); - char *contents; - gsize length; + SoupBuffer *expected; GError *error = NULL; SoupBuffer *body = NULL; @@ -283,34 +237,21 @@ do_signals_test (gboolean should_content_sniff, "signal::content_sniffed", content_sniffed, GINT_TO_POINTER (should_pause), NULL); - g_object_ref (msg); - soup_session_queue_message (session, msg, finished, loop); - - g_main_loop_run (loop); - - if (!should_content_sniff && - g_object_get_data (G_OBJECT (msg), "content-sniffed")) { - debug_printf (1, " content-sniffed got emitted without a sniffer\n"); - errors++; - } else if (should_content_sniff && - !g_object_get_data (G_OBJECT (msg), "content-sniffed")) { - debug_printf (1, " content-sniffed did not get emitted\n"); - errors++; - } + soup_session_send_message (session, msg); - if (empty_response) { - contents = g_strdup (""); - length = 0; + if (should_content_sniff) { + soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") != NULL, + "content-sniffed did not get emitted"); } else { - g_file_get_contents (SRCDIR "/resources/mbox", - &contents, &length, - &error); + soup_test_assert (g_object_get_data (G_OBJECT (msg), "content-sniffed") == NULL, + "content-sniffed got emitted without a sniffer"); } - if (error) { - g_error ("%s", error->message); - g_error_free (error); - exit (1); + if (empty_response) + expected = soup_buffer_new (SOUP_MEMORY_STATIC, "", 0); + else { + expected = soup_test_load_resource ("mbox", &error); + g_assert_no_error (error); } if (!should_accumulate && chunk_data) @@ -318,17 +259,12 @@ do_signals_test (gboolean should_content_sniff, else if (msg->response_body) body = soup_message_body_flatten (msg->response_body); - if (body && body->length != length) { - debug_printf (1, " lengths do not match\n"); - errors++; + if (body) { + soup_assert_cmpmem (body->data, body->length, + expected->data, expected->length); } - if (body && memcmp (body->data, contents, length)) { - debug_printf (1, " downloaded data does not match\n"); - errors++; - } - - g_free (contents); + soup_buffer_free (expected); if (body) soup_buffer_free (body); if (chunk_data) { @@ -338,7 +274,42 @@ do_signals_test (gboolean should_content_sniff, soup_uri_free (uri); g_object_unref (msg); - g_main_loop_unref (loop); +} + +static void +do_signals_tests (gconstpointer data) +{ + gboolean should_content_sniff = GPOINTER_TO_INT (data); + + if (!should_content_sniff) + soup_session_remove_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER); + + do_signals_test (should_content_sniff, + FALSE, FALSE, FALSE, FALSE); + do_signals_test (should_content_sniff, + FALSE, FALSE, TRUE, FALSE); + do_signals_test (should_content_sniff, + FALSE, TRUE, FALSE, FALSE); + do_signals_test (should_content_sniff, + FALSE, TRUE, TRUE, FALSE); + + do_signals_test (should_content_sniff, + TRUE, TRUE, FALSE, FALSE); + do_signals_test (should_content_sniff, + TRUE, TRUE, TRUE, FALSE); + do_signals_test (should_content_sniff, + TRUE, FALSE, FALSE, FALSE); + do_signals_test (should_content_sniff, + TRUE, FALSE, TRUE, FALSE); + + /* FIXME g_test_bug ("587907") */ + do_signals_test (should_content_sniff, + TRUE, TRUE, FALSE, TRUE); + do_signals_test (should_content_sniff, + TRUE, TRUE, TRUE, TRUE); + + if (!should_content_sniff) + soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER); } static void @@ -347,102 +318,125 @@ sniffing_content_sniffed (SoupMessage *msg, const char *content_type, { char **sniffed_type = (char **)data; GString *full_header; - GList *keys; - GList *iter; - - if (params == NULL) { - *sniffed_type = g_strdup (content_type); - return; - } + GHashTableIter iter; + gpointer key, value; full_header = g_string_new (content_type); - g_string_append (full_header, "; "); - - keys = g_hash_table_get_keys (params); - for (iter = keys; iter != NULL; iter = iter->next) { - const gchar *value = (const gchar*) g_hash_table_lookup (params, iter->data); + g_hash_table_iter_init (&iter, params); + while (g_hash_table_iter_next (&iter, &key, &value)) { + if (full_header->len) + g_string_append (full_header, "; "); soup_header_g_string_append_param (full_header, - (const gchar*) iter->data, - value); + (const char *) key, + (const char *) value); } - *sniffed_type = full_header->str; - - g_string_free (full_header, FALSE); - g_list_free (keys); + *sniffed_type = g_string_free (full_header, FALSE); } static void test_sniffing (const char *path, const char *expected_type) { - SoupURI *uri = soup_uri_new_with_base (base_uri, path); - SoupMessage *msg = soup_message_new_from_uri ("GET", uri); - GMainLoop *loop = g_main_loop_new (NULL, TRUE); + SoupURI *uri; + SoupMessage *msg; + SoupRequest *req; + GInputStream *stream; char *sniffed_type = NULL; + const char *req_sniffed_type; + GError *error = NULL; - debug_printf (1, "test_sniffing(\"%s\", \"%s\")\n", path, expected_type); + uri = soup_uri_new_with_base (base_uri, path); + msg = soup_message_new_from_uri ("GET", uri); g_signal_connect (msg, "content-sniffed", G_CALLBACK (sniffing_content_sniffed), &sniffed_type); - g_object_ref (msg); - - soup_session_queue_message (session, msg, finished, loop); - - g_main_loop_run (loop); + soup_session_send_message (session, msg); + g_assert_cmpstr (sniffed_type, ==, expected_type); + g_free (sniffed_type); + g_object_unref (msg); - if (!sniffed_type) { - debug_printf (1, " message was not sniffed!\n"); - errors++; - } else if (strcmp (sniffed_type, expected_type) != 0) { - debug_printf (1, " sniffing failed! expected %s, got %s\n", - expected_type, sniffed_type); - errors++; + req = soup_session_request_uri (session, uri, NULL); + stream = soup_test_request_send (req, NULL, 0, &error); + if (stream) { + soup_test_request_close_stream (req, stream, NULL, &error); + g_object_unref (stream); } - g_free (sniffed_type); + g_assert_no_error (error); + g_clear_error (&error); + + req_sniffed_type = soup_request_get_content_type (req); + g_assert_cmpstr (req_sniffed_type, ==, expected_type); + g_object_unref (req); soup_uri_free (uri); - g_object_unref (msg); - g_main_loop_unref (loop); } static void -test_disabled (const char *path) +do_sniffing_test (gconstpointer data) { - SoupURI *uri = soup_uri_new_with_base (base_uri, path); - SoupMessage *msg = soup_message_new_from_uri ("GET", uri); - GMainLoop *loop = g_main_loop_new (NULL, TRUE); + const char *path_and_result = data; + char **parts; + + parts = g_strsplit (path_and_result, " => ", -1); + g_assert (parts && parts[0] && parts[1] && !parts[2]); + + test_sniffing (parts[0], parts[1]); + g_strfreev (parts); +} + +static void +test_disabled (gconstpointer data) +{ + const char *path = data; + SoupURI *uri; + SoupMessage *msg; + SoupRequest *req; + GInputStream *stream; char *sniffed_type = NULL; + const char *sniffed_content_type; + GError *error = NULL; - soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER); + g_test_bug ("574773"); - debug_printf (1, "test_disabled(\"%s\")\n", path); + uri = soup_uri_new_with_base (base_uri, path); + + msg = soup_message_new_from_uri ("GET", uri); + soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER); g_signal_connect (msg, "content-sniffed", G_CALLBACK (sniffing_content_sniffed), &sniffed_type); - g_object_ref (msg); - - soup_session_queue_message (session, msg, finished, loop); + soup_session_send_message (session, msg); - g_main_loop_run (loop); + g_assert_null (sniffed_type); + g_object_unref (msg); - if (sniffed_type) { - debug_printf (1, " message was sniffed!\n"); - errors++; - g_free (sniffed_type); + req = soup_session_request_uri (session, uri, NULL); + msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); + soup_message_disable_feature (msg, SOUP_TYPE_CONTENT_SNIFFER); + g_object_unref (msg); + stream = soup_test_request_send (req, NULL, 0, &error); + if (stream) { + soup_test_request_close_stream (req, stream, NULL, &error); + g_object_unref (stream); } + g_assert_no_error (error); + + sniffed_content_type = soup_request_get_content_type (req); + g_assert_cmpstr (sniffed_content_type, ==, NULL); + + g_object_unref (req); soup_uri_free (uri); - g_object_unref (msg); - g_main_loop_unref (loop); } int main (int argc, char **argv) { SoupServer *server; + int ret; test_init (argc, argv, NULL); @@ -451,112 +445,171 @@ main (int argc, char **argv) base_uri = soup_uri_new ("http://127.0.0.1/"); soup_uri_set_port (base_uri, soup_server_get_port (server)); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - - /* No sniffer, no content_sniffed should be emitted */ - do_signals_test (FALSE, FALSE, FALSE, FALSE, FALSE); - do_signals_test (FALSE, FALSE, FALSE, TRUE, FALSE); - do_signals_test (FALSE, FALSE, TRUE, FALSE, FALSE); - do_signals_test (FALSE, FALSE, TRUE, TRUE, FALSE); - - do_signals_test (FALSE, TRUE, TRUE, FALSE, FALSE); - do_signals_test (FALSE, TRUE, TRUE, TRUE, FALSE); - do_signals_test (FALSE, TRUE, FALSE, FALSE, FALSE); - do_signals_test (FALSE, TRUE, FALSE, TRUE, FALSE); - - /* Tests that the signals are correctly emitted for empty - * responses; see - * http://bugzilla.gnome.org/show_bug.cgi?id=587907 */ - - do_signals_test (FALSE, TRUE, TRUE, FALSE, TRUE); - do_signals_test (FALSE, TRUE, TRUE, TRUE, TRUE); - + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + NULL); soup_session_add_feature_by_type (session, SOUP_TYPE_CONTENT_SNIFFER); - /* Now, with a sniffer, content_sniffed must be emitted after - * got-headers, and before got-chunk. - */ - do_signals_test (TRUE, FALSE, FALSE, FALSE, FALSE); - do_signals_test (TRUE, FALSE, FALSE, TRUE, FALSE); - do_signals_test (TRUE, FALSE, TRUE, FALSE, FALSE); - do_signals_test (TRUE, FALSE, TRUE, TRUE, FALSE); - - do_signals_test (TRUE, TRUE, TRUE, FALSE, FALSE); - do_signals_test (TRUE, TRUE, TRUE, TRUE, FALSE); - do_signals_test (TRUE, TRUE, FALSE, FALSE, FALSE); - do_signals_test (TRUE, TRUE, FALSE, TRUE, FALSE); - - /* Empty response tests */ - do_signals_test (TRUE, TRUE, TRUE, FALSE, TRUE); - do_signals_test (TRUE, TRUE, TRUE, TRUE, TRUE); - - /* Test the text_or_binary sniffing path */ + g_test_add_data_func ("/sniffing/signals/no-sniffer", + GINT_TO_POINTER (FALSE), + do_signals_tests); + g_test_add_data_func ("/sniffing/signals/with-sniffer", + GINT_TO_POINTER (TRUE), + do_signals_tests); + + /* Test the apache bug sniffing path */ + g_test_add_data_func ("/sniffing/apache-bug/binary", + "/apache_bug/text_binary.txt => application/octet-stream", + do_sniffing_test); + g_test_add_data_func ("/sniffing/apache-bug/text", + "/apache_bug/text.txt => text/plain", + do_sniffing_test); + + /* X-Content-Type-Options: nosniff */ + g_test_add_data_func ("/sniffing/nosniff", + "nosniff/home.gif => no/sniffing-allowed", + do_sniffing_test); /* GIF is a 'safe' type */ - test_sniffing ("/text_or_binary/home.gif", "image/gif"); + g_test_add_data_func ("/sniffing/type/gif", + "text_or_binary/home.gif => image/gif", + do_sniffing_test); /* With our current code, no sniffing is done using GIO, so * the mbox will be identified as text/plain; should we change * this? */ - test_sniffing ("/text_or_binary/mbox", "text/plain"); + g_test_add_data_func ("/sniffing/type/mbox", + "text_or_binary/mbox => text/plain", + do_sniffing_test); /* HTML is considered unsafe for this algorithm, since it is * scriptable, so going from text/plain to text/html is * considered 'privilege escalation' */ - test_sniffing ("/text_or_binary/test.html", "text/plain"); + g_test_add_data_func ("/sniffing/type/html-in-text-context", + "text_or_binary/test.html => text/plain", + do_sniffing_test); /* text/plain with binary content and unknown pattern should be - * application/octet-stream */ - test_sniffing ("/text_or_binary/text_binary.txt", "application/octet-stream"); + * application/octet-stream + */ + g_test_add_data_func ("/sniffing/type/text-binary", + "text_or_binary/text_binary.txt => application/octet-stream", + do_sniffing_test); - /* text/plain with binary content and scriptable pattern should be - * application/octet-stream to avoid 'privilege escalation' */ - test_sniffing ("/text_or_binary/html_binary.html", "application/octet-stream"); + /* text/html with binary content and scriptable pattern should be + * application/octet-stream to avoid 'privilege escalation' + */ + g_test_add_data_func ("/sniffing/type/html-binary", + "text_or_binary/html_binary.html => application/octet-stream", + do_sniffing_test); /* text/plain with binary content and non scriptable known pattern should - * be the given type */ - test_sniffing ("/text_or_binary/ps_binary.ps", "application/postscript"); + * be the given type + */ + g_test_add_data_func ("/sniffing/type/ps", + "text_or_binary/ps_binary.ps => application/postscript", + do_sniffing_test); /* Test the unknown sniffing path */ - - test_sniffing ("/unknown/test.html", "text/html"); - test_sniffing ("/unknown/home.gif", "image/gif"); - test_sniffing ("/unknown/mbox", "text/plain"); - test_sniffing ("/unknown/text_binary.txt", "application/octet-stream"); + g_test_add_data_func ("/sniffing/type/unknown-html", + "unknown/test.html => text/html", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/unknown-gif", + "unknown/home.gif => image/gif", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/unknown-mbox", + "unknown/mbox => text/plain", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/unknown-binary", + "unknown/text_binary.txt => application/octet-stream", + do_sniffing_test); + /* FIXME g_test_bug ("715126") */ + g_test_add_data_func ("/sniffing/type/unknown-leading-space", + "unknown/leading_space.html => text/html", + do_sniffing_test); /* Test the XML sniffing path */ - - test_sniffing ("/type/text_xml/home.gif", "text/xml"); - test_sniffing ("/type/anice_type+xml/home.gif", "anice/type+xml"); - test_sniffing ("/type/application_xml/home.gif", "application/xml"); - - /* Test the image sniffing path */ - - test_sniffing ("/type/image_png/home.gif", "image/gif"); + g_test_add_data_func ("/sniffing/type/xml", + "type/text_xml/home.gif => text/xml", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/xml+xml", + "type/anice_type+xml/home.gif => anice/type+xml", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/application-xml", + "type/application_xml/home.gif => application/xml", + do_sniffing_test); /* Test the feed or html path */ + g_test_add_data_func ("/sniffing/type/html/html", + "type/text_html/test.html => text/html", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/html/rss", + "type/text_html/rss20.xml => application/rss+xml", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/html/atom", + "type/text_html/atom.xml => application/atom+xml", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/html/rdf", + "type/text_html/feed.rdf => application/rss+xml", + do_sniffing_test); - test_sniffing ("/type/text_html/test.html", "text/html"); - test_sniffing ("/type/text_html/rss20.xml", "application/rss+xml"); - test_sniffing ("/type/text_html/atom.xml", "application/atom+xml"); + /* Test the image sniffing path */ + g_test_add_data_func ("/sniffing/type/image/gif", + "type/image_png/home.gif => image/gif", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/image/png", + "type/image_gif/home.png => image/png", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/image/jpeg", + "type/image_png/home.jpg => image/jpeg", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/image/webp", + "type/image_png/tux.webp => image/webp", + do_sniffing_test); + + /* Test audio and video sniffing path */ + g_test_add_data_func ("/sniffing/type/audio/wav", + "type/audio_mpeg/test.wav => audio/wave", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/audio/aiff", + "type/audio_mpeg/test.aiff => audio/aiff", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/audio/ogg", + "type/audio_mpeg/test.ogg => application/ogg", + do_sniffing_test); + g_test_add_data_func ("/sniffing/type/video/webm", + "type/video_theora/test.webm => video/webm", + do_sniffing_test); + + /* Test the MP4 sniffing path */ + g_test_add_data_func ("/sniffing/type/video/mp4", + "unknown/test.mp4 => video/mp4", + do_sniffing_test); /* The spec tells us to only use the last Content-Type header */ - - test_sniffing ("/multiple_headers/home.gif", "image/gif"); + g_test_add_data_func ("/sniffing/multiple-headers", + "multiple_headers/home.gif => image/gif", + do_sniffing_test); /* Test that we keep the parameters when sniffing */ - test_sniffing ("/type/text_html; charset=UTF-8/test.html", "text/html; charset=UTF-8"); + g_test_add_data_func ("/sniffing/parameters", + "type/text_html; charset=UTF-8/test.html => text/html; charset=UTF-8", + do_sniffing_test); /* Test that disabling the sniffer works correctly */ + g_test_add_data_func ("/sniffing/disabled", + "/text_or_binary/home.gif", + test_disabled); - test_disabled ("/text_or_binary/home.gif"); + ret = g_test_run (); soup_uri_free (base_uri); soup_test_session_abort_unref (session); soup_test_server_quit_unref (server); + test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/socket-test.c b/tests/socket-test.c new file mode 100644 index 00000000..5bcc3b0c --- /dev/null +++ b/tests/socket-test.c @@ -0,0 +1,124 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright 2007-2012 Red Hat, Inc. + * Copyright 2012 Nokia Corporation + */ + +#include "test-utils.h" + +#include <gio/gnetworking.h> + +static void +do_unconnected_socket_test (void) +{ + SoupAddress *localhost; + SoupSocket *sock; + SoupSocket *client; + SoupAddress *addr; + guint res; + struct sockaddr_in in_localhost; + + g_test_bug ("673083"); + + in_localhost.sin_family = AF_INET; + in_localhost.sin_port = 0; + in_localhost.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + + localhost = soup_address_new_from_sockaddr ( + (struct sockaddr *) &in_localhost, sizeof (in_localhost)); + g_assert_true (localhost != NULL); + res = soup_address_resolve_sync (localhost, NULL); + g_assert_cmpuint (res, ==, SOUP_STATUS_OK); + + sock = soup_socket_new (SOUP_SOCKET_LOCAL_ADDRESS, localhost, + NULL); + g_assert_true (sock != NULL); + + addr = soup_socket_get_local_address (sock); + g_assert_true (addr != NULL); + g_assert_cmpstr (soup_address_get_physical (addr), ==, "127.0.0.1"); + g_assert_cmpuint (soup_address_get_port (addr), ==, 0); + + /* fails with ENOTCONN */ + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*socket not connected*"); + addr = soup_socket_get_remote_address (sock); + g_test_assert_expected_messages (); + g_assert_null (addr); + + res = soup_socket_listen (sock); + g_assert_true (res); + + addr = soup_socket_get_local_address (sock); + g_assert_true (addr != NULL); + g_assert_cmpstr (soup_address_get_physical (addr), ==, "127.0.0.1"); + g_assert_cmpuint (soup_address_get_port (addr), >, 0); + + client = soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, + soup_socket_get_local_address (sock), + NULL); + res = soup_socket_connect_sync (client, NULL); + g_assert_cmpuint (res, ==, SOUP_STATUS_OK); + addr = soup_socket_get_local_address (client); + g_assert_true (addr != NULL); + addr = soup_socket_get_remote_address (client); + g_assert_true (addr != NULL); + g_assert_cmpstr (soup_address_get_physical (addr), ==, "127.0.0.1"); + g_assert_cmpuint (soup_address_get_port (addr), >, 0); + g_object_unref (client); + + client = soup_socket_new (SOUP_SOCKET_REMOTE_ADDRESS, + soup_socket_get_local_address (sock), + NULL); + /* save it for later */ + + /* listening socket fails with ENOTCONN */ + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*endpoint is not connected*"); + addr = soup_socket_get_remote_address (sock); + g_test_assert_expected_messages (); + g_assert_null (addr); + + soup_socket_disconnect (sock); + + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*socket not connected*"); + addr = soup_socket_get_remote_address (sock); + g_test_assert_expected_messages (); + g_assert_null (addr); + + /* has never been connected */ + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*socket not connected*"); + addr = soup_socket_get_local_address (client); + g_test_assert_expected_messages (); + g_assert_null (addr); + + res = soup_socket_connect_sync (client, NULL); + g_assert_cmpuint (res, ==, SOUP_STATUS_CANT_CONNECT); + + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*socket not connected*"); + addr = soup_socket_get_local_address (client); + g_test_assert_expected_messages (); + g_assert_null (addr); + + g_object_unref (localhost); + g_object_unref (client); + g_object_unref (sock); +} + +int +main (int argc, char **argv) +{ + int ret; + + test_init (argc, argv, NULL); + + g_test_add_func ("/sockets/unconnected", do_unconnected_socket_test); + + ret = g_test_run (); + + test_cleanup (); + return ret; +} diff --git a/tests/soup-tests.gresource.xml b/tests/soup-tests.gresource.xml new file mode 100644 index 00000000..b24a7297 --- /dev/null +++ b/tests/soup-tests.gresource.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/libsoup/tests"> + <file>index.txt</file> + <file>resources/atom.xml</file> + <file>resources/feed.rdf</file> + <file>resources/home.gif</file> + <file>resources/home.jpg</file> + <file>resources/home.png</file> + <file>resources/html_binary.html</file> + <file>resources/leading_space.html</file> + <file>resources/mbox</file> + <file>resources/mbox.gz</file> + <file>resources/mbox.raw</file> + <file>resources/mbox.zlib</file> + <file>resources/ps_binary.ps</file> + <file>resources/rss20.xml</file> + <file>resources/test.aiff</file> + <file>resources/test.html</file> + <file>resources/test.mp4</file> + <file>resources/test.ogg</file> + <file>resources/test.wav</file> + <file>resources/test.webm</file> + <file>resources/text.txt</file> + <file>resources/text_binary.txt</file> + <file>resources/tux.webp</file> + </gresource> +</gresources> diff --git a/tests/ssl-test.c b/tests/ssl-test.c index 6d96bc79..e6bbb615 100644 --- a/tests/ssl-test.c +++ b/tests/ssl-test.c @@ -2,8 +2,10 @@ #include "test-utils.h" +static char *uri; + static void -do_properties_test_for_session (SoupSession *session, char *uri) +do_properties_test_for_session (SoupSession *session, const char *uri) { SoupMessage *msg; GTlsCertificate *cert; @@ -11,42 +13,27 @@ do_properties_test_for_session (SoupSession *session, char *uri) msg = soup_message_new ("GET", uri); soup_session_send_message (session, msg); - if (msg->status_code != SOUP_STATUS_OK) { - debug_printf (1, " FAILED: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); if (soup_message_get_https_status (msg, &cert, &flags)) { - if (!G_IS_TLS_CERTIFICATE (cert)) { - debug_printf (1, " No certificate?\n"); - errors++; - } - if (flags != G_TLS_CERTIFICATE_UNKNOWN_CA) { - debug_printf (1, " Wrong cert flags (got %x, wanted %x)\n", - flags, G_TLS_CERTIFICATE_UNKNOWN_CA); - errors++; - } - } else { - debug_printf (1, " Response not https\n"); - errors++; - } - if (soup_message_get_flags (msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED) { - debug_printf (1, " CERTIFICATE_TRUSTED set?\n"); - errors++; - } + g_assert_true (G_IS_TLS_CERTIFICATE (cert)); + g_assert_cmpuint (flags, ==, G_TLS_CERTIFICATE_UNKNOWN_CA); + } else + soup_test_assert (FALSE, "Response not https"); + + g_test_bug ("665182"); + g_assert_false (soup_message_get_flags (msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED); g_object_unref (msg); } static void -do_properties_tests (char *uri) +do_async_properties_tests (void) { SoupSession *session; - debug_printf (1, "\nSoupMessage properties\n"); + SOUP_TEST_SKIP_IF_NO_TLS; - debug_printf (1, " async\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); g_object_set (G_OBJECT (session), SOUP_SESSION_SSL_CA_FILE, "/dev/null", @@ -54,8 +41,15 @@ do_properties_tests (char *uri) NULL); do_properties_test_for_session (session, uri); soup_test_session_abort_unref (session); +} + +static void +do_sync_properties_tests (void) +{ + SoupSession *session; + + SOUP_TEST_SKIP_IF_NO_TLS; - debug_printf (1, " sync\n"); session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); g_object_set (G_OBJECT (session), SOUP_SESSION_SSL_CA_FILE, "/dev/null", @@ -65,81 +59,74 @@ do_properties_tests (char *uri) soup_test_session_abort_unref (session); } +typedef struct { + const char *name; + gboolean sync; + gboolean strict; + gboolean with_ca_list; + guint expected_status; +} StrictnessTest; + +static const StrictnessTest strictness_tests[] = { + { "/ssl/strictness/async/strict/with-ca", + FALSE, TRUE, TRUE, SOUP_STATUS_OK }, + { "/ssl/strictness/async/strict/without-ca", + FALSE, TRUE, FALSE, SOUP_STATUS_SSL_FAILED }, + { "/ssl/strictness/async/non-strict/with-ca", + FALSE, FALSE, TRUE, SOUP_STATUS_OK }, + { "/ssl/strictness/async/non-strict/without-ca", + FALSE, FALSE, FALSE, SOUP_STATUS_OK }, + { "/ssl/strictness/sync/strict/with-ca", + TRUE, TRUE, TRUE, SOUP_STATUS_OK }, + { "/ssl/strictness/sync/strict/without-ca", + TRUE, TRUE, FALSE, SOUP_STATUS_SSL_FAILED }, + { "/ssl/strictness/sync/non-strict/with-ca", + TRUE, FALSE, TRUE, SOUP_STATUS_OK }, + { "/ssl/strictness/sync/non-strict/without-ca", + TRUE, FALSE, FALSE, SOUP_STATUS_OK }, +}; + static void -do_one_strict_test (SoupSession *session, char *uri, - gboolean strict, gboolean with_ca_list, - guint expected_status) +do_strictness_test (gconstpointer data) { + const StrictnessTest *test = data; + SoupSession *session; SoupMessage *msg; + GTlsCertificateFlags flags = 0; - /* Note that soup_test_session_new() sets - * SOUP_SESSION_SSL_CA_FILE by default, and turns off - * SOUP_SESSION_SSL_STRICT. - */ + SOUP_TEST_SKIP_IF_NO_TLS; - g_object_set (G_OBJECT (session), - SOUP_SESSION_SSL_STRICT, strict, - SOUP_SESSION_SSL_CA_FILE, with_ca_list ? SRCDIR "/test-cert.pem" : "/dev/null", - NULL); - /* Close existing connections with old params */ - soup_session_abort (session); + session = soup_test_session_new (test->sync ? SOUP_TYPE_SESSION_SYNC : SOUP_TYPE_SESSION_ASYNC, + NULL); + if (!test->strict) { + g_object_set (G_OBJECT (session), + SOUP_SESSION_SSL_STRICT, FALSE, + NULL); + } + if (!test->with_ca_list) { + g_object_set (G_OBJECT (session), + SOUP_SESSION_SSL_CA_FILE, "/dev/null", + NULL); + } msg = soup_message_new ("GET", uri); soup_session_send_message (session, msg); - if (msg->status_code != expected_status) { - debug_printf (1, " FAILED: %d %s (expected %d %s)\n", - msg->status_code, msg->reason_phrase, - expected_status, - soup_status_get_phrase (expected_status)); - if (msg->status_code == SOUP_STATUS_SSL_FAILED) { - GTlsCertificateFlags flags = 0; - - soup_message_get_https_status (msg, NULL, &flags); - debug_printf (1, " tls error flags: 0x%x\n", flags); - } - errors++; - } else if (with_ca_list && SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - if (!(soup_message_get_flags (msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED)) { - debug_printf (1, " CERTIFICATE_TRUSTED not set?\n"); - errors++; - } - } else { - if (with_ca_list && soup_message_get_flags (msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED) { - debug_printf (1, " CERTIFICATE_TRUSTED set?\n"); - errors++; - } - } - - g_object_unref (msg); -} + soup_test_assert_message_status (msg, test->expected_status); -static void -do_strict_tests (char *uri) -{ - SoupSession *session; + g_test_bug ("690176"); + g_assert_true (soup_message_get_https_status (msg, NULL, &flags)); - debug_printf (1, "\nstrict/nonstrict\n"); + g_test_bug ("665182"); + if (test->with_ca_list && SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) + g_assert_true (soup_message_get_flags (msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED); + else + g_assert_false (soup_message_get_flags (msg) & SOUP_MESSAGE_CERTIFICATE_TRUSTED); - session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - debug_printf (1, " async with CA list\n"); - do_one_strict_test (session, uri, TRUE, TRUE, SOUP_STATUS_OK); - debug_printf (1, " async without CA list\n"); - do_one_strict_test (session, uri, TRUE, FALSE, SOUP_STATUS_SSL_FAILED); - debug_printf (1, " async non-strict with CA list\n"); - do_one_strict_test (session, uri, FALSE, TRUE, SOUP_STATUS_OK); - debug_printf (1, " async non-strict without CA list\n"); - do_one_strict_test (session, uri, FALSE, FALSE, SOUP_STATUS_OK); - soup_test_session_abort_unref (session); + if (msg->status_code == SOUP_STATUS_SSL_FAILED && + test->expected_status != SOUP_STATUS_SSL_FAILED) + debug_printf (1, " tls error flags: 0x%x\n", flags); - session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); - debug_printf (1, " sync with CA list\n"); - do_one_strict_test (session, uri, TRUE, TRUE, SOUP_STATUS_OK); - debug_printf (1, " sync without CA list\n"); - do_one_strict_test (session, uri, TRUE, FALSE, SOUP_STATUS_SSL_FAILED); - debug_printf (1, " sync non-strict with CA list\n"); - do_one_strict_test (session, uri, FALSE, TRUE, SOUP_STATUS_OK); - debug_printf (1, " sync non-strict without CA list\n"); - do_one_strict_test (session, uri, FALSE, FALSE, SOUP_STATUS_OK); + g_object_unref (msg); soup_test_session_abort_unref (session); } @@ -160,7 +147,9 @@ do_session_property_tests (void) char *ca_file; SoupSession *session; - debug_printf (1, "session properties\n"); + g_test_bug ("673678"); + + SOUP_TEST_SKIP_IF_NO_TLS; session = soup_session_async_new (); g_signal_connect (session, "notify::ssl-use-system-ca-file", @@ -175,20 +164,12 @@ do_session_property_tests (void) "tls-database", &tlsdb, "ssl-ca-file", &ca_file, NULL); - if (use_system) { - debug_printf (1, " ssl-use-system-ca-file defaults to TRUE?\n"); - errors++; - } - if (tlsdb) { - debug_printf (1, " tls-database set by default?\n"); - errors++; - g_object_unref (tlsdb); - } - if (ca_file) { - debug_printf (1, " ca-file set by default?\n"); - errors++; - g_free (ca_file); - } + soup_test_assert (!use_system, + "ssl-use-system-ca-file defaults to TRUE"); + soup_test_assert (tlsdb == NULL, + "tls-database set by default"); + soup_test_assert (ca_file == NULL, + "ca-file set by default"); use_system_changed = tlsdb_changed = ca_file_changed = FALSE; g_object_set (G_OBJECT (session), @@ -199,68 +180,37 @@ do_session_property_tests (void) "tls-database", &tlsdb, "ssl-ca-file", &ca_file, NULL); - if (!use_system) { - debug_printf (1, " setting ssl-use-system-ca-file failed\n"); - errors++; - } - if (!tlsdb) { - debug_printf (1, " setting ssl-use-system-ca-file didn't set tls-database\n"); - errors++; - } else - g_object_unref (tlsdb); - if (ca_file) { - debug_printf (1, " setting ssl-use-system-ca-file set ssl-ca-file\n"); - errors++; - g_free (ca_file); - } - if (!use_system_changed) { - debug_printf (1, " setting ssl-use-system-ca-file didn't emit notify::ssl-use-system-ca-file\n"); - errors++; - } - if (!tlsdb_changed) { - debug_printf (1, " setting ssl-use-system-ca-file didn't emit notify::tls-database\n"); - errors++; - } - if (ca_file_changed) { - debug_printf (1, " setting ssl-use-system-ca-file emitted notify::ssl-ca-file\n"); - errors++; - } + soup_test_assert (use_system, + "setting ssl-use-system-ca-file failed"); + g_assert_true (use_system_changed); + soup_test_assert (tlsdb != NULL, + "setting ssl-use-system-ca-file didn't set tls-database"); + g_assert_true (tlsdb_changed); + g_clear_object (&tlsdb); + soup_test_assert (ca_file == NULL, + "setting ssl-use-system-ca-file set ssl-ca-file"); + g_assert_false (ca_file_changed); use_system_changed = tlsdb_changed = ca_file_changed = FALSE; g_object_set (G_OBJECT (session), - "ssl-ca-file", SRCDIR "/test-cert.pem", + "ssl-ca-file", g_test_get_filename (G_TEST_DIST, "/test-cert.pem", NULL), NULL); g_object_get (G_OBJECT (session), "ssl-use-system-ca-file", &use_system, "tls-database", &tlsdb, "ssl-ca-file", &ca_file, NULL); - if (use_system) { - debug_printf (1, " setting ssl-ca-file left ssl-use-system-ca-file set\n"); - errors++; - } - if (!tlsdb) { - debug_printf (1, " setting ssl-ca-file didn't set tls-database\n"); - errors++; - } else - g_object_unref (tlsdb); - if (!ca_file) { - debug_printf (1, " setting ssl-ca-file failed\n"); - errors++; - } else - g_free (ca_file); - if (!use_system_changed) { - debug_printf (1, " setting ssl-ca-file didn't emit notify::ssl-use-system-ca-file\n"); - errors++; - } - if (!tlsdb_changed) { - debug_printf (1, " setting ssl-ca-file didn't emit notify::tls-database\n"); - errors++; - } - if (!ca_file_changed) { - debug_printf (1, " setting ssl-ca-file didn't emit notify::ssl-ca-file\n"); - errors++; - } + soup_test_assert (!use_system, + "setting ssl-ca-file left ssl-use-system-ca-file set"); + g_assert_true (use_system_changed); + soup_test_assert (tlsdb != NULL, + "setting ssl-ca-file didn't set tls-database"); + g_assert_true (tlsdb_changed); + g_clear_object (&tlsdb); + soup_test_assert (ca_file != NULL, + "setting ssl-ca-file failed"); + g_assert_true (ca_file_changed); + g_free (ca_file); use_system_changed = tlsdb_changed = ca_file_changed = FALSE; g_object_set (G_OBJECT (session), @@ -271,32 +221,15 @@ do_session_property_tests (void) "tls-database", &tlsdb, "ssl-ca-file", &ca_file, NULL); - if (use_system) { - debug_printf (1, " setting tls-database NULL left ssl-use-system-ca-file set\n"); - errors++; - } - if (tlsdb) { - debug_printf (1, " setting tls-database NULL failed\n"); - errors++; - g_object_unref (tlsdb); - } - if (ca_file) { - debug_printf (1, " setting tls-database didn't clear ssl-ca-file\n"); - errors++; - g_free (ca_file); - } - if (use_system_changed) { - debug_printf (1, " setting tls-database emitted notify::ssl-use-system-ca-file\n"); - errors++; - } - if (!tlsdb_changed) { - debug_printf (1, " setting tls-database didn't emit notify::tls-database\n"); - errors++; - } - if (!ca_file_changed) { - debug_printf (1, " setting tls-database didn't emit notify::ssl-ca-file\n"); - errors++; - } + soup_test_assert (!use_system, + "setting tls-database NULL left ssl-use-system-ca-file set"); + g_assert_false (use_system_changed); + soup_test_assert (tlsdb == NULL, + "setting tls-database NULL failed"); + g_assert_true (tlsdb_changed); + soup_test_assert (ca_file == NULL, + "setting tls-database didn't clear ssl-ca-file"); + g_assert_true (ca_file_changed); soup_test_session_abort_unref (session); } @@ -319,7 +252,7 @@ int main (int argc, char **argv) { SoupServer *server; - char *uri; + int i, ret; test_init (argc, argv, NULL); @@ -328,15 +261,25 @@ main (int argc, char **argv) soup_server_add_handler (server, NULL, server_handler, NULL, NULL); uri = g_strdup_printf ("https://127.0.0.1:%u/", soup_server_get_port (server)); + } - do_session_property_tests (); - do_strict_tests (uri); - do_properties_tests (uri); + g_test_add_func ("/ssl/session-properties", do_session_property_tests); + g_test_add_func ("/ssl/message-properties/async", do_async_properties_tests); + g_test_add_func ("/ssl/message-properties/sync", do_sync_properties_tests); + for (i = 0; i < G_N_ELEMENTS (strictness_tests); i++) { + g_test_add_data_func (strictness_tests[i].name, + &strictness_tests[i], + do_strictness_test); + } + + ret = g_test_run (); + + if (tls_available) { g_free (uri); soup_test_server_quit_unref (server); } test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/streaming-test.c b/tests/streaming-test.c index 239e0ce8..8d8c02ef 100644 --- a/tests/streaming-test.c +++ b/tests/streaming-test.c @@ -7,28 +7,8 @@ #define RESPONSE_CHUNK_SIZE 1024 -char *full_response, *full_response_md5; -gsize full_response_length; - -static void -get_full_response (void) -{ - GError *error = NULL; - - if (!g_file_get_contents (SRCDIR "/index.txt", - &full_response, - &full_response_length, - &error)) { - g_printerr ("Could not read index file %s: %s\n", - SRCDIR "/index.txt", error->message); - g_error_free (error); - exit (1); - } - - full_response_md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5, - (guchar *)full_response, - full_response_length); -} +SoupBuffer *full_response; +char *full_response_md5; static void write_next_chunk (SoupMessage *msg, gpointer user_data) @@ -36,12 +16,12 @@ write_next_chunk (SoupMessage *msg, gpointer user_data) gsize *offset = user_data; gsize chunk_length; - chunk_length = MIN (RESPONSE_CHUNK_SIZE, full_response_length - *offset); + chunk_length = MIN (RESPONSE_CHUNK_SIZE, full_response->length - *offset); if (chunk_length > 0) { debug_printf (2, " writing chunk\n"); soup_message_body_append (msg->response_body, SOUP_MEMORY_STATIC, - full_response + *offset, + full_response->data + *offset, chunk_length); *offset += chunk_length; } else { @@ -74,7 +54,7 @@ server_callback (SoupServer *server, SoupMessage *msg, soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_CONTENT_LENGTH); soup_message_headers_set_content_length (msg->response_headers, - full_response_length); + full_response->length); } else if (!strcmp (path, "/eof")) { soup_message_headers_set_encoding (msg->response_headers, SOUP_ENCODING_EOF); @@ -106,44 +86,49 @@ do_request (SoupSession *session, SoupURI *base_uri, char *path) soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, " message failed: %d %s\n", - msg->status_code, msg->reason_phrase); - errors++; - } - - if (msg->response_body->length != full_response_length) { - debug_printf (1, " received length mismatch: expected %d, got %d\n", - (int)full_response_length, (int)msg->request_body->length); - errors++; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); + g_assert_cmpint (msg->response_body->length, ==, full_response->length); md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5, (guchar *)msg->response_body->data, msg->response_body->length); - if (strcmp (md5, full_response_md5) != 0) { - debug_printf (1, " data mismatch: expected %s, got %s\n", - full_response_md5, md5); - errors++; - } + g_assert_cmpstr (md5, ==, full_response_md5); g_free (md5); g_object_unref (msg); } static void -do_tests (SoupURI *base_uri) +do_chunked_test (gconstpointer data) { + SoupURI *base_uri = (SoupURI *)data; SoupSession *session; session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); - debug_printf (1, "Chunked encoding\n"); do_request (session, base_uri, "chunked"); - debug_printf (1, "\n"); - debug_printf (1, "Content-Length encoding\n"); + soup_test_session_abort_unref (session); +} + +static void +do_content_length_test (gconstpointer data) +{ + SoupURI *base_uri = (SoupURI *)data; + SoupSession *session; + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); do_request (session, base_uri, "content-length"); - debug_printf (1, "\n"); - debug_printf (1, "EOF encoding\n"); + soup_test_session_abort_unref (session); +} + +static void +do_eof_test (gconstpointer data) +{ + SoupURI *base_uri = (SoupURI *)data; + SoupSession *session; + + g_test_bug ("572153"); + + session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, NULL); do_request (session, base_uri, "eof"); soup_test_session_abort_unref (session); } @@ -155,9 +140,14 @@ main (int argc, char **argv) SoupServer *server; guint port; SoupURI *base_uri; + int ret; test_init (argc, argv, NULL); - get_full_response (); + + full_response = soup_test_get_index (); + full_response_md5 = g_compute_checksum_for_data (G_CHECKSUM_MD5, + (guchar *)full_response->data, + full_response->length); server = soup_test_server_new (FALSE); soup_server_add_handler (server, NULL, @@ -168,14 +158,19 @@ main (int argc, char **argv) base_uri = soup_uri_new ("http://127.0.0.1"); soup_uri_set_port (base_uri, port); - do_tests (base_uri); - soup_uri_free (base_uri); + g_test_add_data_func ("/streaming/chunked", base_uri, do_chunked_test); + g_test_add_data_func ("/streaming/content-length", base_uri, do_content_length_test); + g_test_add_data_func ("/streaming/eof", base_uri, do_eof_test); + + ret = g_test_run (); + + soup_uri_free (base_uri); g_main_loop_unref (loop); - g_free (full_response); g_free (full_response_md5); soup_test_server_quit_unref (server); test_cleanup (); - return errors != 0; + + return ret; } diff --git a/tests/test-utils.c b/tests/test-utils.c index f040b738..bc160aea 100644 --- a/tests/test-utils.c +++ b/tests/test-utils.c @@ -12,8 +12,9 @@ static gboolean apache_running; #endif static SoupLogger *logger; +static SoupBuffer *index_buffer; -int debug_level, errors; +int debug_level; gboolean expect_warning, tls_available; static int http_debug_level; @@ -37,7 +38,7 @@ static GOptionEntry debug_entry[] = { { "debug", 'd', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, increment_debug_level, "Enable (or increase) test-specific debugging", NULL }, - { "http-debug", 'h', G_OPTION_FLAG_NO_ARG, + { "http-debug", 'H', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, increment_http_debug_level, "Enable (or increase) HTTP-level debugging", NULL }, { NULL } @@ -54,21 +55,6 @@ quit (int sig) exit (1); } -static void -test_log_handler (const char *log_domain, GLogLevelFlags log_level, - const char *message, gpointer user_data) -{ - if (log_level & (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL)) { - if (expect_warning) { - expect_warning = FALSE; - debug_printf (2, "Got expected warning: %s\n", message); - return; - } else - errors++; - } - g_log_default_handler (log_domain, log_level, message, user_data); -} - void test_init (int argc, char **argv, GOptionEntry *entries) { @@ -78,7 +64,8 @@ test_init (int argc, char **argv, GOptionEntry *entries) GTlsBackend *tls_backend; setlocale (LC_ALL, ""); - g_type_init (); + g_setenv ("GSETTINGS_BACKEND", "memory", TRUE); + g_setenv ("GIO_USE_PROXY_RESOLVER", "dummy", TRUE); name = strrchr (argv[0], '/'); if (!name++) @@ -87,6 +74,10 @@ test_init (int argc, char **argv, GOptionEntry *entries) name += 3; g_set_prgname (name); + g_test_init (&argc, &argv, NULL); + g_test_set_nonfatal_assertions (); + g_test_bug_base ("https://bugzilla.gnome.org/"); + opts = g_option_context_new (NULL); g_option_context_add_main_entries (opts, debug_entry, NULL); if (entries) @@ -104,8 +95,6 @@ test_init (int argc, char **argv, GOptionEntry *entries) /* Exit cleanly on ^C in case we're valgrinding. */ signal (SIGINT, quit); - g_log_set_default_handler (test_log_handler, NULL); - tls_backend = g_tls_backend_get_default (); tls_available = g_tls_backend_supports_tls (tls_backend); } @@ -120,16 +109,12 @@ test_cleanup (void) if (logger) g_object_unref (logger); + if (index_buffer) + soup_buffer_free (index_buffer); g_main_context_unref (g_main_context_default ()); debug_printf (1, "\n"); - if (errors) { - g_print ("%s: %d error(s).%s\n", - g_get_prgname (), errors, - debug_level == 0 ? " Run with '-d' for details" : ""); - } else - g_print ("%s: OK\n", g_get_prgname ()); } void @@ -150,30 +135,52 @@ debug_printf (int level, const char *format, ...) static gboolean apache_cmd (const char *cmd) { - const char *argv[8]; - char *cwd, *conf; + GPtrArray *argv; + char *server_root, *cwd, *pid_file; +#ifdef HAVE_APACHE_2_4 + char *default_runtime_dir; +#endif int status; gboolean ok; + server_root = g_test_build_filename (G_TEST_BUILT, "", NULL); + cwd = g_get_current_dir (); - conf = g_build_filename (cwd, "httpd.conf", NULL); - - argv[0] = APACHE_HTTPD; - argv[1] = "-d"; - argv[2] = cwd; - argv[3] = "-f"; - argv[4] = conf; - argv[5] = "-k"; - argv[6] = cmd; - argv[7] = NULL; - - ok = g_spawn_sync (cwd, (char **)argv, NULL, 0, NULL, NULL, +#ifdef HAVE_APACHE_2_4 + default_runtime_dir = g_strdup_printf ("DefaultRuntimeDir %s", cwd); +#endif + pid_file = g_strdup_printf ("PidFile %s/httpd.pid", cwd); + + argv = g_ptr_array_new (); + g_ptr_array_add (argv, APACHE_HTTPD); + g_ptr_array_add (argv, "-d"); + g_ptr_array_add (argv, server_root); + g_ptr_array_add (argv, "-f"); + g_ptr_array_add (argv, "httpd.conf"); + +#ifdef HAVE_APACHE_2_4 + g_ptr_array_add (argv, "-c"); + g_ptr_array_add (argv, default_runtime_dir); +#endif + g_ptr_array_add (argv, "-c"); + g_ptr_array_add (argv, pid_file); + + g_ptr_array_add (argv, "-k"); + g_ptr_array_add (argv, (char *)cmd); + g_ptr_array_add (argv, NULL); + + ok = g_spawn_sync (cwd, (char **)argv->pdata, NULL, 0, NULL, NULL, NULL, NULL, &status, NULL); if (ok) ok = (status == 0); + g_free (server_root); g_free (cwd); - g_free (conf); + g_free (pid_file); +#ifdef HAVE_APACHE_2_4 + g_free (default_runtime_dir); +#endif + g_ptr_array_free (argv, TRUE); return ok; } @@ -181,6 +188,9 @@ apache_cmd (const char *cmd) void apache_init (void) { + if (g_getenv ("SOUP_TESTS_IN_MAKE_CHECK")) + return; + if (!apache_cmd ("start")) { g_printerr ("Could not start apache\n"); exit (1); @@ -218,15 +228,18 @@ soup_test_session_new (GType type, ...) va_list args; const char *propname; SoupSession *session; + char *cafile; va_start (args, type); propname = va_arg (args, const char *); session = (SoupSession *)g_object_new_valist (type, propname, args); va_end (args); + cafile = g_test_build_filename (G_TEST_DIST, "test-cert.pem", NULL); g_object_set (G_OBJECT (session), - SOUP_SESSION_SSL_CA_FILE, SRCDIR "/test-cert.pem", + SOUP_SESSION_SSL_CA_FILE, cafile, NULL); + g_free (cafile); if (http_debug_level && !logger) { SoupLoggerLogLevel level = MIN ((SoupLoggerLogLevel)http_debug_level, SOUP_LOGGER_LOG_BODY); @@ -243,16 +256,10 @@ soup_test_session_new (GType type, ...) void soup_test_session_abort_unref (SoupSession *session) { - g_object_add_weak_pointer (G_OBJECT (session), (gpointer *)&session); - soup_session_abort (session); - g_object_unref (session); - if (session) { - errors++; - debug_printf (1, "leaked SoupSession!\n"); - g_object_remove_weak_pointer (G_OBJECT (session), (gpointer *)&session); - } + g_assert_cmpint (G_OBJECT (session)->ref_count, ==, 1); + g_object_unref (session); } static gpointer run_server_thread (gpointer user_data); @@ -262,14 +269,14 @@ test_server_new (gboolean in_own_thread, gboolean ssl) { SoupServer *server; GMainContext *async_context; - const char *ssl_cert_file, *ssl_key_file; + char *ssl_cert_file, *ssl_key_file; SoupAddress *addr; async_context = in_own_thread ? g_main_context_new () : NULL; if (ssl) { - ssl_cert_file = SRCDIR "/test-cert.pem"; - ssl_key_file = SRCDIR "/test-key.pem"; + ssl_cert_file = g_test_build_filename (G_TEST_DIST, "test-cert.pem", NULL); + ssl_key_file = g_test_build_filename (G_TEST_DIST, "test-key.pem", NULL); } else ssl_cert_file = ssl_key_file = NULL; @@ -284,6 +291,8 @@ test_server_new (gboolean in_own_thread, gboolean ssl) g_object_unref (addr); if (async_context) g_main_context_unref (async_context); + g_free (ssl_cert_file); + g_free (ssl_key_file); if (!server) { g_printerr ("Unable to create server\n"); @@ -334,9 +343,6 @@ soup_test_server_quit_unref (SoupServer *server) { GThread *thread; - g_object_add_weak_pointer (G_OBJECT (server), - (gpointer *)&server); - thread = g_object_get_data (G_OBJECT (server), "thread"); if (thread) { soup_add_completion (soup_server_get_async_context (server), @@ -344,19 +350,14 @@ soup_test_server_quit_unref (SoupServer *server) g_thread_join (thread); } else soup_server_quit (server); - g_object_unref (server); - if (server) { - errors++; - debug_printf (1, "leaked SoupServer!\n"); - g_object_remove_weak_pointer (G_OBJECT (server), - (gpointer *)&server); - } + g_assert_cmpint (G_OBJECT (server)->ref_count, ==, 1); + g_object_unref (server); } typedef struct { - GMainLoop *loop; - GAsyncResult *result; + GMainLoop *loop; + GAsyncResult *result; } AsyncAsSyncData; static void @@ -364,51 +365,269 @@ async_as_sync_callback (GObject *object, GAsyncResult *result, gpointer user_data) { - AsyncAsSyncData *data = user_data; + AsyncAsSyncData *data = user_data; + GMainContext *context; + + data->result = g_object_ref (result); + context = g_main_loop_get_context (data->loop); + while (g_main_context_pending (context)) + g_main_context_iteration (context, FALSE); + g_main_loop_quit (data->loop); +} + +typedef struct { + SoupRequest *req; + GCancellable *cancellable; + SoupTestRequestFlags flags; +} CancelData; + +static CancelData * +create_cancel_data (SoupRequest *req, + GCancellable *cancellable, + SoupTestRequestFlags flags) +{ + CancelData *cancel_data; + + if (!flags) + return NULL; + + cancel_data = g_slice_new0 (CancelData); + cancel_data->flags = flags; + if (flags & SOUP_TEST_REQUEST_CANCEL_MESSAGE && SOUP_IS_REQUEST_HTTP (req)) + cancel_data->req = g_object_ref (req); + else if (flags & SOUP_TEST_REQUEST_CANCEL_CANCELLABLE) + cancel_data->cancellable = g_object_ref (cancellable); + return cancel_data; +} - data->result = g_object_ref (result); - g_main_loop_quit (data->loop); +static void inline +cancel_message_or_cancellable (CancelData *cancel_data) +{ + if (cancel_data->flags & SOUP_TEST_REQUEST_CANCEL_MESSAGE) { + SoupRequest *req = cancel_data->req; + SoupMessage *msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); + soup_session_cancel_message (soup_request_get_session (req), msg, + SOUP_STATUS_CANCELLED); + g_object_unref (msg); + g_object_unref (req); + } else if (cancel_data->flags & SOUP_TEST_REQUEST_CANCEL_CANCELLABLE) { + g_cancellable_cancel (cancel_data->cancellable); + g_object_unref (cancel_data->cancellable); + } + g_slice_free (CancelData, cancel_data); +} + +static gboolean +cancel_request_timeout (gpointer data) +{ + cancel_message_or_cancellable ((CancelData *) data); + return FALSE; +} + +static gpointer +cancel_request_thread (gpointer data) +{ + g_usleep (100000); /* .1s */ + cancel_message_or_cancellable ((CancelData *) data); + return NULL; } GInputStream * -soup_test_request_send_async_as_sync (SoupRequest *req, - GCancellable *cancellable, - GError **error) +soup_test_request_send (SoupRequest *req, + GCancellable *cancellable, + guint flags, + GError **error) { - AsyncAsSyncData data; - GInputStream *stream; + AsyncAsSyncData data; + GInputStream *stream; + CancelData *cancel_data = create_cancel_data (req, cancellable, flags); - data.loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); + if (SOUP_IS_SESSION_SYNC (soup_request_get_session (req))) { + GThread *thread; - soup_request_send_async (req, cancellable, async_as_sync_callback, &data); - g_main_loop_run (data.loop); + if (cancel_data) + thread = g_thread_new ("cancel_request_thread", cancel_request_thread, + cancel_data); + stream = soup_request_send (req, cancellable, error); + if (cancel_data) + g_thread_unref (thread); + return stream; + } + + data.loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); + if (cancel_data && + (flags & SOUP_TEST_REQUEST_CANCEL_SOON || flags & SOUP_TEST_REQUEST_CANCEL_IMMEDIATE)) { + guint interval = flags & SOUP_TEST_REQUEST_CANCEL_SOON ? 100 : 0; + g_timeout_add_full (G_PRIORITY_HIGH, interval, cancel_request_timeout, cancel_data, NULL); + } + if (cancel_data && (flags & SOUP_TEST_REQUEST_CANCEL_PREEMPTIVE)) + g_cancellable_cancel (cancellable); + soup_request_send_async (req, cancellable, async_as_sync_callback, &data); + g_main_loop_run (data.loop); - stream = soup_request_send_finish (req, data.result, error); + stream = soup_request_send_finish (req, data.result, error); - g_main_loop_unref (data.loop); - g_object_unref (data.result); + if (cancel_data && (flags & SOUP_TEST_REQUEST_CANCEL_AFTER_SEND_FINISH)) { + GMainContext *context; - return stream; + cancel_message_or_cancellable (cancel_data); + + context = g_main_loop_get_context (data.loop); + while (g_main_context_pending (context)) + g_main_context_iteration (context, FALSE); + } + + g_main_loop_unref (data.loop); + g_object_unref (data.result); + + return stream; +} + +gboolean +soup_test_request_read_all (SoupRequest *req, + GInputStream *stream, + GCancellable *cancellable, + GError **error) +{ + char buf[8192]; + AsyncAsSyncData data; + gsize nread; + + if (!SOUP_IS_SESSION_SYNC (soup_request_get_session (req))) + data.loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); + + do { + if (SOUP_IS_SESSION_SYNC (soup_request_get_session (req))) { + nread = g_input_stream_read (stream, buf, sizeof (buf), + cancellable, error); + } else { + g_input_stream_read_async (stream, buf, sizeof (buf), + G_PRIORITY_DEFAULT, cancellable, + async_as_sync_callback, &data); + g_main_loop_run (data.loop); + nread = g_input_stream_read_finish (stream, data.result, error); + g_object_unref (data.result); + } + } while (nread > 0); + + if (!SOUP_IS_SESSION_SYNC (soup_request_get_session (req))) + g_main_loop_unref (data.loop); + + return nread == 0; } gboolean -soup_test_stream_close_async_as_sync (GInputStream *stream, - GCancellable *cancellable, - GError **error) +soup_test_request_close_stream (SoupRequest *req, + GInputStream *stream, + GCancellable *cancellable, + GError **error) { - AsyncAsSyncData data; - gboolean ok; + AsyncAsSyncData data; + gboolean ok; - data.loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); + if (SOUP_IS_SESSION_SYNC (soup_request_get_session (req))) + return g_input_stream_close (stream, cancellable, error); - g_input_stream_close_async (stream, G_PRIORITY_DEFAULT, cancellable, - async_as_sync_callback, &data); - g_main_loop_run (data.loop); + data.loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); - ok = g_input_stream_close_finish (stream, data.result, error); + g_input_stream_close_async (stream, G_PRIORITY_DEFAULT, cancellable, + async_as_sync_callback, &data); + g_main_loop_run (data.loop); - g_main_loop_unref (data.loop); - g_object_unref (data.result); + ok = g_input_stream_close_finish (stream, data.result, error); - return ok; + g_main_loop_unref (data.loop); + g_object_unref (data.result); + + return ok; } + +void +soup_test_register_resources (void) +{ + static gboolean registered = FALSE; + GResource *resource; + char *path; + GError *error = NULL; + + if (registered) + return; + + path = g_test_build_filename (G_TEST_BUILT, "soup-tests.gresource", NULL); + resource = g_resource_load (path, &error); + if (!resource) { + g_printerr ("Could not load resource soup-tests.gresource: %s\n", + error->message); + exit (1); + } + g_free (path); + + g_resources_register (resource); + g_resource_unref (resource); + + registered = TRUE; +} + +SoupBuffer * +soup_test_load_resource (const char *name, + GError **error) +{ + GBytes *bytes; + char *path; + + soup_test_register_resources (); + + path = g_build_path ("/", "/org/gnome/libsoup/tests/resources", name, NULL); + bytes = g_resources_lookup_data (path, G_RESOURCE_LOOKUP_FLAGS_NONE, error); + g_free (path); + + if (!bytes) + return NULL; + + return soup_buffer_new_with_owner (g_bytes_get_data (bytes, NULL), + g_bytes_get_size (bytes), + bytes, + (GDestroyNotify) g_bytes_unref); +} + +SoupBuffer * +soup_test_get_index (void) +{ + if (!index_buffer) { + char *path, *contents; + gsize length; + GError *error = NULL; + + path = g_test_build_filename (G_TEST_DIST, "index.txt", NULL); + if (!g_file_get_contents (path, &contents, &length, &error)) { + g_printerr ("Could not read index.txt: %s\n", + error->message); + exit (1); + } + g_free (path); + + index_buffer = soup_buffer_new (SOUP_MEMORY_TAKE, contents, length); + } + + return index_buffer; +} + +#ifndef G_HAVE_ISO_VARARGS +void +soup_test_assert (gboolean expr, const char *fmt, ...) +{ + char *message; + va_list args; + + if (G_UNLIKELY (!expr)) { + va_start (args, fmt); + message = g_strdup_vprintf (fmt, args); + va_end (args); + + g_assertion_message (G_LOG_DOMAIN, + "???", 0, "???" + message); + g_free (message); + } +} +#endif diff --git a/tests/test-utils.h b/tests/test-utils.h index 9e969038..03637dcf 100644 --- a/tests/test-utils.h +++ b/tests/test-utils.h @@ -1,3 +1,5 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -9,22 +11,47 @@ #include "libsoup/soup.h" #include "libsoup/soup-requester.h" -#include "libsoup/soup-request-data.h" -#include "libsoup/soup-request-file.h" -#include "libsoup/soup-request-http.h" void test_init (int argc, char **argv, GOptionEntry *entries); void test_cleanup (void); -extern int debug_level, errors; -extern gboolean expect_warning, tls_available; +extern int debug_level; +extern gboolean tls_available; +extern gboolean apache_available; void debug_printf (int level, const char *format, ...) G_GNUC_PRINTF (2, 3); +#define SOUP_TEST_SKIP_IF_NO_TLS \ + G_STMT_START { \ + if (!tls_available) { \ + g_test_skip ("TLS is not available"); \ + return; \ + } \ + } G_STMT_END + #ifdef HAVE_APACHE void apache_init (void); void apache_cleanup (void); +#define SOUP_TEST_SKIP_IF_NO_APACHE +#else +#define apache_init() +#define apache_cleanup() +#define SOUP_TEST_SKIP_IF_NO_APACHE \ + G_STMT_START { \ + g_test_skip ("apache is not available"); \ + return; \ + } G_STMT_END #endif +typedef enum { + SOUP_TEST_REQUEST_NONE = 0, + SOUP_TEST_REQUEST_CANCEL_MESSAGE = (1 << 0), + SOUP_TEST_REQUEST_CANCEL_CANCELLABLE = (1 << 1), + SOUP_TEST_REQUEST_CANCEL_SOON = (1 << 2), + SOUP_TEST_REQUEST_CANCEL_IMMEDIATE = (1 << 3), + SOUP_TEST_REQUEST_CANCEL_PREEMPTIVE = (1 << 4), + SOUP_TEST_REQUEST_CANCEL_AFTER_SEND_FINISH = (1 << 5), +} SoupTestRequestFlags; + SoupSession *soup_test_session_new (GType type, ...); void soup_test_session_abort_unref (SoupSession *session); @@ -32,9 +59,68 @@ SoupServer *soup_test_server_new (gboolean in_own_thread); SoupServer *soup_test_server_new_ssl (gboolean in_own_thread); void soup_test_server_quit_unref (SoupServer *server); -GInputStream *soup_test_request_send_async_as_sync (SoupRequest *req, - GCancellable *cancellable, - GError **error); -gboolean soup_test_stream_close_async_as_sync (GInputStream *stream, - GCancellable *cancellable, - GError **error); +GInputStream *soup_test_request_send (SoupRequest *req, + GCancellable *cancellable, + guint flags, + GError **error); +gboolean soup_test_request_read_all (SoupRequest *req, + GInputStream *stream, + GCancellable *cancellable, + GError **error); +gboolean soup_test_request_close_stream (SoupRequest *req, + GInputStream *stream, + GCancellable *cancellable, + GError **error); + +void soup_test_register_resources (void); +SoupBuffer *soup_test_load_resource (const char *name, + GError **error); + +SoupBuffer *soup_test_get_index (void); + +#ifdef G_HAVE_ISO_VARARGS +#define soup_test_assert(expr, ...) \ +G_STMT_START { \ + char *_message; \ + if (G_UNLIKELY (!(expr))) { \ + _message = g_strdup_printf (__VA_ARGS__); \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, __LINE__, G_STRFUNC, \ + _message); \ + g_free (_message); \ + } \ +} G_STMT_END +#else +void soup_test_assert (gboolean expr, const char *fmt, ...); +#endif + +#define soup_test_assert_message_status(msg, status) \ +G_STMT_START { \ + SoupMessage *_msg = (msg); \ + guint _status = (status); \ + char *_message; \ + \ + if (G_UNLIKELY (_msg->status_code != _status)) { \ + _message = g_strdup_printf ("Unexpected status %d %s (expected %d %s)", \ + _msg->status_code, _msg->reason_phrase, \ + _status, soup_status_get_phrase (_status)); \ + g_assertion_message (G_LOG_DOMAIN, \ + __FILE__, __LINE__, G_STRFUNC, \ + _message); \ + g_free (_message); \ + } \ +} G_STMT_END + +#define soup_assert_cmpmem(s1, l1, s2, l2) \ +G_STMT_START { \ + int __l1 = l1, __l2 = l2; \ + gconstpointer __s1 = s1, __s2 = s2; \ + if (G_UNLIKELY ((__l1) != (__l2))) { \ + g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "len(" #s1 ") == len(" #s2 ")", __l1, "==", __l2, \ + 'i'); \ + } else if (G_UNLIKELY (memcmp (__s1, __s2, __l1) != 0)) { \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + "assertion failed (" #s1 " == " #s2 ")"); \ + } \ +} G_STMT_END diff --git a/tests/timeout-test.c b/tests/timeout-test.c index 2bb91d8a..81fb4331 100644 --- a/tests/timeout-test.c +++ b/tests/timeout-test.c @@ -2,31 +2,14 @@ #include "test-utils.h" +static gboolean slow_https; + static void -do_message_to_session (SoupSession *session, const char *uri, - const char *comment, guint expected_status) +message_finished (SoupMessage *msg, gpointer user_data) { - SoupMessage *msg; - - debug_printf (1, " %s\n", comment); - msg = soup_message_new ("GET", uri); - soup_session_send_message (session, msg); - - if (msg->status_code != expected_status) { - debug_printf (1, " FAILED: %d %s (expected %d %s)\n", - msg->status_code, msg->reason_phrase, - expected_status, - soup_status_get_phrase (expected_status)); - errors++; - } + gboolean *finished = user_data; - if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code) && - !soup_message_is_keepalive (msg)) { - debug_printf (1, " ERROR: message is not keepalive!"); - errors++; - } - - g_object_unref (msg); + *finished = TRUE; } static void @@ -39,10 +22,37 @@ request_started_cb (SoupSession *session, SoupMessage *msg, } static void -do_tests_for_session (SoupSession *timeout_session, - SoupSession *idle_session, - SoupSession *plain_session, - char *fast_uri, char *slow_uri) +do_message_to_session (SoupSession *session, const char *uri, + const char *comment, guint expected_status) +{ + SoupMessage *msg; + gboolean finished = FALSE; + + if (comment) + debug_printf (1, " msg %s\n", comment); + msg = soup_message_new ("GET", uri); + + g_signal_connect (msg, "finished", + G_CALLBACK (message_finished), &finished); + soup_session_send_message (session, msg); + + soup_test_assert_message_status (msg, expected_status); + if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) + g_assert_true (soup_message_is_keepalive (msg)); + g_assert_true (finished); + + g_signal_handlers_disconnect_by_func (msg, + G_CALLBACK (message_finished), + &finished); + g_object_unref (msg); +} + +static void +do_msg_tests_for_session (SoupSession *timeout_session, + SoupSession *idle_session, + SoupSession *plain_session, + const char *fast_uri, + const char *slow_uri) { SoupSocket *ret, *idle_first, *idle_second; SoupSocket *plain_first, *plain_second; @@ -51,14 +61,14 @@ do_tests_for_session (SoupSession *timeout_session, g_signal_connect (idle_session, "request-started", G_CALLBACK (request_started_cb), &ret); do_message_to_session (idle_session, fast_uri, "fast to idle", SOUP_STATUS_OK); - idle_first = ret; + idle_first = g_object_ref (ret); } if (plain_session) { g_signal_connect (plain_session, "request-started", G_CALLBACK (request_started_cb), &ret); do_message_to_session (plain_session, fast_uri, "fast to plain", SOUP_STATUS_OK); - plain_first = ret; + plain_first = g_object_ref (ret); } do_message_to_session (timeout_session, fast_uri, "fast to timeout", SOUP_STATUS_OK); @@ -71,10 +81,9 @@ do_tests_for_session (SoupSession *timeout_session, (gpointer)request_started_cb, &ret); - if (idle_first == idle_second) { - debug_printf (1, " ERROR: idle_session did not close first connection\n"); - errors++; - } + soup_test_assert (idle_first != idle_second, + "idle_session did not close first connection"); + g_object_unref (idle_first); } if (plain_session) { @@ -84,46 +93,174 @@ do_tests_for_session (SoupSession *timeout_session, (gpointer)request_started_cb, &ret); - if (plain_first != plain_second) { - debug_printf (1, " ERROR: plain_session closed connection\n"); - errors++; - } + soup_test_assert (plain_first == plain_second, + "plain_session closed connection"); + g_object_unref (plain_first); + } +} + +static void +do_request_to_session (SoupSession *session, const char *uri, + const char *comment, gboolean expect_timeout) +{ + SoupRequest *req; + SoupMessage *msg; + GInputStream *stream; + GError *error = NULL; + gboolean finished = FALSE; + + debug_printf (1, " req %s\n", comment); + req = soup_session_request (session, uri, NULL); + msg = soup_request_http_get_message (SOUP_REQUEST_HTTP (req)); + + g_signal_connect (msg, "finished", + G_CALLBACK (message_finished), &finished); + stream = soup_test_request_send (req, NULL, 0, &error); + + if (expect_timeout) + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT); + else + g_assert_no_error (error); + g_clear_error (&error); + + if (stream) { + soup_test_request_read_all (req, stream, NULL, &error); + g_assert_no_error (error); + } + + if (stream) { + soup_test_request_close_stream (req, stream, NULL, &error); + g_assert_no_error (error); + g_object_unref (stream); } + + if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) + g_assert_true (soup_message_is_keepalive (msg)); + g_assert_true (finished); + + g_signal_handlers_disconnect_by_func (msg, + G_CALLBACK (message_finished), + &finished); + g_object_unref (msg); + g_object_unref (req); } static void -do_timeout_tests (char *fast_uri, char *slow_uri) +do_req_tests_for_session (SoupSession *timeout_session, + SoupSession *idle_session, + SoupSession *plain_session, + const char *fast_uri, + const char *slow_uri) +{ + SoupSocket *ret, *idle_first, *idle_second; + SoupSocket *plain_first, *plain_second; + + if (idle_session) { + g_signal_connect (idle_session, "request-started", + G_CALLBACK (request_started_cb), &ret); + do_request_to_session (idle_session, fast_uri, "fast to idle", FALSE); + idle_first = g_object_ref (ret); + } + + if (plain_session) { + g_signal_connect (plain_session, "request-started", + G_CALLBACK (request_started_cb), &ret); + do_request_to_session (plain_session, fast_uri, "fast to plain", FALSE); + plain_first = g_object_ref (ret); + } + + do_request_to_session (timeout_session, fast_uri, "fast to timeout", FALSE); + do_request_to_session (timeout_session, slow_uri, "slow to timeout", TRUE); + + if (idle_session) { + do_request_to_session (idle_session, fast_uri, "fast to idle", FALSE); + idle_second = ret; + g_signal_handlers_disconnect_by_func (idle_session, + (gpointer)request_started_cb, + &ret); + + soup_test_assert (idle_first != idle_second, + "idle_session did not close first connection"); + g_object_unref (idle_first); + } + + if (plain_session) { + do_request_to_session (plain_session, fast_uri, "fast to plain", FALSE); + plain_second = ret; + g_signal_handlers_disconnect_by_func (plain_session, + (gpointer)request_started_cb, + &ret); + + soup_test_assert (plain_first == plain_second, + "plain_session closed connection"); + g_object_unref (plain_first); + } +} + +static void +do_async_timeout_tests (gconstpointer data) { SoupSession *timeout_session, *idle_session, *plain_session; + const char *fast_uri = data; + const char *slow_uri = g_build_path ("/", fast_uri, "slow", NULL); + gboolean extra_slow; + + if (g_str_has_prefix (fast_uri, "https")) { + SOUP_TEST_SKIP_IF_NO_TLS; + + extra_slow = slow_https; + } else + extra_slow = FALSE; - debug_printf (1, " async\n"); timeout_session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_TIMEOUT, 1, - NULL); + SOUP_SESSION_TIMEOUT, extra_slow ? 3 : 1, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, + NULL); idle_session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_IDLE_TIMEOUT, 1, + SOUP_SESSION_IDLE_TIMEOUT, extra_slow ? 2 : 1, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); /* The "plain" session also has an idle timeout, but it's longer * than the test takes, so for our purposes it should behave like * it has no timeout. */ plain_session = soup_test_session_new (SOUP_TYPE_SESSION_ASYNC, - SOUP_SESSION_IDLE_TIMEOUT, 2, + SOUP_SESSION_IDLE_TIMEOUT, 20, + SOUP_SESSION_USE_THREAD_CONTEXT, TRUE, NULL); - do_tests_for_session (timeout_session, idle_session, plain_session, - fast_uri, slow_uri); + + do_msg_tests_for_session (timeout_session, idle_session, plain_session, + fast_uri, slow_uri); + do_req_tests_for_session (timeout_session, idle_session, plain_session, + fast_uri, slow_uri); soup_test_session_abort_unref (timeout_session); soup_test_session_abort_unref (idle_session); soup_test_session_abort_unref (plain_session); +} + +static void +do_sync_timeout_tests (gconstpointer data) +{ + SoupSession *timeout_session, *plain_session; + const char *fast_uri = data; + const char *slow_uri = g_build_path ("/", fast_uri, "slow", NULL); + gboolean extra_slow; + + if (g_str_has_prefix (fast_uri, "https")) { + SOUP_TEST_SKIP_IF_NO_TLS; + + extra_slow = slow_https; + } else + extra_slow = FALSE; - debug_printf (1, " sync\n"); timeout_session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, - SOUP_SESSION_TIMEOUT, 1, + SOUP_SESSION_TIMEOUT, extra_slow ? 3 : 1, NULL); /* SOUP_SESSION_TIMEOUT doesn't work with sync sessions */ plain_session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); - do_tests_for_session (timeout_session, NULL, plain_session, fast_uri, slow_uri); + do_msg_tests_for_session (timeout_session, NULL, plain_session, fast_uri, slow_uri); + do_req_tests_for_session (timeout_session, NULL, plain_session, fast_uri, slow_uri); soup_test_session_abort_unref (timeout_session); soup_test_session_abort_unref (plain_session); } @@ -154,44 +291,63 @@ server_handler (SoupServer *server, soup_server_pause_message (server, msg); g_object_set_data (G_OBJECT (msg), "server", server); soup_add_timeout (soup_server_get_async_context (server), - 1100, timeout_finish_message, msg); + 4000, timeout_finish_message, msg); } } int main (int argc, char **argv) { - SoupServer *server; - char *fast_uri, *slow_uri; + SoupServer *server, *https_server = NULL; + char *uri, *https_uri = NULL; + int ret; test_init (argc, argv, NULL); - debug_printf (1, "http\n"); server = soup_test_server_new (TRUE); soup_server_add_handler (server, NULL, server_handler, NULL, NULL); - fast_uri = g_strdup_printf ("http://127.0.0.1:%u/", - soup_server_get_port (server)); - slow_uri = g_strdup_printf ("http://127.0.0.1:%u/slow", - soup_server_get_port (server)); - do_timeout_tests (fast_uri, slow_uri); - g_free (fast_uri); - g_free (slow_uri); - soup_test_server_quit_unref (server); + uri = g_strdup_printf ("http://127.0.0.1:%u/", + soup_server_get_port (server)); if (tls_available) { - debug_printf (1, "\nhttps\n"); - server = soup_test_server_new_ssl (TRUE); - soup_server_add_handler (server, NULL, server_handler, NULL, NULL); - fast_uri = g_strdup_printf ("https://127.0.0.1:%u/", - soup_server_get_port (server)); - slow_uri = g_strdup_printf ("https://127.0.0.1:%u/slow", - soup_server_get_port (server)); - do_timeout_tests (fast_uri, slow_uri); - g_free (fast_uri); - g_free (slow_uri); - soup_test_server_quit_unref (server); - } + SoupSession *test_session; + gint64 start, end; + + https_server = soup_test_server_new_ssl (TRUE); + soup_server_add_handler (https_server, NULL, server_handler, NULL, NULL); + https_uri = g_strdup_printf ("https://127.0.0.1:%u/", + soup_server_get_port (https_server)); + + /* The 1-second timeouts are too fast for some machines... */ + test_session = soup_test_session_new (SOUP_TYPE_SESSION, NULL); + start = g_get_monotonic_time (); + do_message_to_session (test_session, uri, NULL, SOUP_STATUS_OK); + end = g_get_monotonic_time (); + soup_test_session_abort_unref (test_session); + debug_printf (2, " (https request took %0.3fs)\n", (end - start) / 1000000.0); + if (end - start > 750000) { + debug_printf (1, " (using extra-slow mode)\n\n"); + slow_https = TRUE; + } else { + debug_printf (2, "\n"); + slow_https = FALSE; + } + } else + https_uri = g_strdup ("https://fail."); + + g_test_add_data_func ("/timeout/http/async", uri, do_async_timeout_tests); + g_test_add_data_func ("/timeout/http/sync", uri, do_sync_timeout_tests); + g_test_add_data_func ("/timeout/https/async", https_uri, do_async_timeout_tests); + g_test_add_data_func ("/timeout/https/sync", https_uri, do_sync_timeout_tests); + + ret = g_test_run (); + + g_free (uri); + g_free (https_uri); + soup_test_server_quit_unref (server); + if (https_server) + soup_test_server_quit_unref (https_server); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/tld-test.c b/tests/tld-test.c index 5f66c686..4fad8625 100644 --- a/tests/tld-test.c +++ b/tests/tld-test.c @@ -7,124 +7,186 @@ /* From http://publicsuffix.org/list/test.txt */ static struct { - const char *hostname; - const char *result; + const char *hostname; + const char *result; + SoupTLDError error; } tld_tests[] = { - /* NULL input. Not checked here because the API requires a valid hostname. */ - /* { NULL, NULL }, */ - /* Mixed case. Not checked because the API requires a valid hostname. */ - /* { "COM", NULL }, */ - /* { "example.COM", "example.com" }, */ - /* { "WwW.example.COM", "example.com" }, */ - /* Leading dot. */ - { ".com", NULL }, - { ".example", NULL }, - { ".example.com", NULL }, - { ".example.example", NULL }, - /* Unlisted TLD.*/ - { "example", NULL }, - { "example.example", NULL }, - { "b.example.example", NULL }, - { "a.b.example.example", NULL }, - /* Listed, but non-Internet, TLD. */ - { "local", NULL }, - { "example.local", NULL }, - { "b.example.local", NULL }, - { "a.b.example.local", NULL }, - /* TLD with only 1 rule. */ - { "biz", NULL }, - { "domain.biz", "domain.biz" }, - { "b.domain.biz", "domain.biz" }, - { "a.b.domain.biz", "domain.biz" }, - /* TLD with some 2-level rules. */ - { "com", NULL }, - { "example.com", "example.com" }, - { "b.example.com", "example.com" }, - { "a.b.example.com", "example.com" }, - { "uk.com", NULL }, - { "example.uk.com", "example.uk.com" }, - { "b.example.uk.com", "example.uk.com" }, - { "a.b.example.uk.com", "example.uk.com" }, - { "test.ac", "test.ac" }, - /* TLD with only 1 (wildcard) rule. */ - { "cy", NULL }, - { "c.cy", NULL }, - { "b.c.cy", "b.c.cy" }, - { "a.b.c.cy", "b.c.cy" }, - /* More complex TLD. */ - { "jp", NULL }, - { "test.jp", "test.jp" }, - { "www.test.jp", "test.jp" }, - { "ac.jp", NULL }, - { "test.ac.jp", "test.ac.jp" }, - { "www.test.ac.jp", "test.ac.jp" }, - { "kyoto.jp", NULL }, - { "c.kyoto.jp", NULL }, - { "b.c.kyoto.jp", "b.c.kyoto.jp" }, - { "a.b.c.kyoto.jp", "b.c.kyoto.jp" }, - { "pref.kyoto.jp", "pref.kyoto.jp" }, /* Exception rule. */ - { "www.pref.kyoto.jp", "pref.kyoto.jp" }, /* Exception rule. */ - { "city.kyoto.jp", "city.kyoto.jp" }, /* Exception rule. */ - { "www.city.kyoto.jp", "city.kyoto.jp" }, /* Exception rule. */ - /* TLD with a wildcard rule and exceptions. */ - { "om", NULL }, - { "test.om", NULL }, - { "b.test.om", "b.test.om" }, - { "a.b.test.om", "b.test.om" }, - { "songfest.om", "songfest.om" }, - { "www.songfest.om", "songfest.om" }, - /* US K12. */ - { "us", NULL }, - { "test.us", "test.us" }, - { "www.test.us", "test.us" }, - { "ak.us", NULL }, - { "test.ak.us", "test.ak.us" }, - { "www.test.ak.us", "test.ak.us" }, - { "k12.ak.us", NULL }, - { "test.k12.ak.us", "test.k12.ak.us" }, - { "www.test.k12.ak.us", "test.k12.ak.us" }, - /* This is not in http://publicsuffix.org/list/test.txt but we want to check it anyway. */ - { "co.uk", NULL }, - /* The original list does not include non-ASCII tests. Let's add a couple. */ - { "公司.cn", NULL }, - { "a.b.åfjord.no", "b.åfjord.no" } + /* NULL input. Not checked here because the API requires a valid hostname. */ + /* { NULL, NULL, -1 }, */ + /* Mixed case. Not checked because the API requires a valid hostname. */ + /* { "COM", NULL, -1 }, */ + /* { "example.COM", "example.com", -1 }, */ + /* { "WwW.example.COM", "example.com", -1 }, */ + /* Leading dot. */ + { ".com", NULL, SOUP_TLD_ERROR_INVALID_HOSTNAME }, + { ".example", NULL, SOUP_TLD_ERROR_INVALID_HOSTNAME }, + { ".example.com", NULL, SOUP_TLD_ERROR_INVALID_HOSTNAME }, + { ".example.example", NULL, SOUP_TLD_ERROR_INVALID_HOSTNAME }, + /* TLD with only 1 rule. */ + { "biz", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "domain.biz", "domain.biz", -1 }, + { "b.domain.biz", "domain.biz", -1 }, + { "a.b.domain.biz", "domain.biz", -1 }, + /* TLD with some 2-level rules. */ + { "com", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "example.com", "example.com", -1 }, + { "b.example.com", "example.com", -1 }, + { "a.b.example.com", "example.com", -1 }, + { "uk.com", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "example.uk.com", "example.uk.com", -1 }, + { "b.example.uk.com", "example.uk.com", -1 }, + { "a.b.example.uk.com", "example.uk.com", -1 }, + { "test.ac", "test.ac", -1 }, + /* TLD with only 1 (wildcard) rule. */ + { "cy", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "c.cy", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "b.c.cy", "b.c.cy", -1 }, + { "a.b.c.cy", "b.c.cy", -1 }, + /* More complex TLD. */ + { "jp", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "test.jp", "test.jp", -1 }, + { "www.test.jp", "test.jp", -1 }, + { "ac.jp", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "test.ac.jp", "test.ac.jp", -1 }, + { "www.test.ac.jp", "test.ac.jp", -1 }, + { "kyoto.jp", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "minami.kyoto.jp", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "b.minami.kyoto.jp", "b.minami.kyoto.jp", -1 }, + { "a.b.minami.kyoto.jp", "b.minami.kyoto.jp", -1 }, + { "pref.kyoto.jp", "pref.kyoto.jp", -1 }, + { "www.pref.kyoto.jp", "pref.kyoto.jp", -1 }, + { "city.kyoto.jp", "city.kyoto.jp", -1 }, + { "www.city.kyoto.jp", "city.kyoto.jp", -1 }, + /* TLD with a wildcard rule and exceptions. */ + { "ck", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "test.ck", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "b.test.ck", "b.test.ck", -1 }, + { "a.b.test.ck", "b.test.ck", -1 }, + { "www.ck", "www.ck", -1 }, + { "www.www.ck", "www.ck", -1 }, + /* US K12. */ + { "us", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "test.us", "test.us", -1 }, + { "www.test.us", "test.us", -1 }, + { "ak.us", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "test.ak.us", "test.ak.us", -1 }, + { "www.test.ak.us", "test.ak.us", -1 }, + { "k12.ak.us", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "test.k12.ak.us", "test.k12.ak.us", -1 }, + { "www.test.k12.ak.us", "test.k12.ak.us", -1 }, + /* IDN labels. */ + { "食狮.com.cn", "食狮.com.cn", -1 }, + { "食狮.公司.cn", "食狮.公司.cn", -1 }, + { "www.食狮.公司.cn", "食狮.公司.cn", -1 }, + { "shishi.公司.cn", "shishi.公司.cn", -1 }, + { "公司.cn", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "食狮.中国", "食狮.中国", -1 }, + { "www.食狮.中国", "食狮.中国", -1 }, + { "shishi.中国", "shishi.中国", -1 }, + { "中国", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + /* Same as above, but punycoded. */ + { "xn--85x722f.com.cn", "xn--85x722f.com.cn", -1 }, + { "xn--85x722f.xn--55qx5d.cn", "xn--85x722f.xn--55qx5d.cn", -1 }, + { "www.xn--85x722f.xn--55qx5d.cn", "xn--85x722f.xn--55qx5d.cn", -1 }, + { "shishi.xn--55qx5d.cn", "shishi.xn--55qx5d.cn", -1 }, + { "xn--55qx5d.cn", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "xn--85x722f.xn--fiqs8s", "xn--85x722f.xn--fiqs8s", -1 }, + { "www.xn--85x722f.xn--fiqs8s", "xn--85x722f.xn--fiqs8s", -1 }, + { "shishi.xn--fiqs8s", "shishi.xn--fiqs8s", -1 }, + { "xn--fiqs8s", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + /* End of publicsuffix.org tests */ + + /* Let's just double-check this one... */ + { "co.uk", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "test.co.uk", "test.co.uk", -1 }, + { "www.test.co.uk", "test.co.uk", -1 }, + + /* Two levels of non-ASCII */ + { "våler.østfold.no", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "test.våler.østfold.no", "test.våler.østfold.no", -1 }, + { "www.test.våler.østfold.no", "test.våler.østfold.no", -1 }, + { "xn--vler-qoa.xn--stfold-9xa.no", NULL, SOUP_TLD_ERROR_NOT_ENOUGH_DOMAINS }, + { "test.xn--vler-qoa.xn--stfold-9xa.no", "test.xn--vler-qoa.xn--stfold-9xa.no", -1 }, + { "www.test.xn--vler-qoa.xn--stfold-9xa.no", "test.xn--vler-qoa.xn--stfold-9xa.no", -1 }, +}, + +/* Non Internet TLDs have NULL as expected result + */ +non_inet_tld_tests[] = { + /* Unlisted TLD.*/ + { "example", NULL }, + { "example.example", NULL }, + { "b.example.example", NULL }, + { "a.b.example.example", NULL }, + /* Listed, but non-Internet, TLD. */ + { "local", NULL }, + { "example.local", NULL }, + { "b.example.local", NULL }, + { "a.b.example.local", NULL } }; +static void +do_inet_tests (void) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (tld_tests); i++) { + GError *error = NULL; + gboolean is_public; + const char *base_domain; + + debug_printf (1, "Testing %s\n", tld_tests[i].hostname); + + is_public = soup_tld_domain_is_public_suffix (tld_tests[i].hostname); + base_domain = soup_tld_get_base_domain (tld_tests[i].hostname, &error); + + if (base_domain) { + g_assert_no_error (error); + g_assert_false (is_public); + g_assert_cmpstr (base_domain, ==, tld_tests[i].result); + } else { + g_assert_null (tld_tests[i].result); + g_assert_error (error, SOUP_TLD_ERROR, tld_tests[i].error); + g_clear_error (&error); + } + } +} + +static void +do_non_inet_tests (void) +{ + int i; + + g_test_bug ("679230"); + g_test_bug ("681085"); + + for (i = 0; i < G_N_ELEMENTS (non_inet_tld_tests); i++) { + gboolean is_public; + const char *base_domain; + + debug_printf (1, "Testing %s\n", non_inet_tld_tests[i].hostname); + + is_public = soup_tld_domain_is_public_suffix (non_inet_tld_tests[i].hostname); + base_domain = soup_tld_get_base_domain (non_inet_tld_tests[i].hostname, NULL); + + g_assert_false (is_public); + g_assert_null (base_domain); + } +} + int main (int argc, char **argv) { - int i; + int ret; test_init (argc, argv, NULL); - errors = 0; - for (i = 0; i < G_N_ELEMENTS (tld_tests); ++i) { - gboolean is_public = soup_tld_domain_is_public_suffix (tld_tests[i].hostname); - const char *base_domain = soup_tld_get_base_domain (tld_tests[i].hostname, NULL); - - debug_printf (1, "Testing %s: ", tld_tests[i].hostname); - if (tld_tests[i].result) { - /* Public domains have NULL expected results. */ - if (is_public || g_strcmp0 (tld_tests[i].result, base_domain)) { - debug_printf (1, "ERROR: %s got %s (%s expected)\n", - tld_tests[i].hostname, base_domain, tld_tests[i].result); - ++errors; - } else - debug_printf (1, "OK\n"); - } else { - /* If there is no expected result then either the domain is public or - * the hostname invalid (for example starts with a leading dot). - */ - if (!is_public && base_domain) { - debug_printf (1, "ERROR: public domain %s got %s (none expected)\n", - tld_tests[i].hostname, base_domain); - ++errors; - } else - debug_printf (1, "OK\n"); - } - } + g_test_add_func ("/tld/inet", do_inet_tests); + g_test_add_func ("/tld/non-inet", do_non_inet_tests); + + ret = g_test_run (); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/uri-parsing.c b/tests/uri-parsing.c index 08274a28..d56b655f 100644 --- a/tests/uri-parsing.c +++ b/tests/uri-parsing.c @@ -3,123 +3,155 @@ #include "test-utils.h" static struct { - const char *uri_string, *result; + const char *uri_string, *result, *bugref; const SoupURI bits; } abs_tests[] = { - { "foo:", "foo:", + { "foo:", "foo:", NULL, { "foo", NULL, NULL, NULL, 0, "", NULL, NULL } }, - { "file:/dev/null", "file:/dev/null", + { "file:/dev/null", "file:/dev/null", NULL, { "file", NULL, NULL, NULL, 0, "/dev/null", NULL, NULL } }, - { "file:///dev/null", "file:///dev/null", + { "file:///dev/null", "file:///dev/null", NULL, { "file", NULL, NULL, "", 0, "/dev/null", NULL, NULL } }, - { "ftp://user@host/path", "ftp://user@host/path", + { "ftp://user@host/path", "ftp://user@host/path", NULL, { "ftp", "user", NULL, "host", 21, "/path", NULL, NULL } }, - { "ftp://user@host:9999/path", "ftp://user@host:9999/path", + { "ftp://user@host:9999/path", "ftp://user@host:9999/path", NULL, { "ftp", "user", NULL, "host", 9999, "/path", NULL, NULL } }, - { "ftp://user:password@host/path", "ftp://user@host/path", + { "ftp://user:password@host/path", "ftp://user@host/path", NULL, { "ftp", "user", "password", "host", 21, "/path", NULL, NULL } }, - { "ftp://user:password@host:9999/path", "ftp://user@host:9999/path", + { "ftp://user:password@host:9999/path", "ftp://user@host:9999/path", NULL, { "ftp", "user", "password", "host", 9999, "/path", NULL, NULL } }, - { "ftp://user:password@host", "ftp://user@host", + { "ftp://user:password@host", "ftp://user@host", NULL, { "ftp", "user", "password", "host", 21, "", NULL, NULL } }, - { "http://us%65r@host", "http://user@host/", + { "http://us%65r@host", "http://user@host/", NULL, { "http", "user", NULL, "host", 80, "/", NULL, NULL } }, - { "http://us%40r@host", "http://us%40r@host/", + { "http://us%40r@host", "http://us%40r@host/", NULL, { "http", "us\x40r", NULL, "host", 80, "/", NULL, NULL } }, - { "http://us%3ar@host", "http://us%3Ar@host/", + { "http://us%3ar@host", "http://us%3Ar@host/", NULL, { "http", "us\x3ar", NULL, "host", 80, "/", NULL, NULL } }, - { "http://us%2fr@host", "http://us%2Fr@host/", + { "http://us%2fr@host", "http://us%2Fr@host/", NULL, { "http", "us\x2fr", NULL, "host", 80, "/", NULL, NULL } }, - { "http://us%3fr@host", "http://us%3Fr@host/", + { "http://us%3fr@host", "http://us%3Fr@host/", NULL, { "http", "us\x3fr", NULL, "host", 80, "/", NULL, NULL } }, - { "http://host?query", "http://host/?query", + { "http://host?query", "http://host/?query", NULL, { "http", NULL, NULL, "host", 80, "/", "query", NULL } }, { "http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fchildparam%3Dchildvalue¶m=value", - "http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fchildparam%3Dchildvalue¶m=value", + "http://host/path?query=http%3A%2F%2Fhost%2Fpath%3Fchildparam%3Dchildvalue¶m=value", NULL, { "http", NULL, NULL, "host", 80, "/path", "query=http%3A%2F%2Fhost%2Fpath%3Fchildparam%3Dchildvalue¶m=value", NULL } }, { "http://control-chars/%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F", - "http://control-chars/%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F", + "http://control-chars/%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F", NULL, { "http", NULL, NULL, "control-chars", 80, "/%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%7F", NULL, NULL } }, { "http://space/%20", - "http://space/%20", + "http://space/%20", NULL, { "http", NULL, NULL, "space", 80, "/%20", NULL, NULL } }, { "http://delims/%3C%3E%23%25%22", - "http://delims/%3C%3E%23%25%22", + "http://delims/%3C%3E%23%25%22", NULL, { "http", NULL, NULL, "delims", 80, "/%3C%3E%23%25%22", NULL, NULL } }, { "http://unwise-chars/%7B%7D%7C%5C%5E%5B%5D%60", - "http://unwise-chars/%7B%7D%7C%5C%5E%5B%5D%60", + "http://unwise-chars/%7B%7D%7C%5C%5E%5B%5D%60", NULL, { "http", NULL, NULL, "unwise-chars", 80, "/%7B%7D%7C%5C%5E%5B%5D%60", NULL, NULL } }, /* From RFC 2732 */ { "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html", - "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/index.html", + "http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]/index.html", NULL, { "http", NULL, NULL, "FEDC:BA98:7654:3210:FEDC:BA98:7654:3210", 80, "/index.html", NULL, NULL } }, { "http://[1080:0:0:0:8:800:200C:417A]/index.html", - "http://[1080:0:0:0:8:800:200C:417A]/index.html", + "http://[1080:0:0:0:8:800:200C:417A]/index.html", NULL, { "http", NULL, NULL, "1080:0:0:0:8:800:200C:417A", 80, "/index.html", NULL, NULL } }, { "http://[3ffe:2a00:100:7031::1]", - "http://[3ffe:2a00:100:7031::1]/", + "http://[3ffe:2a00:100:7031::1]/", NULL, { "http", NULL, NULL, "3ffe:2a00:100:7031::1", 80, "/", NULL, NULL } }, { "http://[1080::8:800:200C:417A]/foo", - "http://[1080::8:800:200C:417A]/foo", + "http://[1080::8:800:200C:417A]/foo", NULL, { "http", NULL, NULL, "1080::8:800:200C:417A", 80, "/foo", NULL, NULL } }, { "http://[::192.9.5.5]/ipng", - "http://[::192.9.5.5]/ipng", + "http://[::192.9.5.5]/ipng", NULL, { "http", NULL, NULL, "::192.9.5.5", 80, "/ipng", NULL, NULL } }, { "http://[::FFFF:129.144.52.38]:80/index.html", - "http://[::FFFF:129.144.52.38]/index.html", + "http://[::FFFF:129.144.52.38]/index.html", NULL, { "http", NULL, NULL, "::FFFF:129.144.52.38", 80, "/index.html", NULL, NULL } }, { "http://[2010:836B:4179::836B:4179]", - "http://[2010:836B:4179::836B:4179]/", + "http://[2010:836B:4179::836B:4179]/", NULL, { "http", NULL, NULL, "2010:836B:4179::836B:4179", 80, "/", NULL, NULL } }, /* Try to recover certain kinds of invalid URIs */ { "http://host/path with spaces", - "http://host/path%20with%20spaces", + "http://host/path%20with%20spaces", "566530", { "http", NULL, NULL, "host", 80, "/path%20with%20spaces", NULL, NULL } }, - { " http://host/path", "http://host/path", + { " http://host/path", "http://host/path", "594405", { "http", NULL, NULL, "host", 80, "/path", NULL, NULL } }, - { "http://host/path ", "http://host/path", + { "http://host/path ", "http://host/path", "594405", { "http", NULL, NULL, "host", 80, "/path", NULL, NULL } }, - { "http://host ", "http://host/", + { "http://host ", "http://host/", "594405", { "http", NULL, NULL, "host", 80, "/", NULL, NULL } }, - { "http://host:999 ", "http://host:999/", + { "http://host:999 ", "http://host:999/", "594405", { "http", NULL, NULL, "host", 999, "/", NULL, NULL } }, - { "http://host/pa\nth", "http://host/path", + { "http://host/pa\nth", "http://host/path", "594405", { "http", NULL, NULL, "host", 80, "/path", NULL, NULL } }, - { "http:\r\n//host/path", "http://host/path", + { "http:\r\n//host/path", "http://host/path", "594405", { "http", NULL, NULL, "host", 80, "/path", NULL, NULL } }, - { "http://\thost/path", "http://host/path", + { "http://\thost/path", "http://host/path", "594405", { "http", NULL, NULL, "host", 80, "/path", NULL, NULL } }, - /* Bug 594405; 0-length is different from not-present */ - { "http://host/path?", "http://host/path?", + /* 0-length is different from not-present */ + { "http://host/path?", "http://host/path?", "594405", { "http", NULL, NULL, "host", 80, "/path", "", NULL } }, - { "http://host/path#", "http://host/path#", + { "http://host/path#", "http://host/path#", "594405", { "http", NULL, NULL, "host", 80, "/path", NULL, "" } }, - /* Bug 590524; ignore badly-%-encoding */ - { "http://host/path%", "http://host/path%", + /* ignore bad %-encoding */ + { "http://host/path%", "http://host/path%", "590524", { "http", NULL, NULL, "host", 80, "/path%", NULL, NULL } }, - { "http://h%ost/path", "http://h%25ost/path", + { "http://h%ost/path", "http://h%25ost/path", "590524", { "http", NULL, NULL, "h%ost", 80, "/path", NULL, NULL } }, - { "http://host/path%%", "http://host/path%%", + { "http://host/path%%", "http://host/path%%", "590524", { "http", NULL, NULL, "host", 80, "/path%%", NULL, NULL } }, - { "http://host/path%%%", "http://host/path%%%", + { "http://host/path%%%", "http://host/path%%%", "590524", { "http", NULL, NULL, "host", 80, "/path%%%", NULL, NULL } }, - { "http://host/path%/x/", "http://host/path%/x/", + { "http://host/path%/x/", "http://host/path%/x/", "590524", { "http", NULL, NULL, "host", 80, "/path%/x/", NULL, NULL } }, - { "http://host/path%0x/", "http://host/path%0x/", + { "http://host/path%0x/", "http://host/path%0x/", "590524", { "http", NULL, NULL, "host", 80, "/path%0x/", NULL, NULL } }, - { "http://host/path%ax", "http://host/path%ax", + { "http://host/path%ax", "http://host/path%ax", "590524", { "http", NULL, NULL, "host", 80, "/path%ax", NULL, NULL } }, - /* Bug 662806; %-encode non-ASCII characters */ - { "http://host/p\xc3\xa4th/", "http://host/p%C3%A4th/", + /* %-encode non-ASCII characters */ + { "http://host/p\xc3\xa4th/", "http://host/p%C3%A4th/", "662806", { "http", NULL, NULL, "host", 80, "/p%C3%A4th/", NULL, NULL } }, - { "HTTP:////////////////", "http:////////////////", + { "HTTP:////////////////", "http:////////////////", "667637", { "http", NULL, NULL, "", 80, "//////////////", NULL, NULL } }, + + { "http://@host", "http://@host/", NULL, + { "http", "", NULL, "host", 80, "/", NULL, NULL } }, + { "http://:@host", "http://@host/", NULL, + { "http", "", "", "host", 80, "/", NULL, NULL } }, + + { "http://host/keep%00nuls", "http://host/keep%00nuls", NULL, + { "http", NULL, NULL, "host", 80, "/keep%00nuls", NULL, NULL } }, + + /* scheme parsing */ + { "foo0://host/path", "foo0://host/path", "703776", + { "foo0", NULL, NULL, "host", 0, "/path", NULL, NULL } }, + { "f0.o://host/path", "f0.o://host/path", "703776", + { "f0.o", NULL, NULL, "host", 0, "/path", NULL, NULL } }, + { "http++://host/path", "http++://host/path", "703776", + { "http++", NULL, NULL, "host", 0, "/path", NULL, NULL } }, + { "http-ish://host/path", "http-ish://host/path", "703776", + { "http-ish", NULL, NULL, "host", 0, "/path", NULL, NULL } }, + { "99http://host/path", NULL, "703776", + { NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL } }, + { ".http://host/path", NULL, "703776", + { NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL } }, + { "+http://host/path", NULL, "703776", + { NULL, NULL, NULL, NULL, 0, NULL, NULL, NULL } }, + + /* IPv6 scope ID parsing (both correct and incorrect) */ + { "http://[fe80::dead:beef%em1]/", "http://[fe80::dead:beef%25em1]/", NULL, + { "http", NULL, NULL, "fe80::dead:beef%em1", 80, "/", NULL, NULL } }, + { "http://[fe80::dead:beef%25em1]/", "http://[fe80::dead:beef%25em1]/", NULL, + { "http", NULL, NULL, "fe80::dead:beef%em1", 80, "/", NULL, NULL } }, + { "http://[fe80::dead:beef%10]/", "http://[fe80::dead:beef%2510]/", NULL, + { "http", NULL, NULL, "fe80::dead:beef%10", 80, "/", NULL, NULL } } }; static int num_abs_tests = G_N_ELEMENTS(abs_tests); @@ -223,50 +255,18 @@ static struct { static int num_rel_tests = G_N_ELEMENTS(rel_tests); static struct { - const char *one, *two; + const char *one, *two, *bugref; } eq_tests[] = { - { "example://a/b/c/%7Bfoo%7D", "eXAMPLE://a/./b/../b/%63/%7Bfoo%7D" }, - { "http://example.com", "http://example.com/" }, + { "example://a/b/c/%7Bfoo%7D", "eXAMPLE://a/./b/../b/%63/%7Bfoo%7D", "628728" }, + { "http://example.com", "http://example.com/", NULL }, /* From RFC 2616 */ - { "http://abc.com:80/~smith/home.html", "http://abc.com:80/~smith/home.html" }, - { "http://abc.com:80/~smith/home.html", "http://ABC.com/%7Esmith/home.html" }, - { "http://abc.com:80/~smith/home.html", "http://ABC.com:/%7esmith/home.html" }, + { "http://abc.com:80/~smith/home.html", "http://abc.com:80/~smith/home.html", NULL }, + { "http://abc.com:80/~smith/home.html", "http://ABC.com/%7Esmith/home.html", NULL }, + { "http://abc.com:80/~smith/home.html", "http://ABC.com:/%7esmith/home.html", NULL }, }; static int num_eq_tests = G_N_ELEMENTS(eq_tests); -#define test_cmpstr(a, b) _test_cmpstr (#a, #b, a, b) - -static gboolean -_test_cmpstr (const char *got_desc, - const char *exp_desc, - const char *got, - const char *expected) -{ - if (got == expected) - return TRUE; - - if (got == NULL) { - debug_printf (1, "ERR\n %s = NULL, expected %s = \"%s\"\n", - got_desc, exp_desc, expected); - return FALSE; - } - - if (expected == NULL) { - debug_printf (1, "ERR\n %s = \"%s\", expected %s = NULL\n", - got_desc, got, exp_desc); - return FALSE; - } - - if (strcmp (got, expected) != 0) { - debug_printf (1, "ERR\n %s = \"%s\", expected %s = \"%s\"\n", - got_desc, got, exp_desc, expected); - return FALSE; - } - - return TRUE; -} - -static gboolean +static void do_uri (SoupURI *base_uri, const char *base_str, const char *in_uri, const char *out_uri, const SoupURI *bits) @@ -275,76 +275,94 @@ do_uri (SoupURI *base_uri, const char *base_str, char *uri_string; if (base_uri) { - debug_printf (1, "<%s> + <%s> = <%s>? ", base_str, in_uri, + debug_printf (1, "<%s> + <%s> = <%s>\n", base_str, in_uri, out_uri ? out_uri : "ERR"); uri = soup_uri_new_with_base (base_uri, in_uri); } else { - debug_printf (1, "<%s> => <%s>? ", in_uri, + debug_printf (1, "<%s> => <%s>\n", in_uri, out_uri ? out_uri : "ERR"); uri = soup_uri_new (in_uri); } if (!uri) { - if (out_uri) { - debug_printf (1, "ERR\n Could not parse %s\n", in_uri); - return FALSE; - } else { - debug_printf (1, "OK\n"); - return TRUE; - } + g_assert_null (out_uri); + return; } if (bits != NULL) { - gboolean failed = FALSE; - - if (!test_cmpstr (uri->scheme, bits->scheme)) - failed = TRUE; + g_assert_cmpstr (uri->scheme, ==, bits->scheme); + g_assert_cmpstr (uri->user, ==, bits->user); + g_assert_cmpstr (uri->password, ==, bits->password); + g_assert_cmpstr (uri->host, ==, bits->host); + g_assert_cmpuint (uri->port, ==, bits->port); + g_assert_cmpstr (uri->path, ==, bits->path); + g_assert_cmpstr (uri->query, ==, bits->query); + g_assert_cmpstr (uri->fragment, ==, bits->fragment); + } - if (!test_cmpstr (uri->user, bits->user)) - failed = TRUE; + uri_string = soup_uri_to_string (uri, FALSE); + soup_uri_free (uri); - if (!test_cmpstr (uri->password, bits->password)) - failed = TRUE; + g_assert_cmpstr (uri_string, ==, out_uri); + g_free (uri_string); +} - if (!test_cmpstr (uri->host, bits->host)) - failed = TRUE; +static void +do_absolute_uri_tests (void) +{ + int i; - if (uri->port != bits->port) { - debug_printf (1, "ERR\n port was %u, expected %u\n", - uri->port, bits->port); - failed = TRUE; - } + for (i = 0; i < num_abs_tests; i++) { + if (abs_tests[i].bugref) + g_test_bug (abs_tests[i].bugref); + do_uri (NULL, NULL, abs_tests[i].uri_string, + abs_tests[i].result, &abs_tests[i].bits); + } +} - if (!test_cmpstr (uri->path, bits->path)) - failed = TRUE; +static void +do_relative_uri_tests (void) +{ + SoupURI *base_uri; + char *uri_string; + int i; - if (!test_cmpstr (uri->query, bits->query)) - failed = TRUE; + base_uri = soup_uri_new (base); + if (!base_uri) { + g_printerr ("Could not parse %s!\n", base); + exit (1); + } - if (!test_cmpstr (uri->fragment, bits->fragment)) - failed = TRUE; + uri_string = soup_uri_to_string (base_uri, FALSE); + g_assert_cmpstr (uri_string, ==, base); + g_free (uri_string); - if (failed) - return FALSE; + for (i = 0; i < num_rel_tests; i++) { + do_uri (base_uri, base, rel_tests[i].uri_string, + rel_tests[i].result, &rel_tests[i].bits); } + soup_uri_free (base_uri); +} - uri_string = soup_uri_to_string (uri, FALSE); - soup_uri_free (uri); +static void +do_equality_tests (void) +{ + SoupURI *uri1, *uri2; + int i; - if (!out_uri) { - debug_printf (1, "ERR\n Got %s\n", uri_string); - return FALSE; - } + for (i = 0; i < num_eq_tests; i++) { + if (eq_tests[i].bugref) + g_test_bug (eq_tests[i].bugref); - if (strcmp (uri_string, out_uri) != 0) { - debug_printf (1, "NO\n Unparses to <%s>\n", uri_string); - g_free (uri_string); - return FALSE; - } - g_free (uri_string); + uri1 = soup_uri_new (eq_tests[i].one); + uri2 = soup_uri_new (eq_tests[i].two); - debug_printf (1, "OK\n"); - return TRUE; + debug_printf (1, "<%s> == <%s>\n", eq_tests[i].one, eq_tests[i].two); + g_assert_true (soup_uri_equal (uri1, uri2)); + + soup_uri_free (uri1); + soup_uri_free (uri2); + } } static void @@ -353,146 +371,83 @@ do_soup_uri_null_tests (void) SoupURI *uri, *uri2; char *uri_string; - debug_printf (1, "\nsoup_uri_new (NULL)\n"); + g_test_bug ("667637"); + g_test_bug ("670431"); + uri = soup_uri_new (NULL); - if (SOUP_URI_IS_VALID (uri) || SOUP_URI_VALID_FOR_HTTP (uri)) { - debug_printf (1, " ERROR: soup_uri_new(NULL) returns valid URI?\n"); - errors++; - } + g_assert_false (SOUP_URI_IS_VALID (uri)); + g_assert_false (SOUP_URI_VALID_FOR_HTTP (uri)); /* This implicitly also verifies that none of these methods g_warn */ - if (soup_uri_get_scheme (uri) || - soup_uri_get_user (uri) || - soup_uri_get_password (uri) || - soup_uri_get_host (uri) || - soup_uri_get_port (uri) || - soup_uri_get_path (uri) || - soup_uri_get_query (uri) || - soup_uri_get_fragment (uri)) { - debug_printf (1, " ERROR: soup_uri_new(NULL) returns non-empty URI?\n"); - errors++; - } - - expect_warning = TRUE; + g_assert_null (soup_uri_get_scheme (uri)); + g_assert_null (soup_uri_get_user (uri)); + g_assert_null (soup_uri_get_password (uri)); + g_assert_null (soup_uri_get_host (uri)); + g_assert_cmpint (soup_uri_get_port (uri), ==, 0); + g_assert_null (soup_uri_get_path (uri)); + g_assert_null (soup_uri_get_query (uri)); + g_assert_null (soup_uri_get_fragment (uri)); + + g_test_expect_message ("libsoup", G_LOG_LEVEL_CRITICAL, + "*base == NULL*"); uri2 = soup_uri_new_with_base (uri, "/path"); - if (uri2 || expect_warning) { - debug_printf (1, " ERROR: soup_uri_new_with_base didn't fail on NULL URI?\n"); - errors++; - expect_warning = FALSE; - } + g_test_assert_expected_messages (); + g_assert_null (uri2); - expect_warning = TRUE; + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*SOUP_URI_IS_VALID*"); uri_string = soup_uri_to_string (uri, FALSE); - if (expect_warning) { - debug_printf (1, " ERROR: soup_uri_to_string didn't fail on NULL URI?\n"); - errors++; - expect_warning = FALSE; - } else if (*uri_string) { - debug_printf (1, " ERROR: soup_uri_to_string on NULL URI returned '%s'\n", - uri_string); - errors++; - } + g_test_assert_expected_messages (); + g_assert_cmpstr (uri_string, ==, ""); g_free (uri_string); soup_uri_set_scheme (uri, SOUP_URI_SCHEME_HTTP); - if (SOUP_URI_IS_VALID (uri) || SOUP_URI_VALID_FOR_HTTP (uri)) { - debug_printf (1, " ERROR: setting scheme on NULL URI makes it valid?\n"); - errors++; - } + g_assert_false (SOUP_URI_IS_VALID (uri)); + g_assert_false (SOUP_URI_VALID_FOR_HTTP (uri)); - expect_warning = TRUE; + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*SOUP_URI_IS_VALID*"); uri_string = soup_uri_to_string (uri, FALSE); - if (expect_warning) { - debug_printf (1, " ERROR: soup_uri_to_string didn't fail on scheme-only URI?\n"); - errors++; - expect_warning = FALSE; - } else if (strcmp (uri_string, "http:") != 0) { - debug_printf (1, " ERROR: soup_uri_to_string returned '%s' instead of 'http:'\n", - uri_string); - errors++; - } + g_test_assert_expected_messages (); + g_assert_cmpstr (uri_string, ==, "http:"); g_free (uri_string); soup_uri_set_host (uri, "localhost"); - if (SOUP_URI_IS_VALID (uri)) { - debug_printf (1, " ERROR: setting scheme+host on NULL URI makes it valid?\n"); - errors++; - } - if (SOUP_URI_VALID_FOR_HTTP (uri)) { - debug_printf (1, " ERROR: setting scheme+host on NULL URI makes it valid for http?\n"); - errors++; - } + g_assert_false (SOUP_URI_IS_VALID (uri)); + g_assert_false (SOUP_URI_VALID_FOR_HTTP (uri)); - expect_warning = TRUE; + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*SOUP_URI_IS_VALID*"); uri_string = soup_uri_to_string (uri, FALSE); - if (expect_warning) { - debug_printf (1, " ERROR: soup_uri_to_string didn't fail on scheme+host URI?\n"); - errors++; - expect_warning = FALSE; - } else if (strcmp (uri_string, "http://localhost/") != 0) { - debug_printf (1, " ERROR: soup_uri_to_string with NULL path returned '%s' instead of 'http://localhost/'\n", - uri_string); - errors++; - } + g_test_assert_expected_messages (); + g_assert_cmpstr (uri_string, ==, "http://localhost/"); g_free (uri_string); - expect_warning = TRUE; + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*SOUP_URI_IS_VALID*"); uri2 = soup_uri_new_with_base (uri, "/path"); - if (expect_warning) { - debug_printf (1, " ERROR: soup_uri_new_with_base didn't warn on NULL+scheme URI?\n"); - errors++; - expect_warning = FALSE; - } else if (!uri2) { - debug_printf (1, " ERROR: soup_uri_new_with_base didn't fix path on NULL+scheme URI\n"); - errors++; - } + g_test_assert_expected_messages (); + g_assert_true (uri2 != NULL); if (uri2) { uri_string = soup_uri_to_string (uri2, FALSE); - if (!uri_string) { - debug_printf (1, " ERROR: soup_uri_to_string failed on uri2?\n"); - errors++; - } else if (strcmp (uri_string, "http://localhost/path") != 0) { - debug_printf (1, " ERROR: soup_uri_to_string returned '%s' instead of 'http://localhost/path'\n", - uri_string); - errors++; - } + g_assert_cmpstr (uri_string, ==, "http://localhost/path"); g_free (uri_string); soup_uri_free (uri2); } - expect_warning = TRUE; + g_test_expect_message ("libsoup", G_LOG_LEVEL_WARNING, + "*path != NULL*"); soup_uri_set_path (uri, NULL); - if (expect_warning) { - debug_printf (1, " ERROR: setting path to NULL doesn't warn\n"); - errors++; - expect_warning = FALSE; - } - if (!uri->path || *uri->path) { - debug_printf (1, " ERROR: setting path to NULL != \"\"\n"); - errors++; - soup_uri_set_path (uri, ""); - } + g_test_assert_expected_messages (); + g_assert_cmpstr (uri->path, ==, ""); uri_string = soup_uri_to_string (uri, FALSE); - if (!uri_string) { - debug_printf (1, " ERROR: soup_uri_to_string failed on complete URI?\n"); - errors++; - } else if (strcmp (uri_string, "http://localhost/") != 0) { - debug_printf (1, " ERROR: soup_uri_to_string with empty path returned '%s' instead of 'http://localhost/'\n", - uri_string); - errors++; - } + g_assert_cmpstr (uri_string, ==, "http://localhost/"); g_free (uri_string); - if (!SOUP_URI_IS_VALID (uri)) { - debug_printf (1, " ERROR: setting scheme+path on NULL URI doesn't make it valid?\n"); - errors++; - } - if (!SOUP_URI_VALID_FOR_HTTP (uri)) { - debug_printf (1, " ERROR: setting scheme+host+path on NULL URI doesn't make it valid for http?\n"); - errors++; - } + g_assert_true (SOUP_URI_IS_VALID (uri)); + g_assert_true (SOUP_URI_VALID_FOR_HTTP (uri)); soup_uri_free (uri); } @@ -517,29 +472,23 @@ do_normalization_tests (void) char *normalized; int i; - debug_printf (1, "\nsoup_uri_normalize\n"); + g_test_bug ("680018"); for (i = 0; i < num_normalization_tests; i++) { if (normalization_tests[i].unescape_extra) { - debug_printf (1, "<%s> unescaping <%s> => <%s>: ", + debug_printf (1, "<%s> unescaping <%s> => <%s>\n", normalization_tests[i].uri_string, normalization_tests[i].unescape_extra, normalization_tests[i].result); } else { - debug_printf (1, "<%s> => <%s>: ", + debug_printf (1, "<%s> => <%s>\n", normalization_tests[i].uri_string, normalization_tests[i].result); } normalized = soup_uri_normalize (normalization_tests[i].uri_string, normalization_tests[i].unescape_extra); - - if (!strcmp (normalized, normalization_tests[i].result)) - debug_printf (1, "OK\n"); - else { - debug_printf (1, "NO, got <%s>\n", normalized); - errors++; - } + g_assert_cmpstr (normalized, ==, normalization_tests[i].result); g_free (normalized); } } @@ -547,62 +496,18 @@ do_normalization_tests (void) int main (int argc, char **argv) { - SoupURI *base_uri, *uri1, *uri2; - char *uri_string; - int i; + int ret; test_init (argc, argv, NULL); - debug_printf (1, "Absolute URI parsing\n"); - for (i = 0; i < num_abs_tests; i++) { - if (!do_uri (NULL, NULL, abs_tests[i].uri_string, - abs_tests[i].result, &abs_tests[i].bits)) - errors++; - } - - debug_printf (1, "\nRelative URI parsing\n"); - base_uri = soup_uri_new (base); - if (!base_uri) { - g_printerr ("Could not parse %s!\n", base); - exit (1); - } - - uri_string = soup_uri_to_string (base_uri, FALSE); - if (strcmp (uri_string, base) != 0) { - g_printerr ("URI <%s> unparses to <%s>\n", - base, uri_string); - errors++; - } - g_free (uri_string); - - for (i = 0; i < num_rel_tests; i++) { - if (!do_uri (base_uri, base, rel_tests[i].uri_string, - rel_tests[i].result, &rel_tests[i].bits)) - errors++; - } - soup_uri_free (base_uri); - - debug_printf (1, "\nURI equality testing\n"); - for (i = 0; i < num_eq_tests; i++) { - uri1 = soup_uri_new (eq_tests[i].one); - uri2 = soup_uri_new (eq_tests[i].two); - debug_printf (1, "<%s> == <%s>? ", eq_tests[i].one, eq_tests[i].two); - if (soup_uri_equal (uri1, uri2)) - debug_printf (1, "OK\n"); - else { - debug_printf (1, "NO\n"); - debug_printf (1, "%s : %s : %s\n%s : %s : %s\n", - uri1->scheme, uri1->host, uri1->path, - uri2->scheme, uri2->host, uri2->path); - errors++; - } - soup_uri_free (uri1); - soup_uri_free (uri2); - } + g_test_add_func ("/uri/absolute", do_absolute_uri_tests); + g_test_add_func ("/uri/relative", do_relative_uri_tests); + g_test_add_func ("/uri/equality", do_equality_tests); + g_test_add_func ("/uri/null", do_soup_uri_null_tests); + g_test_add_func ("/uri/normalization", do_normalization_tests); - do_soup_uri_null_tests (); - do_normalization_tests (); + ret = g_test_run (); test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/xmlrpc-server-test.c b/tests/xmlrpc-server-test.c index 8ad2187b..bfeb200a 100644 --- a/tests/xmlrpc-server-test.c +++ b/tests/xmlrpc-server-test.c @@ -5,12 +5,12 @@ #include "test-utils.h" +static char *uri; + #ifdef G_GNUC_BEGIN_IGNORE_DEPRECATIONS G_GNUC_BEGIN_IGNORE_DEPRECATIONS #endif -GMainLoop *loop; - static void type_error (SoupMessage *msg, GType expected, GValueArray *params, int bad_value) { @@ -180,7 +180,17 @@ do_echo (SoupMessage *msg, GValueArray *params) soup_xmlrpc_set_response (msg, G_TYPE_VALUE_ARRAY, out); g_value_array_free (out); +} + +static void +do_ping (SoupMessage *msg, GValueArray *params) +{ + if (params->n_values) { + args_error (msg, params, 0); + return; + } + soup_xmlrpc_set_response (msg, G_TYPE_STRING, "pong"); } static void @@ -216,6 +226,8 @@ server_callback (SoupServer *server, SoupMessage *msg, do_dateChange (msg, params); else if (!strcmp (method_name, "echo")) do_echo (msg, params); + else if (!strcmp (method_name, "ping")) + do_ping (msg, params); else { soup_xmlrpc_set_fault (msg, SOUP_XMLRPC_FAULT_SERVER_ERROR_REQUESTED_METHOD_NOT_FOUND, "Unknown method %s", method_name); @@ -225,73 +237,64 @@ server_callback (SoupServer *server, SoupMessage *msg, g_value_array_free (params); } -static void -xmlrpc_test_exited (GPid pid, int status, gpointer data) -{ - errors = WIFEXITED (status) ? WEXITSTATUS (status) : 1; - g_main_loop_quit (loop); -} - static gboolean -xmlrpc_test_print (GIOChannel *io, GIOCondition cond, gpointer data) +run_xmlrpc_test (char **argv, + char **stdout_out, + char **stderr_out, + GError **error) { - char *line; - gsize len; - GIOStatus status; + gboolean ok; + int status; - if (!(cond & G_IO_IN)) - return FALSE; + argv[0] = g_test_build_filename (G_TEST_BUILT, "xmlrpc-test", NULL); + ok = g_spawn_sync (NULL, argv, NULL, 0, NULL, NULL, + stdout_out, stderr_out, &status, + error); + g_free (argv[0]); - status = g_io_channel_read_line (io, &line, &len, NULL, NULL); - if (status == G_IO_STATUS_NORMAL) { - /* Don't print the exit status, just the debug stuff */ - if (strncmp (line, "xmlrpc-test:", strlen ("xmlrpc-test:")) != 0) - g_print ("%s", line); - g_free (line); - return TRUE; - } else if (status == G_IO_STATUS_AGAIN) - return TRUE; - else + if (!ok) return FALSE; + + return g_spawn_check_exit_status (status, error); } static void -do_xmlrpc_tests (SoupURI *uri) +do_one_xmlrpc_test (gconstpointer data) { - char *argv[8]; - int arg, out; - gboolean ok; - GPid pid; + const char *path = data; + char *argv[12]; + char *stdout_out, *stderr_out; GError *error = NULL; - GIOChannel *child_out; + int arg; - argv[0] = "./xmlrpc-test"; - argv[1] = "-s"; - argv[2] = "-u"; - argv[3] = soup_uri_to_string (uri, FALSE); + argv[0] = NULL; + argv[1] = "-S"; + argv[2] = "-U"; + argv[3] = uri; + argv[4] = "-q"; + argv[5] = "-p"; + argv[6] = (char *) path; for (arg = 0; arg < debug_level && arg < 3; arg++) - argv[arg + 4] = "-d"; - argv[arg + 4] = NULL; - - ok = g_spawn_async_with_pipes (NULL, argv, NULL, - G_SPAWN_DO_NOT_REAP_CHILD, - NULL, NULL, &pid, - NULL, &out, NULL, - &error); - g_free (argv[3]); - - if (!ok) { - g_print ("Could not run xmlrpc-test: %s\n", error->message); - errors++; - return; + argv[arg + 7] = "-d"; + argv[arg + 7] = NULL; + + run_xmlrpc_test (argv, &stdout_out, &stderr_out, &error); + if (stdout_out) { + g_print ("%s", stdout_out); + g_free (stdout_out); + } + if (stderr_out) { + g_printerr ("%s", stderr_out); + g_free (stderr_out); } - g_child_watch_add (pid, xmlrpc_test_exited, NULL); - child_out = g_io_channel_unix_new (out); - g_io_add_watch (child_out, G_IO_IN | G_IO_ERR | G_IO_HUP, - xmlrpc_test_print, NULL); - g_io_channel_unref (child_out); + if ( g_error_matches (error, G_SPAWN_EXIT_ERROR, 1) + || g_error_matches (error, G_SPAWN_EXIT_ERROR, 77)) + g_test_fail (); + else + g_assert_no_error (error); + g_clear_error (&error); } gboolean run_tests = TRUE; @@ -307,29 +310,61 @@ int main (int argc, char **argv) { SoupServer *server; - SoupURI *uri; + int ret; test_init (argc, argv, no_test_entry); - server = soup_test_server_new (FALSE); + server = soup_test_server_new (run_tests); soup_server_add_handler (server, "/xmlrpc-server.php", server_callback, NULL, NULL); - - loop = g_main_loop_new (NULL, TRUE); + uri = g_strdup_printf ("http://127.0.0.1:%u/xmlrpc-server.php", + soup_server_get_port (server)); if (run_tests) { - uri = soup_uri_new ("http://127.0.0.1/xmlrpc-server.php"); - soup_uri_set_port (uri, soup_server_get_port (server)); - do_xmlrpc_tests (uri); - soup_uri_free (uri); - } else + char *out, **tests, *path; + char *list_argv[4]; + GError *error = NULL; + int i; + + list_argv[0] = NULL; + list_argv[1] = "-S"; + list_argv[2] = "-l"; + list_argv[3] = NULL; + + if (!run_xmlrpc_test (list_argv, &out, NULL, &error)) { + g_printerr ("'xmlrpc-test -l' failed: %s\n", error->message); + g_error_free (error); + return 1; + } + + tests = g_strsplit (out, "\n", -1); + g_free (out); + + for (i = 0; tests[i] && *tests[i]; i++) { + g_assert_true (g_str_has_prefix (tests[i], "/xmlrpc/")); + path = g_strdup_printf ("/xmlrpc-server/%s", tests[i] + strlen ("/xmlrpc/")); + g_test_add_data_func (path, tests[i], do_one_xmlrpc_test); + g_free (path); + } + + ret = g_test_run (); + + g_strfreev (tests); + } else { + GMainLoop *loop; + g_print ("Listening on port %d\n", soup_server_get_port (server)); - g_main_loop_run (loop); - g_main_loop_unref (loop); + loop = g_main_loop_new (NULL, TRUE); + g_main_loop_run (loop); + g_main_loop_unref (loop); + + ret = 0; + } soup_test_server_quit_unref (server); + g_free (uri); if (run_tests) test_cleanup (); - return errors != 0; + return ret; } diff --git a/tests/xmlrpc-server.php b/tests/xmlrpc-server.php index 2e3dd381..66cb2be7 100644 --- a/tests/xmlrpc-server.php +++ b/tests/xmlrpc-server.php @@ -2,13 +2,13 @@ function paramfault () { - # xmlrpc-epi-php translates this into a real <fault> + # xmlrpc-php translates this into a real <fault> $fault["faultCode"] = -32602; $fault["faultString"] = "bad parameter"; return $fault; } -# We only check the params in sum(), because that's the one that +# We only check the params in sum(), because that is the one that # xmlrpc-test tests will fail if given bad args function sum ($method_name, $params, $app_data) @@ -69,8 +69,16 @@ function echo_ ($method_name, $params, $app_data) return $params[0]; } -# Work around xmlrpc-epi-php lossage; otherwise the datetime values -# we return will sometimes get a DST adjustment we don't want. +function ping ($method_name, $params, $app_data) +{ + if (count ($params) == 0) + return "pong"; + else + return paramfault (); +} + +# Work around xmlrpc-php lossage; otherwise the datetime values +# we return will sometimes get a DST adjustment we do not want. putenv ("TZ="); $xmlrpc_server = xmlrpc_server_create (); @@ -79,6 +87,7 @@ xmlrpc_server_register_method($xmlrpc_server, "countBools", "countBools"); xmlrpc_server_register_method($xmlrpc_server, "md5sum", "md5sum"); xmlrpc_server_register_method($xmlrpc_server, "dateChange", "dateChange"); xmlrpc_server_register_method($xmlrpc_server, "echo", "echo_"); +xmlrpc_server_register_method($xmlrpc_server, "ping", "ping"); $response = xmlrpc_server_call_method ($xmlrpc_server, implode("\r\n", file('php://input')), diff --git a/tests/xmlrpc-test.c b/tests/xmlrpc-test.c index ec67e4d1..f6b20b8f 100644 --- a/tests/xmlrpc-test.c +++ b/tests/xmlrpc-test.c @@ -14,6 +14,18 @@ static const char *default_uri = "http://127.0.0.1:47524/xmlrpc-server.php"; static const char *uri = NULL; static gboolean server_test = FALSE; +#ifdef HAVE_PHP_XMLRPC +#define SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER +#else +#define SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER \ + G_STMT_START { \ + if (!server_test) { \ + g_test_skip ("php-xmlrpc is not available"); \ + return; \ + } \ + } G_STMT_END +#endif + static const char *const value_type[] = { "BAD", "int", @@ -27,44 +39,26 @@ static const char *const value_type[] = { }; static gboolean -do_xmlrpc (const char *method, GValue *retval, ...) +send_xmlrpc (const char *body, GValue *retval) { SoupMessage *msg; - va_list args; - GValueArray *params; GError *err = NULL; - char *body; - - va_start (args, retval); - params = soup_value_array_from_args (args); - va_end (args); - - body = soup_xmlrpc_build_method_call (method, params->values, - params->n_values); - g_value_array_free (params); - if (!body) - return FALSE; msg = soup_message_new ("POST", uri); - soup_message_set_request (msg, "text/xml", SOUP_MEMORY_TAKE, + soup_message_set_request (msg, "text/xml", SOUP_MEMORY_COPY, body, strlen (body)); soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, "ERROR: %d %s\n", msg->status_code, - msg->reason_phrase); - g_object_unref (msg); - return FALSE; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); if (!soup_xmlrpc_parse_method_response (msg->response_body->data, msg->response_body->length, retval, &err)) { if (err) { - debug_printf (1, "FAULT: %d %s\n", err->code, err->message); + soup_test_assert (FALSE, "FAULT: %d %s\n", err->code, err->message); g_error_free (err); } else - debug_printf (1, "ERROR: could not parse response\n"); + soup_test_assert (FALSE, "ERROR: could not parse response\n"); g_object_unref (msg); return FALSE; } @@ -74,13 +68,36 @@ do_xmlrpc (const char *method, GValue *retval, ...) } static gboolean +do_xmlrpc (const char *method, GValue *retval, ...) +{ + va_list args; + GValueArray *params; + char *body; + gboolean ret; + + va_start (args, retval); + params = soup_value_array_from_args (args); + va_end (args); + + body = soup_xmlrpc_build_method_call (method, params->values, + params->n_values); + g_value_array_free (params); + if (!body) + return FALSE; + + ret = send_xmlrpc (body, retval); + g_free (body); + + return ret; +} + +static gboolean check_xmlrpc (GValue *value, GType type, ...) { va_list args; if (!G_VALUE_HOLDS (value, type)) { - debug_printf (1, "ERROR: could not parse response\n"); - g_value_unset (value); + g_assert_true (G_VALUE_HOLDS (value, type)); return FALSE; } @@ -90,7 +107,7 @@ check_xmlrpc (GValue *value, GType type, ...) return TRUE; } -static gboolean +static void test_sum (void) { GValueArray *ints; @@ -98,7 +115,9 @@ test_sum (void) GValue retval; gboolean ok; - debug_printf (1, "sum (array of int -> int): "); + SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER; + + debug_printf (2, "sum (array of int -> int): "); ints = g_value_array_new (10); for (i = sum = 0; i < 10; i++) { @@ -116,14 +135,13 @@ test_sum (void) g_value_array_free (ints); if (!ok) - return FALSE; + return; - debug_printf (2, "%d: ", result); - debug_printf (1, "%s\n", result == sum ? "OK!" : "WRONG!"); - return result == sum; + debug_printf (2, "%d\n", result); + g_assert_cmpint (result, ==, sum); } -static gboolean +static void test_countBools (void) { GValueArray *bools; @@ -133,7 +151,9 @@ test_countBools (void) gboolean val, ok; GHashTable *result; - debug_printf (1, "countBools (array of boolean -> struct of ints): "); + SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER; + + debug_printf (2, "countBools (array of boolean -> struct of ints): "); bools = g_value_array_new (10); for (i = trues = falses = 0; i < 10; i++) { @@ -153,25 +173,19 @@ test_countBools (void) check_xmlrpc (&retval, G_TYPE_HASH_TABLE, &result)); g_value_array_free (bools); if (!ok) - return FALSE; + return; + + g_assert_true (soup_value_hash_lookup (result, "true", G_TYPE_INT, &ret_trues)); + g_assert_true (soup_value_hash_lookup (result, "false", G_TYPE_INT, &ret_falses)); - if (!soup_value_hash_lookup (result, "true", G_TYPE_INT, &ret_trues)) { - debug_printf (1, "NO 'true' value in response\n"); - return FALSE; - } - if (!soup_value_hash_lookup (result, "false", G_TYPE_INT, &ret_falses)) { - debug_printf (1, "NO 'false' value in response\n"); - return FALSE; - } g_hash_table_destroy (result); - debug_printf (2, "{ true: %d, false: %d } ", ret_trues, ret_falses); - ok = (trues == ret_trues) && (falses == ret_falses); - debug_printf (1, "%s\n", ok ? "OK!" : "WRONG!"); - return ok; + debug_printf (2, "{ true: %d, false: %d }\n", ret_trues, ret_falses); + g_assert_cmpint (trues, ==, ret_trues); + g_assert_cmpint (falses, ==, ret_falses); } -static gboolean +static void test_md5sum (void) { GByteArray *data, *result; @@ -182,7 +196,9 @@ test_md5sum (void) GValue retval; gboolean ok; - debug_printf (1, "md5sum (base64 -> base64): "); + SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER; + + debug_printf (2, "md5sum (base64 -> base64)\n"); data = g_byte_array_new (); g_byte_array_set_size (data, 256); @@ -200,21 +216,14 @@ test_md5sum (void) check_xmlrpc (&retval, SOUP_TYPE_BYTE_ARRAY, &result)); g_byte_array_free (data, TRUE); if (!ok) - return FALSE; + return; - if (result->len != digest_len) { - debug_printf (1, "result has WRONG length (%d)\n", result->len); - g_byte_array_free (result, TRUE); - return FALSE; - } - - ok = (memcmp (digest, result->data, digest_len) == 0); - debug_printf (1, "%s\n", ok ? "OK!" : "WRONG!"); + soup_assert_cmpmem (result->data, result->len, + digest, digest_len); g_byte_array_free (result, TRUE); - return ok; } -static gboolean +static void test_dateChange (void) { GHashTable *structval; @@ -223,7 +232,9 @@ test_dateChange (void) GValue retval; gboolean ok; - debug_printf (1, "dateChange (date, struct of ints -> time): "); + SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER; + + debug_printf (2, "dateChange (date, struct of ints -> time)\n"); date = soup_date_new (1970 + (g_random_int_range (0, 50)), 1 + g_random_int_range (0, 12), @@ -288,26 +299,24 @@ test_dateChange (void) g_hash_table_destroy (structval); if (!ok) { soup_date_free (date); - return FALSE; + return; } if (debug_level >= 2) { timestamp = soup_date_to_string (result, SOUP_DATE_ISO8601_XMLRPC); - debug_printf (2, "%s: ", timestamp); + debug_printf (2, "%s\n", timestamp); g_free (timestamp); } - ok = ((date->year == result->year) && - (date->month == result->month) && - (date->day == result->day) && - (date->hour == result->hour) && - (date->minute == result->minute) && - (date->second == result->second)); + g_assert_cmpint (date->year, ==, result->year); + g_assert_cmpint (date->month, ==, result->month); + g_assert_cmpint (date->day, ==, result->day); + g_assert_cmpint (date->hour, ==, result->hour); + g_assert_cmpint (date->minute, ==, result->minute); + g_assert_cmpint (date->second, ==, result->second); + soup_date_free (date); soup_date_free (result); - - debug_printf (1, "%s\n", ok ? "OK!" : "WRONG!"); - return ok; } static const char *const echo_strings[] = { @@ -325,15 +334,16 @@ static const char *const echo_strings_broken[] = { "amp; so is lt;thisgt;" }; -static gboolean +static void test_echo (void) { GValueArray *originals, *echoes; GValue retval; int i; - gboolean php_bug = FALSE; - debug_printf (1, "echo (array of string -> array of string): "); + SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER; + + debug_printf (2, "echo (array of string -> array of string):\n"); originals = g_value_array_new (N_ECHO_STRINGS); for (i = 0; i < N_ECHO_STRINGS; i++) { @@ -347,7 +357,7 @@ test_echo (void) G_TYPE_INVALID) && check_xmlrpc (&retval, G_TYPE_VALUE_ARRAY, &echoes))) { g_value_array_free (originals); - return FALSE; + return; } g_value_array_free (originals); @@ -356,36 +366,71 @@ test_echo (void) debug_printf (2, "%s\"%s\"", i == 0 ? "[" : ", ", g_value_get_string (&echoes->values[i])); } - debug_printf (2, "] -> "); + debug_printf (2, "]\n"); } - if (echoes->n_values != N_ECHO_STRINGS) { - debug_printf (1, " WRONG! Wrong number of return strings"); - g_value_array_free (echoes); - return FALSE; - } + g_assert_cmpint (echoes->n_values, ==, N_ECHO_STRINGS); for (i = 0; i < echoes->n_values; i++) { - if (strcmp (echo_strings[i], g_value_get_string (&echoes->values[i])) != 0) { - if (!server_test && strcmp (echo_strings_broken[i], g_value_get_string (&echoes->values[i])) == 0) - php_bug = TRUE; - else { - debug_printf (1, " WRONG! Mismatch at %d\n", i + 1); - g_value_array_free (echoes); - return FALSE; - } + if (!server_test && strcmp (echo_strings_broken[i], g_value_get_string (&echoes->values[i])) == 0) { + g_test_skip ("PHP bug"); + g_value_array_free (echoes); + return; } + + g_assert_cmpstr (echo_strings[i], ==, g_value_get_string (&echoes->values[i])); } - if (php_bug) - debug_printf (1, "WRONG, but it's php's fault\n"); - else - debug_printf (1, "OK!\n"); g_value_array_free (echoes); - return TRUE; } -static gboolean +static void +test_ping (gconstpointer include_params) +{ + GValueArray *params; + GValue retval; + char *request; + char *out; + gboolean ret; + + g_test_bug ("671661"); + + SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER; + + debug_printf (2, "ping (void (%s) -> string)\n", + include_params ? "empty <params>" : "no <params>"); + + params = soup_value_array_new (); + request = soup_xmlrpc_build_method_call ("ping", params->values, + params->n_values); + g_value_array_free (params); + if (!request) + return; + + if (!include_params) { + char *params, *end; + + params = strstr (request, "<params/>"); + if (!params) { + soup_test_assert (FALSE, "ERROR: XML did not contain <params/>!"); + return; + } + end = params + strlen ("<params/>"); + memmove (params, end, strlen (end) + 1); + } + + ret = send_xmlrpc (request, &retval); + g_free (request); + + if (!ret || !check_xmlrpc (&retval, G_TYPE_STRING, &out)) + return; + + g_assert_cmpstr (out, ==, "pong"); + + g_free (out); +} + +static void do_bad_xmlrpc (const char *body) { SoupMessage *msg; @@ -397,12 +442,7 @@ do_bad_xmlrpc (const char *body) body, strlen (body)); soup_session_send_message (session, msg); - if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) { - debug_printf (1, "ERROR: %d %s\n", msg->status_code, - msg->reason_phrase); - g_object_unref (msg); - return FALSE; - } + soup_test_assert_message_status (msg, SOUP_STATUS_OK); if (!soup_xmlrpc_parse_method_response (msg->response_body->data, msg->response_body->length, @@ -412,44 +452,43 @@ do_bad_xmlrpc (const char *body) err->code, err->message); g_error_free (err); g_object_unref (msg); - return TRUE; + return; } else - debug_printf (1, "ERROR: could not parse response\n"); + soup_test_assert (FALSE, "ERROR: could not parse response\n"); } else - debug_printf (1, "Unexpectedly got successful response!\n"); + soup_test_assert (FALSE, "Unexpectedly got successful response!\n"); g_object_unref (msg); - return FALSE; } -static gboolean +static void test_fault_malformed (void) { - debug_printf (1, "malformed request: "); + SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER; - return do_bad_xmlrpc ("<methodCall/>"); + do_bad_xmlrpc ("<methodCall/>"); } -static gboolean +static void test_fault_method (void) { - debug_printf (1, "request to non-existent method: "); + SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER; - return do_bad_xmlrpc ("<methodCall><methodName>no_such_method</methodName><params><param><value><int>1</int></value></param></params></methodCall>"); + do_bad_xmlrpc ("<methodCall><methodName>no_such_method</methodName><params><param><value><int>1</int></value></param></params></methodCall>"); } -static gboolean +static void test_fault_args (void) { - debug_printf (1, "request with invalid args: "); + SOUP_TEST_SKIP_IF_NO_XMLRPC_SERVER; - return do_bad_xmlrpc ("<methodCall><methodName>sum</methodName><params><param><value><int>1</int></value></param></params></methodCall>"); + do_bad_xmlrpc ("<methodCall><methodName>sum</methodName><params><param><value><int>1</int></value></param></params></methodCall>"); } static GOptionEntry xmlrpc_entries[] = { - { "uri", 'u', 0, G_OPTION_ARG_STRING, &uri, + { "uri", 'U', 0, G_OPTION_ARG_STRING, &uri, "Alternate URI for server", NULL }, - { "server-test", 's', 0, G_OPTION_ARG_NONE, &server_test, + { "server-test", 'S', 0, G_OPTION_ARG_NONE, &server_test, "If this is being run from xmlrpc-server-test", NULL }, { NULL } }; @@ -457,34 +496,32 @@ static GOptionEntry xmlrpc_entries[] = { int main (int argc, char **argv) { + int ret; + test_init (argc, argv, xmlrpc_entries); - if (!uri) { + if (!uri && !server_test) { apache_init (); uri = default_uri; } session = soup_test_session_new (SOUP_TYPE_SESSION_SYNC, NULL); - if (!test_sum ()) - errors++; - if (!test_countBools ()) - errors++; - if (!test_md5sum ()) - errors++; - if (!test_dateChange ()) - errors++; - if (!test_echo ()) - errors++; - if (!test_fault_malformed ()) - errors++; - if (!test_fault_method ()) - errors++; - if (!test_fault_args ()) - errors++; + g_test_add_func ("/xmlrpc/sum", test_sum); + g_test_add_func ("/xmlrpc/countBools", test_countBools); + g_test_add_func ("/xmlrpc/md5sum", test_md5sum); + g_test_add_func ("/xmlrpc/dateChange", test_dateChange); + g_test_add_func ("/xmlrpc/echo", test_echo); + g_test_add_data_func ("/xmlrpc/ping/empty-params", GINT_TO_POINTER (TRUE), test_ping); + g_test_add_data_func ("/xmlrpc/ping/no-params", GINT_TO_POINTER (FALSE), test_ping); + g_test_add_func ("/xmlrpc/fault/malformed", test_fault_malformed); + g_test_add_func ("/xmlrpc/fault/method", test_fault_method); + g_test_add_func ("/xmlrpc/fault/args", test_fault_args); + + ret = g_test_run (); soup_test_session_abort_unref (session); test_cleanup (); - return errors != 0; + return ret; } |