Filters

A filter is used to alter a transaction moving to or from the server. Behaviours can be implemented in filters to modify transactions, for example, change headers, add headers, and remove headers. The transaction body can also be changed to terminate or cancel a transaction, either to resubmit a replacement, or perform other operations on the client device using information from transaction headers or body.

HTTP filters can be loaded explicitly (by the client) or implicitly. The way the filter is loaded is dependent on the default_data setting in the IMPLEMENTATION_INFO section of the filter's resource file. The options are:

  • HTTP/+FILTERNAME: The filter is always loaded

  • HTTP/FILTERNAME: The filter is loaded unless the client removes it

  • HTTP/-FILTERNAME: The filter is not loaded unless the client adds it

In the preceding options, FILTERNAME is the name of your filter.

An RHTTPSession contains a queue that can hold zero or more filters, which are arranged in a priority order. The filter objects are shared amongst all transactions. To join the filter queue, a filter must be registered on the session, providing registration details that specify its triggers.

Filter triggers include: particular events, the presence of particular headers, or particular status codes.

When an event occurs on a transaction, the event traverses the filter queue either from the client to the server, or from the server back to the client. Priority order determines which filters are visited first when events traverse away from the client (that is, events that originate from the client such as THTTPEvent::ESubmit, which is sent when RHTTPTransaction::SubmitL() is called). Reverse priority order applies to events that traverse back towards the client (that is, those that originate from the server, such as THTTPEvent::EResponseComplete).

Adding, removing and querying filters

When an RHTTPSession is opened, a standard set of filters is pre-installed. The client does not need to do anything further if these filters are acceptable. The standard set includes:

  • Redirection filter

  • Validation filter

The client may add more filters of its own, or remove filters from the pre-installed set. RHTTPFilterCollection, for which a handle is obtained using RHTTPSession::FilterCollection(), provides facilities for adding and deleting filters, and for querying what filters are installed. TFilterConfigurationIterator allows a client to enumerate and query all available filters, and to install and uninstall a selected one.

Filters may only be added to, or removed from, a session when no transactions are opened on that session. This is easy to determine immediately when a session is opened. To help the client in determining the condition later, RHTTPFilterCollection::CanChangeFilters() is provided.

The collection of currently installed filters can be queried using an iterator. The following sample code demonstrates the use of the iterator:

void CHttpClient::ShowFilters()
    {
    RHTTPFilterCollection filtColl = iSess.FilterCollection();
    THTTPFilterIterator iter = filtColl.Query();

    THTTPFilterRegistration regInfo;
    iter.First();
    TInt lines = 0;
    while (!iter.AtEnd())
        {
        // Get next filter registration info
        regInfo = iter();

        TBuf<KMaxFilterNameLength> name;
        name.Copy(iSess.StringPool().StringF(regInfo.iName).DesC().Left(KMaxFilterNameLength));
        TBuf<KMaxHeaderNameLength> header;
        header.Copy(iSess.StringPool().StringF(regInfo.iHeader).DesC().Left(KMaxHeaderNameLength));

        Printf(_L("\n%16S | %4d  | %4d   | %16S |   %3d  | %2d"),
        &name, regInfo.iPosition, regInfo.iEvent.iStatus, &header, regInfo.iStatus, regInfo.iHandle);
        ++iter;
        }
    filtColl.Close();
    }

Authentication filter

The authentication filter provides an easy way of supporting basic and digest authentication, as defined in RFC2617. As it needs to get passwords, it is not installed as standard, but only when a MHTTPAuthenticationCallback installs it. To use it, you need to implement a subclass of MHTTPAuthenticationCallback, and should refer to the documentation of that class for more details.

Users of HTTP authentication should be aware of its security limitations. In particular, basic authentication passes passwords in plaintext. MHTTPAuthenticationCallback::GetCredentialsL() is told the authentication scheme being used, and applications where plaintext passwords would be an issue must consider rejecting challenges using basic authentication. See section 4 of RFC2617 for more details.

The authentication filter supports the following:

  • Basic (Base64) authentication

  • Digest authentication using the MD5 algorithm and the 'auth' Quality of Protection.

  • The older RFC2069 style digest authentication for backwards compatibility.

The MD5-sess algorithm is not supported as no major servers support it. The 'auth-int' QoP (Quality of Protection) is not supported as it does not add any real benefit. For integrity checking, SSL is preferred.

The filter remembers passwords and attempts to use them for subsequent challenges where appropriate. It will forget them if they turn out to be wrong. Currently, there is no facility to persist the passwords. For basic authentication, an attempt is made to supply the username and password with the first request, if the URI suggests that a previously stored username and password are applicable. This is not done for digest, as that introduces extra complications into the digest algorithm.

If an authentication challenge is received which the filter cannot understand, or if the MHTTPAuthenticationCallback does not supply credentials (returns EFalse) the filter effectively does nothing, that is, the client will receive a 401 error response in the same way as other error responses.

There are two alternative methods of supplying the username and password with a request. Clients that already know the username and password can consider using these methods and getting their MHTTPAuthenticationCallback::GetCredentialsL() to always return EFalse. The first method is to supply a URI of the form http://<username>:<password>@host/. In this case, the username and the password are removed from the URI when it is submitted, but will be used for any subsequent authentication challenge. The other method is to define transaction properties called 'username' and 'password' containing the username and password.

The filter is registered at position MHTTPFilter::EStatusCodeHandler for handling the '401' return code, and at position EStatusCodeHandler + 1 for handling submit events.

Redirection filter

The Redirection filter handles 300-series status codes from HTTP servers. These are used to tell the client of the correct location of a resource that has moved. Most clients will want this situation to be handled transparently: that is, to make a new request for the resource at the location specified by the server using the 'Location' header.

When an HTTP response is received that includes the status codes 300, 301, 302, 303, or 307, the Redirection filter cancels the current transaction and uses the URI from the 'Location' response header to resubmit a request on the same transaction. This means that all the headers in the original client request are preserved in the new request.

If no 'Location' header is found in the server response, the filter sends a KErrHttpRedirectNoLocationField error to the client. The response body can contain further information about the possible location of the resource.

In the case of the HTTP 305 'Use Proxy' status code, the transaction is not resubmitted. Instead, a KErrHttpRedirectUseProxy error is sent to the client. The 'Location' header will contain the address of a proxy to which the client must send the request for that URL. In practice, this means the client should modify the properties of their current RHTTPSession as described in Session and transaction properties.

HTTP 304 'Not Modified' responses are not handled by the redirection filter as they are used to indicate that the client contains a valid copy of the resource in the cache.

It is possible for the client request to be redirected more than once. To prevent the request getting into an endless loop of redirections, successive redirections that the filter will handle is limited to five.

Validation filter

The Validation filter has three main roles:

  • It checks the presence of a request body to ensure that it is consistent with the HTTP method in use.

  • It validates client requests to ensure that they do not include inappropriate header fields.

  • It provides a simplified result status for transactions.

Request bodies are only allowed for HTTP methods POST and PUT. If GET, HEAD, or TRACE requests contain a body, the filter will cancel the transaction and send a KErrHttpRequestHasBody error event to the client.

If the request headers contain any fields that are defined by RFC 2616 as response header fields, then the error event KErrHttpInvalidHeaderInRequest is sent to the client. Invalid fields are removed, and the transaction is allowed to continue.

If the request does not have a body, and the request headers contain any fields that are defined by RFC 2616 as entity header fields, then KErrHttpInvalidHeaderInRequest is sent to the client, the fields are removed, and the transaction continues.

If the request has a body, and the 'Content-Type' header is not present in the request headers, then the transaction will be cancelled and the KErrHttpEntityHeaderMissingContentType error event is sent to the client.

See also Headers and the header enumerations in HTTP for further information about different header types.

When a transaction response is received, the validation filter will determine from the status code if the transaction has been a success or a failure. The two events THTTPEvent::ESucceeded and THTTPEvent::EFailed are used to inform the client of this. The client can assume that no further events for that transaction will arrive after it has received either of these two events.

Configuring and installing filters

Apart from the standard set of filters, the client must manually install any other filter it wishes to use. Some filters may need to be configured before they can be installed; this should be done when the filter is instantiated.

An example of where filter configuration is needed is the authentication filter, which requires a callback to gather user credentials from the client. The client must implement the MHTTPAuthenticationCallback class, and at the time it opens an HTTP session must specify the object that implements the callback to configure the filter.

From httpexampleclient.h:

class CHttpClient : public CBase, public MHTTPDataSupplier, 
                    public MHTTPAuthenticationCallback
    {
public:
    ...
    // methods inherited from MHTTPAuthenticationCallback
    virtual TBool GetCredentialsL(const TUriC8& aURI, RString aRealm, 
                                 RStringF aAuthenticationType,
                                 RString& aUsername, 
                                 RString& aPassword);
    ...

From httpexampleclient.cpp:

void CHttpClient::ConstructL()
    {
    ...

    // Open the RHTTPSession and install this class as the callback for authentication requests
    iSess.OpenL();
    InstallAuthenticationL(iSess);
    ...
    }

In this case, MHTTPAuthenticationCallback::InstallAuthenticationL() is already implemented in the library. It creates and installs the filter on the client's behalf:

void MHTTPAuthenticationCallback::InstallAuthenticationL(RHTTPSession aSession)
    {
    // Create an authentication filter. This will install itself, and
    // will delete itself when uninstalled, so we don't need to keep
    // track of it at all.
    CAuthenticationFilter::NewL(*this, aSession);
    }

However in other cases, the client may have to construct the filter itself and configure it, before installing it on the session, for example:

void CAuthenticationFilter::ConstructL(RHTTPSession aSession)
    {
    ...
    // Register for WWW-Authenticate headers and 401 status codes
    aSession.FilterCollection().AddFilterL(*this, 
        THTTPEvent::EGotResponseHeaders, 
        iStringPool.StringF(HTTP::EWWWAuthenticate,RHTTPSession::GetTable()), 
        401, 
        EStatusCodeHandler, 
        iStringPool.StringF(HTTP::EAuthentication,RHTTPSession::GetTable()));
    ...
}

Writing a filter

This section provides an overview of writing filters. Refer to the functions mentioned for more details.

Filters need to derive from the MHTTPFilter class. Normally, the constructor or NewL() of a filter would take a session as a parameter, and would then register itself by calling RHTTPFilterCollection::AddFilterL().

Filters can often delete themselves automatically. If a filter only registers itself once, it can delete itself simply by overriding MHFUnload() and delete this in it. If you register several times, it is probably easiest to overload both MHTTPFilter::MHFLoad() and MHTTPFilter::MHFUnload(), increment a reference count in MHFLoad(), decrement it in MHTTPFilter::MHFUnload() and when it reaches zero, delete this.

A filter is notified of events through MHTTPFilterBase::MHFRunL(), similar to client notification. If the MHFRunL() implementation leaves, the filter must handle the error in MHTTPFilterBase::MHFRunError(). There is a potential problem there, in that you may well want to tell the client that something has gone wrong by RHTTPTransaction::SendEventL(), which can itself leave. If it does leave, you may be forced to call RHTTPTransaction::Fail(), which cancels the transaction and sends an THTTPEvent::EUnrecoverableError message outwards.

It is important to note that a filter object is per-session, and so might be shared by several transactions. This means that if you have a per-transaction state that you need to store, it must be stored in the transaction's property set. Do not store any per-transaction information in the filter object.