From 86cee35e3eee3e4b07be997cbc4dead5ed2acac1 Mon Sep 17 00:00:00 2001 From: Alex Graveley Date: Tue, 12 Mar 2002 00:39:21 +0000 Subject: [PATCH] Bump development version to 0.7.99. 2002-03-11 Alex Graveley * configure.in, src/libsoup/soup-private.h: Bump development version to 0.7.99. * Merge all changes from soup-0-6 branch. --- ChangeLog | 556 ++++++++++++++++++- TODO | 6 +- configure.in | 128 +++-- libsoup/Makefile.am | 6 +- libsoup/soup-auth.c | 201 +------ libsoup/soup-context.c | 216 +++++--- libsoup/soup-error.h | 1 + libsoup/soup-headers.c | 204 ++++++- libsoup/soup-headers.h | 42 +- libsoup/soup-message.c | 240 +++++++- libsoup/soup-message.h | 28 +- libsoup/soup-misc.c | 310 +++++++++-- libsoup/soup-misc.h | 28 + libsoup/soup-ntlm.c | 415 +++----------- libsoup/soup-ntlm.h | 3 +- libsoup/soup-openssl.c | 60 +- libsoup/soup-private.h | 57 +- libsoup/soup-queue.c | 463 +++++++++++----- libsoup/soup-queue.h | 18 +- libsoup/soup-server.c | 1305 +++++++++++++++++++++++++++++++++++--------- libsoup/soup-server.h | 138 +++-- libsoup/soup-socket-unix.c | 115 +++- libsoup/soup-socket.c | 141 +++-- libsoup/soup-socket.h | 12 +- libsoup/soup-ssl-proxy.c | 18 +- libsoup/soup-ssl.c | 6 +- libsoup/soup-transfer.c | 406 ++++++++++---- libsoup/soup-transfer.h | 31 +- libsoup/soup-uri.c | 220 +++++++- libsoup/soup-uri.h | 4 +- 30 files changed, 3877 insertions(+), 1501 deletions(-) diff --git a/ChangeLog b/ChangeLog index 577814c..d8b6ed9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,554 @@ +2002-03-11 Alex Graveley + + * configure.in, src/libsoup/soup-private.h: Bump development + version to 0.7.99. + + * Merge all changes from soup-0-6 branch. + +2002-03-07 Alex Graveley + + * configure.in: SOUP_AGE goes to 1, SOUP_CURRENT goes to 4. + Update version to 0.6.99. + + * src/libsoup/soup-private.h (VERSION): Update win version to + Win/0.6.99. + +2002-03-07 Alex Graveley + + * src/libsoup/soup-context.c (soup_context_connect_cb): Remove + debugging g_print. + +2002-03-07 Alex Graveley + + * src/libsoup/soup-socket.h: Add soup_address_lookup_in_cache. + + * src/libsoup/soup-socket.c (soup_socket_connect): Handle host + address already being resolved by checking in address cache. + Allows us to return a non-null state pointer if soup_address_new + calls its callback inline, but soup_socket_new does not. + + * src/libsoup/soup-socket-unix.c (lookup_in_cache_internal): Impl, + checks the address cache for an existing resolved address or + in progress lookup. + (soup_address_lookup_in_cache): Impl, return existing resolved + address if it exists. + (soup_address_new): use lookup_in_cache_internal. + + * src/libsoup/soup-socket-win.c (soup_address_get_cached): Impl, + return NULL as windows has no address hash. + +2002-02-26 JP Rosevear + + * src/libsoup/soup-socket-unix.c (soup_address_new): inaddr is a + different type for inet_addr, calculate sizeof directly + +2002-02-25 JP Rosevear + + * src/libsoup/soup-socket-unix.c: apparently some platforms don't + define INADDR_NONE either + +2002-02-25 JP Rosevear + + * src/soup-httpd/Makefile.am: add popt cflags to includes + + * src/libsoup/soup-socket.c: apparently INET_ADDRSTRLEN is not + defined on some unix platforms + + * src/libsoup/soup-socket-unix.c (soup_address_new): handle not + have inet_pton and inet_aton with inet_addr + + * configure.in: check for inet_aton as well and allow config + options to specify popt location + +2002-02-23 Alex Graveley + + * src/libsoup/soup-queue.c (proxy_connect): Pull proxy connection + code out of soup_queue_connect_cb to here. + (start_request): Delay calling soup_message_issue_callback here. + (soup_queue_connect_cb): Ditto. + (soup_idle_handle_new_requests): Call soup_message_issue_callback + if req->errorcode is set. This avoids an FMW when setting the + connect tag on a request which has a connect error and has been + freed. + + * src/libsoup/soup-message.c (requeue_connect_cb): Call + soup_message_issue_callback if req->errorcode is set. + + * src/libsoup/soup-context.c (soup_context_connect_cb): Add + timeout handler to retry connect if new connection failed and + there are existing connections to this host. + (soup_context_get_connection): Drop timeout interval to 150 from 500. + + * src/libsoup/soup-misc.c: Set default connection limit to 10. + +2002-02-17 Alex Graveley + + * src/libsoup/soup-socket.c (soup_socket_connect_inetaddr_cb): + Check result from soup_socket_new. + + * src/libsoup/soup-queue.c (soup_idle_handle_new_requests): Check + result from soup_context_get_connection. + + * src/libsoup/soup-context.c (soup_context_connect_cb): Don't + access 'data' after free. + (soup_context_get_connection): Check return from + soup_socket_connect, as a null result means our callback has been + issued and our state freed. + +2002-02-15 Dan Winship + + * src/libsoup/soup-queue.c (soup_queue_shutdown): Only + g_source_remove the idle handler if it's been set, to avoid a glib + warning. + +2002-02-14 Alex Graveley + + * src/libsoup/soup-message.c (soup_message_send): Check if + soup_initialized is FALSE signifying that soup_shutdown has been + called while processing. + + * src/libsoup/soup-queue.c (soup_queue_shutdown): Set + soup_initialized to FALSE so any currently running synch sends + will quit. + +2002-02-13 Ettore Perazzoli + + * src/libsoup/soup-ntlm.c (soup_ntlm_response): Set `decodelen' to + be 5 bytes less than the challenge's length to avoid overflowing + the input buffer. + +2002-02-13 Dan Winship + + * src/libsoup/soup-queue.c (soup_queue_add_request, + soup_queue_remove_request, soup_queue_first_request, + soup_queue_next_request): Routines to all simulataneous iteration + and modification of the active request list. Fixes Ximian 14030. + (soup_idle_handle_new_requests): Use the above functions. + (soup_queue_message): Likewise. + (soup_queue_shutdown): Likewise. + + * src/libsoup/soup-message.c (soup_message_cleanup): Use + soup_queue_remove_request. + +2002-02-10 Alex Graveley + + * src/libsoup/soup-transfer.c (soup_transfer_read_cb): Temporary + fix for 100% cpu usage as HUPs are not being caught in some cases. + +2002-02-08 Dan Winship + + * src/soup-ssl-proxy/soup-openssl.c (soup_openssl_write): Handle + SSL_ERROR_WANT_READ here too. Fixes at least Ximian 19792 and + maybe others. + 2002-01-17 Joe Shaw - * src/libsoup/soup-message.c: Add a - soup_message_get_http_version() function. + * src/libsoup/soup-message.c: Add soup_message_get_http_version() + function. + +2002-01-08 Alex Graveley + + * src/libsoup/soup-error.h: Add SOUP_ERROR_MOVED_TEMPORARILY + (which was in early HTTP1.1 and HTTP1.0 specs) defined to + SOUP_ERROR_FOUND (its replacement). + + * configure.in: Use /usr/lib as the default location for nss and + nspr libs. Also, NSS static libs are named differently from their + dynamic version. + +2002-01-08 Dan Winship + + * src/libsoup/soup-transfer.c (issue_final_callback): Remove the + gio sources from the reader before invoking the read_done + callback, since the connection will be marked free before the + user's callback is called, so if that callback posts another + request synchronously, the callback will end up getting called + back again when the new request finishes, which is wrong. + +2002-01-08 Alex Graveley + + * TODO: Update + + * acconfig.h: undef SSL_PROXY_NAME + + * src/libsoup/soup-ssl.c (soup_ssl_get_iochannel): Execute + customly chosen SSL proxy name. + + * src/soup-ssl-proxy/Makefile.am (install-exec-hook): Add + install-time hook for customly named SSL proxy. + + * configure.in: Don't let -lpopt sneak into $LIBS. Add option to + allow custom name for the openssl proxy. Add option to allow + static linking of the SSL library. + +2002-01-02 Rodrigo Moya + + * src/libsoup-apache/soup-apache.c (soup_apache_handler): updated + for the new field names in SoupServerAuthBasic + +2001-12-21 Alex Graveley + + * src/libsoup/Makefile.am (libsoup_la_SOURCES): Add + soup-server-auth.c. + + * src/libsoup/soup-server-auth.c: Implement server-side + basic/digest authentication. + (soup_server_auth_get_user): Impl. Allows server auth/path handler + to get the username. + (soup_server_auth_check_passwd): Impl. Allows server auth/path + handler to check a password against the one used in the request. + + * src/libsoup/soup-server.c (call_handler): Create SoupServerAuth + and call auth context's callback before calling path handler. + (message_new): Don't create temporary context, just pass NULL. + (soup_server_register): Copy auth context correctly. + (soup_server_message_new): If a server message already exists, + return that. + (soup_server_message_finish): Start server message as well as + finish, in case message_start is never called. + + * src/libsoup/soup-private.h: Include sys/types.h so sockaddr + works for FreeBSD. + + * src/libsoup/soup-misc.c: Move base64 encoding/decoding here from + soup-ntlm.c. Remove copyright notice as soup_base64_encode is now + taken from evolution. + (soup_base64_encode): Use soup_base64_encode_close. + (soup_base64_decode): Impl. Use soup_base64_decode_step. + + * src/libsoup/soup-misc.h: Make base64 code public. + + * src/libsoup/soup-ntlm.c: Use base64 code from soup-misc.c. + + * src/libsoup/soup-headers.c: Move header parameter parsing to + here from soup-auth.c. + + * src/libsoup/soup-auth.c: Use header parsing from soup-headers.c. + (digest_init_func): Handle empty digest + realm or user passwd. + (decode_data_type): Return 0 on empty value. + + * src/libsoup/soup-uri.c (soup_uri_equal): Impl. + +2001-12-19 Alex Graveley + + * src/soup-ssl-proxy/soup-openssl.c (soup_openssl_seed): #define + RAND_status to constants for older versions of OpenSSL (before + 0.9.5). + + * src/soup-ssl-proxy/soup-ssl-proxy.c (main): Check that ssh + channel is created before adding watches. + +2001-12-17 Alex Graveley + + * src/soup-ssl-proxy/soup-openssl.c (soup_openssl_seed): Use + RAND_status less often. + +2001-12-17 Alex Graveley + + * src/soup-ssl-proxy/soup-openssl.c (soup_openssl_init): Implement + a simple PRNG seed if needed. + +2001-12-15 Alex Graveley + + * src/libsoup/soup-server.c (message_new): Don't use temporary + context to create message, just pass a NULL one. + +2001-12-14 Alex Graveley + + * tests/server-test.c (push_callback): Add test for server push, + which creates a SoupServerMessage and sends the headers, body, and + ends the transfer at 4 second intervals. + (soup_server_init): Update for new server api. + + * tests/cgi-test.c (soup_server_init): Update for new server api. + + * src/libsoup/soup-transfer.c (soup_transfer_write_simple): + Impl. Just write the headers and data buffer passed. + (soup_transfer_write): Use callback to get header data, remove + header done callback. + (soup_transfer_write_pause): Pause the write by removing the + writable io watch. + (soup_transfer_write_unpause): Unpause by adding it back again. + (write_chunk_sep): Handle (rare) case where 0 length chunk is + first chunk. Final chunks end in \r\n\r\n not just \r\n. + + * src/libsoup/soup-server.c (get_header_cgi_cb): Create header for + delayed writes if soup_server_message_start() has been called, + otherwise pause write. + (get_header_cb): Ditto, use chunked encoding if client supports + it. + (get_chunk_cb): Free the current chunk, not the last one. Pause + write if no new data. + (soup_server_message_start): Impl. Asyncronously send the headers + of the request. Unpause write. + (soup_server_message_add_data): Unpause write. + (soup_server_message_finish): Unpause write. + + * src/libsoup/soup-queue.c (start_request): Use + soup_transfer_write_simple. + + * src/libsoup/soup-socket.c: Comment formatting. + +2001-12-13 Alex Graveley + + * src/libsoup/soup-queue.c (start_request): Use + soup_transfer_write_simple. + +2001-12-13 Alex Graveley + + * src/libsoup-apache/soup-apache.c (soup_apache_get_server): + Impl. Returns the SoupServer which handles this request's port and + protocol, creating and initializing the server if this is its + first use. + (soup_apache_handler): Check auth_type not auth.type. Update for + new SoupServerContext members. + +2001-12-09 Alex Graveley + + * src/libsoup/soup-uri.c (normalize_path): Add path normalization + code from libxml. + +2001-12-06 Alex Graveley + + * src/libsoup/soup-socket.c (soup_socket_connect_tcp_cb): Free + state before callback. + (soup_socket_connect_inetaddr_cb): Don't shadow param. + +2001-12-06 Alex Graveley + + * src/libsoup/soup-method.[ch]: Create. + + * src/libsoup/soup-dav-server.c: Rehash to be controlled by + caller through soup_dav_server_process, instead of registering + paths with the server internally. This adds the restricition of + only being able to service dav move/copy requests to paths under + the caller's registered path. + + * src/libsoup/soup-server.c (call_handler): Add message, request + path, and method id to SoupServerContext. + + * src/libsoup/soup-message.h: Remove method defines. + +2001-12-05 Alex Graveley + + * src/libsoup/soup-server.c (soup_server_run): Allow recursive + calls and/or blocking on a running async server. + +2001-12-05 Alex Graveley + + * src/libsoup/soup-fault.c: Formatting nitpicks. + + * src/libsoup/soup-server.c (free_handler): Call unregister handler. + + * src/libsoup/soup-server.h: Add unregister callback to + SoupServerHandler so handlers can free temporary resources on + server shutdown. + + * src/libsoup/soup-transfer.c (soup_transfer_write_cb): Call + headers_done_cb if wrote >= header_len. + + * src/libsoup/soup-dav-server.c (soup_dav_server_register): Pass + dav_unregister_handler to soup_server_register, to cleanup state + on server free. + + * src/libsoup/soup-env.c: Rehash. + + * src/libsoup/soup-message.c (soup_message_new): Allow NULL + contexts. + (soup_message_copy): Impl. Copy request/response buffers. + + * src/libsoup/soup-queue.c (soup_queue_message): Handle NULL + message contexts by issuing a cancelled callback. Break out queue + initialization to soup_queue_initialize(). + +2001-12-04 Dan Winship + + * src/libsoup/soup-message.c (soup_message_requeue): Don't try to + wait for the rest of the data if the read_tag is 0 (eg, if the + write attempt got an EPIPE). Fixes a crash if the server + idle-timeouts a keepalived connection. + +2001-12-02 Alex Graveley + + * src/libsoup/soup-private.h: Remove req_header from + SoupMessagePrivate. + + * src/libsoup/soup-message.c (finalize_message): No more + req_header. + + * src/libsoup/soup-queue.c (start_request): Ditto. + + * src/libsoup/soup-server.c (read_done_cgi_cb): Ditto. + (read_done_cb): Ditto. + +2001-12-01 Alex Graveley + + * src/libsoup/soup-server-auth.h: Pull the SoupServerAuth from + soup-server.h to here. + + * tests/cgi-test.c: + * tests/mod-server-test.c: + * tests/server-test.c: Update for new server api. Add a handler + that sets the errorphrase, add some response headers, and prints + the request body, uri, and headers in the response. + + * src/soup-httpd/soup-httpd.c: Update for new server api, remove + module reloading, which trivializes the server code. + + * src/libsoup-apache/soup-apache.c: Update for new server api. + + * src/libsoup/soup-uri.c (soup_uri_copy): Copy element by element, + don't stringify/unstringify. + + * src/libsoup/soup-uri.h: Remove query_elems from SoupUri. + + * src/libsoup/soup-transfer.c (soup_transfer_write): Take an + encoding argument to allow chunked writes, and a write_chunk_cb to + allow new data to be written. Use a secondary buffer to make + writes (and especially chunked writes) larger, and also simplify + the code. + (write_chunk_sep): Impl. + (write_chunk): Impl. + + * src/libsoup/soup-socket.c (soup_socket_connect_inetaddr_cb): + Free state before issuing failure callback. Add FIXME, as this + shouldn't be needed but avoids a segfault. + + * src/libsoup/soup-socket-unix.c: Formatting. + + * src/libsoup/soup-server.c: Reimplement to support server push + through SoupServerMessage, CGI support, and persistent + connections. Modules are now passed a SoupServer during init, + instead of having well-known global server objects -- these have + been removed. + + * src/libsoup/soup-queue.c (start_request): Update for new + transfer_write interface, passing content length as the encoding + and a NULL write_chunk_cb. + + * src/libsoup/soup-private.h: Add server, server_sock, and + server_msg to _SoupMessagePrivate. Gut _SoupServer. + + * src/libsoup/soup-message.c (soup_message_requeue): Cleanup + boolean eval. + + * src/libsoup/soup-headers.c (soup_headers_parse_response): Wrap + at 80. + + * src/libsoup/soup-dav-server.[ch]: Update for new server handler + interface. + + * configure.in: Check for sys/ioctl.h and sys/filio.h + +2001-11-30 Dan Winship + + * src/libsoup/soup-headers.c (soup_headers_parse_status_line): + Split this functionality out of soup_headers_parse_response. + + * src/libsoup/Makefile.am (libsoupinclude_HEADERS): Add + soup-message.h to the public headers. + +2001-11-21 Alex Graveley + + * src/libsoup/soup-socket.c (soup_address_set_sockaddr): Remove. + (soup_address_get_sockaddr): Impl. Return the address's internal + sockaddr, and its length. + +2001-11-20 Dan Winship + + * src/libsoup/soup-ntlm.c (soup_ntlm_request): Simplify. Don't + specify a host and domain, and tweak the flags to a new magic + value that makes the server return some info such as a default + domain to pick. + (soup_ntlm_response): Allow "host" and "domain" to be NULL. If + domain is NULL, use the default from the server response. + + * src/libsoup/soup-auth.c (ntlm_init): soup_ntlm_request no longer + takes host/domain. soup_ntlm_response can take NULL host/domain. + +2001-11-19 Alex Graveley + + * src/libsoup/soup-private.h: Add auth member to SoupConnection. + + * src/libsoup/soup-queue.h: Export soup_queue_connect_cb. + + * src/libsoup/soup-queue.c (soup_encode_http_auth): Get auth token + from connection's auth. + + * src/libsoup/soup-message.c (soup_message_run_handlers): Break if + status after running a handler is also SOUP_STATUS_CONNECTING. + (soup_message_requeue): If the connection uses NTLM, reset + error/finished callbacks. + (requeue_read_finished): Impl. Requeue the message using the + current connection on success. + (requeue_read_error): Impl. Queue the message again and get a new + connection. + (requeue_connect_cb): Impl. Use the old connection's auth data + before passing off to soup_queue_connect_cb. + (soup_message_cleanup): Only reset callbacks if the reader and + connection exist. + (authorize_handler): Set/get the connection's auth data if it + exists and the new auth created uses NTLM. + + * src/libsoup/soup-transfer.c (soup_transfer_read_cb): Dump read + data to stderr if DUMP is defined. + (soup_transfer_write_cb): ditto for writes. + + * src/libsoup/soup-context.c (soup_context_connect_cb): Don't ref + context here, but rather + (soup_context_get_connection): Here, in case the context goes away + while connecting. + +2001-11-14 Alex Graveley + + * src/libsoup/soup-socket-win.c (soup_address_get_name_cb): Don't + leak state on failure, pass address's name, not a copy. + (soup_address_new_cb): Don't access things after we realloc to a + smaller size. + +2001-11-13 Alex Graveley + + * src/libsoup/soup-queue.c (soup_queue_error_cb): Use + soup_message_requeue. + (soup_queue_read_headers_cb): Don't end the transfer if a handler + requeues the message. Close the connection on malformed headers. + (soup_queue_read_chunk_cb): Ditto for the former. + + * src/libsoup/soup-message.c (soup_message_requeue): Impl. Requeue + the message using existing callback/user_data. + (authorize_handler): Use soup_message_requeue. + (redirect_handler): Ditto. + + * src/libsoup/soup-transfer.c (soup_transfer_read_set_callbacks): + Impl. Allows changing of the reader's callbacks to ignore certain + events (like chunk data) and change the user_data. + + * src/libsoup/soup-message.c (soup_message_cleanup): If still + reading response, reset the SoupReader's callbacks to only release + (on finish) or release and close (on error) the connection, + instead of terminating the read which could leave data in the + socket to to hork future messages which use this connection. Also + NULL the message's references to the connection and SoupReader so + that they are not disposed of on message free. + +2001-11-12 Alex Graveley + + * src/libsoup/soup-queue.c (start_request): Impl. Move request + sending code here from soup_queue_connect_cb. + (proxy_https_connect): Impl. Send intermediate CONNECT request to + proxy for SSL connections. If successful, convert connection to + SSL. + (soup_queue_connect_cb): Call proxy_https_connect if we are using + a proxy and the destination is SSL. Call start_request. + (soup_idle_handle_new_requests): If request already has a + connection, just call start_request. + (soup_get_request_header): Special case URI is hostname:port for + CONNECT requests. + (soup_queue_read_headers_cb): Special case connection persistence + for CONNECT requests: if message is successful, assume valid + connection. Special case body handling for CONNECT requests: + assume zero length body. 2001-11-08 Alex Graveley @@ -26,11 +573,6 @@ 2001-11-08 Alex Graveley - * configure.in: Bump HEAD version to 0.6.99. - * src/libsoup/soup-private.h (VERSION): Ditto. - -2001-11-08 Alex Graveley - * configure.in: Version 0.6.0. * src/libsoup/soup-private.h (VERSION): Ditto. diff --git a/TODO b/TODO index 388afa0..6d6e9c9 100644 --- a/TODO +++ b/TODO @@ -11,13 +11,11 @@ TODO: * Simple Soap Example clients and servers (time, updates, terraserver, ...) -* More SOCKS testing +* Auto-detect SOCKS version * Use gconf for config data -* Handle gzip encoded responses, requests - -* Server Digest and NTLM authentication +* Handle gzip transfer encoding * UDDI support diff --git a/configure.in b/configure.in index dbcc441..74aa944 100644 --- a/configure.in +++ b/configure.in @@ -5,10 +5,10 @@ dnl ******************************************* AC_INIT(src/libsoup/soup.h) # Increment on interface addition. Reset on removal. -SOUP_AGE=0 +SOUP_AGE=1 # Increment on interface add, remove, or change. -SOUP_CURRENT=3 +SOUP_CURRENT=4 # Increment on source change. Reset when CURRENT changes. SOUP_REVISION=0 @@ -18,7 +18,7 @@ AC_SUBST(SOUP_REVISION) AC_SUBST(SOUP_AGE) # Update in src/soup-core/soup-private.h for Windows -AM_INIT_AUTOMAKE(soup, 0.6.99) +AM_INIT_AUTOMAKE(soup, 0.7.99) AM_CONFIG_HEADER(config.h) AM_MAINTAINER_MODE @@ -136,11 +136,28 @@ dnl *********************** dnl *** Checks for popt *** dnl *********************** -AC_CHECK_LIB(popt, poptGetContext,, AC_MSG_ERROR([popt is required])) -AC_CHECK_HEADERS(popt.h,, AC_MSG_ERROR([popt.h is required])) - -POPT_CFLAGS="$CPPFLAGS" -POPT_LIBS="$LIBS" +AC_ARG_WITH(popt-includes, + [ --with-popt-includes Specify location of popt headers], + [popt_inc_prefix=-I$withval]) + +AC_ARG_WITH(popt-libs, + [ --with-popt-libs Specify location of popt libs], + [popt_prefix=$withval], + [popt_prefix=/usr/lib]) + +save_CPPFLAGS=$CPPFLAGS +save_LIBS=$LIBS +CPPFLAGS="$CPPFLAGS $popt_inc_prefix" +LIBS="$LIBS -L$prefix" + +AC_CHECK_LIB(popt, + poptGetContext, + [POPT_LIBS="$LIBS -lpopt"] , + AC_MSG_ERROR([popt is required])) +AC_CHECK_HEADERS(popt.h, [POPT_CFLAGS="$CFLAGS"], AC_MSG_ERROR([popt.h is required])) + +CPPFLAGS=$save_CPPFLAGS +LIBS=$save_LIBS AC_SUBST(POPT_CFLAGS) AC_SUBST(POPT_LIBS) @@ -185,11 +202,12 @@ dnl ********************************* AC_CHECK_HEADERS(unistd.h) AC_CHECK_HEADERS(netinet/in.h netinet/tcp.h) AC_CHECK_HEADERS(sys/socket.h sys/sockio.h sys/poll.h sys/param.h sys/wait.h) +AC_CHECK_HEADERS(sys/ioctl.h sys/filio.h) AC_CHECK_FUNC(socket, , AC_CHECK_LIB(socket, socket)) AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, gethostbyname)) -AC_CHECK_FUNCS(inet_pton) +AC_CHECK_FUNCS(inet_pton inet_aton) ### Check if we have gethostbyname_r (if so, assume gethostbyaddr_r). AC_CHECK_FUNC(gethostbyname_r, @@ -284,7 +302,9 @@ AC_ARG_WITH(nspr-includes, [nspr_inc_prefix=-I$withval]) AC_ARG_WITH(nspr-libs, - [ --with-nspr-libs Specify location of Netscape Portable Runtime libs],[nspr_prefix=-L$withval]) + [ --with-nspr-libs Specify location of Netscape Portable Runtime libs], + [nspr_prefix=$withval], + [nspr_prefix=/usr/lib]) AC_ARG_WITH(nss-includes, [ --with-nss-includes Specify location of NSS header files], @@ -292,7 +312,8 @@ AC_ARG_WITH(nss-includes, AC_ARG_WITH(nss-libs, [ --with-nss-libs Specify location of NSS libs], - [nss_prefix=-L$withval]) + [nss_prefix=$withval], + [nss_prefix=/usr/lib]) AC_ARG_WITH(openssl-includes, @@ -301,49 +322,80 @@ AC_ARG_WITH(openssl-includes, AC_ARG_WITH(openssl-libs, [ --with-openssl-libs Specify location of OpenSSL libs], - [openssl_prefix=-L$withval], - [openssl_prefix=-L/usr/lib]) - -enable_openssl="no" -enable_nss="no" + [openssl_prefix=$withval], + [openssl_prefix=/usr/lib]) + +### +### Allow for a custom SSL proxy name +### +AC_ARG_WITH(ssl-proxy-name, + [ --with-ssl-proxy-name Custom name for ssl proxy executable [default=soup-ssl-proxy]], + [SSL_PROXY_NAME=$withval], + [SSL_PROXY_NAME=soup-ssl-proxy]) + +AC_DEFINE_UNQUOTED(SSL_PROXY_NAME, "${SSL_PROXY_NAME}") +AC_SUBST(SSL_PROXY_NAME) + +### +### Try to link statically with the SSL library +### +AC_ARG_ENABLE(ssl-link-static, + [ --enable-static-ssl Link with SSL library statically [default=no]], + [enable_static_ssl=yes]) if test "x$enable_ssl" = xyes; then + ### + ### Check for OpenSSL + ### save_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$openssl_inc_prefix" + CPPFLAGS="$CPPFLAGS $openssl_inc_prefix" AC_CHECK_LIB(dl, dlopen, DL_LDFLAGS="-ldl", DL_LDFLAGS="") AC_CHECK_HEADERS(openssl/ssl.h openssl/err.h, - [OPENSSL_CFLAGS="$CPPFLAGS" - OPENSSL_LIBS="$openssl_prefix -lssl -lcrypto $DL_LDFLAGS" - enable_openssl="yes"], - [OPENSSL_CFLAGS="" - OPENSSL_LIBS="" - enable_openssl="no" - break]) - AC_SUBST(OPENSSL_CFLAGS) - AC_SUBST(OPENSSL_LIBS) - CPPFLAGS=$save_CPPFLAGS + [enable_openssl="yes"], + [enable_openssl="no"; break]) if test "x$enable_openssl" = xyes; then + if test "x$enable_static_ssl" = "xyes"; then + OPENSSL_LIBS="$openssl_prefix/libssl.a $openssl_prefix/libcrypto.a" + else + OPENSSL_LIBS="-L$openssl_prefix -lssl -lcrypto $DL_LDFLAGS" + fi + OPENSSL_CFLAGS=$CPPFLAGS AC_DEFINE(HAVE_OPENSSL) + else + OPENSSL_LIBS= + OPENSSL_CFLAGS= fi + AC_SUBST(OPENSSL_CFLAGS) + AC_SUBST(OPENSSL_LIBS) + CPPFLAGS=$save_CPPFLAGS + + ### + ### Check for Mozilla NSS + ### save_CPPFLAGS=$CPPFLAGS - CPPFLAGS="$nspr_inc_prefix $nss_inc_prefix" + CPPFLAGS="$CPPFLAGS $nspr_inc_prefix $nss_inc_prefix" AC_CHECK_HEADERS(nss.h ssl.h pk11func.h, - [NSS_CFLAGS="$CPPFLAGS" - NSS_LIBS="-lpthread $nspr_prefix -lnspr4 -lplc4 -lplds4 $nss_prefix -lnss3 -lssl3" - enable_nss="yes"], - [NSS_CFLAGS="" - NSS_LIBS="" - enable_nss="no" - break]) - AC_SUBST(NSS_CFLAGS) - AC_SUBST(NSS_LIBS) - CPPFLAGS=$save_CPPFLAGS + [enable_nss="yes"], + [enable_nss="no"; break]) if test "x$enable_nss" = xyes; then + if test "x$enable_static_ssl" = "xyes"; then + NSS_LIBS="-lpthread $nspr_prefix/libnspr4.a $nspr_prefix/libplc4.a $nspr_prefix/libplds4.a $nss_prefix/libnssb.a" + else + NSS_LIBS="-lpthread -L$nspr_prefix -lnspr4 -lplc4 -lplds4 $nss_prefix -lnss3 -lssl3" + fi + NSS_CFLAGS=$CPPFLAGS AC_DEFINE(HAVE_NSS) + else + NSS_LIBS= + NSS_CFLAGS= fi + + AC_SUBST(NSS_CFLAGS) + AC_SUBST(NSS_LIBS) + CPPFLAGS=$save_CPPFLAGS fi diff --git a/libsoup/Makefile.am b/libsoup/Makefile.am index 9387118..e47c575 100644 --- a/libsoup/Makefile.am +++ b/libsoup/Makefile.am @@ -22,10 +22,13 @@ libsoupinclude_HEADERS = \ soup-env.h \ soup-error.h \ soup-fault.h \ + soup-headers.h \ soup-message.h \ + soup-method.h \ soup-misc.h \ soup-parser.h \ soup-serializer.h \ + soup-server-auth.h \ soup-server.h \ soup-socket.h \ soup-uri.h @@ -50,9 +53,9 @@ libsoup_la_SOURCES = \ soup-env.c \ soup-error.c \ soup-fault.c \ - soup-headers.h \ soup-headers.c \ soup-message.c \ + soup-method.c \ soup-misc.c \ soup-nss.h \ soup-nss.c \ @@ -64,6 +67,7 @@ libsoup_la_SOURCES = \ soup-queue.c \ soup-serializer.c \ soup-server.c \ + soup-server-auth.c \ soup-socket.c \ soup-socket-unix.c \ soup-socks.h \ diff --git a/libsoup/soup-auth.c b/libsoup/soup-auth.c index b5c1813..dd28292 100644 --- a/libsoup/soup-auth.c +++ b/libsoup/soup-auth.c @@ -26,23 +26,17 @@ #include #include -#include #include #include #include "md5-utils.h" #include "soup-auth.h" #include "soup-context.h" +#include "soup-headers.h" #include "soup-message.h" #include "soup-private.h" #include "soup-ntlm.h" -static char *copy_token_if_exists (GHashTable *tokens, char *t); -static char *decode_token (char **in); -static GHashTable *parse_param_list (const char *header); -static void destroy_param_hash (GHashTable *table); - - /* * Basic Authentication Support */ @@ -73,12 +67,12 @@ basic_parse_func (SoupAuth *auth, const char *header) header += sizeof ("Basic"); - tokens = parse_param_list (header); + tokens = soup_header_param_parse_list (header); if (!tokens) return; - auth->realm = copy_token_if_exists (tokens, "realm"); + auth->realm = soup_header_param_copy_token (tokens, "realm"); - destroy_param_hash (tokens); + soup_header_param_destroy_hash (tokens); } static void @@ -319,6 +313,9 @@ decode_data_type (DataType *dtype, const char *name) { int i; + if (!name) + return 0; + for (i = 0; dtype[i].name; i++) { if (!g_strcasecmp (dtype[i].name, name)) return dtype[i].type; @@ -348,20 +345,20 @@ digest_parse_func (SoupAuth *auth, const char *header) header += sizeof ("Digest"); - tokens = parse_param_list (header); + tokens = soup_header_param_parse_list (header); if (!tokens) return; - auth->realm = copy_token_if_exists (tokens, "realm"); + auth->realm = soup_header_param_copy_token (tokens, "realm"); - digest->nonce = copy_token_if_exists (tokens, "nonce"); + digest->nonce = soup_header_param_copy_token (tokens, "nonce"); - tmp = copy_token_if_exists (tokens, "qop"); + tmp = soup_header_param_copy_token (tokens, "qop"); ptr = tmp; while (ptr && *ptr) { char *token; - token = decode_token (&ptr); + token = soup_header_param_decode_token (&ptr); if (token) digest->qop_options |= decode_qop (token); g_free (token); @@ -372,7 +369,7 @@ digest_parse_func (SoupAuth *auth, const char *header) g_free (tmp); - tmp = copy_token_if_exists (tokens, "stale"); + tmp = soup_header_param_copy_token (tokens, "stale"); if (tmp && g_strcasecmp (tmp, "true") == 0) digest->stale = TRUE; @@ -381,11 +378,11 @@ digest_parse_func (SoupAuth *auth, const char *header) g_free (tmp); - tmp = copy_token_if_exists (tokens, "algorithm"); + tmp = soup_header_param_copy_token (tokens, "algorithm"); digest->algorithm = decode_algorithm (tmp); g_free (tmp); - destroy_param_hash (tokens); + soup_header_param_destroy_hash (tokens); } static void @@ -394,18 +391,21 @@ digest_init_func (SoupAuth *auth, const SoupUri *uri) SoupAuthDigest *digest = (SoupAuthDigest *) auth; MD5Context ctx; guchar d[16]; - char *tmp; digest->user = g_strdup (uri->user); /* compute A1 */ md5_init (&ctx); + md5_update (&ctx, uri->user, strlen (uri->user)); + md5_update (&ctx, ":", 1); - md5_update (&ctx, auth->realm, strlen (auth->realm)); + if (auth->realm) + md5_update (&ctx, auth->realm, strlen (auth->realm)); + md5_update (&ctx, ":", 1); - tmp = uri->passwd ? uri->passwd : ""; - md5_update (&ctx, tmp, strlen (tmp)); + if (uri->passwd) + md5_update (&ctx, uri->passwd, strlen (uri->passwd)); if (digest->algorithm == ALGORITHM_MD5_SESS) { md5_final (&ctx, d); @@ -546,9 +546,7 @@ ntlm_init (SoupAuth *sa, const SoupUri *uri) domain = ntlm_get_authmech_token (uri, "domain="); if (strlen (auth->header) < sizeof ("NTLM")) - auth->response = - soup_ntlm_request (host ? host : "UNKNOWN", - domain ? domain : "UNKNOWN"); + auth->response = soup_ntlm_request (); else { gchar lm_hash [21], nt_hash [21]; @@ -560,8 +558,8 @@ ntlm_init (SoupAuth *sa, const SoupUri *uri) uri->user, (gchar *) &lm_hash, (gchar *) &nt_hash, - host ? host : "UNKNOWN", - domain ? domain : "UNKNOWN"); + host, + domain); auth->completed = TRUE; } @@ -761,152 +759,3 @@ soup_auth_invalidates_prior (SoupAuth *new_auth, SoupAuth *old_auth) return new_auth->compare_func (new_auth, old_auth); } - - -/* - * Internal parsing routines - */ - -static char * -copy_token_if_exists (GHashTable *tokens, char *t) -{ - char *data; - - g_return_val_if_fail (tokens, NULL); - g_return_val_if_fail (t, NULL); - - if ( (data = g_hash_table_lookup (tokens, t))) - return g_strdup (data); - else - return NULL; -} - -static void -decode_lwsp (char **in) -{ - char *inptr = *in; - - while (isspace (*inptr)) - inptr++; - - *in = inptr; -} - -static char * -decode_quoted_string (char **in) -{ - char *inptr = *in; - char *out = NULL, *outptr; - int outlen; - int c; - - decode_lwsp (&inptr); - if (*inptr == '"') { - char *intmp; - int skip = 0; - - /* first, calc length */ - inptr++; - intmp = inptr; - while ( (c = *intmp++) && c != '"') { - if (c == '\\' && *intmp) { - intmp++; - skip++; - } - } - - outlen = intmp - inptr - skip; - out = outptr = g_malloc (outlen + 1); - - while ( (c = *inptr++) && c != '"') { - if (c == '\\' && *inptr) { - c = *inptr++; - } - *outptr++ = c; - } - *outptr = 0; - } - - *in = inptr; - - return out; -} - -static char * -decode_token (char **in) -{ - char *inptr = *in; - char *start; - - decode_lwsp (&inptr); - start = inptr; - - while (*inptr && *inptr != '=' && *inptr != ',') - inptr++; - - if (inptr > start) { - *in = inptr; - return g_strndup (start, inptr - start); - } - else - return NULL; -} - -static char * -decode_value (char **in) -{ - char *inptr = *in; - - decode_lwsp (&inptr); - if (*inptr == '"') - return decode_quoted_string (in); - else - return decode_token (in); -} - -static GHashTable * -parse_param_list (const char *header) -{ - char *ptr; - gboolean added = FALSE; - GHashTable *params = g_hash_table_new (soup_str_case_hash, - soup_str_case_equal); - - ptr = (char *) header; - while (ptr && *ptr) { - char *name; - char *value; - - name = decode_token (&ptr); - if (*ptr == '=') { - ptr++; - value = decode_value (&ptr); - g_hash_table_insert (params, name, value); - added = TRUE; - } - - if (*ptr == ',') - ptr++; - } - - if (!added) { - g_hash_table_destroy (params); - params = NULL; - } - - return params; -} - -static void -destroy_param_hash_elements (gpointer key, gpointer value, gpointer user_data) -{ - g_free (key); - g_free (value); -} - -static void -destroy_param_hash (GHashTable *table) -{ - g_hash_table_foreach (table, destroy_param_hash_elements, NULL); - g_hash_table_destroy (table); -} diff --git a/libsoup/soup-context.c b/libsoup/soup-context.c index 94070c3..28d2c5f 100644 --- a/libsoup/soup-context.c +++ b/libsoup/soup-context.c @@ -284,19 +284,53 @@ struct SoupConnectData { }; static void +prune_connection_foreach (gchar *hostname, + SoupHost *serv, + SoupConnection **last) +{ + GSList *conns = serv->connections; + + while (conns) { + SoupConnection *conn = conns->data; + + if (!conn->in_use) { + if (*last == NULL || + (*last)->last_used_id > conn->last_used_id) + *last = conn; + } + + conns = conns->next; + } +} + +static gboolean +prune_least_used_connection (void) +{ + SoupConnection *last = NULL; + + g_hash_table_foreach (soup_hosts, + (GHFunc) prune_connection_foreach, + &last); + if (last) { + connection_free (last); + return TRUE; + } + + return FALSE; +} + +static gboolean retry_connect_timeout_cb (struct SoupConnectData *data); + +static void soup_context_connect_cb (SoupSocket *socket, SoupSocketConnectStatus status, gpointer user_data) { struct SoupConnectData *data = user_data; SoupContext *ctx = data->ctx; - SoupConnectCallbackFn cb = data->cb; - gpointer cb_data = data->user_data; SoupConnection *new_conn; GIOChannel *chan; - g_free (data); - switch (status) { case SOUP_SOCKET_CONNECT_ERROR_NONE: new_conn = g_new0 (SoupConnection, 1); @@ -307,8 +341,8 @@ soup_context_connect_cb (SoupSocket *socket, new_conn->in_use = TRUE; new_conn->last_used_id = 0; - soup_context_ref (ctx); new_conn->context = ctx; + soup_context_ref (ctx); chan = soup_connection_get_iochannel (new_conn); new_conn->death_tag = @@ -319,95 +353,129 @@ soup_context_connect_cb (SoupSocket *socket, g_io_channel_unref (chan); ctx->server->connections = - g_slist_prepend (ctx->server->connections, - new_conn); + g_slist_prepend (ctx->server->connections, new_conn); - (*cb) (ctx, SOUP_CONNECT_ERROR_NONE, new_conn, cb_data); + (*data->cb) (ctx, + SOUP_CONNECT_ERROR_NONE, + new_conn, + data->user_data); break; case SOUP_SOCKET_CONNECT_ERROR_ADDR_RESOLVE: connection_count--; - (*cb) (ctx, SOUP_CONNECT_ERROR_ADDR_RESOLVE, NULL, cb_data); + (*data->cb) (ctx, + SOUP_CONNECT_ERROR_ADDR_RESOLVE, + NULL, + data->user_data); break; case SOUP_SOCKET_CONNECT_ERROR_NETWORK: connection_count--; - (*cb) (ctx, SOUP_CONNECT_ERROR_NETWORK, NULL, cb_data); + /* + * Check if another connection exists to this server + * before reporting error. + */ + if (ctx->server->connections) { + data->timeout_tag = + g_timeout_add ( + 150, + (GSourceFunc) retry_connect_timeout_cb, + data); + return; + } + + (*data->cb) (ctx, + SOUP_CONNECT_ERROR_NETWORK, + NULL, + data->user_data); break; } + + soup_context_unref (ctx); + g_free (data); } -static SoupConnection * -soup_try_existing_connections (SoupContext *ctx) +static gboolean +try_existing_connections (SoupContext *ctx, + SoupConnectCallbackFn cb, + gpointer user_data) { GSList *conns = ctx->server->connections; while (conns) { SoupConnection *conn = conns->data; - if (!conn->in_use && - conn->port == (guint) ctx->uri->port && - conn->keep_alive) - return conn; - - conns = conns->next; - } - - return NULL; -} - -static void -soup_prune_foreach (gchar *hostname, - SoupHost *serv, - SoupConnection **last) -{ - GSList *conns = serv->connections; + if (conn->in_use == FALSE && + conn->keep_alive == TRUE && + conn->port == (guint) ctx->uri->port) { + /* Set connection to in use */ + conn->in_use = TRUE; - while (conns) { - SoupConnection *conn = conns->data; + /* Reset connection context */ + soup_context_ref (ctx); + soup_context_unref (conn->context); + conn->context = ctx; - if (!conn->in_use) { - if (*last == NULL || - (*last)->last_used_id > conn->last_used_id) - *last = conn; + /* Issue success callback */ + (*cb) (ctx, SOUP_CONNECT_ERROR_NONE, conn, user_data); + return TRUE; } conns = conns->next; } -} - -static gboolean -soup_prune_least_used_connection (void) -{ - SoupConnection *last = NULL; - - g_hash_table_foreach (soup_hosts, - (GHFunc) soup_prune_foreach, - &last); - - if (last) { - connection_free (last); - return TRUE; - } return FALSE; } static gboolean -soup_prune_timeout (struct SoupConnectData *data) +try_create_connection (struct SoupConnectData **dataptr) { + struct SoupConnectData *data = *dataptr; gint conn_limit = soup_get_connection_limit (); + gpointer connect_tag; + /* + * Check if we are allowed to create a new connection, otherwise wait + * for next timeout. + */ if (conn_limit && connection_count >= conn_limit && - !soup_try_existing_connections (data->ctx) && - !soup_prune_least_used_connection ()) - return TRUE; + !prune_least_used_connection ()) { + data->connect_tag = 0; + return FALSE; + } - soup_context_get_connection (data->ctx, data->cb, data->user_data); - g_free (data); + connection_count++; + + data->timeout_tag = 0; + connect_tag = soup_socket_connect (data->ctx->uri->host, + data->ctx->uri->port, + soup_context_connect_cb, + data); + /* + * NOTE: soup_socket_connect can fail immediately and call our + * callback which will delete the state. + */ + if (connect_tag) + data->connect_tag = connect_tag; + else + *dataptr = NULL; - return FALSE; + return TRUE; +} + +static gboolean +retry_connect_timeout_cb (struct SoupConnectData *data) +{ + if (try_existing_connections (data->ctx, + data->cb, + data->user_data)) { + soup_context_unref (data->ctx); + g_free (data); + return FALSE; + } + + return try_create_connection (&data) == FALSE; } /** @@ -436,46 +504,26 @@ soup_context_get_connection (SoupContext *ctx, SoupConnectCallbackFn cb, gpointer user_data) { - SoupConnection *conn; struct SoupConnectData *data; - gint conn_limit; g_return_val_if_fail (ctx != NULL, NULL); - if ((conn = soup_try_existing_connections (ctx))) { - conn->in_use = TRUE; - - soup_context_ref (ctx); - soup_context_unref (conn->context); - conn->context = ctx; - - (*cb) (ctx, SOUP_CONNECT_ERROR_NONE, conn, user_data); + /* Look for an existing unused connection */ + if (try_existing_connections (ctx, cb, user_data)) return NULL; - } data = g_new0 (struct SoupConnectData, 1); - data->ctx = ctx; data->cb = cb; data->user_data = user_data; - conn_limit = soup_get_connection_limit (); + data->ctx = ctx; + soup_context_ref (ctx); - if (conn_limit && - connection_count >= conn_limit && - !soup_prune_least_used_connection ()) + if (!try_create_connection (&data)) data->timeout_tag = - g_timeout_add (500, - (GSourceFunc) soup_prune_timeout, + g_timeout_add (150, + (GSourceFunc) retry_connect_timeout_cb, data); - else { - connection_count++; - - data->connect_tag = - soup_socket_connect (ctx->uri->host, - ctx->uri->port, - soup_context_connect_cb, - data); - } return data; } diff --git a/libsoup/soup-error.h b/libsoup/soup-error.h index 79fe298..496cd80 100644 --- a/libsoup/soup-error.h +++ b/libsoup/soup-error.h @@ -61,6 +61,7 @@ typedef enum { SOUP_ERROR_MULTIPLE_CHOICES = 300, SOUP_ERROR_MOVED_PERMANANTLY = 301, SOUP_ERROR_FOUND = 302, + SOUP_ERROR_MOVED_TEMPORARILY = SOUP_ERROR_FOUND, SOUP_ERROR_SEE_OTHER = 303, SOUP_ERROR_NOT_MODIFIED = 304, SOUP_ERROR_USE_PROXY = 305, diff --git a/libsoup/soup-headers.c b/libsoup/soup-headers.c index 6212774..99cf232 100644 --- a/libsoup/soup-headers.c +++ b/libsoup/soup-headers.c @@ -10,8 +10,10 @@ #include #include +#include #include "soup-headers.h" +#include "soup-private.h" /* * "HTTP/1.1 200 OK\r\nContent-Length: 1234\r\n 567\r\n\r\n" @@ -150,29 +152,21 @@ soup_headers_parse_request (gchar *str, } gboolean -soup_headers_parse_response (gchar *str, - gint len, - GHashTable *dest, - SoupHttpVersion *ver, - guint *status_code, - gchar **status_phrase) +soup_headers_parse_status_line (const char *status_line, + SoupHttpVersion *ver, + guint *status_code, + gchar **status_phrase) { - guint http_major, http_minor; + guint http_major, http_minor, code; guint phrase_start = 0; - if (!str || !*str || len < sizeof ("HTTP/0.0 000 A\r\n\r\n")) - goto THROW_MALFORMED_HEADER; - - if (sscanf (str, + if (sscanf (status_line, "HTTP/%1u.%1u %3u %n", &http_major, &http_minor, - status_code, + &code, &phrase_start) < 3 || !phrase_start) - goto THROW_MALFORMED_HEADER; - - if (!soup_headers_parse (str, len, dest)) - goto THROW_MALFORMED_HEADER; + return FALSE; if (ver) { if (http_major == 1 && http_minor == 1) @@ -181,10 +175,186 @@ soup_headers_parse_response (gchar *str, *ver = SOUP_HTTP_1_0; } - *status_phrase = g_strdup (&str [phrase_start]); + if (status_code) + *status_code = code; + + if (status_phrase) + *status_phrase = g_strdup (status_line + phrase_start); + + return TRUE; +} + +gboolean +soup_headers_parse_response (gchar *str, + gint len, + GHashTable *dest, + SoupHttpVersion *ver, + guint *status_code, + gchar **status_phrase) +{ + if (!str || !*str || len < sizeof ("HTTP/0.0 000 A\r\n\r\n")) + goto THROW_MALFORMED_HEADER; + + if (!soup_headers_parse (str, len, dest)) + goto THROW_MALFORMED_HEADER; + + if (!soup_headers_parse_status_line (str, + ver, + status_code, + status_phrase)) + goto THROW_MALFORMED_HEADER; return TRUE; THROW_MALFORMED_HEADER: return FALSE; } + + +/* + * HTTP parameterized header parsing + */ + +char * +soup_header_param_copy_token (GHashTable *tokens, char *t) +{ + char *data; + + g_return_val_if_fail (tokens, NULL); + g_return_val_if_fail (t, NULL); + + if ( (data = g_hash_table_lookup (tokens, t))) + return g_strdup (data); + else + return NULL; +} + +static void +decode_lwsp (char **in) +{ + char *inptr = *in; + + while (isspace (*inptr)) + inptr++; + + *in = inptr; +} + +static char * +decode_quoted_string (char **in) +{ + char *inptr = *in; + char *out = NULL, *outptr; + int outlen; + int c; + + decode_lwsp (&inptr); + if (*inptr == '"') { + char *intmp; + int skip = 0; + + /* first, calc length */ + inptr++; + intmp = inptr; + while ( (c = *intmp++) && c != '"') { + if (c == '\\' && *intmp) { + intmp++; + skip++; + } + } + + outlen = intmp - inptr - skip; + out = outptr = g_malloc (outlen + 1); + + while ( (c = *inptr++) && c != '"') { + if (c == '\\' && *inptr) { + c = *inptr++; + } + *outptr++ = c; + } + *outptr = 0; + } + + *in = inptr; + + return out; +} + +char * +soup_header_param_decode_token (char **in) +{ + char *inptr = *in; + char *start; + + decode_lwsp (&inptr); + start = inptr; + + while (*inptr && *inptr != '=' && *inptr != ',') + inptr++; + + if (inptr > start) { + *in = inptr; + return g_strndup (start, inptr - start); + } + else + return NULL; +} + +static char * +decode_value (char **in) +{ + char *inptr = *in; + + decode_lwsp (&inptr); + if (*inptr == '"') + return decode_quoted_string (in); + else + return soup_header_param_decode_token (in); +} + +GHashTable * +soup_header_param_parse_list (const char *header) +{ + char *ptr; + gboolean added = FALSE; + GHashTable *params = g_hash_table_new (soup_str_case_hash, + soup_str_case_equal); + + ptr = (char *) header; + while (ptr && *ptr) { + char *name; + char *value; + + name = soup_header_param_decode_token (&ptr); + if (*ptr == '=') { + ptr++; + value = decode_value (&ptr); + g_hash_table_insert (params, name, value); + added = TRUE; + } + + if (*ptr == ',') + ptr++; + } + + if (!added) { + g_hash_table_destroy (params); + params = NULL; + } + + return params; +} + +static void +destroy_param_hash_elements (gpointer key, gpointer value, gpointer user_data) +{ + g_free (key); + g_free (value); +} + +void +soup_header_param_destroy_hash (GHashTable *table) +{ + g_hash_table_foreach (table, destroy_param_hash_elements, NULL); + g_hash_table_destroy (table); +} diff --git a/libsoup/soup-headers.h b/libsoup/soup-headers.h index 6139269..5f33480 100644 --- a/libsoup/soup-headers.h +++ b/libsoup/soup-headers.h @@ -16,18 +16,34 @@ /* HTTP Header Parsing */ -gboolean soup_headers_parse_request (gchar *str, - gint len, - GHashTable *dest, - gchar **req_method, - gchar **req_path, - SoupHttpVersion *ver); - -gboolean soup_headers_parse_response (gchar *str, - gint len, - GHashTable *dest, - SoupHttpVersion *ver, - guint *status_code, - gchar **status_phrase); +gboolean soup_headers_parse_request (gchar *str, + gint len, + GHashTable *dest, + gchar **req_method, + gchar **req_path, + SoupHttpVersion *ver); + +gboolean soup_headers_parse_status_line (const char *status_line, + SoupHttpVersion *ver, + guint *status_code, + gchar **status_phrase); + +gboolean soup_headers_parse_response (gchar *str, + gint len, + GHashTable *dest, + SoupHttpVersion *ver, + guint *status_code, + gchar **status_phrase); + +/* HTTP parameterized header parsing */ + +char *soup_header_param_decode_token (char **in); + +GHashTable *soup_header_param_parse_list (const char *header); + +char *soup_header_param_copy_token (GHashTable *tokens, + char *t); + +void soup_header_param_destroy_hash (GHashTable *table); #endif /*SOUP_HEADERS_H*/ diff --git a/libsoup/soup-message.c b/libsoup/soup-message.c index f8919c4..654ee19 100644 --- a/libsoup/soup-message.c +++ b/libsoup/soup-message.c @@ -47,12 +47,9 @@ soup_message_new (SoupContext *context, const gchar *method) { SoupMessage *ret; - g_return_val_if_fail (context, NULL); - ret = g_new0 (SoupMessage, 1); ret->priv = g_new0 (SoupMessagePrivate, 1); ret->status = SOUP_STATUS_IDLE; - ret->context = context; ret->method = method ? method : SOUP_METHOD_GET; ret->request_headers = g_hash_table_new (soup_str_case_hash, @@ -63,7 +60,7 @@ soup_message_new (SoupContext *context, const gchar *method) ret->priv->http_version = SOUP_HTTP_1_1; - soup_context_ref (context); + soup_message_set_context (ret, context); return ret; } @@ -102,6 +99,76 @@ soup_message_new_full (SoupContext *context, return ret; } +static void +copy_header (gchar *key, gchar *value, GHashTable *dest_hash) +{ + soup_message_add_header (dest_hash, key, value); +} + +/* + * Copy context, method, error, request buffer, response buffer, request + * headers, response headers, message flags, http version. + * + * Callback function, content handlers, message status, server, server socket, + * and server message are NOT copied. + */ +SoupMessage * +soup_message_copy (SoupMessage *req) +{ + SoupMessage *cpy; + + cpy = soup_message_new (req->context, req->method); + + cpy->errorcode = req->errorcode; + cpy->errorclass = req->errorclass; + cpy->errorphrase = req->errorphrase; + + cpy->request.owner = SOUP_BUFFER_SYSTEM_OWNED; + cpy->request.length = cpy->request.length; + cpy->request.body = g_memdup (req->request.body, + req->request.length); + + cpy->response.owner = SOUP_BUFFER_SYSTEM_OWNED; + cpy->response.length = cpy->response.length; + cpy->response.body = g_memdup (req->response.body, + req->response.length); + + soup_message_foreach_header (req->request_headers, + (GHFunc) copy_header, + cpy->request_headers); + + soup_message_foreach_header (req->response_headers, + (GHFunc) copy_header, + cpy->response_headers); + + cpy->priv->msg_flags = req->priv->msg_flags; + + /* + * Note: We don't copy content handlers or callback function as this is + * a nightmare double free situation. + */ + + cpy->priv->http_version = req->priv->http_version; + + return cpy; +} + +static void +release_connection (const SoupDataBuffer *data, + gpointer user_data) +{ + SoupConnection *conn = user_data; + soup_connection_release (conn); +} + +static void +release_and_close_connection (gboolean headers_done, gpointer user_data) +{ + SoupConnection *conn = user_data; + soup_connection_set_keep_alive (conn, FALSE); + soup_connection_release (conn); +} + /** * soup_message_cleanup: * @req: a %SoupMessage. @@ -115,6 +182,19 @@ soup_message_cleanup (SoupMessage *req) { g_return_if_fail (req != NULL); + if (req->connection && + req->priv->read_tag && + req->status == SOUP_STATUS_READING_RESPONSE) { + soup_transfer_read_set_callbacks (req->priv->read_tag, + NULL, + NULL, + release_connection, + release_and_close_connection, + req->connection); + req->priv->read_tag = 0; + req->connection = NULL; + } + if (req->priv->read_tag) { soup_transfer_read_cancel (req->priv->read_tag); req->priv->read_tag = 0; @@ -129,12 +209,13 @@ soup_message_cleanup (SoupMessage *req) soup_context_cancel_connect (req->priv->connect_tag); req->priv->connect_tag = NULL; } + if (req->connection) { soup_connection_release (req->connection); req->connection = NULL; } - soup_active_requests = g_slist_remove (soup_active_requests, req); + soup_queue_remove_request (req); } static void @@ -156,16 +237,14 @@ handler_free (SoupHandlerData *data) static void finalize_message (SoupMessage *req) { - soup_context_unref (req->context); + if (req->context) + soup_context_unref (req->context); if (req->request.owner == SOUP_BUFFER_SYSTEM_OWNED) g_free (req->request.body); if (req->response.owner == SOUP_BUFFER_SYSTEM_OWNED) g_free (req->response.body); - if (req->priv->req_header) - g_string_free (req->priv->req_header, TRUE); - soup_message_clear_headers (req->request_headers); g_hash_table_destroy (req->request_headers); @@ -458,9 +537,114 @@ soup_message_queue (SoupMessage *req, SoupCallbackFn callback, gpointer user_data) { + g_return_if_fail (req != NULL); soup_queue_message (req, callback, user_data); } +typedef struct { + SoupMessage *msg; + SoupAuth *conn_auth; +} RequeueConnectData; + +static void +requeue_connect_cb (SoupContext *ctx, + SoupConnectErrorCode err, + SoupConnection *conn, + gpointer user_data) +{ + RequeueConnectData *data = user_data; + + if (conn && !conn->auth) + conn->auth = data->conn_auth; + else + soup_auth_free (data->conn_auth); + + soup_queue_connect_cb (ctx, err, conn, data->msg); + if (data->msg->errorcode) + soup_message_issue_callback (data->msg); + + g_free (data); +} + +static void +requeue_read_error (gboolean body_started, gpointer user_data) +{ + RequeueConnectData *data = user_data; + SoupMessage *msg = data->msg; + SoupContext *dest_ctx = msg->connection->context; + + soup_context_ref (dest_ctx); + + soup_connection_set_keep_alive (msg->connection, FALSE); + soup_connection_release (msg->connection); + msg->connection = NULL; + + soup_queue_message (msg, + msg->priv->callback, + msg->priv->user_data); + + msg->status = SOUP_STATUS_CONNECTING; + + msg->priv->connect_tag = + soup_context_get_connection (dest_ctx, + requeue_connect_cb, + data); + + soup_context_unref (dest_ctx); +} + +static void +requeue_read_finished (const SoupDataBuffer *buf, + gpointer user_data) +{ + RequeueConnectData *data = user_data; + SoupMessage *msg = data->msg; + SoupConnection *conn = msg->connection; + + if (!soup_connection_is_keep_alive (msg->connection)) + requeue_read_error (FALSE, data); + else { + msg->connection = NULL; + + soup_queue_message (msg, + msg->priv->callback, + msg->priv->user_data); + + msg->connection = conn; + } +} + +/** + * soup_message_requeue: + * @req: a %SoupMessage + * + * This causes @req to be placed back on the queue to be attempted again. + **/ +void +soup_message_requeue (SoupMessage *req) +{ + g_return_if_fail (req != NULL); + + if (req->connection && req->connection->auth && req->priv->read_tag) { + RequeueConnectData *data = NULL; + + data = g_new0 (RequeueConnectData, 1); + data->msg = req; + data->conn_auth = req->connection->auth; + + soup_transfer_read_set_callbacks (req->priv->read_tag, + NULL, + NULL, + requeue_read_finished, + requeue_read_error, + data); + req->priv->read_tag = 0; + } else + soup_queue_message (req, + req->priv->callback, + req->priv->user_data); +} + /** * soup_message_send: * @msg: a %SoupMessage. @@ -480,9 +664,14 @@ soup_message_send (SoupMessage *msg) while (1) { g_main_iteration (TRUE); + if (msg->status == SOUP_STATUS_FINISHED || SOUP_ERROR_IS_TRANSPORT (msg->errorcode)) break; + + /* Quit if soup_shutdown has been called */ + if (!soup_initialized) + return SOUP_ERROR_CANCELLED; } return msg->errorclass; @@ -539,7 +728,11 @@ authorize_handler (SoupMessage *msg, gpointer user_data) */ soup_auth_initialize (auth, uri); - old_auth = soup_auth_lookup (ctx); + if (auth->type == SOUP_AUTH_TYPE_NTLM) + old_auth = msg->connection->auth; + else + old_auth = soup_auth_lookup (ctx); + if (old_auth) { if (!soup_auth_invalidates_prior (auth, old_auth)) { soup_auth_free (auth); @@ -547,7 +740,12 @@ authorize_handler (SoupMessage *msg, gpointer user_data) } } - soup_auth_set_context (auth, ctx); + if (auth->type == SOUP_AUTH_TYPE_NTLM) { + if (old_auth) + soup_auth_free (old_auth); + msg->connection->auth = auth; + } else + soup_auth_set_context (auth, ctx); return SOUP_HANDLER_RESEND; @@ -810,7 +1008,6 @@ timeout_handler (gpointer user_data) SoupHandlerData *data = user_data; SoupMessage *msg = data->msg; SoupHandlerResult result; - GSList *iter; switch (data->type) { case SOUP_HANDLER_PREPARE: @@ -824,13 +1021,6 @@ timeout_handler (gpointer user_data) case SOUP_HANDLER_FINISHED: if (msg->status == SOUP_STATUS_FINISHED) goto REMOVE_SOURCE; - case SOUP_HANDLER_DATA_SENT: - iter = msg->priv->content_handlers; - while (iter) { - SoupHandlerData *hd = iter->data; - if (!g_strcasecmp (hd->name, "server-message")) - goto REMOVE_SOURCE; - } } result = (*data->handler_cb) (msg, data->user_data); @@ -1060,10 +1250,12 @@ soup_message_set_context (SoupMessage *msg, SoupContext *new_ctx) { g_return_if_fail (msg != NULL); - g_return_if_fail (new_ctx != NULL); - soup_context_unref (msg->context); - soup_context_ref (new_ctx); + if (msg->context) + soup_context_unref (msg->context); + + if (new_ctx) + soup_context_ref (new_ctx); msg->context = new_ctx; } @@ -1073,7 +1265,9 @@ soup_message_get_context (SoupMessage *msg) { g_return_val_if_fail (msg != NULL, NULL); - soup_context_ref (msg->context); + if (msg->context) + soup_context_ref (msg->context); + return msg->context; } diff --git a/libsoup/soup-message.h b/libsoup/soup-message.h index c93c177..98b3b22 100644 --- a/libsoup/soup-message.h +++ b/libsoup/soup-message.h @@ -14,6 +14,7 @@ #include #include #include +#include typedef enum { SOUP_STATUS_IDLE = 0, @@ -36,21 +37,6 @@ typedef struct { guint length; } SoupDataBuffer; -#define SOUP_METHOD_POST "POST" -#define SOUP_METHOD_GET "GET" -#define SOUP_METHOD_HEAD "HEAD" -#define SOUP_METHOD_OPTIONS "OPTIONS" -#define SOUP_METHOD_PUT "PUT" -#define SOUP_METHOD_MOVE "MOVE" -#define SOUP_METHOD_COPY "COPY" -#define SOUP_METHOD_DELETE "DELETE" -#define SOUP_METHOD_TRACE "TRACE" -#define SOUP_METHOD_CONNECT "CONNECT" -#define SOUP_METHOD_MKCOL "MKCOL" -#define SOUP_METHOD_PROPPATCH "PROPPATCH" -#define SOUP_METHOD_PROPFIND "PROPFIND" -#define SOUP_METHOD_SEARCH "SEARCH" - typedef struct _SoupMessage SoupMessage; typedef struct _SoupMessagePrivate SoupMessagePrivate; @@ -92,6 +78,8 @@ SoupMessage *soup_message_new_full (SoupContext *context, gchar *req_body, gulong req_length); +SoupMessage *soup_message_copy (SoupMessage *req); + void soup_message_free (SoupMessage *req); void soup_message_cancel (SoupMessage *req); @@ -102,6 +90,8 @@ void soup_message_queue (SoupMessage *req, SoupCallbackFn callback, gpointer user_data); +void soup_message_requeue (SoupMessage *req); + void soup_message_add_header (GHashTable *hash, const gchar *name, const gchar *value); @@ -183,18 +173,10 @@ guint soup_message_get_flags (SoupMessage *msg); * Handler Registration */ typedef enum { - /* - * Client-side events - */ SOUP_HANDLER_PREPARE = 0, SOUP_HANDLER_HEADERS, SOUP_HANDLER_DATA, SOUP_HANDLER_FINISHED, - - /* - * Server-side events - */ - SOUP_HANDLER_DATA_SENT } SoupHandlerEvent; enum { diff --git a/libsoup/soup-misc.c b/libsoup/soup-misc.c index 400cddc..1310fe5 100644 --- a/libsoup/soup-misc.c +++ b/libsoup/soup-misc.c @@ -5,10 +5,7 @@ * Authors: * Alex Graveley (alex@ximian.com) * - * soup_base64_encode() written by Chris Blizzard , - * and is Copyright (C) 1998 Free Software Foundation. - * - * All else Copyright (C) 2000, Helix Code, Inc. + * Copyright (C) 2000, Helix Code, Inc. */ #include @@ -22,7 +19,7 @@ gboolean soup_initialized = FALSE; -static guint max_connections = 0; +static guint max_connections = 10; static SoupContext *proxy_context = NULL; @@ -149,8 +146,158 @@ soup_substring_index (gchar *str, gint len, gchar *substr) return -1; } -const char base64_alphabet[65] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; +/* Base64 utils (straight from camel-mime-utils.c) */ +#define d(x) + +static char *base64_alphabet = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * call this when finished encoding everything, to + * flush off the last little bit + */ +int +soup_base64_encode_close (const guchar *in, + int inlen, + gboolean break_lines, + guchar *out, + int *state, + int *save) +{ + int c1, c2; + unsigned char *outptr = out; + + if (inlen > 0) + outptr += soup_base64_encode_step (in, + inlen, + break_lines, + outptr, + state, + save); + + c1 = ((unsigned char *) save) [1]; + c2 = ((unsigned char *) save) [2]; + + d(printf("mode = %d\nc1 = %c\nc2 = %c\n", + (int)((char *) save) [0], + (int)((char *) save) [1], + (int)((char *) save) [2])); + + switch (((char *) save) [0]) { + case 2: + outptr [2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ]; + g_assert (outptr [2] != 0); + goto skip; + case 1: + outptr[2] = '='; + skip: + outptr [0] = base64_alphabet [ c1 >> 2 ]; + outptr [1] = base64_alphabet [ c2 >> 4 | ( (c1&0x3) << 4 )]; + outptr [3] = '='; + outptr += 4; + break; + } + if (break_lines) + *outptr++ = '\n'; + + *save = 0; + *state = 0; + + return outptr-out; +} + +/* + * performs an 'encode step', only encodes blocks of 3 characters to the + * output at a time, saves left-over state in state and save (initialise to + * 0 on first invocation). + */ +int +soup_base64_encode_step (const guchar *in, + int len, + gboolean break_lines, + guchar *out, + int *state, + int *save) +{ + register guchar *outptr; + register const guchar *inptr; + + if (len <= 0) + return 0; + + inptr = in; + outptr = out; + + d (printf ("we have %d chars, and %d saved chars\n", + len, + ((char *) save) [0])); + + if (len + ((char *) save) [0] > 2) { + const guchar *inend = in+len-2; + register int c1, c2, c3; + register int already; + + already = *state; + + switch (((char *) save) [0]) { + case 1: c1 = ((unsigned char *) save) [1]; goto skip1; + case 2: c1 = ((unsigned char *) save) [1]; + c2 = ((unsigned char *) save) [2]; goto skip2; + } + + /* + * yes, we jump into the loop, no i'm not going to change it, + * it's beautiful! + */ + while (inptr < inend) { + c1 = *inptr++; + skip1: + c2 = *inptr++; + skip2: + c3 = *inptr++; + *outptr++ = base64_alphabet [ c1 >> 2 ]; + *outptr++ = base64_alphabet [ c2 >> 4 | + ((c1&0x3) << 4) ]; + *outptr++ = base64_alphabet [ ((c2 &0x0f) << 2) | + (c3 >> 6) ]; + *outptr++ = base64_alphabet [ c3 & 0x3f ]; + /* this is a bit ugly ... */ + if (break_lines && (++already)>=19) { + *outptr++='\n'; + already = 0; + } + } + + ((char *)save)[0] = 0; + len = 2-(inptr-inend); + *state = already; + } + + d(printf("state = %d, len = %d\n", + (int)((char *)save)[0], + len)); + + if (len>0) { + register char *saveout; + + /* points to the slot for the next char to save */ + saveout = & (((char *)save)[1]) + ((char *)save)[0]; + + /* len can only be 0 1 or 2 */ + switch(len) { + case 2: *saveout++ = *inptr++; + case 1: *saveout++ = *inptr++; + } + ((char *)save)[0]+=len; + } + + d(printf("mode = %d\nc1 = %c\nc2 = %c\n", + (int)((char *)save)[0], + (int)((char *)save)[1], + (int)((char *)save)[2])); + + return outptr-out; +} /** * soup_base64_encode: @@ -165,58 +312,117 @@ const char base64_alphabet[65] = gchar * soup_base64_encode (const gchar *text, gint inlen) { - char *buffer = NULL; - char *point = NULL; - int outlen = 0; - - /* check our args */ - if (text == NULL) - return NULL; - - /* Use 'buffer' to store the output. Work out how big it should be... - * This must be a multiple of 4 bytes */ - - /* check our arg...avoid a pesky FPE */ - if (inlen == 0) { - buffer = g_malloc (sizeof(char)); - buffer[0] = '\0'; - return buffer; - } - - outlen = (inlen*4)/3; - if ((inlen % 3) > 0) /* got to pad */ - outlen += 4 - (inlen % 3); - - buffer = g_malloc (outlen + 1); /* +1 for the \0 */ - memset (buffer, 0, outlen + 1); /* initialize to zero */ + unsigned char *out; + int state = 0, outlen; + unsigned int save = 0; + + out = g_malloc (inlen * 4 / 3 + 5); + outlen = soup_base64_encode_close (text, + inlen, + FALSE, + out, + &state, + &save); + out[outlen] = '\0'; + return (char *) out; +} - /* now do the main stage of conversion, 3 bytes at a time, - * leave the trailing bytes (if there are any) for later */ +static unsigned char camel_mime_base64_rank[256] = { + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, + 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, + 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, +}; - for (point=buffer; inlen>=3; inlen-=3, text+=3) { - *(point++) = base64_alphabet [*text>>2]; - *(point++) = base64_alphabet [(*text<<4 & 0x30) | - *(text+1)>>4]; - *(point++) = base64_alphabet [(*(text+1)<<2 & 0x3c) | - *(text+2)>>6]; - *(point++) = base64_alphabet [*(text+2) & 0x3f]; +/** + * base64_decode_step: decode a chunk of base64 encoded data + * @in: input stream + * @len: max length of data to decode + * @out: output stream + * @state: holds the number of bits that are stored in @save + * @save: leftover bits that have not yet been decoded + * + * Decodes a chunk of base64 encoded data + **/ +int +soup_base64_decode_step (const guchar *in, + int len, + guchar *out, + int *state, + guint *save) +{ + register const guchar *inptr; + register guchar *outptr; + const guchar *inend; + guchar c; + register unsigned int v; + int i; + + inend = in+len; + outptr = out; + + /* convert 4 base64 bytes to 3 normal bytes */ + v=*save; + i=*state; + inptr = in; + while (inptr < inend) { + c = camel_mime_base64_rank [*inptr++]; + if (c != 0xff) { + v = (v<<6) | c; + i++; + if (i==4) { + *outptr++ = v>>16; + *outptr++ = v>>8; + *outptr++ = v; + i=0; + } + } } - /* Now deal with the trailing bytes */ - if (inlen) { - /* We always have one trailing byte */ - *(point++) = base64_alphabet [*text>>2]; - *(point++) = base64_alphabet [(*text<<4 & 0x30) | - (inlen==2?*(text+1)>>4:0)]; - *(point++) = (inlen == 1 ? - '=' : - base64_alphabet [*(text+1)<<2 & 0x3c]); - *(point++) = '='; + *save = v; + *state = i; + + /* quick scan back for '=' on the end somewhere */ + /* fortunately we can drop 1 output char for each trailing = (upto 2) */ + i=2; + while (inptr > in && i) { + inptr--; + if (camel_mime_base64_rank [*inptr] != 0xff) { + if (*inptr == '=') + outptr--; + i--; + } } - *point = '\0'; + /* if i!= 0 then there is a truncation error! */ + return outptr - out; +} + +gchar * +soup_base64_decode (const gchar *text, + gint *out_len) +{ + gchar *ret; + gint inlen, state = 0, save = 0; + + inlen = strlen (text); + ret = g_malloc0 (inlen); + + *out_len = soup_base64_decode_step (text, inlen, ret, &state, &save); - return buffer; + return ret; } #define ALLOW_UNLESS_DENIED TRUE diff --git a/libsoup/soup-misc.h b/libsoup/soup-misc.h index 3fb9432..be80993 100644 --- a/libsoup/soup-misc.h +++ b/libsoup/soup-misc.h @@ -65,6 +65,34 @@ typedef void (*SoupAuthorizeFn) (SoupAuthType type, void soup_set_authorize_callback (SoupAuthorizeFn authfn, gpointer user_data); +/* Base64 encoding/decoding */ + +gchar *soup_base64_encode (const gchar *text, + gint len); + +int soup_base64_encode_close (const guchar *in, + int inlen, + gboolean break_lines, + guchar *out, + int *state, + int *save); + +int soup_base64_encode_step (const guchar *in, + int len, + gboolean break_lines, + guchar *out, + int *state, + int *save); + +gchar *soup_base64_decode (const gchar *text, + gint *out_len); + +int soup_base64_decode_step (const guchar *in, + int len, + guchar *out, + int *state, + guint *save); + /* Useful debugging routines */ void soup_debug_print_headers (SoupMessage *req); diff --git a/libsoup/soup-ntlm.c b/libsoup/soup-ntlm.c index 1695222..64f809f 100644 --- a/libsoup/soup-ntlm.c +++ b/libsoup/soup-ntlm.c @@ -15,31 +15,11 @@ #endif #include +#include #include #include "soup-ntlm.h" -#include "soup-private.h" - -/* Base64 */ -static int base64_encode_close (unsigned char *in, - int inlen, - gboolean break_lines, - unsigned char *out, - int *state, - int *save); - -static int base64_encode_step (unsigned char *in, - int len, - gboolean break_lines, - unsigned char *out, - int *state, - int *save); - -static int base64_decode_step (unsigned char *in, - int len, - unsigned char *out, - int *state, - unsigned int *save); +#include "soup-misc.h" /* MD4 */ static void md4sum (const unsigned char *in, @@ -110,16 +90,10 @@ typedef struct { guchar zero_pad[2]; } NTLMString; -#define NTLM_REQUEST_HEADER "NTLMSSP\x00\x01\x00\x00\x00\xB2\x03\x00\x00" - -typedef struct { - guchar header[16]; - NTLMString domain; - NTLMString host; -} NTLMRequest; - -#define NTLM_CHALLENGE_NONCE_OFFSET 24 -#define NTLM_CHALLENGE_NONCE_LENGTH 8 +#define NTLM_CHALLENGE_NONCE_OFFSET 24 +#define NTLM_CHALLENGE_NONCE_LENGTH 8 +#define NTLM_CHALLENGE_DOMAIN_OFFSET 48 +#define NTLM_CHALLENGE_DOMAIN_LEN_OFFSET 44 #define NTLM_RESPONSE_HEADER "NTLMSSP\x00\x03\x00\x00\x00" #define NTLM_RESPONSE_FLAGS "\x82\x01" @@ -155,46 +129,9 @@ ntlm_set_string (NTLMString *string, int *offset, int len) } char * -soup_ntlm_request (const char *host, const char *domain) +soup_ntlm_request (void) { - NTLMRequest req; - unsigned char *out, *p; - int hlen = strlen (host), dlen = strlen (domain), offset; - int state, save; - - memset (&req, 0, sizeof (req)); - memcpy (req.header, NTLM_REQUEST_HEADER, sizeof (req.header)); - - offset = sizeof (req); - ntlm_set_string (&req.host, &offset, hlen); - ntlm_set_string (&req.domain, &offset, dlen); - - out = g_malloc (((offset + 3) * 4) / 3 + 6); - strncpy (out, "NTLM ", 5); - p = out + 5; - - state = save = 0; - p += base64_encode_step ((guchar *) &req, - sizeof (req), - FALSE, - p, - &state, - &save); - p += base64_encode_step ((guchar *) host, - hlen, - FALSE, - p, - &state, - &save); - p += base64_encode_close ((guchar *) domain, - dlen, - FALSE, - p, - &state, - &save); - *p = '\0'; - - return out; + return g_strdup ("NTLM TlRMTVNTUAABAAAABoIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA"); } char * @@ -205,8 +142,7 @@ soup_ntlm_response (const char *challenge, const char *host, const char *domain) { - int hlen = strlen (host), dlen = strlen (domain); - int ulen = strlen (user), offset, decodelen; + int hlen, dlen, ulen, offset, decodelen; guchar lm_resp[24], nt_resp[24], *nonce; NTLMResponse resp; char *chall; @@ -216,30 +152,41 @@ soup_ntlm_response (const char *challenge, if (strncmp (challenge, "NTLM ", 5) != 0) return NULL; - decodelen = strlen (challenge); + decodelen = strlen (challenge) - 5; chall = g_malloc (decodelen); state = save = 0; - base64_decode_step ((guchar *) challenge + 5, - decodelen, - chall, - &state, - &save); + soup_base64_decode_step ((guchar *) challenge + 5, + decodelen, + chall, + &state, + &save); nonce = chall + NTLM_CHALLENGE_NONCE_OFFSET; nonce [NTLM_CHALLENGE_NONCE_LENGTH] = '\0'; calc_response (lm_hash, nonce, lm_resp); calc_response (nt_hash, nonce, nt_resp); - g_free (chall); memset (&resp, 0, sizeof (resp)); memcpy (resp.header, NTLM_RESPONSE_HEADER, sizeof (resp.header)); memcpy (resp.flags, NTLM_RESPONSE_FLAGS, sizeof (resp.flags)); offset = sizeof (resp); + + if (domain) + dlen = strlen (domain); + else { + /* Grab the default domain from the challenge */ + domain = chall + NTLM_CHALLENGE_DOMAIN_OFFSET; + dlen = atoi (chall + NTLM_CHALLENGE_DOMAIN_LEN_OFFSET); + } ntlm_set_string (&resp.domain, &offset, dlen); + ulen = strlen (user); ntlm_set_string (&resp.user, &offset, ulen); + if (!host) + host = "UNKNOWN"; + hlen = strlen (host); 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)); @@ -250,43 +197,45 @@ soup_ntlm_response (const char *challenge, state = save = 0; - p += base64_encode_step ((guchar *) &resp, - sizeof (resp), - FALSE, - p, - &state, - &save); - p += base64_encode_step ((guchar *) domain, - dlen, - FALSE, - p, - &state, - &save); - p += base64_encode_step ((guchar *) user, - ulen, - FALSE, - p, - &state, - &save); - p += base64_encode_step ((guchar *) host, - hlen, - FALSE, - p, - &state, - &save); - p += base64_encode_step (lm_resp, - sizeof (lm_resp), - FALSE, - p, - &state, - &save); - p += base64_encode_close (nt_resp, - sizeof (nt_resp), - FALSE, - p, - &state, - &save); + p += soup_base64_encode_step ((guchar *) &resp, + sizeof (resp), + FALSE, + p, + &state, + &save); + p += soup_base64_encode_step ((guchar *) domain, + dlen, + FALSE, + p, + &state, + &save); + p += soup_base64_encode_step ((guchar *) user, + ulen, + FALSE, + p, + &state, + &save); + p += soup_base64_encode_step ((guchar *) host, + hlen, + FALSE, + p, + &state, + &save); + p += soup_base64_encode_step (lm_resp, + sizeof (lm_resp), + FALSE, + p, + &state, + &save); + + p += soup_base64_encode_close (nt_resp, + sizeof (nt_resp), + FALSE, + p, + &state, + &save); *p = '\0'; + g_free (chall); return out; } @@ -335,240 +284,6 @@ calc_response (const guchar *key, const guchar *plaintext, guchar *results) des (ks, results + 16); } -/* Base64 utils (straight from camel-mime-utils.c) */ -#define d(x) - -static char *base64_alphabet = -"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -static unsigned char camel_mime_base64_rank[256] = { - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, - 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, - 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, - 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, -}; - -/* - * call this when finished encoding everything, to - * flush off the last little bit - */ -static int -base64_encode_close (unsigned char *in, - int inlen, - gboolean break_lines, - unsigned char *out, - int *state, - int *save) -{ - int c1, c2; - unsigned char *outptr = out; - - if (inlen > 0) - outptr += base64_encode_step (in, - inlen, - break_lines, - outptr, - state, - save); - - c1 = ((unsigned char *) save) [1]; - c2 = ((unsigned char *) save) [2]; - - d(printf("mode = %d\nc1 = %c\nc2 = %c\n", - (int)((char *) save) [0], - (int)((char *) save) [1], - (int)((char *) save) [2])); - - switch (((char *) save) [0]) { - case 2: - outptr [2] = base64_alphabet[ ( (c2 &0x0f) << 2 ) ]; - g_assert (outptr [2] != 0); - goto skip; - case 1: - outptr[2] = '='; - skip: - outptr [0] = base64_alphabet [ c1 >> 2 ]; - outptr [1] = base64_alphabet [ c2 >> 4 | ( (c1&0x3) << 4 )]; - outptr [3] = '='; - outptr += 4; - break; - } - if (break_lines) - *outptr++ = '\n'; - - *save = 0; - *state = 0; - - return outptr-out; -} - -/* - * performs an 'encode step', only encodes blocks of 3 characters to the - * output at a time, saves left-over state in state and save (initialise to - * 0 on first invocation). - */ -static int -base64_encode_step(unsigned char *in, - int len, - gboolean break_lines, - unsigned char *out, - int *state, - int *save) -{ - register unsigned char *inptr, *outptr; - - if (len <= 0) - return 0; - - inptr = in; - outptr = out; - - d (printf ("we have %d chars, and %d saved chars\n", - len, - ((char *) save) [0])); - - if (len + ((char *) save) [0] > 2) { - unsigned char *inend = in+len-2; - register int c1, c2, c3; - register int already; - - already = *state; - - switch (((char *) save) [0]) { - case 1: c1 = ((unsigned char *) save) [1]; goto skip1; - case 2: c1 = ((unsigned char *) save) [1]; - c2 = ((unsigned char *) save) [2]; goto skip2; - } - - /* - * yes, we jump into the loop, no i'm not going to change it, - * it's beautiful! - */ - while (inptr < inend) { - c1 = *inptr++; - skip1: - c2 = *inptr++; - skip2: - c3 = *inptr++; - *outptr++ = base64_alphabet [ c1 >> 2 ]; - *outptr++ = base64_alphabet [ c2 >> 4 | - ((c1&0x3) << 4) ]; - *outptr++ = base64_alphabet [ ((c2 &0x0f) << 2) | - (c3 >> 6) ]; - *outptr++ = base64_alphabet [ c3 & 0x3f ]; - /* this is a bit ugly ... */ - if (break_lines && (++already)>=19) { - *outptr++='\n'; - already = 0; - } - } - - ((char *)save)[0] = 0; - len = 2-(inptr-inend); - *state = already; - } - - d(printf("state = %d, len = %d\n", - (int)((char *)save)[0], - len)); - - if (len>0) { - register char *saveout; - - /* points to the slot for the next char to save */ - saveout = & (((char *)save)[1]) + ((char *)save)[0]; - - /* len can only be 0 1 or 2 */ - switch(len) { - case 2: *saveout++ = *inptr++; - case 1: *saveout++ = *inptr++; - } - ((char *)save)[0]+=len; - } - - d(printf("mode = %d\nc1 = %c\nc2 = %c\n", - (int)((char *)save)[0], - (int)((char *)save)[1], - (int)((char *)save)[2])); - - return outptr-out; -} - - -/** - * base64_decode_step: decode a chunk of base64 encoded data - * @in: input stream - * @len: max length of data to decode - * @out: output stream - * @state: holds the number of bits that are stored in @save - * @save: leftover bits that have not yet been decoded - * - * Decodes a chunk of base64 encoded data - **/ -static int -base64_decode_step (unsigned char *in, - int len, - unsigned char *out, - int *state, - unsigned int *save) -{ - register unsigned char *inptr, *outptr; - unsigned char *inend, c; - register unsigned int v; - int i; - - inend = in+len; - outptr = out; - - /* convert 4 base64 bytes to 3 normal bytes */ - v=*save; - i=*state; - inptr = in; - while (inptr < inend) { - c = camel_mime_base64_rank [*inptr++]; - if (c != 0xff) { - v = (v<<6) | c; - i++; - if (i==4) { - *outptr++ = v>>16; - *outptr++ = v>>8; - *outptr++ = v; - i=0; - } - } - } - - *save = v; - *state = i; - - /* quick scan back for '=' on the end somewhere */ - /* fortunately we can drop 1 output char for each trailing = (upto 2) */ - i=2; - while (inptr > in && i) { - inptr--; - if (camel_mime_base64_rank [*inptr] != 0xff) { - if (*inptr == '=') - outptr--; - i--; - } - } - - /* if i!= 0 then there is a truncation error! */ - return outptr - out; -} - /* * MD4 encoder. (The one everyone else uses is not GPL-compatible; diff --git a/libsoup/soup-ntlm.h b/libsoup/soup-ntlm.h index 1f4eea2..3149805 100644 --- a/libsoup/soup-ntlm.h +++ b/libsoup/soup-ntlm.h @@ -19,8 +19,7 @@ void soup_ntlm_lanmanager_hash (const char *password, void soup_ntlm_nt_hash (const char *password, char hash[21]); -char *soup_ntlm_request (const char *host, - const char *domain); +char *soup_ntlm_request (void); char *soup_ntlm_response (const char *challenge, const char *user, diff --git a/libsoup/soup-openssl.c b/libsoup/soup-openssl.c index e6ddbe8..770b1ea 100644 --- a/libsoup/soup-openssl.c +++ b/libsoup/soup-openssl.c @@ -14,8 +14,15 @@ #ifdef HAVE_OPENSSL_SSL_H +#ifdef HAVE_UNISTD_H +#include +#endif + #include +#include + #include +#include #include #include "soup-openssl.h" @@ -71,6 +78,8 @@ soup_openssl_write (GIOChannel *channel, if (result < 0) { *bytes_written = 0; + if (SSL_get_error (chan->ssl, result) == SSL_ERROR_WANT_READ) + return G_IO_ERROR_AGAIN; switch (errno) { case EINVAL: return G_IO_ERROR_INVAL; @@ -195,6 +204,48 @@ GIOFuncs soup_openssl_channel_funcs = { static SSL_CTX *ssl_context = NULL; +#if SSL_LIBRARY_VERSION >= 0x00905100 +# define CHECK_OPENSSL_SEEDED RAND_status() +# define CHECK_OPENSSL_SEEDED_FINAL RAND_status() +#else +# define CHECK_OPENSSL_SEEDED FALSE +# define CHECK_OPENSSL_SEEDED_FINAL TRUE +#endif + +static gboolean +soup_openssl_seed (void) +{ + pid_t pid; + struct timeval tv; + guchar stack [1024], *heap; + + if (!CHECK_OPENSSL_SEEDED) { + /* Seed with pid */ + pid = getpid (); + RAND_seed ((guchar *) &pid, sizeof (pid_t)); + + /* Seed with current time */ + if (gettimeofday (&tv, NULL) == 0) + RAND_seed ((guchar *) &tv, sizeof (struct timeval)); + + /* Seed with untouched stack (1024) */ + RAND_seed (stack, sizeof (stack)); + + /* Quit now if we are adequately seeded */ + if (CHECK_OPENSSL_SEEDED) + return TRUE; + + /* Seed with untouched heap (1024) */ + heap = g_malloc (1024); + if (heap) + RAND_seed (heap, 1024); + g_free (heap); + + return CHECK_OPENSSL_SEEDED_FINAL; + } else + return TRUE; +} + GIOChannel * soup_openssl_get_iochannel (GIOChannel *sock) { @@ -207,10 +258,15 @@ soup_openssl_get_iochannel (GIOChannel *sock) g_return_val_if_fail (sock != NULL, NULL); - if (!ssl_context && !soup_openssl_init ()) goto THROW_CREATE_ERROR; + if (!ssl_context && !soup_openssl_init ()) + goto THROW_CREATE_ERROR; + + if (!soup_openssl_seed ()) + g_warning ("SSL random number seed failed."); sockfd = g_io_channel_unix_get_fd (sock); - if (!sockfd) goto THROW_CREATE_ERROR; + if (!sockfd) + goto THROW_CREATE_ERROR; ssl = SSL_new (ssl_context); if (!ssl) { diff --git a/libsoup/soup-private.h b/libsoup/soup-private.h index 5fc92a1..5500508 100644 --- a/libsoup/soup-private.h +++ b/libsoup/soup-private.h @@ -37,6 +37,8 @@ # endif #endif +#include + #ifdef HAVE_NETINET_IN_H #include #endif @@ -46,12 +48,13 @@ #endif #ifdef SOUP_WIN32 -#define VERSION "Win/0.6.99" +#define VERSION "Win/0.7.99" #include #include #include #endif +#include #include #include #include @@ -103,6 +106,7 @@ struct _SoupConnection { SoupContext *context; GIOChannel *channel; SoupSocket *socket; + SoupAuth *auth; guint port; gboolean in_use; guint last_used_id; @@ -110,39 +114,41 @@ struct _SoupConnection { guint death_tag; }; -struct _SoupMessagePrivate { - SoupConnectId connect_tag; - guint read_tag; - guint write_tag; - guint timeout_tag; +struct _SoupServer { + SoupProtocol proto; + gint port; - GString *req_header; + guint refcnt; + GMainLoop *loop; - SoupCallbackFn callback; - gpointer user_data; + guint accept_tag; + SoupSocket *listen_sock; - guint msg_flags; + GIOChannel *cgi_read_chan; + GIOChannel *cgi_write_chan; - GSList *content_handlers; + GHashTable *handlers; /* KEY: path, VALUE: SoupServerHandler */ + SoupServerHandler *default_handler; +}; - SoupHttpVersion http_version; +struct _SoupMessagePrivate { + SoupConnectId connect_tag; + guint read_tag; + guint write_tag; + guint timeout_tag; - SoupServer *server; - SoupSocket *server_sock; -}; + SoupCallbackFn callback; + gpointer user_data; -struct _SoupServer { - SoupProtocol proto; - gint port; + guint msg_flags; - GMainLoop *loop; + GSList *content_handlers; - guint accept_tag; - SoupSocket *sock; + SoupHttpVersion http_version; - GHashTable *handlers; - GSList *static_handlers; - SoupServerHandler default_handler; + SoupServer *server; + SoupSocket *server_sock; + SoupServerMessage *server_msg; }; /* from soup-message.c */ @@ -164,9 +170,6 @@ gint soup_substring_index (gchar *str, gint len, gchar *substr); -gchar *soup_base64_encode (const gchar *text, - gint len); - /* from soup-socket.c */ gboolean soup_gethostbyname (const gchar *hostname, diff --git a/libsoup/soup-queue.c b/libsoup/soup-queue.c index 0faa268..16492b9 100644 --- a/libsoup/soup-queue.c +++ b/libsoup/soup-queue.c @@ -27,9 +27,10 @@ #include "soup-misc.h" #include "soup-private.h" #include "soup-socks.h" +#include "soup-ssl.h" #include "soup-transfer.h" -GSList *soup_active_requests = NULL; +static GSList *soup_active_requests = NULL, *soup_active_request_next = NULL; static guint soup_queue_idle_tag = 0; @@ -80,9 +81,7 @@ soup_queue_error_cb (gboolean body_started, gpointer user_data) /* * FIXME: Use exponential backoff here */ - soup_message_queue (req, - req->priv->callback, - req->priv->user_data); + soup_message_requeue (req); } else { soup_message_set_error (req, SOUP_ERROR_IO); soup_message_issue_callback (req); @@ -96,27 +95,6 @@ soup_queue_error_cb (gboolean body_started, gpointer user_data) } } -static gboolean -soup_parse_headers (const GString *headers, - SoupHttpVersion *version, - SoupMessage *req) -{ - if (!soup_headers_parse_response (headers->str, - headers->len, - req->response_headers, - version, - &req->errorcode, - (gchar **) &req->errorphrase)) { - soup_message_set_error (req, SOUP_ERROR_MALFORMED); - soup_message_issue_callback (req); - return FALSE; - } - - req->errorclass = soup_error_get_class (req->errorcode); - - return TRUE; -} - static SoupTransferDone soup_queue_read_headers_cb (const GString *headers, SoupTransferEncoding *encoding, @@ -124,43 +102,65 @@ soup_queue_read_headers_cb (const GString *headers, gpointer user_data) { SoupMessage *req = user_data; - const gchar *connection, *length, *enc; SoupHttpVersion version; + GHashTable *resp_hdrs; + SoupMethodId meth_id; - if (!soup_parse_headers (headers, &version, req)) - return SOUP_TRANSFER_END; + if (!soup_headers_parse_response (headers->str, + headers->len, + req->response_headers, + &version, + &req->errorcode, + (gchar **) &req->errorphrase)) { + soup_message_set_error_full (req, + SOUP_ERROR_MALFORMED, + "Unable to parse response " + "headers"); + goto THROW_MALFORMED_HEADER; + } + + meth_id = soup_method_get_id (req->method); + resp_hdrs = req->response_headers; + + req->errorclass = soup_error_get_class (req->errorcode); /* - * Handle connection persistence + * Handle connection persistence + * Close connection if: + * - Connection header is "close" + * - HTTP 1.0 and Connection header is not present */ - connection = soup_message_get_header (req->response_headers, - "Connection"); + connection = soup_message_get_header (resp_hdrs, "Connection"); if ((connection && !g_strcasecmp (connection, "close")) || (!connection && version == SOUP_HTTP_1_0)) soup_connection_set_keep_alive (req->connection, FALSE); + /* + * Handle successful CONNECT request by keeping connection open + */ + if (meth_id == SOUP_METHOD_ID_CONNECT && !SOUP_MESSAGE_IS_ERROR (req)) + soup_connection_set_keep_alive (req->connection, TRUE); + /* - * Special case handling for: + * Special case zero body handling for: * - HEAD requests (where content-length must be ignored) + * - CONNECT requests (no body expected) * - 1xx Informational responses (where no body is allowed) */ - if (!g_strcasecmp (req->method, "HEAD") || + if (meth_id == SOUP_METHOD_ID_HEAD || + meth_id == SOUP_METHOD_ID_CONNECT || req->errorclass == SOUP_ERROR_CLASS_INFORMATIONAL) { - *encoding = SOUP_TRANSFER_CONTENT_LENGTH; - *content_len = 0; - goto RUN_HANDLERS; + *encoding = SOUP_TRANSFER_CONTENT_LENGTH; + *content_len = 0; + goto SUCCESS_CONTINUE; } /* - * Handle Content-Length or Chunked encoding + * Handle Content-Length encoding */ - length = soup_message_get_header (req->response_headers, - "Content-Length"); - enc = soup_message_get_header (req->response_headers, - "Transfer-Encoding"); - + length = soup_message_get_header (resp_hdrs, "Content-Length"); if (length) { *encoding = SOUP_TRANSFER_CONTENT_LENGTH; *content_len = atoi (length); @@ -169,9 +169,15 @@ soup_queue_read_headers_cb (const GString *headers, SOUP_ERROR_MALFORMED, "Invalid Content-Length"); goto THROW_MALFORMED_HEADER; - } + } + goto SUCCESS_CONTINUE; } - else if (enc) { + + /* + * Handle Chunked encoding + */ + enc = soup_message_get_header (resp_hdrs, "Transfer-Encoding"); + if (enc) { if (g_strcasecmp (enc, "chunked") == 0) *encoding = SOUP_TRANSFER_CHUNKED; else { @@ -181,15 +187,15 @@ soup_queue_read_headers_cb (const GString *headers, "Unknown Response Encoding"); goto THROW_MALFORMED_HEADER; } + goto SUCCESS_CONTINUE; } - RUN_HANDLERS: - if (soup_message_run_handlers (req, SOUP_HANDLER_HEADERS)) - return SOUP_TRANSFER_END; - + SUCCESS_CONTINUE: + soup_message_run_handlers (req, SOUP_HANDLER_HEADERS); return SOUP_TRANSFER_CONTINUE; THROW_MALFORMED_HEADER: + soup_connection_set_keep_alive (req->connection, FALSE); soup_message_issue_callback (req); return SOUP_TRANSFER_END; } @@ -204,8 +210,7 @@ soup_queue_read_chunk_cb (const SoupDataBuffer *data, req->response.length = data->length; req->response.body = data->body; - if (soup_message_run_handlers (req, SOUP_HANDLER_DATA)) - return SOUP_TRANSFER_END; + soup_message_run_handlers (req, SOUP_HANDLER_DATA); return SOUP_TRANSFER_CONTINUE; } @@ -255,7 +260,11 @@ soup_encode_http_auth (SoupMessage *msg, GString *header, gboolean proxy_auth) ctx = proxy_auth ? soup_get_proxy () : msg->context; - auth = soup_auth_lookup (ctx); + if (msg->connection->auth) + auth = msg->connection->auth; + else + auth = soup_auth_lookup (ctx); + if (auth) { token = soup_auth_authorize (auth, msg); if (token) { @@ -346,7 +355,15 @@ soup_get_request_header (SoupMessage *req) proxy = soup_get_proxy (); suri = soup_context_get_uri (req->context); - if (proxy) + if (!g_strcasecmp (req->method, "CONNECT")) + /* + * CONNECT URI is hostname:port for tunnel destination + */ + uri = g_strdup_printf ("%s:%d", suri->host, suri->port); + else if (proxy) + /* + * Proxy expects full URI to destination + */ uri = soup_uri_to_string (suri, FALSE); else if (suri->querystring) uri = g_strconcat (suri->path, "?", suri->querystring, NULL); @@ -359,7 +376,6 @@ soup_get_request_header (SoupMessage *req) "%s %s HTTP/1.0\r\n", req->method, uri); - g_free (uri); /* @@ -418,88 +434,176 @@ soup_queue_write_done_cb (gpointer user_data) } static void -soup_queue_connect_cb (SoupContext *ctx, - SoupConnectErrorCode err, - SoupConnection *conn, - gpointer user_data) +start_request (SoupContext *ctx, SoupMessage *req) { - SoupMessage *req = user_data; - SoupProtocol proto; GIOChannel *channel; gboolean overwrt; - req->priv->connect_tag = NULL; + channel = soup_connection_get_iochannel (req->connection); + if (!channel) { + SoupProtocol proto; + gchar *phrase; - switch (err) { - case SOUP_CONNECT_ERROR_NONE: proto = soup_context_get_uri (ctx)->protocol; - if (soup_connection_is_new (conn) && - (proto == SOUP_PROTOCOL_SOCKS4 || - proto == SOUP_PROTOCOL_SOCKS5)) { - soup_connect_socks_proxy (conn, - req->context, - soup_queue_connect_cb, - req); - return; - } + if (proto == SOUP_PROTOCOL_HTTPS) + phrase = "Unable to create secure data channel"; + else + phrase = "Unable to create data channel"; - channel = soup_connection_get_iochannel (conn); - if (!channel) { - gchar *phrase; - - if (proto == SOUP_PROTOCOL_HTTPS) - phrase = "Unable to create secure data channel"; - else - phrase = "Unable to create data channel"; - - if (ctx != req->context) - soup_message_set_error_full ( - req, - SOUP_ERROR_CANT_CONNECT_PROXY, - phrase); - else - soup_message_set_error_full ( - req, - SOUP_ERROR_CANT_CONNECT, - phrase); + if (ctx != req->context) + soup_message_set_error_full ( + req, + SOUP_ERROR_CANT_CONNECT_PROXY, + phrase); + else + soup_message_set_error_full ( + req, + SOUP_ERROR_CANT_CONNECT, + phrase); - soup_message_issue_callback (req); - return; - } + /* NOTE: Don't call soup_message_issue_callback here */ + return; + } - if (req->priv->req_header) { - g_string_free (req->priv->req_header, TRUE); - req->priv->req_header = NULL; - } + req->priv->write_tag = + soup_transfer_write_simple (channel, + soup_get_request_header (req), + &req->request, + soup_queue_write_done_cb, + soup_queue_error_cb, + req); - req->priv->req_header = soup_get_request_header (req); + overwrt = req->priv->msg_flags & SOUP_MESSAGE_OVERWRITE_CHUNKS; - req->priv->write_tag = - soup_transfer_write (channel, - req->priv->req_header, - &req->request, - NULL, - soup_queue_write_done_cb, - soup_queue_error_cb, - req); + req->priv->read_tag = + soup_transfer_read (channel, + overwrt, + soup_queue_read_headers_cb, + soup_queue_read_chunk_cb, + soup_queue_read_done_cb, + soup_queue_error_cb, + req); - overwrt = req->priv->msg_flags & SOUP_MESSAGE_OVERWRITE_CHUNKS; + g_io_channel_unref (channel); - req->priv->read_tag = - soup_transfer_read (channel, - overwrt, - soup_queue_read_headers_cb, - soup_queue_read_chunk_cb, - soup_queue_read_done_cb, - soup_queue_error_cb, - req); + req->status = SOUP_STATUS_SENDING_REQUEST; +} - g_io_channel_unref (channel); +static SoupHandlerResult +proxy_https_connect_cb (SoupMessage *msg, gpointer user_data) +{ + gboolean *ret = user_data; + + if (!SOUP_MESSAGE_IS_ERROR (msg)) { + /* + * Bless the connection to SSL + */ + msg->connection->channel = + soup_ssl_get_iochannel (msg->connection->channel); + + /* + * Avoid releasing the connection on message free + */ + msg->connection = NULL; + + *ret = TRUE; + } + + return SOUP_HANDLER_CONTINUE; +} + +static gboolean +proxy_https_connect (SoupContext *proxy, + SoupConnection *conn, + SoupContext *dest_ctx) +{ + SoupProtocol proxy_proto; + SoupMessage *connect_msg; + gboolean ret = FALSE; + + proxy_proto = soup_context_get_uri (proxy)->protocol; + + if (proxy_proto != SOUP_PROTOCOL_HTTP && + proxy_proto != SOUP_PROTOCOL_HTTPS) + return FALSE; + + connect_msg = soup_message_new (dest_ctx, SOUP_METHOD_CONNECT); + connect_msg->connection = conn; + soup_message_add_handler (connect_msg, + SOUP_HANDLER_FINISHED, + NULL, + proxy_https_connect_cb, + &ret); + soup_message_send (connect_msg); + soup_message_free (connect_msg); + + return ret; +} - req->status = SOUP_STATUS_SENDING_REQUEST; - req->connection = conn; +static gboolean +proxy_connect (SoupContext *ctx, SoupMessage *req, SoupConnection *conn) +{ + SoupProtocol proto, dest_proto; + + /* + * Only attempt proxy connect if the connection's context is different + * from the requested context, and if the connection is new + */ + if (ctx == req->context || !soup_connection_is_new (conn)) + return FALSE; + proto = soup_context_get_uri (ctx)->protocol; + dest_proto = soup_context_get_uri (req->context)->protocol; + + /* Handle SOCKS proxy negotiation */ + if ((proto == SOUP_PROTOCOL_SOCKS4 || proto == SOUP_PROTOCOL_SOCKS5)) { + soup_connect_socks_proxy (conn, + req->context, + soup_queue_connect_cb, + req); + return TRUE; + } + + /* Handle HTTPS tunnel setup via proxy CONNECT request. */ + if (dest_proto == SOUP_PROTOCOL_HTTPS) { + /* Syncronously send CONNECT request */ + if (!proxy_https_connect (ctx, conn, req->context)) { + soup_message_set_error_full ( + req, + SOUP_ERROR_CANT_CONNECT_PROXY, + "Unable to create secure data " + "tunnel through proxy"); + + /* NOTE: Don't call soup_message_issue_callback here */ + return TRUE; + } + } + + return FALSE; +} + +void +soup_queue_connect_cb (SoupContext *ctx, + SoupConnectErrorCode err, + SoupConnection *conn, + gpointer user_data) +{ + SoupMessage *req = user_data; + + req->priv->connect_tag = NULL; + req->connection = conn; + + switch (err) { + case SOUP_CONNECT_ERROR_NONE: + /* + * NOTE: proxy_connect will either set an error or call us + * again after proxy negotiation. + */ + if (proxy_connect (ctx, req, conn)) + return; + + start_request (ctx, req); break; case SOUP_CONNECT_ERROR_ADDR_RESOLVE: @@ -514,7 +618,7 @@ soup_queue_connect_cb (SoupContext *ctx, SOUP_ERROR_CANT_CONNECT, "Unable to resolve hostname"); - soup_message_issue_callback (req); + /* NOTE: Don't call soup_message_issue_callback here */ break; case SOUP_CONNECT_ERROR_NETWORK: @@ -525,20 +629,55 @@ soup_queue_connect_cb (SoupContext *ctx, soup_message_set_error (req, SOUP_ERROR_CANT_CONNECT); - soup_message_issue_callback (req); + /* NOTE: Don't call soup_message_issue_callback here */ break; } return; } +void +soup_queue_add_request (SoupMessage *req) +{ + soup_active_requests = g_slist_prepend (soup_active_requests, req); +} + +void +soup_queue_remove_request (SoupMessage *req) +{ + if (soup_active_request_next && soup_active_request_next->data == req) + soup_queue_next_request (); + soup_active_requests = g_slist_remove (soup_active_requests, req); +} + +SoupMessage * +soup_queue_first_request (void) +{ + if (!soup_active_requests) + return NULL; + + soup_active_request_next = soup_active_requests->next; + return soup_active_requests->data; +} + +SoupMessage * +soup_queue_next_request (void) +{ + SoupMessage *ret; + + if (!soup_active_request_next) + return NULL; + ret = soup_active_request_next->data; + soup_active_request_next = soup_active_request_next->next; + return ret; +} + static gboolean soup_idle_handle_new_requests (gpointer unused) { - GSList *iter; + SoupMessage *req = soup_queue_first_request (); - for (iter = soup_active_requests; iter; iter = iter->next) { - SoupMessage *req = iter->data; + for (; req; req = soup_queue_next_request ()) { SoupContext *ctx, *proxy; if (req->status != SOUP_STATUS_QUEUED) @@ -548,36 +687,63 @@ soup_idle_handle_new_requests (gpointer unused) ctx = proxy ? proxy : req->context; req->status = SOUP_STATUS_CONNECTING; - req->priv->connect_tag = - soup_context_get_connection (ctx, - soup_queue_connect_cb, - req); + + if (req->connection && + soup_connection_is_keep_alive (req->connection)) + start_request (ctx, req); + else { + gpointer connect_tag; + + connect_tag = + soup_context_get_connection ( + ctx, + soup_queue_connect_cb, + req); + + if (req->errorcode) + soup_message_issue_callback (req); + else if (connect_tag) + req->priv->connect_tag = connect_tag; + } } soup_queue_idle_tag = 0; return FALSE; } -void -soup_queue_message (SoupMessage *req, - SoupCallbackFn callback, - gpointer user_data) +static void +soup_queue_initialize (void) { - g_return_if_fail (req != NULL); - if (!soup_initialized) soup_load_config (NULL); if (!soup_queue_idle_tag) soup_queue_idle_tag = g_idle_add (soup_idle_handle_new_requests, NULL); +} - if (req->status != SOUP_STATUS_IDLE) - soup_message_cleanup (req); +void +soup_queue_message (SoupMessage *req, + SoupCallbackFn callback, + gpointer user_data) +{ + g_return_if_fail (req != NULL); req->priv->callback = callback; req->priv->user_data = user_data; + if (!req->context) { + soup_message_set_error_full (req, + SOUP_ERROR_CANCELLED, + "Attempted to queue a message " + "with no destination context"); + soup_message_issue_callback (req); + return; + } + + if (req->status != SOUP_STATUS_IDLE) + soup_message_cleanup (req); + switch (req->response.owner) { case SOUP_BUFFER_USER_OWNED: soup_message_set_error_full (req, @@ -587,11 +753,9 @@ soup_queue_message (SoupMessage *req, "buffer."); soup_message_issue_callback (req); return; - case SOUP_BUFFER_SYSTEM_OWNED: g_free (req->response.body); break; - case SOUP_BUFFER_STATIC: break; } @@ -602,17 +766,19 @@ soup_queue_message (SoupMessage *req, soup_message_clear_headers (req->response_headers); + req->errorcode = 0; + req->errorclass = 0; + if (req->errorphrase) { g_free ((gchar *) req->errorphrase); req->errorphrase = NULL; } - req->errorcode = 0; - req->errorclass = 0; - req->status = SOUP_STATUS_QUEUED; - soup_active_requests = g_slist_prepend (soup_active_requests, req); + soup_queue_add_request (req); + + soup_queue_initialize (); } /** @@ -624,11 +790,16 @@ soup_queue_message (SoupMessage *req, void soup_queue_shutdown (void) { - GSList *iter; + SoupMessage *req; - g_source_remove (soup_queue_idle_tag); - soup_queue_idle_tag = 0; + soup_initialized = FALSE; + + if (soup_queue_idle_tag) { + g_source_remove (soup_queue_idle_tag); + soup_queue_idle_tag = 0; + } - for (iter = soup_active_requests; iter; iter = iter->next) - soup_message_cancel (iter->data); + req = soup_queue_first_request (); + for (; req; req = soup_queue_next_request ()) + soup_message_cancel (req); } diff --git a/libsoup/soup-queue.h b/libsoup/soup-queue.h index c6b1718..30ba5d3 100644 --- a/libsoup/soup-queue.h +++ b/libsoup/soup-queue.h @@ -15,10 +15,20 @@ #include -void soup_queue_message (SoupMessage *req, - SoupCallbackFn callback, - gpointer user_data); +void soup_queue_message (SoupMessage *req, + SoupCallbackFn callback, + gpointer user_data); -void soup_queue_shutdown (void); +void soup_queue_connect_cb (SoupContext *ctx, + SoupConnectErrorCode err, + SoupConnection *conn, + gpointer user_data); + +void soup_queue_add_request (SoupMessage *req); +void soup_queue_remove_request (SoupMessage *req); +SoupMessage *soup_queue_first_request (void); +SoupMessage *soup_queue_next_request (void); + +void soup_queue_shutdown (void); #endif /* SOUP_QUEUE_H */ diff --git a/libsoup/soup-server.c b/libsoup/soup-server.c index 81ab981..6b6087d 100644 --- a/libsoup/soup-server.c +++ b/libsoup/soup-server.c @@ -8,10 +8,34 @@ * Copyright (C) 2001, Ximian, Inc. */ +/* + * FIXME: Split into soup-server-cgi.[ch] and soup-server-dyn.[ch] + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + #ifdef HAVE_UNISTD_H #include #endif +#ifdef HAVE_SYS_FILIO_H +#include +#endif + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +#ifdef SOUP_WIN32 +#define ioctl ioctlsocket +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#endif + +extern char **environ; + #include #include #include @@ -25,71 +49,121 @@ #define SOUP_PROTOCOL_CGI 0xff -SoupServer cgi_server = { - SOUP_PROTOCOL_CGI -}; - -SoupServer httpd_server = { - SOUP_PROTOCOL_HTTP -}; - -SoupServer httpd_ssl_server = { - SOUP_PROTOCOL_HTTPS +struct _SoupServerMessage { + SoupMessage *msg; + GSList *chunks; /* CONTAINS: SoupDataBuffer* */ + gboolean started; + gboolean finished; }; -SoupServer *SOUP_CGI_SERVER = &cgi_server; -SoupServer *SOUP_HTTPD_SERVER = &httpd_server; -SoupServer *SOUP_HTTPD_SSL_SERVER = &httpd_ssl_server; - SoupServer * soup_server_new (SoupProtocol proto, guint port) { SoupServer *serv; SoupSocket *sock = NULL; + GIOChannel *read_chan = NULL, *write_chan = NULL; - if (proto != SOUP_PROTOCOL_CGI) { + if (proto == SOUP_PROTOCOL_CGI) { + read_chan = g_io_channel_unix_new (STDIN_FILENO); + if (!read_chan) return NULL; + + write_chan = g_io_channel_unix_new (STDOUT_FILENO); + if (!write_chan) { + g_io_channel_unref (read_chan); + return NULL; + } + } else { sock = soup_socket_server_new (port); if (!sock) return NULL; + + port = soup_socket_get_port (sock); } serv = g_new0 (SoupServer, 1); - serv->port = soup_socket_get_port (sock); + serv->refcnt = 1; + serv->port = port; serv->proto = proto; - serv->sock = sock; + serv->listen_sock = sock; + serv->cgi_read_chan = read_chan; + serv->cgi_write_chan = write_chan; return serv; } -static gboolean -free_handler (char *path, SoupServerHandler *hand) +SoupServer * +soup_server_cgi (void) +{ + static SoupServer *cgi = NULL; + + if (!cgi) + cgi = soup_server_new (SOUP_PROTOCOL_CGI, 0); + + return cgi; +} + +static void +free_handler (SoupServer *server, SoupServerHandler *hand) { - g_free (hand->path); + if (hand->unregister) + (*hand->unregister) (server, hand, hand->user_data); + + if (hand->auth_ctx) { + g_free ((gchar *) hand->auth_ctx->basic_info.realm); + g_free ((gchar *) hand->auth_ctx->digest_info.realm); + g_free (hand->auth_ctx); + } + + g_free ((gchar *) hand->path); g_free (hand); +} +static gboolean +free_handler_foreach (gchar *key, SoupServerHandler *hand, SoupServer *server) +{ + free_handler (server, hand); return TRUE; } void -soup_server_free (SoupServer *serv) +soup_server_ref (SoupServer *serv) { g_return_if_fail (serv != NULL); - if (serv->sock) - soup_socket_unref (serv->sock); + ++serv->refcnt; +} + +void +soup_server_unref (SoupServer *serv) +{ + g_return_if_fail (serv != NULL); - g_hash_table_foreach_remove (serv->handlers, - (GHRFunc) free_handler, - NULL); - g_hash_table_destroy (serv->handlers); + --serv->refcnt; - g_slist_free (serv->static_handlers); + if (serv->refcnt == 0) { + if (serv->accept_tag) + g_source_remove (serv->accept_tag); - if (serv->accept_tag) - g_source_remove (serv->accept_tag); + if (serv->listen_sock) + soup_socket_unref (serv->listen_sock); - g_main_destroy (serv->loop); + if (serv->cgi_read_chan) + g_io_channel_unref (serv->cgi_read_chan); - g_free (serv); + if (serv->cgi_write_chan) + g_io_channel_unref (serv->cgi_write_chan); + + if (serv->default_handler) + free_handler (serv, serv->default_handler); + + g_hash_table_foreach_remove (serv->handlers, + (GHRFunc) free_handler_foreach, + serv); + g_hash_table_destroy (serv->handlers); + + g_main_destroy (serv->loop); + + g_free (serv); + } } gint @@ -99,34 +173,244 @@ soup_server_get_port (SoupServer *serv) return serv->port; } -static inline void -destroy_message (SoupMessage *req) +SoupProtocol +soup_server_get_protocol (SoupServer *serv) +{ + g_return_val_if_fail (serv != NULL, 0); + return serv->proto; +} + +static void +free_chunk (gpointer chunk, gpointer notused) +{ + SoupDataBuffer *buf = chunk; + + if (buf->owner == SOUP_BUFFER_SYSTEM_OWNED) + g_free (buf->body); + + g_free (buf); +} + +typedef struct { + SoupServer *server; + SoupSocket *server_sock; +} ServerConnectData; + +static gboolean start_another_request (GIOChannel *serv_chan, + GIOCondition condition, + gpointer user_data); + +static void +destroy_message (SoupMessage *msg) { - soup_socket_unref (req->priv->server_sock); + SoupServer *server = msg->priv->server; + SoupSocket *server_sock = msg->priv->server_sock; + SoupServerMessage *server_msg = msg->priv->server_msg; + + if (server_sock) { + if (server_msg && msg->priv->http_version == SOUP_HTTP_1_0) + /* + * Close the socket if we are using HTTP/1.0 and + * did not specify a Content-Length response header. + */ + soup_socket_unref (server_sock); + else { + /* + * Listen for another request on this connection + */ + ServerConnectData *data; + GIOChannel *chan; + + data = g_new0 (ServerConnectData, 1); + data->server = msg->priv->server; + data->server_sock = server_sock; + + chan = soup_socket_get_iochannel (server_sock); + g_io_add_watch (chan, + G_IO_IN|G_IO_PRI| + G_IO_ERR|G_IO_HUP|G_IO_NVAL, + start_another_request, + data); + g_io_channel_unref (chan); + } + } + + if (server_msg) { + g_slist_foreach (server_msg->chunks, free_chunk, NULL); + g_slist_free (server_msg->chunks); + g_free (server_msg); + } - g_free ((gchar *) req->method); + /* + * If CGI, service one message and quit + */ + if (server->proto == SOUP_PROTOCOL_CGI) + g_main_quit (server->loop); - if (req->priv->server->proto == SOUP_PROTOCOL_CGI) - g_main_quit (req->priv->server->loop); + soup_server_unref (server); - soup_message_free (req); + g_free ((gchar *) msg->method); + soup_message_free (msg); } static void error_cb (gboolean body_started, gpointer user_data) { - SoupMessage *req = user_data; + SoupMessage *msg = user_data; - destroy_message (req); + destroy_message (msg); } static void write_done_cb (gpointer user_data) { - SoupMessage *req = user_data; + SoupMessage *msg = user_data; + + msg->priv->write_tag = 0; + destroy_message (msg); +} + +static SoupTransferDone +read_headers_cgi (SoupMessage *msg, + gint *content_len) +{ + SoupContext *ctx; - req->priv->write_tag = 0; - destroy_message (req); + /* + * Get request HTTP method + */ + (gchar *) msg->method = g_strdup (g_getenv ("REQUEST_METHOD")); + + /* + * Get content length of request body + */ + { + const gchar *length; + length = g_getenv ("CONTENT_LENGTH"); + + *content_len = length ? atoi (length) : 0; + } + + /* + * Determine request HTTP version + */ + { + const gchar *proto; + proto = g_getenv ("SERVER_PROTOCOL"); + if (proto) { + if (!g_strcasecmp (proto, "HTTP/1.1")) + msg->priv->http_version = SOUP_HTTP_1_1; + else + msg->priv->http_version = SOUP_HTTP_1_0; + } else + msg->priv->http_version = SOUP_HTTP_1_0; + } + + /* + * Generate correct context for request + */ + { + const gchar *host, *https; + gchar *url; + + host = g_getenv ("HTTP_HOST"); + if (!host) + host = g_getenv ("SERVER_ADDR"); + + /* + * MS IIS sets $HTTPS to "off" if not using HTTPS + */ + https = g_getenv ("HTTPS"); + if (https && !g_strcasecmp (https, "OFF")) + https = NULL; + + url = g_strconcat (https ? "https://" : "http://", + host, + ":", + g_getenv ("SERVER_PORT"), + g_getenv ("REQUEST_URI"), + NULL); + + ctx = soup_context_get (url); + g_free (url); + + if (!ctx) goto THROW_MALFORMED_HEADER; + + soup_message_set_context (msg, ctx); + soup_context_unref (ctx); + } + + /* + * Load request headers from environment. Header environment variables + * are of the form "HTTP_=" + */ + { + gint iter; + for (iter = 0; environ [iter]; iter++) { + gchar *env = environ [iter]; + + if (!strncmp (env, "HTTP_", 5)) { + gchar *cpy, *iter; + + cpy = iter = g_strdup (env + 5); + + if (!cpy) + continue; + + /* + * Replace '_' with '-' in header names + */ + while (*iter && *iter != '=') { + if (*iter == '_') + *iter = '-'; + iter++; + } + + if (*cpy && *iter) { + /* + * Skip '=' between key and value + */ + *iter++ = '\0'; + + soup_message_add_header ( + msg->request_headers, + cpy, + iter); + } + + g_free (cpy); + } + } + } + + return SOUP_TRANSFER_CONTINUE; + + THROW_MALFORMED_HEADER: + destroy_message (msg); + + return SOUP_TRANSFER_END; +} + +#define SOUP_SOCKADDR_IN(s) (*((struct sockaddr_in*) &s)) + +static gchar * +get_server_sockname (gint fd) +{ + struct sockaddr name; + int namelen; + gchar *host = NULL; + guchar *p; + + if (getsockname (fd, &name, &namelen) == 0) { + p = (guchar*) &(SOUP_SOCKADDR_IN(name).sin_addr); + host = g_strdup_printf ("%d.%d.%d.%d", + p [0], + p [1], + p [2], + p [3]); + } + + return host; } static SoupTransferDone @@ -137,8 +421,7 @@ read_headers_cb (const GString *headers, { SoupMessage *msg = user_data; SoupContext *ctx; - gchar *req_path = NULL, *url; - const gchar *connection, *length, *enc, *req_host = NULL; + gchar *req_path = NULL; if (!soup_headers_parse_request (headers->str, headers->len, @@ -148,57 +431,98 @@ read_headers_cb (const GString *headers, &msg->priv->http_version)) goto THROW_MALFORMED_HEADER; - /* Handle connection persistence */ - connection = soup_message_get_header (msg->request_headers, - "Connection"); - - /* FIXME: Make this work. - if (connection && g_strcasecmp (connection, "close") == 0) - soup_connection_set_keep_alive (req->connection, FALSE); - */ - - /* Handle Content-Length or Chunked encoding */ - length = soup_message_get_header (msg->request_headers, - "Content-Length"); - enc = soup_message_get_header (msg->request_headers, - "Transfer-Encoding"); - - if (length) { - *encoding = SOUP_TRANSFER_CONTENT_LENGTH; - *content_len = atoi (length); - if (*content_len < 0) - goto THROW_MALFORMED_HEADER; - } else if (enc) { - if (g_strcasecmp (enc, "chunked") == 0) - *encoding = SOUP_TRANSFER_CHUNKED; - else { - g_warning ("Unknown encoding type in HTTP request."); - goto THROW_MALFORMED_HEADER; + /* + * Handle request body encoding + */ + { + const gchar *length, *enc; + + /* Handle Content-Length or Chunked encoding */ + length = soup_message_get_header (msg->request_headers, + "Content-Length"); + enc = soup_message_get_header (msg->request_headers, + "Transfer-Encoding"); + + if (enc) { + if (g_strcasecmp (enc, "chunked") == 0) + *encoding = SOUP_TRANSFER_CHUNKED; + else { + g_warning ("Unknown encoding type in HTTP " + "request."); + goto THROW_MALFORMED_HEADER; + } + } else if (length) { + *encoding = SOUP_TRANSFER_CONTENT_LENGTH; + *content_len = atoi (length); + if (*content_len < 0) + goto THROW_MALFORMED_HEADER; + } else { + *encoding = SOUP_TRANSFER_CONTENT_LENGTH; + *content_len = 0; } } - /* Generate correct context for request */ - req_host = soup_message_get_header (msg->request_headers, "Host"); - if (req_host) - url = g_strconcat ("http://", req_host, req_path, NULL); - else - url = g_strdup (req_path); - - ctx = soup_context_get (url); - g_free (url); + /* + * Generate correct context for request + */ + { + gchar *url = NULL; + const gchar *req_host = NULL; + SoupServer *server = msg->priv->server; + + req_host = soup_message_get_header (msg->request_headers, + "Host"); + + if (req_host) { + url = + g_strdup_printf ( + "%s%s:%d%s", + server->proto == SOUP_PROTOCOL_HTTPS ? + "https://" : + "http://", + req_host, + server->port, + req_path); + } else if (*req_path != '/') { + /* + * Check for absolute URI + */ + SoupUri *absolute; + + absolute = soup_uri_new (req_path); + if (absolute) { + url = g_strdup (req_path); + soup_uri_free (absolute); + } else + goto THROW_MALFORMED_HEADER; + } else { + /* + * No Host header, no AbsoluteUri + */ + SoupSocket *server_sock = msg->priv->server_sock; + gchar *host; + + host = get_server_sockname (server_sock->sockfd); + url = + g_strdup_printf ( + "%s%s:%d%s", + server->proto == + SOUP_PROTOCOL_HTTPS ? + "https://" : + "http://", + host ? host : "localhost", + server->port, + req_path); + } - /* No Host, no AbsoluteUri */ - if (!ctx) { - /* FIXME: Get local socket host name */ - url = g_strconcat ("http://localhost/", req_path, NULL); ctx = soup_context_get (url); g_free (url); - } - if (!ctx) goto THROW_MALFORMED_HEADER; + if (!ctx) goto THROW_MALFORMED_HEADER; - soup_message_set_context (msg, ctx); - soup_context_unref (ctx); + soup_message_set_context (msg, ctx); + soup_context_unref (ctx); + } g_free (req_path); @@ -213,34 +537,39 @@ read_headers_cb (const GString *headers, } static void -write_header (gchar *key, GSList *vals, SoupMessage *msg) -{ - while (vals) { - g_string_sprintfa (msg->priv->req_header, - "%s: %s\r\n", - key, - (gchar *) vals->data); - vals = vals->next; - } +write_header (gchar *key, gchar *value, GString *ret) +{ + g_string_sprintfa (ret, "%s: %s\r\n", key, value); } static GString * -get_response_header (SoupMessage *req) +get_response_header (SoupMessage *req, + gboolean status_line, + SoupTransferEncoding encoding) { GString *ret = g_string_new (NULL); - g_string_sprintfa (ret, - "HTTP/1.1 %d %s\r\n", - req->errorcode, - req->errorphrase); - - g_string_sprintfa (ret, - "Content-Length: %d\r\n", - req->response.length); - - g_hash_table_foreach (req->response_headers, - (GHFunc) write_header, - req); + if (status_line) + g_string_sprintfa (ret, + "HTTP/1.1 %d %s\r\n", + req->errorcode, + req->errorphrase); + else + g_string_sprintfa (ret, + "Status: %d %s\r\n", + req->errorcode, + req->errorphrase); + + if (encoding == SOUP_TRANSFER_CONTENT_LENGTH) + g_string_sprintfa (ret, + "Content-Length: %d\r\n", + req->response.length); + else if (encoding == SOUP_TRANSFER_CHUNKED) + g_string_append (ret, "Transfer-Encoding: chunked\r\n"); + + soup_message_foreach_header (req->response_headers, + (GHFunc) write_header, + ret); g_string_append (ret, "\r\n"); @@ -264,19 +593,204 @@ set_response_error (SoupMessage *req, } static void +call_handler (SoupMessage *req, + const SoupDataBuffer *req_data, + const gchar *handler_path) +{ + SoupServer *server = req->priv->server; + SoupServerHandler *hand; + SoupServerAuth *auth = NULL; + + g_return_if_fail (req != NULL); + + req->request.owner = req_data->owner; + req->request.length = req_data->length; + req->request.body = req_data->body; + + req->status = SOUP_STATUS_FINISHED; + + hand = soup_server_get_handler (server, handler_path); + if (!hand) { + set_response_error (req, SOUP_ERROR_NOT_FOUND, NULL, NULL); + return; + } + + if (hand->auth_ctx) { + SoupServerAuthContext *auth_ctx = hand->auth_ctx; + const GSList *auth_hdrs; + + auth_hdrs = soup_message_get_header_list (req->request_headers, + "Authorization"); + auth = soup_server_auth_new (auth_ctx, auth_hdrs, req); + + if (auth_ctx->callback) { + gboolean ret = FALSE; + + ret = (*auth_ctx->callback) (auth_ctx, + auth, + req, + auth_ctx->user_data); + if (!ret) { + soup_server_auth_context_challenge ( + auth_ctx, + req, + "WWW-Authenticate"); + + if (!req->errorcode) + soup_message_set_error ( + req, + SOUP_ERROR_UNAUTHORIZED); + + return; + } + } else if (req->errorcode) { + soup_server_auth_context_challenge ( + auth_ctx, + req, + "WWW-Authenticate"); + return; + } + } + + if (hand->callback) { + SoupServerContext servctx = { + req, + req->context->uri->path, + soup_method_get_id (req->method), + auth, + server, + hand + }; + + /* Call method handler */ + (*hand->callback) (&servctx, req, hand->user_data); + } + + if (auth) + soup_server_auth_free (auth); +} + +static void +get_header_cgi_cb (GString **out_hdr, + gpointer user_data) +{ + SoupMessage *msg = user_data; + SoupServerMessage *server_msg = msg->priv->server_msg; + + if (server_msg && server_msg->started) + *out_hdr = get_response_header (msg, + FALSE, + SOUP_TRANSFER_UNKNOWN); + else + soup_transfer_write_pause (msg->priv->write_tag); +} + +static void +get_header_cb (GString **out_hdr, + gpointer user_data) +{ + SoupMessage *msg = user_data; + SoupServerMessage *server_msg = msg->priv->server_msg; + SoupTransferEncoding encoding; + + if (server_msg && server_msg->started) { + if (msg->priv->http_version == SOUP_HTTP_1_0) + encoding = SOUP_TRANSFER_UNKNOWN; + else + encoding = SOUP_TRANSFER_CHUNKED; + + *out_hdr = get_response_header (msg, TRUE, encoding); + } else + soup_transfer_write_pause (msg->priv->write_tag); +} + +static SoupTransferDone +get_chunk_cb (SoupDataBuffer *out_next, gpointer user_data) +{ + SoupMessage *msg = user_data; + SoupServerMessage *server_msg = msg->priv->server_msg; + + if (server_msg->chunks) { + SoupDataBuffer *next = server_msg->chunks->data; + + out_next->owner = next->owner; + out_next->body = next->body; + out_next->length = next->length; + + server_msg->chunks = g_slist_remove (server_msg->chunks, next); + + /* + * Caller will free the response body, so just free the + * SoupDataBuffer struct. + */ + g_free (next); + + return SOUP_TRANSFER_CONTINUE; + } + else if (server_msg->finished) { + return SOUP_TRANSFER_END; + } + else { + soup_transfer_write_pause (msg->priv->write_tag); + return SOUP_TRANSFER_CONTINUE; + } +} + +static void +read_done_cgi_cb (const SoupDataBuffer *data, + gpointer user_data) +{ + SoupMessage *req = user_data; + SoupServer *server = req->priv->server; + GIOChannel *channel; + + req->priv->read_tag = 0; + + call_handler (req, data, g_getenv ("PATH_INFO")); + + channel = server->cgi_write_chan; + + if (req->priv->server_msg) { + req->priv->write_tag = + soup_transfer_write (channel, + SOUP_TRANSFER_UNKNOWN, + get_header_cgi_cb, + get_chunk_cb, + write_done_cb, + error_cb, + req); + + /* + * Pause write until soup_server_message_start() + */ + if (!req->priv->server_msg->started) + soup_transfer_write_pause (req->priv->write_tag); + } else { + GString *header; + header = get_response_header (req, + FALSE, + SOUP_TRANSFER_CONTENT_LENGTH); + req->priv->write_tag = + soup_transfer_write_simple (channel, + header, + &req->response, + write_done_cb, + error_cb, + req); + } + + return; +} + +static void read_done_cb (const SoupDataBuffer *data, gpointer user_data) { SoupMessage *req = user_data; - SoupServerHandler *hand; + SoupSocket *server_sock = req->priv->server_sock; GIOChannel *channel; - const gchar *path; - - req->request.owner = data->owner; - req->request.length = data->length; - req->request.body = data->body; - req->status = SOUP_STATUS_FINISHED; + req->priv->read_tag = 0; /* FIXME: Do this in soap handler action = soup_message_get_header (req->request_headers, "SOAPAction"); @@ -291,164 +805,336 @@ read_done_cb (const SoupDataBuffer *data, } */ - path = soup_context_get_uri (req->context)->path; - - hand = soup_server_get_handler (req->priv->server, path); - if (!hand) { - if (req->priv->server->default_handler.cb) - hand = &req->priv->server->default_handler; - else { - set_response_error (req, 404, NULL, NULL); - goto START_WRITE; - } + call_handler (req, data, soup_context_get_uri (req->context)->path); + + channel = soup_socket_get_iochannel (server_sock); + + if (req->priv->server_msg) { + SoupTransferEncoding encoding; + + if (req->priv->http_version == SOUP_HTTP_1_0) + encoding = SOUP_TRANSFER_UNKNOWN; + else + encoding = SOUP_TRANSFER_CHUNKED; + + req->priv->write_tag = + soup_transfer_write (channel, + encoding, + get_header_cb, + get_chunk_cb, + write_done_cb, + error_cb, + req); + + /* + * Pause write until soup_server_message_start() + */ + if (!req->priv->server_msg->started) + soup_transfer_write_pause (req->priv->write_tag); + } else { + GString *header; + header = get_response_header (req, + TRUE, + SOUP_TRANSFER_CONTENT_LENGTH); + req->priv->write_tag = + soup_transfer_write_simple (channel, + header, + &req->response, + write_done_cb, + error_cb, + req); } - /* Call method handler */ - if (hand->cb) (*hand->cb) (req, NULL, hand->user_data); + g_io_channel_unref (channel); - START_WRITE: - channel = soup_socket_get_iochannel (req->priv->server_sock); + return; +} - req->priv->req_header = get_response_header (req); - req->priv->read_tag = 0; - req->priv->write_tag = - soup_transfer_write (channel, - req->priv->req_header, - &req->response, - NULL, - write_done_cb, - error_cb, - req); +static SoupMessage * +message_new (SoupServer *server) +{ + SoupMessage *msg; - g_io_channel_unref (channel); + /* + * Create an empty message to hold request state. + */ + msg = soup_message_new (NULL, NULL); + if (msg) { + msg->priv->server = server; + soup_server_ref (server); + } - return; + return msg; } -static void -conn_accept (GIOChannel *chan, +static gboolean +start_another_request (GIOChannel *serv_chan, + GIOCondition condition, + gpointer user_data) +{ + ServerConnectData *data = user_data; + SoupMessage *msg; + int fd, cnt; + + fd = g_io_channel_unix_get_fd (serv_chan); + + if (!(condition & G_IO_IN) || + ioctl (fd, FIONREAD, &cnt) < 0 || + cnt <= 0) + soup_socket_unref (data->server_sock); + else { + msg = message_new (data->server); + if (!msg) { + g_warning ("Unable to create new incoming message\n"); + soup_socket_unref (data->server_sock); + } else { + msg->priv->server_sock = data->server_sock; + msg->priv->read_tag = + soup_transfer_read (serv_chan, + FALSE, + read_headers_cb, + NULL, + read_done_cb, + error_cb, + msg); + } + } + + g_free (data); + return FALSE; +} + +static gboolean +conn_accept (GIOChannel *serv_chan, GIOCondition condition, - SoupServer *serv) + gpointer user_data) { - GIOChannel *channel; - SoupSocket *sock; - SoupContext *ctx; + SoupServer *server = user_data; SoupMessage *msg; - SoupUri uri = { - serv->proto, - NULL, - NULL, - NULL, - "localhost", - serv->port, - "/", - NULL, - NULL - }; - - sock = soup_socket_server_try_accept (serv->sock); - if (!sock) return; - - channel = soup_socket_get_iochannel (sock); + GIOChannel *chan; + SoupSocket *sock; - /* - * Create a fake context until the request is read - * and we can generate a valid one. - */ - ctx = soup_context_from_uri (&uri); - msg = soup_message_new (ctx, NULL); + sock = soup_socket_server_try_accept (server->listen_sock); + if (!sock) return TRUE; - msg->priv->server = serv; - msg->priv->server_sock = sock; + msg = message_new (server); + if (!msg) { + g_warning ("Unable to create new incoming message\n"); + return TRUE; + } chan = soup_socket_get_iochannel (sock); - if (serv->proto == SOUP_PROTOCOL_HTTPS) + if (server->proto == SOUP_PROTOCOL_HTTPS) { chan = soup_ssl_get_iochannel (chan); + g_io_channel_unref (sock->iochannel); + g_io_channel_ref (chan); + sock->iochannel = chan; + } + msg->priv->server_sock = sock; msg->priv->read_tag = - soup_transfer_read ( - chan, - FALSE, - read_headers_cb, - NULL, - read_done_cb, - error_cb, - msg); + soup_transfer_read (chan, + FALSE, + read_headers_cb, + NULL, + read_done_cb, + error_cb, + msg); g_io_channel_unref (chan); + + return TRUE; +} + +typedef struct { + SoupMessage *msg; + guint content_len; + GByteArray *recv_buf; +} CgiReader; + +static gboolean +cgi_read (GIOChannel *serv_chan, + GIOCondition condition, + gpointer user_data) +{ + CgiReader *reader = user_data; + + if (!(condition & G_IO_IN)) + goto DONE_READING; + else { + while (reader->recv_buf->len < reader->content_len) { + guchar read_buf [RESPONSE_BLOCK_SIZE]; + gint bytes_read; + GIOError error; + + error = g_io_channel_read (serv_chan, + read_buf, + sizeof (read_buf), + &bytes_read); + + if (error == G_IO_ERROR_AGAIN) + return TRUE; + + if (error != G_IO_ERROR_NONE) + goto DONE_READING; + + if (bytes_read) + g_byte_array_append (reader->recv_buf, + read_buf, + bytes_read); + else + break; + } + } + + DONE_READING: + if (reader->recv_buf->len == reader->content_len) { + SoupDataBuffer buf; + + g_byte_array_append (reader->recv_buf, "\0", 1); + + buf.owner = SOUP_BUFFER_SYSTEM_OWNED; + buf.body = reader->recv_buf->data; + buf.length = reader->recv_buf->len; + + read_done_cgi_cb (&buf, reader->msg); + + g_byte_array_free (reader->recv_buf, FALSE); + } else + g_byte_array_free (reader->recv_buf, TRUE); + + g_free (reader); + + return FALSE; } void -soup_server_run_async (SoupServer *serv) +soup_server_run_async (SoupServer *server) { - g_return_if_fail (serv != NULL); - g_return_if_fail (serv->port >= 0); + g_return_if_fail (server != NULL); + + if (server->proto == SOUP_PROTOCOL_CGI) { + SoupMessage *msg; + gint content_len = 0; + + msg = message_new (server); + if (!msg) { + g_warning ("Unable to create new incoming message\n"); + return; + } + + if (read_headers_cgi (msg, &content_len) == SOUP_TRANSFER_END) + goto START_ERROR; + + if (content_len > 0) { + CgiReader *reader; + + reader = g_new0 (CgiReader, 1); + reader->msg = msg; + reader->content_len = content_len; + reader->recv_buf = g_byte_array_new (); + + g_io_add_watch (server->cgi_read_chan, + G_IO_IN|G_IO_ERR|G_IO_HUP|G_IO_NVAL, + (GIOFunc) cgi_read, + reader); + } else { + SoupDataBuffer buf = { + SOUP_BUFFER_STATIC, + "", + 0 + }; + + read_done_cgi_cb (&buf, msg); + } + } else { + GIOChannel *chan; - if (!serv->sock) { - serv->sock = soup_socket_server_new (serv->port); - if (!serv->sock) goto START_ERROR; + if (!server->listen_sock) + goto START_ERROR; + + /* + * Listen for new connections (if not already) + */ + if (!server->accept_tag) { + chan = soup_socket_get_iochannel (server->listen_sock); + + server->accept_tag = + g_io_add_watch (chan, + G_IO_IN, + (GIOFunc) conn_accept, + server); + + g_io_channel_unref (chan); + } } - if (!serv->accept_tag) - serv->accept_tag = - g_io_add_watch (soup_socket_get_iochannel (serv->sock), - G_IO_IN, - (GIOFunc) conn_accept, - serv); + soup_server_ref (server); return; START_ERROR: - if (serv->loop) { - g_main_destroy (serv->loop); - serv->loop = NULL; + if (server->loop) { + g_main_destroy (server->loop); + server->loop = NULL; } - return; } void -soup_server_run (SoupServer *serv) +soup_server_run (SoupServer *server) { - g_return_if_fail (serv != NULL); - g_return_if_fail (serv->port >= 0); + g_return_if_fail (server != NULL); - serv->loop = g_main_new (TRUE); - - soup_server_run_async (serv); + if (!server->loop) { + server->loop = g_main_new (TRUE); + soup_server_run_async (server); + } - if (serv->loop) - g_main_run (serv->loop); + if (server->loop) + g_main_run (server->loop); } void -soup_server_quit (SoupServer *serv) +soup_server_quit (SoupServer *server) { - g_return_if_fail (serv != NULL); + g_return_if_fail (server != NULL); - g_main_quit (serv->loop); + g_main_quit (server->loop); + soup_server_unref (server); } -void -soup_server_add_list (SoupServer *serv, - SoupServerHandler *list) +static void +append_handler (gpointer key, gpointer value, gpointer user_data) { - g_return_if_fail (serv != NULL); + GSList **ret = user_data; + + *ret = g_slist_append (*ret, value); +} + +GSList * +soup_server_list_handlers (SoupServer *server) +{ + GSList *ret = NULL; - serv->static_handlers = g_slist_prepend (serv->static_handlers, list); + g_hash_table_foreach (server->handlers, append_handler, &ret); + + return ret; } SoupServerHandler * -soup_server_get_handler (SoupServer *serv, const gchar *path) +soup_server_get_handler (SoupServer *server, const gchar *path) { - GSList *iter; gchar *mypath, *dir; SoupServerHandler *hand = NULL; - g_return_val_if_fail (serv != NULL, NULL); - g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (server != NULL, NULL); - if (!serv->handlers) return NULL; + if (!path) + return server->default_handler; + + if (!server->handlers) + return NULL; mypath = g_strdup (path); @@ -458,85 +1144,168 @@ soup_server_get_handler (SoupServer *serv, const gchar *path) dir = mypath; do { - hand = g_hash_table_lookup (serv->handlers, mypath); + hand = g_hash_table_lookup (server->handlers, mypath); if (hand) { g_free (mypath); return hand; } - for (iter = serv->static_handlers; iter; iter = iter->next) { - hand = iter->data; - while (hand && hand->path) { - if (!strcmp (hand->path, mypath)) { - g_free (mypath); - return hand; - } - hand++; - } - } - dir = strrchr (mypath, '/'); if (dir) *dir = '\0'; } while (dir); g_free (mypath); - return NULL; + + return server->default_handler; } void -soup_server_register (SoupServer *serv, - const gchar *path, - guint auth_types, - SoupServerCallbackFn cb, - gpointer user_data) +soup_server_register (SoupServer *server, + const gchar *path, + SoupServerAuthContext *auth_ctx, + SoupServerCallbackFn callback, + SoupServerUnregisterFn unregister, + gpointer user_data) { - SoupServerHandler *hand; + SoupServerHandler *new_hand; + SoupServerAuthContext *new_auth_ctx = NULL; - g_return_if_fail (serv != NULL); - g_return_if_fail (path != NULL); + g_return_if_fail (server != NULL); + g_return_if_fail (callback != NULL); - hand = g_new0 (SoupServerHandler, 1); - hand->path = g_strdup (path); - hand->auth_types = auth_types; - hand->cb = cb; - hand->user_data = user_data; + if (auth_ctx) { + new_auth_ctx = g_new0 (SoupServerAuthContext, 1); - if (!serv->handlers) - serv->handlers = g_hash_table_new (g_str_hash, g_str_equal); - else - soup_server_unregister (serv, path); + new_auth_ctx->types = auth_ctx->types; + new_auth_ctx->callback = auth_ctx->callback; + new_auth_ctx->user_data = auth_ctx->user_data; - g_hash_table_insert (serv->handlers, hand->path, hand); + new_auth_ctx->basic_info.realm = + g_strdup (auth_ctx->basic_info.realm); + + new_auth_ctx->digest_info.realm = + g_strdup (auth_ctx->digest_info.realm); + new_auth_ctx->digest_info.allow_algorithms = + auth_ctx->digest_info.allow_algorithms; + new_auth_ctx->digest_info.force_integrity = + auth_ctx->digest_info.force_integrity; + } + + new_hand = g_new0 (SoupServerHandler, 1); + new_hand->path = g_strdup (path); + new_hand->auth_ctx = new_auth_ctx; + new_hand->callback = callback; + new_hand->unregister = unregister; + new_hand->user_data = user_data; + + if (path) { + if (!server->handlers) + server->handlers = g_hash_table_new (g_str_hash, + g_str_equal); + else + soup_server_unregister (server, new_hand->path); + + g_hash_table_insert (server->handlers, + (gchar *) new_hand->path, + new_hand); + } else + server->default_handler = new_hand; } void -soup_server_unregister (SoupServer *serv, const gchar *path) +soup_server_unregister (SoupServer *server, const gchar *path) { SoupServerHandler *hand; - g_return_if_fail (serv != NULL); - g_return_if_fail (path != NULL); + g_return_if_fail (server != NULL); + + if (!path) { + if (server->default_handler) { + free_handler (server, server->default_handler); + server->default_handler = NULL; + } + return; + } - if (!serv->handlers) return; + if (!server->handlers) return; - hand = g_hash_table_lookup (serv->handlers, path); + hand = g_hash_table_lookup (server->handlers, path); if (hand) { - g_hash_table_remove (serv->handlers, path); + g_hash_table_remove (server->handlers, path); + free_handler (server, hand); + } +} + +SoupServerMessage * +soup_server_message_new (SoupMessage *src_msg) +{ + SoupServerMessage *ret; + + g_return_val_if_fail (src_msg != NULL, NULL); + + if (src_msg->priv->server_msg) + return src_msg->priv->server_msg; + + ret = g_new0 (SoupServerMessage, 1); + ret->msg = src_msg; + + src_msg->priv->server_msg = ret; + + return ret; +} + +void +soup_server_message_start (SoupServerMessage *serv_msg) +{ + g_return_if_fail (serv_msg != NULL); + + serv_msg->started = TRUE; + + soup_transfer_write_unpause (serv_msg->msg->priv->write_tag); +} + +void +soup_server_message_add_data (SoupServerMessage *serv_msg, + SoupOwnership owner, + gchar *body, + gulong length) +{ + SoupDataBuffer *buf; + + g_return_if_fail (serv_msg != NULL); + g_return_if_fail (body != NULL); + g_return_if_fail (length != 0); + + buf = g_new0 (SoupDataBuffer, 1); + buf->length = length; - g_free (hand->path); - g_free (hand); + if (owner == SOUP_BUFFER_USER_OWNED) { + buf->body = g_memdup (body, length); + buf->owner = SOUP_BUFFER_SYSTEM_OWNED; + } else { + buf->body = body; + buf->owner = owner; } + + serv_msg->chunks = g_slist_append (serv_msg->chunks, buf); + + soup_transfer_write_unpause (serv_msg->msg->priv->write_tag); } -void -soup_server_register_default (SoupServer *serv, - guint auth_types, - SoupServerCallbackFn cb, - gpointer user_data) +void +soup_server_message_finish (SoupServerMessage *serv_msg) { - g_return_if_fail (serv != NULL); + g_return_if_fail (serv_msg != NULL); - serv->default_handler.auth_types = auth_types; - serv->default_handler.cb = cb; - serv->default_handler.user_data = user_data; + serv_msg->started = TRUE; + serv_msg->finished = TRUE; + + soup_transfer_write_unpause (serv_msg->msg->priv->write_tag); +} + +SoupMessage * +soup_server_message_get_source (SoupServerMessage *serv_msg) +{ + g_return_val_if_fail (serv_msg != NULL, NULL); + return serv_msg->msg; } diff --git a/libsoup/soup-server.h b/libsoup/soup-server.h index cac094a..9245381 100644 --- a/libsoup/soup-server.h +++ b/libsoup/soup-server.h @@ -13,104 +13,94 @@ #include #include +#include #include #include +#include -typedef struct { - SoupAuthType type; - const gchar *realm; - const gchar *username; - const gchar *password; -} SoupServerBasicToken; +typedef struct _SoupServer SoupServer; +typedef struct _SoupServerHandler SoupServerHandler; typedef struct { - SoupAuthType type; - const gchar *realm; - const gchar *username; - const gchar *password_hash; -} SoupServerDigestToken; + SoupMessage *msg; + gchar *path; + SoupMethodId method_id; + SoupServerAuth *auth; + SoupServer *server; + SoupServerHandler *handler; +} SoupServerContext; -typedef struct { - SoupAuthType type; - const gchar *host; - const gchar *domain; - const gchar *user; - const gchar *lm_hash; - const gchar *nt_hash; -} SoupServerNTLMToken; - -typedef union { - SoupAuthType type; - SoupServerBasicToken basic; - SoupServerDigestToken digest; - SoupServerNTLMToken ntlm; -} SoupServerAuthToken; - -typedef void (*SoupServerCallbackFn) (SoupMessage *msg, - SoupServerAuthToken *token, - gpointer data); +typedef void (*SoupServerCallbackFn) (SoupServerContext *context, + SoupMessage *msg, + gpointer user_data); -typedef struct { - gchar *path; - guint auth_types; - SoupServerCallbackFn cb; - gpointer user_data; -} SoupServerHandler; +typedef void (*SoupServerUnregisterFn) (SoupServer *server, + SoupServerHandler *handler, + gpointer user_data); -typedef struct _SoupServer SoupServer; +struct _SoupServerHandler { + const gchar *path; + + SoupServerAuthContext *auth_ctx; -extern SoupServer *SOUP_CGI_SERVER; -extern SoupServer *SOUP_HTTPD_SERVER; -extern SoupServer *SOUP_HTTPD_SSL_SERVER; + SoupServerCallbackFn callback; + SoupServerUnregisterFn unregister; + gpointer user_data; +}; -SoupServer * soup_server_new (SoupProtocol proto, - guint port); +SoupServer *soup_server_new (SoupProtocol proto, + guint port); -void soup_server_free (SoupServer *serv); +SoupServer *soup_server_cgi (void); -gint soup_server_get_port (SoupServer *serv); +void soup_server_ref (SoupServer *serv); -void soup_server_run (SoupServer *serv); +void soup_server_unref (SoupServer *serv); -void soup_server_run_async (SoupServer *serv); +SoupProtocol soup_server_get_protocol (SoupServer *serv); -void soup_server_quit (SoupServer *serv); +gint soup_server_get_port (SoupServer *serv); -void soup_server_add_list (SoupServer *serv, - SoupServerHandler *list); +void soup_server_run (SoupServer *serv); -void soup_server_remove_list (SoupServer *serv, - SoupServerHandler *list); +void soup_server_run_async (SoupServer *serv); -void soup_server_register (SoupServer *serv, - const gchar *path, - guint authtype, - SoupServerCallbackFn cb, - gpointer data); +void soup_server_quit (SoupServer *serv); -void soup_server_register_default (SoupServer *serv, - guint authtype, - SoupServerCallbackFn cb, - gpointer data); +void soup_server_register (SoupServer *serv, + const gchar *path, + SoupServerAuthContext *auth_ctx, + SoupServerCallbackFn callback, + SoupServerUnregisterFn unregister, + gpointer user_data); + +void soup_server_unregister (SoupServer *serv, + const gchar *path); + +SoupServerHandler *soup_server_get_handler (SoupServer *serv, + const gchar *path); + +GSList *soup_server_list_handlers (SoupServer *serv); + +/* + * Apache/soup-httpd module initializtion + * Implement soup_server_init() in your shared library. + */ +extern void soup_server_init (SoupServer *server); -void soup_server_unregister (SoupServer *serv, - const gchar *path); +typedef struct _SoupServerMessage SoupServerMessage; -SoupServerHandler *soup_server_get_handler (SoupServer *serv, - const gchar *path); +SoupServerMessage *soup_server_message_new (SoupMessage *src_msg); -void soup_server_set_auth (SoupServer *serv, - const gchar *path, - guint auth_types, - const gchar *realm); +void soup_server_message_start (SoupServerMessage *servmsg); -void soup_server_require_auth (SoupMessage *message, - guint auth_types, - const gchar *realm); +void soup_server_message_add_data (SoupServerMessage *servmsg, + SoupOwnership owner, + gchar *body, + gulong length); +void soup_server_message_finish (SoupServerMessage *servmsg); -/* Apache module initializtion */ -/* Implement soup_server_init() in your library. */ -extern void soup_server_init (void); +SoupMessage *soup_server_message_get_source (SoupServerMessage *servmsg); #endif /* SOUP_SERVER_H */ diff --git a/libsoup/soup-socket-unix.c b/libsoup/soup-socket-unix.c index cb7601e..a46fecc 100644 --- a/libsoup/soup-socket-unix.c +++ b/libsoup/soup-socket-unix.c @@ -73,6 +73,10 @@ #define socklen_t size_t #endif +#ifndef INADDR_NONE +#define INADDR_NONE -1 +#endif + /* * Maintains a list of all currently valid SoupAddresses or active * SoupAddressState lookup requests. @@ -434,7 +438,8 @@ soup_address_new_cb (GIOChannel* iochannel, close (state->fd); waitpid (state->pid, &ret, 0); - if (WIFSIGNALED (ret) || WEXITSTATUS (ret) != 1) goto ERROR; + if (WIFSIGNALED (ret) || WEXITSTATUS (ret) != 1) + goto ERROR; /* * Exit status of one means we are inside a debugger. @@ -460,7 +465,8 @@ soup_address_new_cb (GIOChannel* iochannel, /* Return true if there's more to read */ if ((state->len - 1) != state->buffer [0]) return TRUE; - if (state->len < 2) goto ERROR; + if (state->len < 2) + goto ERROR; /* Success. Copy resolved address. */ sa_in = (struct sockaddr_in*) &state->ia.sa; @@ -475,7 +481,7 @@ soup_address_new_cb (GIOChannel* iochannel, } /* Get state data before realloc */ - cb_list = iter = state->cb_list; + cb_list = state->cb_list; cb_func = state->func; cb_data = state->data; @@ -492,13 +498,12 @@ soup_address_new_cb (GIOChannel* iochannel, (*cb_func) (&state->ia, SOUP_ADDRESS_STATUS_OK, cb_data); - while (iter) { + for (iter = cb_list; iter; iter = iter->next) { SoupAddressCbData *cb = iter->data; (*cb->func) (&state->ia, SOUP_ADDRESS_STATUS_OK, cb->data); g_free (cb); - iter = iter->next; } g_slist_free (cb_list); @@ -525,6 +530,60 @@ soup_address_new_cb (GIOChannel* iochannel, return FALSE; } +static SoupAddress * +lookup_in_cache_internal (const gchar *name, + const gint port, + gboolean *in_progress) +{ + SoupAddress* ia = NULL; + + if (in_progress) + *in_progress = FALSE; + + if (!active_address_hash) + return NULL; + + ia = g_hash_table_lookup (active_address_hash, name); + + if (ia && ia->ref_count >= 0) { + /* + * Existing valid request, use it. + */ + if (soup_address_get_port (ia) == port) { + soup_address_ref (ia); + } else { + /* + * We can reuse the address, but we have to + * change port + */ + SoupAddress *new_ia = soup_address_copy (ia); + + ((struct sockaddr_in*) &new_ia->sa)->sin_port = + g_htons (port); + + ia = new_ia; + } + } + else if (ia && in_progress) + *in_progress = TRUE; + + return ia; +} + +SoupAddress * +soup_address_lookup_in_cache (const gchar *name, const gint port) +{ + SoupAddress *ia; + gboolean in_prog; + + ia = lookup_in_cache_internal (name, port, &in_prog); + + if (in_prog) + return NULL; + + return ia; +} + /** * soup_address_new: * @name: a nice name (eg, mofo.eecs.umich.edu) or a dotted decimal name @@ -566,7 +625,15 @@ soup_address_new (const gchar* name, { pid_t pid = -1; int pipes [2]; +#ifdef HAVE_INET_PTON + struct in_addr inaddr; +#else +# ifdef HAVE_INET_ATON struct in_addr inaddr; +# else + in_addr_t inaddr; +# endif +#endif struct sockaddr_in sa; struct sockaddr_in* sa_in; SoupAddress* ia; @@ -581,8 +648,17 @@ soup_address_new (const gchar* name, #ifdef HAVE_INET_PTON inaddr_ok = inet_pton (AF_INET, name, &inaddr) != 0; #else +# ifdef HAVE_INET_ATON inaddr_ok = inet_aton (name, &inaddr) != 0; +# else + inaddr = inet_addr (name); + if (inaddr == INADDR_NONE) + inaddr_ok = FALSE; + else + inaddr_ok = TRUE; +# endif #endif + if (inaddr_ok) { ia = g_new0 (SoupAddress, 1); ia->ref_count = 1; @@ -592,7 +668,7 @@ soup_address_new (const gchar* name, sa_in->sin_port = g_htons(port); memcpy (&sa_in->sin_addr, (char*) &inaddr, - sizeof(struct in_addr)); + sizeof(inaddr)); (*func) (ia, SOUP_ADDRESS_STATUS_OK, data); return NULL; @@ -602,29 +678,10 @@ soup_address_new (const gchar* name, active_address_hash = g_hash_table_new (soup_str_case_hash, soup_str_case_equal); else { - ia = g_hash_table_lookup (active_address_hash, name); - - if (ia && ia->ref_count >= 0) { - /* - * Existing valid request, use it. - */ - if (soup_address_get_port (ia) == port) { - soup_address_ref (ia); - } else { - /* - * We can reuse the address, but we have to - * change port - */ - SoupAddress *new_ia = soup_address_copy (ia); - soup_address_set_port (new_ia, port); - ia = new_ia; - } + gboolean in_prog; - (*func) (ia, SOUP_ADDRESS_STATUS_OK, data); - - return NULL; - } - else if (ia && soup_address_get_port (ia) == port) { + ia = lookup_in_cache_internal (name, port, &in_prog); + if (in_prog) { /* * Lookup currently in progress. * Add func to list of callbacks in state. @@ -646,6 +703,8 @@ soup_address_new (const gchar* name, return state; } + else if (ia) + return ia; } /* Check to see if we are doing synchronous DNS lookups */ diff --git a/libsoup/soup-socket.c b/libsoup/soup-socket.c index f881471..4b73320 100644 --- a/libsoup/soup-socket.c +++ b/libsoup/soup-socket.c @@ -15,7 +15,6 @@ #ifdef HAVE_CONFIG_H #include #endif - #include #include #include @@ -26,11 +25,7 @@ #ifdef SOUP_WIN32 # define socklen_t gint32 # define SOUP_CLOSE_SOCKET(fd) closesocket(fd) -# define SOUP_SOCKET_IOCHANNEL_NEW(fd) g_io_channel_win32_new_stream_socket(fd) -# ifndef INET_ADDRSTRLEN -# define INET_ADDRSTRLEN 16 -# define INET6_ADDRSTRLEN 46 -# endif +# define SOUP_SOCKET_IOCHANNEL_NEW(fd) g_io_channel_win32_new_socket(fd) #else # include # ifndef socklen_t @@ -40,6 +35,11 @@ # define SOUP_SOCKET_IOCHANNEL_NEW(fd) g_io_channel_unix_new(fd) #endif +#ifndef INET_ADDRSTRLEN +# define INET_ADDRSTRLEN 16 +# define INET6_ADDRSTRLEN 46 +#endif + #define SOUP_SOCKADDR_IN(s) (*((struct sockaddr_in*) &s)) typedef struct { @@ -181,18 +181,22 @@ soup_address_get_port (const SoupAddress* ia) } /** - * soup_address_set_port: - * @ia: Address to set the port number of. - * @port: New port number + * soup_address_get_sockaddr: + * @ia: The %SoupAddress. + * @addrlen: Pointer to socklen_t the returned sockaddr's length is to be + * placed in. * - * Set the port number. + * Return value: const pointer to @ia's sockaddr buffer. **/ -void -soup_address_set_port (const SoupAddress* ia, guint port) +const struct sockaddr * +soup_address_get_sockaddr (SoupAddress *ia, guint *addrlen) { - g_return_if_fail (ia != NULL); + g_return_val_if_fail (ia != NULL, NULL); - ((struct sockaddr_in*) &ia->sa)->sin_port = g_htons (port); + if (addrlen) + *addrlen = sizeof (struct sockaddr_in); + + return &ia->sa; } /** @@ -213,8 +217,11 @@ soup_address_hash (const gpointer p) g_assert(p != NULL); ia = (const SoupAddress*) p; - /* We do pay attention to network byte order just in case the hash - result is saved or sent to a different host. */ + + /* + * We do pay attention to network byte order just in case the hash + * result is saved or sent to a different host. + */ port = (guint32) g_ntohs (((struct sockaddr_in*) &ia->sa)->sin_port); addr = g_ntohl (((struct sockaddr_in*) &ia->sa)->sin_addr.s_addr); @@ -236,7 +243,7 @@ soup_address_equal (const gpointer p1, const gpointer p2) const SoupAddress* ia1 = (const SoupAddress*) p1; const SoupAddress* ia2 = (const SoupAddress*) p2; - g_assert(p1 != NULL && p2 != NULL); + g_assert (p1 != NULL && p2 != NULL); /* Note network byte order doesn't matter */ return ((SOUP_SOCKADDR_IN(ia1->sa).sin_addr.s_addr == @@ -305,17 +312,19 @@ soup_socket_connect_tcp_cb (SoupSocket* socket, gpointer data) { SoupSocketConnectState* state = (SoupSocketConnectState*) data; + SoupSocketConnectFn func = state->func; + gpointer user_data = state->data; + + g_free (state); if (status == SOUP_SOCKET_NEW_STATUS_OK) - (*state->func) (socket, - SOUP_SOCKET_CONNECT_ERROR_NONE, - state->data); + (*func) (socket, + SOUP_SOCKET_CONNECT_ERROR_NONE, + user_data); else - (*state->func) (NULL, - SOUP_SOCKET_CONNECT_ERROR_NETWORK, - state->data); - - g_free (state); + (*func) (NULL, + SOUP_SOCKET_CONNECT_ERROR_NETWORK, + user_data); } static void @@ -326,16 +335,30 @@ soup_socket_connect_inetaddr_cb (SoupAddress* inetaddr, SoupSocketConnectState* state = (SoupSocketConnectState*) data; if (status == SOUP_ADDRESS_STATUS_OK) { + gpointer tcp_id; + state->inetaddr_id = NULL; - state->tcp_id = soup_socket_new (inetaddr, - soup_socket_connect_tcp_cb, - state); + + tcp_id = soup_socket_new (inetaddr, + soup_socket_connect_tcp_cb, + state); + /* + * NOTE: soup_socket_new can fail immediately and call our + * callback which will delete the state. + */ + if (tcp_id) + state->tcp_id = tcp_id; + soup_address_unref (inetaddr); } else { - (*state->func) (NULL, - SOUP_SOCKET_CONNECT_ERROR_ADDR_RESOLVE, - state->data); + SoupSocketConnectFn func = state->func; + gpointer user_data = state->data; + g_free (state); + + (*func) (NULL, + SOUP_SOCKET_CONNECT_ERROR_ADDR_RESOLVE, + user_data); } } @@ -364,7 +387,8 @@ soup_socket_connect (const gchar* hostname, gpointer data) { SoupSocketConnectState* state; - gpointer id; + SoupAddress *cached_addr; + gpointer addr_id, tcp_id; g_return_val_if_fail (hostname != NULL, NULL); g_return_val_if_fail (func != NULL, NULL); @@ -373,20 +397,39 @@ soup_socket_connect (const gchar* hostname, state->func = func; state->data = data; - id = soup_address_new (hostname, - port, - soup_socket_connect_inetaddr_cb, - state); - - /* Note that soup_address_new can fail immediately and call - our callback which will delete the state. The users callback - would be called in the process. */ - - if (id == NULL) return NULL; - - state->inetaddr_id = id; - - return state; + /* Check if a cached version of the address already exists */ + cached_addr = soup_address_lookup_in_cache (hostname, port); + if (cached_addr) { + tcp_id = soup_socket_new (cached_addr, + soup_socket_connect_tcp_cb, + state); + soup_address_unref (cached_addr); + + /* + * NOTE: soup_socket_new can fail immediately and call our + * callback which will delete the state. + */ + if (tcp_id) { + state->tcp_id = tcp_id; + return state; + } else + return NULL; + } else { + addr_id = soup_address_new (hostname, + port, + soup_socket_connect_inetaddr_cb, + state); + + /* + * NOTE: soup_address_new can fail immediately and call our + * callback which will delete the state. + */ + if (addr_id) { + state->inetaddr_id = addr_id; + return state; + } else + return NULL; + } } /** @@ -607,8 +650,10 @@ soup_socket_server_new (const gint port) sa_in->sin_addr.s_addr = g_htonl (INADDR_ANY); sa_in->sin_port = g_htons (port); - /* The socket is set to non-blocking mode later in the Windows - version.*/ + /* + * For Unix, set REUSEADDR and NONBLOCK. + * For Windows, set NONBLOCK during accept. + */ #ifndef SOUP_WIN32 { const int on = 1; diff --git a/libsoup/soup-socket.h b/libsoup/soup-socket.h index f34dc31..a190ecf 100644 --- a/libsoup/soup-socket.h +++ b/libsoup/soup-socket.h @@ -36,8 +36,11 @@ SoupAddressNewId soup_address_new (const gchar* name, void soup_address_new_cancel (SoupAddressNewId id); -SoupAddress *soup_address_new_sync (const gchar *name, - const gint port); +SoupAddress *soup_address_new_sync (const gchar *name, + const gint port); + +SoupAddress *soup_address_lookup_in_cache (const gchar *name, + const gint port); void soup_address_ref (SoupAddress* ia); @@ -65,8 +68,9 @@ gchar* soup_address_get_canonical_name (SoupAddress* ia); gint soup_address_get_port (const SoupAddress* ia); -void soup_address_set_port (const SoupAddress* ia, - guint port); +const struct sockaddr * + soup_address_get_sockaddr (SoupAddress *ia, + guint *addrlen); guint soup_address_hash (const gpointer p); diff --git a/libsoup/soup-ssl-proxy.c b/libsoup/soup-ssl-proxy.c index 6431893..5d94066 100644 --- a/libsoup/soup-ssl-proxy.c +++ b/libsoup/soup-ssl-proxy.c @@ -134,17 +134,27 @@ main (int argc, char** argv) loop = g_main_new (FALSE); env = getenv ("SOCKFD"); - if (!env) g_error ("SOCKFD environment not set."); + if (!env) + g_error ("SOCKFD environment not set."); + sockfd = atoi (env); + if (sockfd <= 0) + g_error ("Invalid SOCKFD environment set."); env = getenv ("SECURITY_POLICY"); - if (!env) g_error ("SECURITY_POLICY environment not set."); - secpol = atoi (env); + if (!env) + g_error ("SECURITY_POLICY environment not set."); + secpol = atoi (env); soup_ssl_proxy_set_security_policy (secpol); read_chan = g_io_channel_unix_new (STDIN_FILENO); + if (!read_chan) + g_error ("Unable to open STDIN"); + write_chan = g_io_channel_unix_new (STDOUT_FILENO); + if (!write_chan) + g_error ("Unable to open STDOUT"); /* Block on socket write */ flags = fcntl(sockfd, F_GETFL, 0); @@ -156,6 +166,8 @@ main (int argc, char** argv) sock_chan = g_io_channel_unix_new (sockfd); sock_chan = soup_ssl_proxy_get_iochannel (sock_chan); + if (!sock_chan) + g_error ("Unable to establish SSL connection"); g_io_add_watch (read_chan, G_IO_IN | G_IO_HUP | G_IO_ERR, diff --git a/libsoup/soup-ssl.c b/libsoup/soup-ssl.c index f37235d..05302ca 100644 --- a/libsoup/soup-ssl.c +++ b/libsoup/soup-ssl.c @@ -101,11 +101,11 @@ soup_ssl_get_iochannel (GIOChannel *sock) putenv (g_strdup_printf ("SECURITY_POLICY=%d", soup_get_security_policy ())); - execl (BINDIR G_DIR_SEPARATOR_S "soup-ssl-proxy", - BINDIR G_DIR_SEPARATOR_S "soup-ssl-proxy", + execl (BINDIR G_DIR_SEPARATOR_S SSL_PROXY_NAME, + BINDIR G_DIR_SEPARATOR_S SSL_PROXY_NAME, NULL); - execlp ("soup-ssl-proxy", "soup-ssl-proxy", NULL); + execlp (SSL_PROXY_NAME, SSL_PROXY_NAME, NULL); g_error ("Error executing SSL Proxy\n"); } diff --git a/libsoup/soup-transfer.c b/libsoup/soup-transfer.c index 83d8a1c..332cd7c 100644 --- a/libsoup/soup-transfer.c +++ b/libsoup/soup-transfer.c @@ -22,6 +22,32 @@ #include "soup-transfer.h" #include "soup-private.h" +#undef DUMP + +#ifdef DUMP +static void +DUMP_READ (guchar *data, gint bytes_read) +{ + gchar *buf = alloca (bytes_read + 1); + memcpy (buf, data, bytes_read); + buf[bytes_read] = '\0'; + + g_warning ("READ %d\n----------\n%s\n----------\n", bytes_read, buf); +} +static void +DUMP_WRITE (guchar *data, gint bytes_written) +{ + gchar *buf = alloca (bytes_written + 1); + memcpy (buf, data, bytes_written); + buf[bytes_written] = '\0'; + + g_warning ("WRITE %d\n----------\n%s\n----------\n", bytes_written,buf); +} +#else +# define DUMP_READ(x,y) +# define DUMP_WRITE(x,y) +#endif + typedef struct { /* * Length remaining to be downloaded of the current chunk data. @@ -41,7 +67,7 @@ typedef struct { guint err_tag; /* - * If TRUE, a callback has been issed which references recv_buf. + * If TRUE, a callback has been issued which references recv_buf. * If the transfer is cancelled before a reference exists, the contents * of recv_buf are free'd. */ @@ -72,13 +98,14 @@ typedef struct { gboolean processing; - const GString *header; - const SoupDataBuffer *src; + SoupTransferEncoding encoding; + GByteArray *write_buf; - guint write_len; gboolean headers_done; + gint chunk_cnt; - SoupWriteHeadersDoneFn headers_done_cb; + SoupWriteGetHeaderFn get_header_cb; + SoupWriteGetChunkFn get_chunk_cb; SoupWriteDoneFn write_done_cb; SoupWriteErrorFn error_cb; gpointer user_data; @@ -94,14 +121,34 @@ soup_transfer_read_cancel (guint tag) if (r->processing) return; - g_source_remove (r->read_tag); - g_source_remove (r->err_tag); + if (r->read_tag) + g_source_remove (r->read_tag); + if (r->err_tag) + g_source_remove (r->err_tag); g_byte_array_free (r->recv_buf, r->callback_issued ? FALSE : TRUE); g_free (r); } +void +soup_transfer_read_set_callbacks (guint tag, + SoupReadHeadersDoneFn headers_done_cb, + SoupReadChunkFn read_chunk_cb, + SoupReadDoneFn read_done_cb, + SoupReadErrorFn error_cb, + gpointer user_data) +{ + SoupReader *r = GINT_TO_POINTER (tag); + + r->headers_done_cb = headers_done_cb; + r->read_chunk_cb = read_chunk_cb; + r->read_done_cb = read_done_cb; + r->error_cb = error_cb; + + r->user_data = user_data; +} + static void issue_final_callback (SoupReader *r) { @@ -119,6 +166,10 @@ issue_final_callback (SoupReader *r) r->callback_issued = TRUE; + g_source_remove (r->read_tag); + g_source_remove (r->err_tag); + r->read_tag = r->err_tag = 0; + IGNORE_CANCEL (r); (*r->read_done_cb) (&buf, r->user_data); UNIGNORE_CANCEL (r); @@ -134,9 +185,9 @@ soup_transfer_read_error_cb (GIOChannel* iochannel, /* * Closing the connection to signify EOF is valid if content length is - * unknown. + * unknown, but only if headers have been sent. */ - if (r->encoding == SOUP_TRANSFER_UNKNOWN) { + if (r->header_len && r->encoding == SOUP_TRANSFER_UNKNOWN) { issue_final_callback (r); goto CANCELLED; } @@ -404,12 +455,14 @@ soup_transfer_read_cb (GIOChannel *iochannel, &bytes_read); if (error == G_IO_ERROR_AGAIN) { - if (total_read) goto PROCESS_READ; + if (total_read) + goto PROCESS_READ; else return TRUE; } if (error != G_IO_ERROR_NONE) { - if (total_read) goto PROCESS_READ; + if (total_read) + goto PROCESS_READ; else { soup_transfer_read_error_cb (iochannel, G_IO_HUP, r); return FALSE; @@ -417,29 +470,40 @@ soup_transfer_read_cb (GIOChannel *iochannel, } if (bytes_read) { - g_byte_array_append (r->recv_buf, read_buf, bytes_read); + DUMP_READ (read_buf, bytes_read); + g_byte_array_append (r->recv_buf, read_buf, bytes_read); total_read += bytes_read; goto READ_AGAIN; } PROCESS_READ: - if (!r->header_len) { - gint index = soup_substring_index (r->recv_buf->data, - r->recv_buf->len, - "\r\n\r\n"); - if (index < 0) return TRUE; + /* + * FIXME: Why are we getting a read_cb if there is no data to read? Yet + * error_cb isn't being called and we get no error from + * g_io_channel_read(). + */ + if (r->header_len == 0 && total_read == 0) { + soup_transfer_read_error_cb (iochannel, G_IO_HUP, r); + return FALSE; + } - index += 4; + if (r->header_len == 0) { + gint index; + + index = soup_substring_index (r->recv_buf->data, + r->recv_buf->len, + "\r\n\r\n"); + if (index < 0) + return TRUE; + else + index += 4; if (r->headers_done_cb) { GString str; SoupTransferDone ret; - r->encoding = SOUP_TRANSFER_UNKNOWN; - r->content_length = 0; - str.len = index; str.str = alloca (index + 1); strncpy (str.str, r->recv_buf->data, index); @@ -457,7 +521,6 @@ soup_transfer_read_cb (GIOChannel *iochannel, } remove_block_at_index (r->recv_buf, 0, index); - r->header_len = index; } @@ -513,6 +576,7 @@ soup_transfer_read (GIOChannel *chan, reader->error_cb = error_cb; reader->user_data = user_data; reader->recv_buf = g_byte_array_new (); + reader->encoding = SOUP_TRANSFER_UNKNOWN; reader->read_tag = g_io_add_watch (chan, @@ -536,9 +600,12 @@ soup_transfer_write_cancel (guint tag) if (w->processing) return; - g_source_remove (w->write_tag); + if (w->write_tag) + g_source_remove (w->write_tag); g_source_remove (w->err_tag); + g_byte_array_free (w->write_buf, TRUE); + g_free (w); } @@ -547,11 +614,9 @@ soup_transfer_write_error_cb (GIOChannel* iochannel, GIOCondition condition, SoupWriter *w) { - gboolean body_started = w->write_len > (guint) w->header->len; - if (w->error_cb) { IGNORE_CANCEL (w); - (*w->error_cb) (body_started, w->user_data); + (*w->error_cb) (w->headers_done, w->user_data); UNIGNORE_CANCEL (w); } @@ -560,73 +625,147 @@ soup_transfer_write_error_cb (GIOChannel* iochannel, return FALSE; } +static gboolean +get_header (SoupWriter *w) +{ + GString *header = NULL; + + IGNORE_CANCEL (w); + (*w->get_header_cb) (&header, w->user_data); + UNIGNORE_CANCEL (w); + + if (header) { + g_byte_array_append (w->write_buf, header->str, header->len); + g_string_free (header, TRUE); + + w->get_header_cb = NULL; + return TRUE; + } + + return FALSE; +} + +static void +write_chunk_sep (GByteArray *arr, gint len, gint chunk_cnt) +{ + gchar *hex; + gchar *end = "0\r\n\r\n"; + + /* + * Only prefix the chunk length with a \r\n if its not the first chunk + */ + if (chunk_cnt) + g_byte_array_append (arr, "\r\n", 2); + + if (len) { + hex = g_strdup_printf ("%x\r\n", len); + g_byte_array_append (arr, hex, strlen (hex)); + g_free (hex); + } else + g_byte_array_append (arr, end, strlen (end)); +} + +static void +get_next_chunk (SoupWriter *w) +{ + SoupTransferStatus ret = SOUP_TRANSFER_END; + SoupDataBuffer buf = { 0 , NULL, 0 }; + + IGNORE_CANCEL (w); + ret = (*w->get_chunk_cb) (&buf, w->user_data); + UNIGNORE_CANCEL (w); + + if (buf.length) { + if (w->encoding == SOUP_TRANSFER_CHUNKED) + write_chunk_sep (w->write_buf, + buf.length, + w->chunk_cnt++); + + g_byte_array_append (w->write_buf, buf.body, buf.length); + + if (buf.owner == SOUP_BUFFER_SYSTEM_OWNED) + g_free (buf.body); + } + + if (ret == SOUP_TRANSFER_END) { + if (w->encoding == SOUP_TRANSFER_CHUNKED) + write_chunk_sep (w->write_buf, 0, w->chunk_cnt); + + w->get_chunk_cb = NULL; + } +} + +#ifdef SIGPIPE +# define IGNORE_PIPE(pipe_handler) pipe_handler = signal (SIGPIPE, SIG_IGN) +# define RESTORE_PIPE(pipe_handler) signal (SIGPIPE, pipe_handler) +#else +# define IGNORE_PIPE(x) +# define RESTORE_PIPE(x) +#endif + static gboolean soup_transfer_write_cb (GIOChannel* iochannel, GIOCondition condition, SoupWriter *w) { - guint head_len, body_len, total_len, total_written, bytes_written; GIOError error; - gchar *write_buf; - guint write_len; - void *pipe_handler; + gpointer pipe_handler; + guint bytes_written = 0; - head_len = w->header->len; - body_len = w->src->length; - total_len = head_len + body_len; - total_written = w->write_len; + /* + * Get the header and first data chunk (if available). + */ + if (w->get_header_cb) { + if (!get_header (w)) + return TRUE; -#ifdef SIGPIPE - pipe_handler = signal (SIGPIPE, SIG_IGN); -#endif + if (w->get_chunk_cb) + get_next_chunk (w); + } + + IGNORE_PIPE (pipe_handler); errno = 0; WRITE_AGAIN: - if (total_written < head_len) { - /* - * Send remaining headers - */ - write_buf = &w->header->str [total_written]; - write_len = head_len - total_written; - } else { - /* - * Send rest of body - */ - guint offset = total_written - head_len; - write_buf = &w->src->body [offset]; - write_len = body_len - offset; - - if (!w->headers_done) { - if (w->headers_done_cb) { - IGNORE_CANCEL (w); - (*w->headers_done_cb) (w->user_data); - UNIGNORE_CANCEL (w); - } - w->headers_done = TRUE; + while (w->write_buf->len) { + error = g_io_channel_write (iochannel, + w->write_buf->data, + w->write_buf->len, + &bytes_written); + + if (error == G_IO_ERROR_AGAIN) + goto WRITE_LATER; + + if (errno != 0 || error != G_IO_ERROR_NONE) { + soup_transfer_write_error_cb (iochannel, G_IO_HUP, w); + goto DONE_WRITING; } - } - error = g_io_channel_write (iochannel, - write_buf, - write_len, - &bytes_written); + if (!bytes_written) + goto WRITE_LATER; - if (error == G_IO_ERROR_AGAIN) { -#ifdef SIGPIPE - signal (SIGPIPE, pipe_handler); -#endif - return TRUE; - } + DUMP_WRITE (w->write_buf->data, bytes_written); - if (errno != 0 || error != G_IO_ERROR_NONE) { - soup_transfer_write_error_cb (iochannel, G_IO_HUP, w); - goto DONE_WRITING; + remove_block_at_index (w->write_buf, 0, bytes_written); } - total_written = (w->write_len += bytes_written); + /* + * When we exit the above block, we are certain that the headers have + * been written. + */ + w->headers_done = TRUE; + + /* + * Get the next data chunk and try again, or quit if paused. + */ + if (w->get_chunk_cb) { + get_next_chunk (w); + + if (!w->write_tag) + goto DONE_WRITING; - if (total_written != total_len) goto WRITE_AGAIN; + } if (w->write_done_cb) { IGNORE_CANCEL (w); @@ -637,33 +776,31 @@ soup_transfer_write_cb (GIOChannel* iochannel, soup_transfer_write_cancel (GPOINTER_TO_INT (w)); DONE_WRITING: - -#ifdef SIGPIPE - signal (SIGPIPE, pipe_handler); -#endif - + RESTORE_PIPE (pipe_handler); return FALSE; + + WRITE_LATER: + RESTORE_PIPE (pipe_handler); + return TRUE; } -guint -soup_transfer_write (GIOChannel *chan, - const GString *header, - const SoupDataBuffer *src, - SoupWriteHeadersDoneFn headers_done_cb, - SoupWriteDoneFn write_done_cb, - SoupWriteErrorFn error_cb, - gpointer user_data) +static SoupWriter * +create_writer (GIOChannel *chan, + SoupTransferEncoding encoding, + SoupWriteDoneFn write_done_cb, + SoupWriteErrorFn error_cb, + gpointer user_data) { SoupWriter *writer; writer = g_new0 (SoupWriter, 1); - writer->channel = chan; - writer->header = header; - writer->src = src; - writer->headers_done_cb = headers_done_cb; + + writer->channel = chan; + writer->encoding = encoding; + writer->write_buf = g_byte_array_new (); writer->write_done_cb = write_done_cb; - writer->error_cb = error_cb; - writer->user_data = user_data; + writer->error_cb = error_cb; + writer->user_data = user_data; writer->write_tag = g_io_add_watch (chan, @@ -677,5 +814,88 @@ soup_transfer_write (GIOChannel *chan, (GIOFunc) soup_transfer_write_error_cb, writer); + return writer; +} + +guint +soup_transfer_write_simple (GIOChannel *chan, + GString *header, + const SoupDataBuffer *src, + SoupWriteDoneFn write_done_cb, + SoupWriteErrorFn error_cb, + gpointer user_data) +{ + SoupWriter *writer; + + writer = create_writer (chan, + SOUP_TRANSFER_CONTENT_LENGTH, + write_done_cb, + error_cb, + user_data); + + if (header) { + g_byte_array_append (writer->write_buf, + header->str, + header->len); + g_string_free (header, TRUE); + } + + if (src && src->length) + g_byte_array_append (writer->write_buf, + src->body, + src->length); + + return GPOINTER_TO_INT (writer); +} + +guint +soup_transfer_write (GIOChannel *chan, + SoupTransferEncoding encoding, + SoupWriteGetHeaderFn get_header_cb, + SoupWriteGetChunkFn get_chunk_cb, + SoupWriteDoneFn write_done_cb, + SoupWriteErrorFn error_cb, + gpointer user_data) +{ + SoupWriter *writer; + + writer = create_writer (chan, + encoding, + write_done_cb, + error_cb, + user_data); + + writer->get_header_cb = get_header_cb; + writer->get_chunk_cb = get_chunk_cb; + return GPOINTER_TO_INT (writer); } + +void +soup_transfer_write_pause (guint tag) +{ + SoupWriter *w = GINT_TO_POINTER (tag); + + g_return_if_fail (tag != 0); + + if (w->write_tag) { + g_source_remove (w->write_tag); + w->write_tag = 0; + } +} + +void +soup_transfer_write_unpause (guint tag) +{ + SoupWriter *w = GINT_TO_POINTER (tag); + + g_return_if_fail (tag != 0); + + if (!w->write_tag) { + w->write_tag = + g_io_add_watch (w->channel, + G_IO_OUT, + (GIOFunc) soup_transfer_write_cb, + w); + } +} diff --git a/libsoup/soup-transfer.h b/libsoup/soup-transfer.h index 2f6b337..ba0d6af 100644 --- a/libsoup/soup-transfer.h +++ b/libsoup/soup-transfer.h @@ -50,20 +50,43 @@ guint soup_transfer_read (GIOChannel *chan, void soup_transfer_read_cancel (guint tag); -typedef void (*SoupWriteHeadersDoneFn) (gpointer user_data); +void soup_transfer_read_set_callbacks (guint tag, + SoupReadHeadersDoneFn headers_done_cb, + SoupReadChunkFn read_chunk_cb, + SoupReadDoneFn read_done_cb, + SoupReadErrorFn error_cb, + gpointer user_data); + typedef void (*SoupWriteDoneFn) (gpointer user_data); typedef void (*SoupWriteErrorFn) (gboolean headers_done, gpointer user_data); +guint soup_transfer_write_simple (GIOChannel *chan, + GString *header, + const SoupDataBuffer *src, + SoupWriteDoneFn write_done_cb, + SoupWriteErrorFn error_cb, + gpointer user_data); + +typedef void (*SoupWriteGetHeaderFn) (GString **out_hdr, + gpointer user_data); + +typedef SoupTransferDone (*SoupWriteGetChunkFn) (SoupDataBuffer *out_next, + gpointer user_data); + guint soup_transfer_write (GIOChannel *chan, - const GString *header, - const SoupDataBuffer *src, - SoupWriteHeadersDoneFn headers_done_cb, + SoupTransferEncoding encoding, + SoupWriteGetHeaderFn get_header_cb, + SoupWriteGetChunkFn get_chunk_cb, SoupWriteDoneFn write_done_cb, SoupWriteErrorFn error_cb, gpointer user_data); +void soup_transfer_write_pause (guint tag); + +void soup_transfer_write_unpause (guint tag); + void soup_transfer_write_cancel (guint tag); #endif /*SOUP_TRANSFER_H*/ diff --git a/libsoup/soup-uri.c b/libsoup/soup-uri.c index f22231b..970e2b4 100644 --- a/libsoup/soup-uri.c +++ b/libsoup/soup-uri.c @@ -104,6 +104,191 @@ soup_uri_get_default_port (SoupProtocol proto) return -1; } +/* + * Ripped off from libxml + */ +static void +normalize_path (gchar *path) +{ + char *cur, *out; + + /* + * Skip all initial "/" chars. We want to get to the beginning of the + * first non-empty segment. + */ + cur = path; + while (cur[0] == '/') + ++cur; + if (cur[0] == '\0') + return; + + /* Keep everything we've seen so far. */ + out = cur; + + /* + * Analyze each segment in sequence for cases (c) and (d). + */ + while (cur[0] != '\0') { + /* + * c) All occurrences of "./", where "." is a complete path + * segment, are removed from the buffer string. + */ + if ((cur[0] == '.') && (cur[1] == '/')) { + cur += 2; + /* + * '//' normalization should be done at this point too + */ + while (cur[0] == '/') + cur++; + continue; + } + + /* + * d) If the buffer string ends with "." as a complete path + * segment, that "." is removed. + */ + if ((cur[0] == '.') && (cur[1] == '\0')) + break; + + /* Otherwise keep the segment. */ + while (cur[0] != '/') { + if (cur[0] == '\0') + goto done_cd; + (out++)[0] = (cur++)[0]; + } + /* nomalize '//' */ + while ((cur[0] == '/') && (cur[1] == '/')) + cur++; + + (out++)[0] = (cur++)[0]; + } + done_cd: + out[0] = '\0'; + + /* Reset to the beginning of the first segment for the next sequence. */ + cur = path; + while (cur[0] == '/') + ++cur; + if (cur[0] == '\0') + return; + + /* + * Analyze each segment in sequence for cases (e) and (f). + * + * e) All occurrences of "/../", where is a + * complete path segment not equal to "..", are removed from the + * buffer string. Removal of these path segments is performed + * iteratively, removing the leftmost matching pattern on each + * iteration, until no matching pattern remains. + * + * f) If the buffer string ends with "/..", where + * is a complete path segment not equal to "..", that + * "/.." is removed. + * + * To satisfy the "iterative" clause in (e), we need to collapse the + * string every time we find something that needs to be removed. Thus, + * we don't need to keep two pointers into the string: we only need a + * "current position" pointer. + */ + while (1) { + char *segp; + + /* + * At the beginning of each iteration of this loop, "cur" points + * to the first character of the segment we want to examine. + */ + + /* Find the end of the current segment. */ + segp = cur; + while ((segp[0] != '/') && (segp[0] != '\0')) + ++segp; + + /* + * If this is the last segment, we're done (we need at least two + * segments to meet the criteria for the (e) and (f) cases). + */ + if (segp[0] == '\0') + break; + + /* + * If the first segment is "..", or if the next segment _isn't_ + * "..", keep this segment and try the next one. + */ + ++segp; + if (((cur[0] == '.') && (cur[1] == '.') && (segp == cur+3)) + || ((segp[0] != '.') || (segp[1] != '.') + || ((segp[2] != '/') && (segp[2] != '\0')))) { + cur = segp; + continue; + } + + /* + * If we get here, remove this segment and the next one and back + * up to the previous segment (if there is one), to implement + * the "iteratively" clause. It's pretty much impossible to + * back up while maintaining two pointers into the buffer, so + * just compact the whole buffer now. + */ + + /* If this is the end of the buffer, we're done. */ + if (segp[2] == '\0') { + cur[0] = '\0'; + break; + } + strcpy(cur, segp + 3); + + /* + * If there are no previous segments, then keep going from + * here. + */ + segp = cur; + while ((segp > path) && ((--segp)[0] == '/')) + ; + if (segp == path) + continue; + + /* + * "segp" is pointing to the end of a previous segment; find + * it's start. We need to back up to the previous segment and + * start over with that to handle things like "foo/bar/../..". + * If we don't do this, then on the first pass we'll remove the + * "bar/..", but be pointing at the second ".." so we won't + * realize we can also remove the "foo/..". + */ + cur = segp; + while ((cur > path) && (cur[-1] != '/')) + --cur; + } + out[0] = '\0'; + + /* + * g) If the resulting buffer string still begins with one or more + * complete path segments of "..", then the reference is + * considered to be in error. Implementations may handle this + * error by retaining these components in the resolved path (i.e., + * treating them as part of the final URI), by removing them from + * the resolved path (i.e., discarding relative levels above the + * root), or by avoiding traversal of the reference. + * + * We discard them from the final path. + */ + if (path[0] == '/') { + cur = path; + while ((cur[1] == '.') && (cur[2] == '.') + && ((cur[3] == '/') || (cur[3] == '\0'))) + cur += 3; + + if (cur != path) { + out = path; + while (cur[0] != '\0') + (out++)[0] = (cur++)[0]; + out[0] = 0; + } + } + + return; +} + /** * soup_uri_new: create a SoupUri object from a string * @uri_string: The string containing the URL to scan @@ -207,13 +392,15 @@ soup_uri_new (const gchar* uri_string) if (path && query) { g_uri->path = g_strndup (path, query - path); g_uri->querystring = g_strdup (++query); - g_uri->query_elems = g_strsplit (g_uri->querystring, "&", 0); g_free (path); } else { g_uri->path = path; g_uri->querystring = NULL; } + if (g_uri->path) + normalize_path (g_uri->path); + return g_uri; } @@ -260,18 +447,40 @@ soup_uri_to_string (const SoupUri *uri, gboolean show_passwd) SoupUri * soup_uri_copy (const SoupUri* uri) { - gchar *uri_str; SoupUri *dup; g_return_val_if_fail (uri != NULL, NULL); - uri_str = soup_uri_to_string (uri, TRUE); - dup = soup_uri_new (uri_str); - g_free (uri_str); + dup = g_new0 (SoupUri, 1); + dup->protocol = uri->protocol; + dup->user = g_strdup (uri->user); + dup->authmech = g_strdup (uri->authmech); + dup->passwd = g_strdup (uri->passwd); + dup->host = g_strdup (uri->host); + dup->port = uri->port; + dup->path = g_strdup (uri->path); + dup->querystring = g_strdup (uri->querystring); return dup; } +gboolean +soup_uri_equal (const SoupUri *u1, + const SoupUri *u2) +{ + if (u1->protocol == u2->protocol && + u1->port == u2->port && + !strcmp (u1->user, u2->user) && + !strcmp (u1->authmech, u2->authmech) && + !strcmp (u1->passwd, u2->passwd) && + !strcmp (u1->host, u2->host) && + !strcmp (u1->path, u2->path) && + !strcmp (u1->querystring, u2->querystring)) + return TRUE; + + return FALSE; +} + void soup_uri_free (SoupUri *uri) { @@ -283,7 +492,6 @@ soup_uri_free (SoupUri *uri) g_free (uri->host); g_free (uri->path); g_free (uri->querystring); - g_strfreev (uri->query_elems); g_free (uri); } diff --git a/libsoup/soup-uri.h b/libsoup/soup-uri.h index e8adea0..f7d297a 100644 --- a/libsoup/soup-uri.h +++ b/libsoup/soup-uri.h @@ -49,7 +49,6 @@ typedef struct { gchar *path; gchar *querystring; - gchar **query_elems; } SoupUri; SoupUri *soup_uri_new (const gchar *uri_string); @@ -59,6 +58,9 @@ gchar *soup_uri_to_string (const SoupUri *uri, SoupUri *soup_uri_copy (const SoupUri *uri); +gboolean soup_uri_equal (const SoupUri *uri1, + const SoupUri *uri2); + void soup_uri_free (SoupUri *uri); void soup_uri_set_auth (SoupUri *uri, -- 2.7.4