1b077aed3SPierre Pronchery=pod 2b077aed3SPierre Pronchery 3b077aed3SPierre Pronchery=head1 NAME 4b077aed3SPierre Pronchery 5b077aed3SPierre ProncheryOSSL_HTTP_open, 6b077aed3SPierre ProncheryOSSL_HTTP_bio_cb_t, 7b077aed3SPierre ProncheryOSSL_HTTP_proxy_connect, 8b077aed3SPierre ProncheryOSSL_HTTP_set1_request, 9b077aed3SPierre ProncheryOSSL_HTTP_exchange, 10b077aed3SPierre ProncheryOSSL_HTTP_get, 11b077aed3SPierre ProncheryOSSL_HTTP_transfer, 12b077aed3SPierre ProncheryOSSL_HTTP_close 13b077aed3SPierre Pronchery- HTTP client high-level functions 14b077aed3SPierre Pronchery 15b077aed3SPierre Pronchery=head1 SYNOPSIS 16b077aed3SPierre Pronchery 17b077aed3SPierre Pronchery #include <openssl/http.h> 18b077aed3SPierre Pronchery 19b077aed3SPierre Pronchery typedef BIO *(*OSSL_HTTP_bio_cb_t)(BIO *bio, void *arg, 20b077aed3SPierre Pronchery int connect, int detail); 21b077aed3SPierre Pronchery OSSL_HTTP_REQ_CTX *OSSL_HTTP_open(const char *server, const char *port, 22b077aed3SPierre Pronchery const char *proxy, const char *no_proxy, 23b077aed3SPierre Pronchery int use_ssl, BIO *bio, BIO *rbio, 24b077aed3SPierre Pronchery OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, 25b077aed3SPierre Pronchery int buf_size, int overall_timeout); 26b077aed3SPierre Pronchery int OSSL_HTTP_proxy_connect(BIO *bio, const char *server, const char *port, 27b077aed3SPierre Pronchery const char *proxyuser, const char *proxypass, 28b077aed3SPierre Pronchery int timeout, BIO *bio_err, const char *prog); 29b077aed3SPierre Pronchery int OSSL_HTTP_set1_request(OSSL_HTTP_REQ_CTX *rctx, const char *path, 30b077aed3SPierre Pronchery const STACK_OF(CONF_VALUE) *headers, 31b077aed3SPierre Pronchery const char *content_type, BIO *req, 32b077aed3SPierre Pronchery const char *expected_content_type, int expect_asn1, 33b077aed3SPierre Pronchery size_t max_resp_len, int timeout, int keep_alive); 34b077aed3SPierre Pronchery BIO *OSSL_HTTP_exchange(OSSL_HTTP_REQ_CTX *rctx, char **redirection_url); 35b077aed3SPierre Pronchery BIO *OSSL_HTTP_get(const char *url, const char *proxy, const char *no_proxy, 36b077aed3SPierre Pronchery BIO *bio, BIO *rbio, 37b077aed3SPierre Pronchery OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, 38b077aed3SPierre Pronchery int buf_size, const STACK_OF(CONF_VALUE) *headers, 39b077aed3SPierre Pronchery const char *expected_content_type, int expect_asn1, 40b077aed3SPierre Pronchery size_t max_resp_len, int timeout); 41b077aed3SPierre Pronchery BIO *OSSL_HTTP_transfer(OSSL_HTTP_REQ_CTX **prctx, 42b077aed3SPierre Pronchery const char *server, const char *port, 43b077aed3SPierre Pronchery const char *path, int use_ssl, 44b077aed3SPierre Pronchery const char *proxy, const char *no_proxy, 45b077aed3SPierre Pronchery BIO *bio, BIO *rbio, 46b077aed3SPierre Pronchery OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, 47b077aed3SPierre Pronchery int buf_size, const STACK_OF(CONF_VALUE) *headers, 48b077aed3SPierre Pronchery const char *content_type, BIO *req, 49b077aed3SPierre Pronchery const char *expected_content_type, int expect_asn1, 50b077aed3SPierre Pronchery size_t max_resp_len, int timeout, int keep_alive); 51b077aed3SPierre Pronchery int OSSL_HTTP_close(OSSL_HTTP_REQ_CTX *rctx, int ok); 52b077aed3SPierre Pronchery 53b077aed3SPierre Pronchery=head1 DESCRIPTION 54b077aed3SPierre Pronchery 55b077aed3SPierre ProncheryOSSL_HTTP_open() initiates an HTTP session using the I<bio> argument if not 56b077aed3SPierre ProncheryNULL, else by connecting to a given I<server> optionally via a I<proxy>. 57b077aed3SPierre Pronchery 58b077aed3SPierre ProncheryTypically the OpenSSL build supports sockets and the I<bio> parameter is NULL. 59b077aed3SPierre ProncheryIn this case I<rbio> must be NULL as well and the I<server> must be non-NULL. 60b077aed3SPierre ProncheryThe function creates a network BIO internally using L<BIO_new_connect(3)> 61b077aed3SPierre Proncheryfor connecting to the given server and the optionally given I<port>, 62b077aed3SPierre Proncherydefaulting to 80 for HTTP or 443 for HTTPS. 63b077aed3SPierre ProncheryThen this internal BIO is used for setting up a connection 64b077aed3SPierre Proncheryand for exchanging one or more request and response. 65b077aed3SPierre ProncheryIf I<bio> is given and I<rbio> is NULL then this I<bio> is used instead. 66b077aed3SPierre ProncheryIf both I<bio> and I<rbio> are given (which may be memory BIOs for instance) 67b077aed3SPierre Proncherythen no explicit connection is set up, but 68b077aed3SPierre ProncheryI<bio> is used for writing requests and I<rbio> for reading responses. 69b077aed3SPierre ProncheryAs soon as the client has flushed I<bio> the server must be ready to provide 70b077aed3SPierre Proncherya response or indicate a waiting condition via I<rbio>. 71b077aed3SPierre Pronchery 72b077aed3SPierre ProncheryIf I<bio> is given, it is an error to provide I<proxy> or I<no_proxy> arguments, 73b077aed3SPierre Proncherywhile I<server> and I<port> arguments may be given to support diagnostic output. 74b077aed3SPierre ProncheryIf I<bio> is NULL the optional I<proxy> parameter can be used to set an 75b077aed3SPierre ProncheryHTTP(S) proxy to use (unless overridden by "no_proxy" settings). 76b077aed3SPierre ProncheryIf TLS is not used this defaults to the environment variable C<http_proxy> 77b077aed3SPierre Proncheryif set, else C<HTTP_PROXY>. 78b077aed3SPierre ProncheryIf I<use_ssl> != 0 it defaults to C<https_proxy> if set, else C<HTTPS_PROXY>. 79b077aed3SPierre ProncheryAn empty proxy string C<""> forbids using a proxy. 80*0d0c8621SEnji CooperOtherwise, the format is 81b077aed3SPierre ProncheryC<[http[s]://][userinfo@]host[:port][/path][?query][#fragment]>, 82b077aed3SPierre Proncherywhere any userinfo, path, query, and fragment given is ignored. 83*0d0c8621SEnji CooperIf the host string is an IPv6 address, it must be enclosed in C<[> and C<]>. 84b077aed3SPierre ProncheryThe default proxy port number is 80, or 443 in case "https:" is given. 85b077aed3SPierre ProncheryThe HTTP client functions connect via the given proxy unless the I<server> 86*0d0c8621SEnji Cooperis found in the optional list I<no_proxy> of proxy hostnames or IP addresses 87*0d0c8621SEnji Cooperseparated by C<,> and/or whitespace (if not NULL; 88b077aed3SPierre Proncherydefault is the environment variable C<no_proxy> if set, else C<NO_PROXY>). 89b077aed3SPierre ProncheryProxying plain HTTP is supported directly, 90b077aed3SPierre Proncherywhile using a proxy for HTTPS connections requires a suitable callback function 91b077aed3SPierre Proncherysuch as OSSL_HTTP_proxy_connect(), described below. 92b077aed3SPierre Pronchery 93b077aed3SPierre ProncheryIf I<use_ssl> is nonzero a TLS connection is requested 94b077aed3SPierre Proncheryand the I<bio_update_fn> parameter must be provided. 95b077aed3SPierre Pronchery 96b077aed3SPierre ProncheryThe parameter I<bio_update_fn>, which is optional if I<use_ssl> is 0, 97b077aed3SPierre Proncherymay be used to modify the connection BIO used by the HTTP client, 98b077aed3SPierre Proncherybut cannot be used when both I<bio> and I<rbio> are given. 99b077aed3SPierre ProncheryI<bio_update_fn> is a BIO connect/disconnect callback function with prototype 100b077aed3SPierre Pronchery 101b077aed3SPierre Pronchery BIO *(*OSSL_HTTP_bio_cb_t)(BIO *bio, void *arg, int connect, int detail) 102b077aed3SPierre Pronchery 103b077aed3SPierre ProncheryThe callback function may modify the BIO provided in the I<bio> argument, 104b077aed3SPierre Proncherywhereby it may make use of a custom defined argument I<arg>, 105b077aed3SPierre Proncherywhich may for instance point to an B<SSL_CTX> structure. 106b077aed3SPierre ProncheryDuring connection establishment, just after calling BIO_do_connect_retry(), the 107b077aed3SPierre Proncherycallback function is invoked with the I<connect> argument being 1 and 108b077aed3SPierre ProncheryI<detail> being 1 if I<use_ssl> is nonzero (i.e., HTTPS is requested), else 0. 109b077aed3SPierre ProncheryOn disconnect I<connect> is 0 and I<detail> is 1 if no error occurred, else 0. 110b077aed3SPierre ProncheryFor instance, on connect the callback may push an SSL BIO to implement HTTPS; 111b077aed3SPierre Proncheryafter disconnect it may do some diagnostic output and pop and free the SSL BIO. 112b077aed3SPierre Pronchery 113b077aed3SPierre ProncheryThe callback function must return either the potentially modified BIO I<bio>. 114b077aed3SPierre Proncheryor NULL to indicate failure, in which case it should not modify the BIO. 115b077aed3SPierre Pronchery 116b077aed3SPierre ProncheryHere is a simple example that supports TLS connections (but not via a proxy): 117b077aed3SPierre Pronchery 118b077aed3SPierre Pronchery BIO *http_tls_cb(BIO *bio, void *arg, int connect, int detail) 119b077aed3SPierre Pronchery { 120b077aed3SPierre Pronchery if (connect && detail) { /* connecting with TLS */ 121b077aed3SPierre Pronchery SSL_CTX *ctx = (SSL_CTX *)arg; 122b077aed3SPierre Pronchery BIO *sbio = BIO_new_ssl(ctx, 1); 123b077aed3SPierre Pronchery 124b077aed3SPierre Pronchery bio = sbio != NULL ? BIO_push(sbio, bio) : NULL; 125b077aed3SPierre Pronchery } else if (!connect) { /* disconnecting */ 126b077aed3SPierre Pronchery BIO *hbio; 127b077aed3SPierre Pronchery 128b077aed3SPierre Pronchery if (!detail) { /* an error has occurred */ 129b077aed3SPierre Pronchery /* optionally add diagnostics here */ 130b077aed3SPierre Pronchery } 131b077aed3SPierre Pronchery BIO_ssl_shutdown(bio); 132b077aed3SPierre Pronchery hbio = BIO_pop(bio); 133b077aed3SPierre Pronchery BIO_free(bio); /* SSL BIO */ 134b077aed3SPierre Pronchery bio = hbio; 135b077aed3SPierre Pronchery } 136b077aed3SPierre Pronchery return bio; 137b077aed3SPierre Pronchery } 138b077aed3SPierre Pronchery 139b077aed3SPierre ProncheryAfter disconnect the modified BIO will be deallocated using BIO_free_all(). 140b077aed3SPierre Pronchery 141b077aed3SPierre ProncheryThe I<buf_size> parameter specifies the response header maximum line length. 142b077aed3SPierre ProncheryA value <= 0 means that the B<OSSL_HTTP_DEFAULT_MAX_LINE_LEN> (4KiB) is used. 143b077aed3SPierre ProncheryI<buf_size> is also used as the number of content bytes that are read at a time. 144b077aed3SPierre Pronchery 145b077aed3SPierre ProncheryIf the I<overall_timeout> parameter is > 0 this indicates the maximum number of 146b077aed3SPierre Proncheryseconds the overall HTTP transfer (i.e., connection setup if needed, 147b077aed3SPierre Proncherysending requests, and receiving responses) is allowed to take until completion. 148b077aed3SPierre ProncheryA value <= 0 enables waiting indefinitely, i.e., no timeout. 149b077aed3SPierre Pronchery 150b077aed3SPierre ProncheryOSSL_HTTP_proxy_connect() may be used by an above BIO connect callback function 151b077aed3SPierre Proncheryto set up an SSL/TLS connection via an HTTPS proxy. 152b077aed3SPierre ProncheryIt promotes the given BIO I<bio> representing a connection 153b077aed3SPierre Proncherypre-established with a TLS proxy using the HTTP CONNECT method, 154b077aed3SPierre Proncheryoptionally using proxy client credentials I<proxyuser> and I<proxypass>, 155b077aed3SPierre Proncheryto connect with TLS protection ultimately to I<server> and I<port>. 156b077aed3SPierre ProncheryIf the I<port> argument is NULL or the empty string it defaults to "443". 157b077aed3SPierre ProncheryIf the I<timeout> parameter is > 0 this indicates the maximum number of 158b077aed3SPierre Proncheryseconds the connection setup is allowed to take. 159b077aed3SPierre ProncheryA value <= 0 enables waiting indefinitely, i.e., no timeout. 160b077aed3SPierre ProncherySince this function is typically called by applications such as 161b077aed3SPierre ProncheryL<openssl-s_client(1)> it uses the I<bio_err> and I<prog> parameters (unless 162b077aed3SPierre ProncheryNULL) to print additional diagnostic information in a user-oriented way. 163b077aed3SPierre Pronchery 164b077aed3SPierre ProncheryOSSL_HTTP_set1_request() sets up in I<rctx> the request header and content data 165b077aed3SPierre Proncheryand expectations on the response using the following parameters. 1666f1af0d7SPierre ProncheryIf <rctx> indicates using a proxy for HTTP (but not HTTPS), the server host 1676f1af0d7SPierre Pronchery(and optionally port) needs to be placed in the header; thus it must be present 1686f1af0d7SPierre Proncheryin I<rctx>. 1696f1af0d7SPierre ProncheryFor backward compatibility, the server (and optional port) may also be given in 1706f1af0d7SPierre Proncherythe I<path> argument beginning with C<http://> (thus giving an absoluteURI). 171b077aed3SPierre ProncheryIf I<path> is NULL it defaults to "/". 172b077aed3SPierre ProncheryIf I<req> is NULL the HTTP GET method will be used to send the request 173b077aed3SPierre Proncheryelse HTTP POST with the contents of I<req> and optional I<content_type>, where 174b077aed3SPierre Proncherythe length of the data in I<req> does not need to be determined in advance: the 175b077aed3SPierre ProncheryBIO will be read on-the-fly while sending the request, which supports streaming. 176b077aed3SPierre ProncheryThe optional list I<headers> may contain additional custom HTTP header lines. 177b077aed3SPierre ProncheryIf the parameter I<expected_content_type> 178b077aed3SPierre Proncheryis not NULL then the client will check that the given content type string 179b077aed3SPierre Proncheryis included in the HTTP header of the response and return an error if not. 180b077aed3SPierre ProncheryIf the I<expect_asn1> parameter is nonzero, 181b077aed3SPierre Proncherya structure in ASN.1 encoding will be expected as response content. 182b077aed3SPierre ProncheryThe I<max_resp_len> parameter specifies the maximum allowed 183b077aed3SPierre Proncheryresponse content length, where the value 0 indicates no limit. 184b077aed3SPierre ProncheryIf the I<timeout> parameter is > 0 this indicates the maximum number of seconds 185b077aed3SPierre Proncherythe subsequent HTTP transfer (sending the request and receiving a response) 186b077aed3SPierre Proncheryis allowed to take. 187b077aed3SPierre ProncheryA value of 0 enables waiting indefinitely, i.e., no timeout. 188b077aed3SPierre ProncheryA value < 0 indicates that the I<overall_timeout> parameter value given 189b077aed3SPierre Proncherywhen opening the HTTP transfer will be used instead. 190b077aed3SPierre ProncheryIf I<keep_alive> is 0 the connection is not kept open 191b077aed3SPierre Proncheryafter receiving a response, which is the default behavior for HTTP 1.0. 192b077aed3SPierre ProncheryIf the value is 1 or 2 then a persistent connection is requested. 193b077aed3SPierre ProncheryIf the value is 2 then a persistent connection is required, 194b077aed3SPierre Proncheryi.e., an error occurs in case the server does not grant it. 195b077aed3SPierre Pronchery 196b077aed3SPierre ProncheryOSSL_HTTP_exchange() exchanges any form of HTTP request and response 197b077aed3SPierre Proncheryas specified by I<rctx>, which must include both connection and request data, 198b077aed3SPierre Proncherytypically set up using OSSL_HTTP_open() and OSSL_HTTP_set1_request(). 199b077aed3SPierre ProncheryIt implements the core of the functions described below. 200b077aed3SPierre ProncheryIf the HTTP method is GET and I<redirection_url> 201b077aed3SPierre Proncheryis not NULL the latter pointer is used to provide any new location that 202b077aed3SPierre Proncherythe server may return with HTTP code 301 (MOVED_PERMANENTLY) or 302 (FOUND). 203b077aed3SPierre ProncheryIn this case the function returns NULL and the caller is 204b077aed3SPierre Proncheryresponsible for deallocating the URL with L<OPENSSL_free(3)>. 205b077aed3SPierre ProncheryIf the response header contains one or more "Content-Length" header lines and/or 206b077aed3SPierre Proncheryan ASN.1-encoded response is expected, which should include a total length, 207b077aed3SPierre Proncherythe length indications received are checked for consistency 208b077aed3SPierre Proncheryand for not exceeding any given maximum response length. 209b077aed3SPierre ProncheryIf an ASN.1-encoded response is expected, the function returns on success 210b077aed3SPierre Proncherythe contents buffered in a memory BIO, which does not support streaming. 211b077aed3SPierre ProncheryOtherwise it returns directly the read BIO that holds the response contents, 212b077aed3SPierre Proncherywhich allows a response of indefinite length and may support streaming. 213b077aed3SPierre ProncheryThe caller is responsible for freeing the BIO pointer obtained. 214b077aed3SPierre Pronchery 215b077aed3SPierre ProncheryOSSL_HTTP_get() uses HTTP GET to obtain data from I<bio> if non-NULL, 216b077aed3SPierre Proncheryelse from the server contained in the I<url>, and returns it as a BIO. 217b077aed3SPierre ProncheryIt supports redirection via HTTP status code 301 or 302. It is meant for 218b077aed3SPierre Proncherytransfers with a single round trip, so does not support persistent connections. 219b077aed3SPierre ProncheryIf I<bio> is non-NULL, any host and port components in the I<url> are not used 220b077aed3SPierre Proncheryfor connecting but the hostname is used, as usual, for the C<Host> header. 221b077aed3SPierre ProncheryAny userinfo and fragment components in the I<url> are ignored. 222b077aed3SPierre ProncheryAny query component is handled as part of the path component. 223b077aed3SPierre ProncheryIf the scheme component of the I<url> is C<https> a TLS connection is requested 224b077aed3SPierre Proncheryand the I<bio_update_fn>, as described for OSSL_HTTP_open(), must be provided. 225b077aed3SPierre ProncheryAlso the remaining parameters are interpreted as described for OSSL_HTTP_open() 226b077aed3SPierre Proncheryand OSSL_HTTP_set1_request(), respectively. 227b077aed3SPierre ProncheryThe caller is responsible for freeing the BIO pointer obtained. 228b077aed3SPierre Pronchery 229b077aed3SPierre ProncheryOSSL_HTTP_transfer() exchanges an HTTP request and response 230b077aed3SPierre Proncheryover a connection managed via I<prctx> without supporting redirection. 231b077aed3SPierre ProncheryIt combines OSSL_HTTP_open(), OSSL_HTTP_set1_request(), OSSL_HTTP_exchange(), 232b077aed3SPierre Proncheryand OSSL_HTTP_close(). 233b077aed3SPierre ProncheryIf I<prctx> is not NULL it reuses any open connection represented by a non-NULL 234b077aed3SPierre ProncheryI<*prctx>. It keeps the connection open if a persistent connection is requested 235b077aed3SPierre Proncheryor required and this was granted by the server, else it closes the connection 236b077aed3SPierre Proncheryand assigns NULL to I<*prctx>. 237b077aed3SPierre ProncheryThe remaining parameters are interpreted as described for OSSL_HTTP_open() 238b077aed3SPierre Proncheryand OSSL_HTTP_set1_request(), respectively. 239b077aed3SPierre ProncheryThe caller is responsible for freeing the BIO pointer obtained. 240b077aed3SPierre Pronchery 241b077aed3SPierre ProncheryOSSL_HTTP_close() closes the connection and releases I<rctx>. 242b077aed3SPierre ProncheryThe I<ok> parameter is passed to any BIO update function 243b077aed3SPierre Proncherygiven during setup as described above for OSSL_HTTP_open(). 244b077aed3SPierre ProncheryIt must be 1 if no error occurred during the HTTP transfer and 0 otherwise. 245b077aed3SPierre Pronchery 246b077aed3SPierre Pronchery=head1 NOTES 247b077aed3SPierre Pronchery 248b077aed3SPierre ProncheryThe names of the environment variables used by this implementation: 249b077aed3SPierre ProncheryC<http_proxy>, C<HTTP_PROXY>, C<https_proxy>, C<HTTPS_PROXY>, C<no_proxy>, and 250b077aed3SPierre ProncheryC<NO_PROXY>, have been chosen for maximal compatibility with 251b077aed3SPierre Proncheryother HTTP client implementations such as wget, curl, and git. 252b077aed3SPierre Pronchery 253b077aed3SPierre Pronchery=head1 RETURN VALUES 254b077aed3SPierre Pronchery 255b077aed3SPierre ProncheryOSSL_HTTP_open() returns on success a B<OSSL_HTTP_REQ_CTX>, else NULL. 256b077aed3SPierre Pronchery 257b077aed3SPierre ProncheryOSSL_HTTP_proxy_connect() and OSSL_HTTP_set1_request() 258b077aed3SPierre Proncheryreturn 1 on success, 0 on error. 259b077aed3SPierre Pronchery 260b077aed3SPierre ProncheryOn success, OSSL_HTTP_exchange(), OSSL_HTTP_get(), and OSSL_HTTP_transfer() 261b077aed3SPierre Proncheryreturn a memory BIO that buffers all the data received if an ASN.1-encoded 262b077aed3SPierre Proncheryresponse is expected, otherwise a BIO that may support streaming. 263b077aed3SPierre ProncheryThe BIO must be freed by the caller. 264b077aed3SPierre ProncheryOn failure, they return NULL. 265b077aed3SPierre ProncheryFailure conditions include connection/transfer timeout, parse errors, etc. 266b077aed3SPierre ProncheryThe caller is responsible for freeing the BIO pointer obtained. 267b077aed3SPierre Pronchery 268b077aed3SPierre ProncheryOSSL_HTTP_close() returns 0 if anything went wrong while disconnecting, else 1. 269b077aed3SPierre Pronchery 270b077aed3SPierre Pronchery=head1 SEE ALSO 271b077aed3SPierre Pronchery 272b077aed3SPierre ProncheryL<OSSL_HTTP_parse_url(3)>, L<BIO_new_connect(3)>, 273b077aed3SPierre ProncheryL<ASN1_item_i2d_mem_bio(3)>, L<ASN1_item_d2i_bio(3)>, 274b077aed3SPierre ProncheryL<OSSL_HTTP_is_alive(3)> 275b077aed3SPierre Pronchery 276b077aed3SPierre Pronchery=head1 HISTORY 277b077aed3SPierre Pronchery 278b077aed3SPierre ProncheryAll the functions described here were added in OpenSSL 3.0. 279b077aed3SPierre Pronchery 280b077aed3SPierre Pronchery=head1 COPYRIGHT 281b077aed3SPierre Pronchery 2826f1af0d7SPierre ProncheryCopyright 2019-2023 The OpenSSL Project Authors. All Rights Reserved. 283b077aed3SPierre Pronchery 284b077aed3SPierre ProncheryLicensed under the Apache License 2.0 (the "License"). You may not use 285b077aed3SPierre Proncherythis file except in compliance with the License. You can obtain a copy 286b077aed3SPierre Proncheryin the file LICENSE in the source distribution or at 287b077aed3SPierre ProncheryL<https://www.openssl.org/source/license.html>. 288b077aed3SPierre Pronchery 289b077aed3SPierre Pronchery=cut 290