e576bb2002-10-11Martin Nilsson /* || This file is part of Pike. For copyright information see COPYRIGHT. || Pike is distributed under GPL, LGPL and MPL. See the file COPYING || for more information.
fc03522005-04-08Henrik Grubbström (Grubba) || $Id: roxen.c,v 1.42 2005/04/08 17:23:46 grubba Exp $
e576bb2002-10-11Martin Nilsson */
33805b2000-08-12Per Hedbor #define NO_PIKE_SHORTHAND #include "global.h" #include "config.h" #include "machine.h" #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <ctype.h> #include <string.h> #include <time.h> #include <limits.h> #include "fdlib.h" #include "stralloc.h" #include "pike_macros.h" #include "machine.h" #include "object.h" #include "constants.h" #include "interpret.h" #include "svalue.h" #include "mapping.h" #include "array.h" #include "builtin_functions.h" #include "module_support.h" #include "backend.h" #include "threads.h" #include "operators.h"
ced8a32001-02-13Henrik Grubbström (Grubba) /*! @module _Roxen */ /*! @class HeaderParser */
33805b2000-08-12Per Hedbor 
6b87882001-06-21Martin Stjernholm #define THP ((struct header_buf *)Pike_fp->current_storage)
33805b2000-08-12Per Hedbor struct header_buf {
fc03522005-04-08Henrik Grubbström (Grubba)  unsigned char *headers; unsigned char *pnt;
2d41d42001-10-02Per Hedbor  ptrdiff_t hsize, left;
3c64ee2001-02-20Per Hedbor  int slash_n, spc;
33805b2000-08-12Per Hedbor };
e15d142002-10-14Henrik Grubbström (Grubba) static void f_hp_init( struct object *o ) { THP->headers = NULL; THP->pnt = NULL; THP->hsize = 0;
8f77192004-01-27Henrik Grubbström (Grubba)  THP->left = 0; THP->spc = THP->slash_n = 0;
e15d142002-10-14Henrik Grubbström (Grubba) }
2d41d42001-10-02Per Hedbor static void f_hp_exit( struct object *o ) { if( THP->headers ) free( THP->headers );
e15d142002-10-14Henrik Grubbström (Grubba)  THP->headers = NULL; THP->pnt = NULL; THP->hsize = 0;
2d41d42001-10-02Per Hedbor } static void f_hp_feed( INT32 args )
ced8a32001-02-13Henrik Grubbström (Grubba) /*! @decl array(string|mapping) feed(string data) */
33805b2000-08-12Per Hedbor { struct pike_string *str = Pike_sp[-1].u.string;
3c64ee2001-02-20Per Hedbor  struct header_buf *hp = THP;
21dc262003-04-14Martin Stjernholm  int str_len;
4d4cc22001-09-24Henrik Grubbström (Grubba)  int tot_slash_n=hp->slash_n, slash_n = 0, spc = hp->spc;
fc03522005-04-08Henrik Grubbström (Grubba)  unsigned char *pp,*ep;
33805b2000-08-12Per Hedbor  struct svalue *tmp; struct mapping *headers;
aad5842000-08-12Henrik Grubbström (Grubba)  ptrdiff_t os=0, i, j, l;
33805b2000-08-12Per Hedbor  unsigned char *in; if( Pike_sp[-1].type != PIKE_T_STRING )
b2d3e42000-12-01Fredrik Hübinette (Hubbe)  Pike_error("Wrong type of argument to feed()\n");
2d41d42001-10-02Per Hedbor  if( str->size_shift ) Pike_error("Wide string headers not supported\n");
21dc262003-04-14Martin Stjernholm  str_len = str->len; while( str_len >= hp->left )
2d41d42001-10-02Per Hedbor  {
fc03522005-04-08Henrik Grubbström (Grubba)  unsigned char *buf;
2d41d42001-10-02Per Hedbor  if( THP->hsize > 512 * 1024 ) Pike_error("Too many headers\n"); THP->hsize += 8192;
9e361c2002-02-14Henrik Grubbström (Grubba)  buf = THP->headers;
2d41d42001-10-02Per Hedbor  THP->headers = realloc( THP->headers, THP->hsize ); if( !THP->headers ) {
9e361c2002-02-14Henrik Grubbström (Grubba)  free(buf);
2d41d42001-10-02Per Hedbor  THP->hsize = 0; THP->left = 0;
8f77192004-01-27Henrik Grubbström (Grubba)  THP->spc = THP->slash_n = 0; THP->pnt = NULL;
2d41d42001-10-02Per Hedbor  Pike_error("Running out of memory in header parser\n"); }
9e361c2002-02-14Henrik Grubbström (Grubba)  THP->left += 8192;
2d41d42001-10-02Per Hedbor  THP->pnt = (THP->headers + THP->hsize - THP->left); }
33805b2000-08-12Per Hedbor 
21dc262003-04-14Martin Stjernholm  MEMCPY( hp->pnt, str->str, str_len );
d5ea292001-02-01Per Hedbor  pop_n_elems( args );
9e361c2002-02-14Henrik Grubbström (Grubba)  /* FIXME: The below does not support lines terminated with just \r. */
21dc262003-04-14Martin Stjernholm  for( ep=(hp->pnt+str_len),pp=MAXIMUM(hp->headers,hp->pnt-3);
d5ea292001-02-01Per Hedbor  pp<ep && slash_n<2; pp++ ) if( *pp == ' ' ) spc++; else if( *pp == '\n' ) slash_n++, tot_slash_n++; else if( *pp != '\r' ) slash_n=0;
33805b2000-08-12Per Hedbor 
3c64ee2001-02-20Per Hedbor  hp->slash_n = tot_slash_n; hp->spc = spc;
2d41d42001-10-02Per Hedbor 
21dc262003-04-14Martin Stjernholm  hp->left -= str_len; hp->pnt += str_len;
3c64ee2001-02-20Per Hedbor  hp->pnt[0] = 0;
d5ea292001-02-01Per Hedbor 
33805b2000-08-12Per Hedbor  if( slash_n != 2 ) {
d5ea292001-02-01Per Hedbor  /* one newline, but less than 2 space, * --> HTTP/0.9 or broken request */ if( (spc < 2) && tot_slash_n ) {
128a452003-12-09Martin Nilsson  push_constant_text( "" );
d5ea292001-02-01Per Hedbor  /* This includes (all eventual) \r\n etc. */
3c64ee2001-02-20Per Hedbor  push_text( hp->headers );
d5ea292001-02-01Per Hedbor  f_aggregate_mapping( 0 ); f_aggregate( 3 ); return; }
33805b2000-08-12Per Hedbor  push_int( 0 ); return; }
3c64ee2001-02-20Per Hedbor  push_string( make_shared_binary_string( pp, hp->pnt - pp ) ); /*leftovers*/
33805b2000-08-12Per Hedbor  headers = allocate_mapping( 5 );
3c64ee2001-02-20Per Hedbor  in = hp->headers; l = pp - hp->headers;
4a84492000-08-13David Hedbor 
33805b2000-08-12Per Hedbor  /* find first line here */ for( i = 0; i < l; i++ )
9e361c2002-02-14Henrik Grubbström (Grubba)  if((in[i] == '\n') || (in[i] == '\r'))
33805b2000-08-12Per Hedbor  break;
9e361c2002-02-14Henrik Grubbström (Grubba)  push_string( make_shared_binary_string( in, i ) ); if((in[i] == '\r') && (in[i+1] == '\n'))
33805b2000-08-12Per Hedbor  i++;
9e361c2002-02-14Henrik Grubbström (Grubba)  i++;
33805b2000-08-12Per Hedbor  in += i; l -= i;
9e361c2002-02-14Henrik Grubbström (Grubba)  /* Parse headers. */
33805b2000-08-12Per Hedbor  for(i = 0; i < l; i++) {
9e361c2002-02-14Henrik Grubbström (Grubba)  if(in[i] > 64 && in[i] < 91) in[i]+=32; /* lower_case */
4a84492000-08-13David Hedbor  else if( in[i] == ':' )
33805b2000-08-12Per Hedbor  {
9e361c2002-02-14Henrik Grubbström (Grubba)  /* FIXME: Does not support white space before the colon. */
33805b2000-08-12Per Hedbor  /* in[os..i-1] == the header */
9e361c2002-02-14Henrik Grubbström (Grubba)  int val_cnt = 0;
33805b2000-08-12Per Hedbor  push_string(make_shared_binary_string((char*)in+os,i-os));
d5ea292001-02-01Per Hedbor 
9e361c2002-02-14Henrik Grubbström (Grubba)  /* Skip the colon and initial white space. */ os = i+1; while((in[os]==' ') || (in[os]=='\t')) os++; /* NOTE: We need to support MIME header continuation lines * (Opera uses this...). */ do { for(j=os;j<l;j++) /* Find end of line */ if( in[j] == '\n' || in[j]=='\r') break; push_string(make_shared_binary_string((char*)in+os,j-os)); val_cnt++; if((in[j] == '\r') && (in[j+1] == '\n')) j++; os = j+1; i = j; /* Check for continuation line. */ } while ((os < l) && ((in[os] == ' ') || (in[os] == '\t'))); if (val_cnt > 1) { /* Join partial header values. */ f_add(val_cnt); }
33805b2000-08-12Per Hedbor  if((tmp = low_mapping_lookup(headers, Pike_sp-2))) {
9e361c2002-02-14Henrik Grubbström (Grubba)  f_aggregate( 1 ); if( tmp->type == PIKE_T_ARRAY ) {
50ea682003-03-14Henrik Grubbström (Grubba)  ref_push_array(tmp->u.array);
9e361c2002-02-14Henrik Grubbström (Grubba)  map_delete(headers, Pike_sp-3); f_add(2); } else {
50ea682003-03-14Henrik Grubbström (Grubba)  ref_push_string(tmp->u.string);
9e361c2002-02-14Henrik Grubbström (Grubba)  f_aggregate(1); map_delete(headers, Pike_sp-3); f_add(2); }
33805b2000-08-12Per Hedbor  } mapping_insert(headers, Pike_sp-2, Pike_sp-1);
9e361c2002-02-14Henrik Grubbström (Grubba) 
33805b2000-08-12Per Hedbor  pop_n_elems(2); } } push_mapping( headers );
d5ea292001-02-01Per Hedbor  f_aggregate( 3 ); /* data, firstline, headers */
33805b2000-08-12Per Hedbor }
2d41d42001-10-02Per Hedbor static void f_hp_create( INT32 args )
ced8a32001-02-13Henrik Grubbström (Grubba) /*! @decl void create(void) */
33805b2000-08-12Per Hedbor {
e15d142002-10-14Henrik Grubbström (Grubba)  if (THP->headers) { free(THP->headers); THP->headers = NULL; } THP->headers = xalloc( 8192 );
33805b2000-08-12Per Hedbor  THP->pnt = THP->headers;
2d41d42001-10-02Per Hedbor  THP->hsize = 8192;
33805b2000-08-12Per Hedbor  THP->left = 8192;
3c64ee2001-02-20Per Hedbor  THP->spc = THP->slash_n = 0;
ced8a32001-02-13Henrik Grubbström (Grubba)  pop_n_elems(args); push_int(0);
33805b2000-08-12Per Hedbor }
4183032001-10-03Martin Nilsson /*! @endclass */
2d41d42001-10-02Per Hedbor static void f_make_http_headers( INT32 args )
ced8a32001-02-13Henrik Grubbström (Grubba) /*! @decl string @ *! make_http_headers(mapping(string:string|array(string)) headers) */
33805b2000-08-12Per Hedbor { int total_len = 0, e;
fc03522005-04-08Henrik Grubbström (Grubba)  unsigned char *pnt;
33805b2000-08-12Per Hedbor  struct mapping *m; struct keypair *k; struct pike_string *res; if( Pike_sp[-1].type != PIKE_T_MAPPING )
b2d3e42000-12-01Fredrik Hübinette (Hubbe)  Pike_error("Wrong argument type to make_http_headers(mapping heads)\n");
33805b2000-08-12Per Hedbor  m = Pike_sp[-1].u.mapping; /* loop to check len */ NEW_MAPPING_LOOP( m->data ) { if( k->ind.type != PIKE_T_STRING || k->ind.u.string->size_shift )
b2d3e42000-12-01Fredrik Hübinette (Hubbe)  Pike_error("Wrong argument type to make_http_headers("
33805b2000-08-12Per Hedbor  "mapping(string(8bit):string(8bit)|array(string(8bit))) heads)\n");
5fc9212001-10-08Per Hedbor  if( k->val.type == PIKE_T_STRING && !k->val.u.string->size_shift )
33805b2000-08-12Per Hedbor  total_len += k->val.u.string->len + 2 + k->ind.u.string->len + 2; else if( k->val.type == PIKE_T_ARRAY ) { struct array *a = k->val.u.array;
aad5842000-08-12Henrik Grubbström (Grubba)  ptrdiff_t i, kl = k->ind.u.string->len + 2 ;
33805b2000-08-12Per Hedbor  for( i = 0; i<a->size; i++ ) if( a->item[i].type != PIKE_T_STRING||a->item[i].u.string->size_shift )
b2d3e42000-12-01Fredrik Hübinette (Hubbe)  Pike_error("Wrong argument type to make_http_headers("
33805b2000-08-12Per Hedbor  "mapping(string(8bit):string(8bit)|" "array(string(8bit))) heads)\n"); else total_len += kl + a->item[i].u.string->len + 2; } else
b2d3e42000-12-01Fredrik Hübinette (Hubbe)  Pike_error("Wrong argument type to make_http_headers("
33805b2000-08-12Per Hedbor  "mapping(string(8bit):string(8bit)|" "array(string(8bit))) heads)\n"); } total_len += 2; res = begin_shared_string( total_len );
fc03522005-04-08Henrik Grubbström (Grubba)  pnt = STR0(res);
33805b2000-08-12Per Hedbor #define STRADD(X)\ for( l=X.u.string->len,s=X.u.string->str,c=0; c<l; c++ )\ *(pnt++)=*(s++); NEW_MAPPING_LOOP( m->data ) {
fc03522005-04-08Henrik Grubbström (Grubba)  unsigned char *s;
aad5842000-08-12Henrik Grubbström (Grubba)  ptrdiff_t l, c;
33805b2000-08-12Per Hedbor  if( k->val.type == PIKE_T_STRING ) { STRADD( k->ind ); *(pnt++) = ':'; *(pnt++) = ' '; STRADD( k->val ); *(pnt++) = '\r'; *(pnt++) = '\n'; } else { struct array *a = k->val.u.array;
aad5842000-08-12Henrik Grubbström (Grubba)  ptrdiff_t i, kl = k->ind.u.string->len + 2;
33805b2000-08-12Per Hedbor  for( i = 0; i<a->size; i++ ) { STRADD( k->ind ); *(pnt++) = ':'; *(pnt++) = ' '; STRADD( a->item[i] );*(pnt++) = '\r';*(pnt++) = '\n'; } } } *(pnt++) = '\r'; *(pnt++) = '\n'; pop_n_elems( args ); push_string( end_shared_string( res ) ); }
2d41d42001-10-02Per Hedbor static void f_http_decode_string(INT32 args)
ced8a32001-02-13Henrik Grubbström (Grubba) /*! @decl string http_decode_string(string encoded) *! *! Decodes an http transport-encoded string. */
33805b2000-08-12Per Hedbor { int proc;
8f77192004-01-27Henrik Grubbström (Grubba)  int size_shift = 0; int adjust_len = 0;
44a6812004-01-27Henrik Grubbström (Grubba)  p_wchar0 *foo, *bar, *end;
33805b2000-08-12Per Hedbor  struct pike_string *newstr;
8f77192004-01-27Henrik Grubbström (Grubba)  if (!args || Pike_sp[-args].type != PIKE_T_STRING || Pike_sp[-args].u.string->size_shift) Pike_error("Invalid argument to http_decode_string(string(8bit));\n");
44a6812004-01-27Henrik Grubbström (Grubba)  foo = bar = STR0(Pike_sp[-args].u.string); end = foo + Pike_sp[-args].u.string->len;
8f77192004-01-27Henrik Grubbström (Grubba)  /* count '%' and wide characters */ for (proc=0; foo<end; foo++) { if (*foo=='%') { proc++; if (foo[1] == 'u' || foo[1] == 'U') { /* %uXXXX */ if (foo[2] != '0' || foo[3] != '0') { size_shift = 1; }
2140112004-01-27Henrik Grubbström (Grubba)  foo += 5; if (foo < end) { adjust_len += 5; } else {
77171d2004-01-27Henrik Grubbström (Grubba)  adjust_len += end - (foo - 4);
2140112004-01-27Henrik Grubbström (Grubba)  }
8f77192004-01-27Henrik Grubbström (Grubba)  } else { foo += 2;
2140112004-01-27Henrik Grubbström (Grubba)  if (foo < end) { adjust_len += 2; } else {
77171d2004-01-27Henrik Grubbström (Grubba)  adjust_len += end - (foo - 1);
2140112004-01-27Henrik Grubbström (Grubba)  }
8f77192004-01-27Henrik Grubbström (Grubba)  } } }
33805b2000-08-12Per Hedbor  if (!proc) { pop_n_elems(args-1); return; }
8f77192004-01-27Henrik Grubbström (Grubba)  newstr = begin_wide_shared_string(Pike_sp[-args].u.string->len - adjust_len, size_shift); if (size_shift) {
25a5272004-01-27Henrik Grubbström (Grubba)  p_wchar1 *dest = STR1(newstr);
8f77192004-01-27Henrik Grubbström (Grubba)  for (proc=0; bar<end; dest++) if (*bar=='%') { if (bar[1] == 'u' || bar[1] == 'U') {
2140112004-01-27Henrik Grubbström (Grubba)  if (bar<end-5) *dest = (((bar[2]<'A')?(bar[2]&15):((bar[2]+9)&15))<<12)| (((bar[3]<'A')?(bar[3]&15):((bar[3]+9)&15))<<8)| (((bar[4]<'A')?(bar[4]&15):((bar[4]+9)&15))<<4)| ((bar[5]<'A')?(bar[5]&15):((bar[5]+9)&15));
8f77192004-01-27Henrik Grubbström (Grubba)  else *dest=0;
2140112004-01-27Henrik Grubbström (Grubba)  bar+=6;
8f77192004-01-27Henrik Grubbström (Grubba)  } else { if (bar<end-2) *dest=(((bar[1]<'A')?(bar[1]&15):((bar[1]+9)&15))<<4)| ((bar[2]<'A')?(bar[2]&15):((bar[2]+9)&15)); else *dest=0; bar+=3; } } else { *dest=*(bar++); } } else { foo = newstr->str; for (proc=0; bar<end; foo++) if (*bar=='%') { if (bar[1] == 'u' || bar[1] == 'U') { /* We know that the following two characters are zeros. */
2140112004-01-27Henrik Grubbström (Grubba)  bar+=3;
8f77192004-01-27Henrik Grubbström (Grubba)  } if (bar<end-2) *foo=(((bar[1]<'A')?(bar[1]&15):((bar[1]+9)&15))<<4)| ((bar[2]<'A')?(bar[2]&15):((bar[2]+9)&15)); else *foo=0; bar+=3; } else { *foo=*(bar++); } }
33805b2000-08-12Per Hedbor  pop_n_elems(args); push_string(end_shared_string(newstr)); }
77ef812001-06-30Per Hedbor static void f_html_encode_string( INT32 args )
d579c82001-12-08Martin Nilsson /*! @decl string html_encode_string(mixed in) *! *! Encodes the @[in] data as an HTML safe string. */
77ef812001-06-30Per Hedbor { struct pike_string *str; int newlen; if( args != 1 ) Pike_error("Wrong number of arguments to html_encode_string\n" ); switch( Pike_sp[-1].type ) {
2a33f02002-07-02Per Hedbor  void o_cast_to_string();
77ef812001-06-30Per Hedbor  case PIKE_T_INT: /* Optimization, this is basically a inlined cast_int_to_string */ { char buf[21], *b = buf+19; int neg, i, j=0; i = Pike_sp[-1].u.integer; pop_stack(); if( i < 0 ) { neg = 1; i = -i; } else neg = 0; buf[20] = 0;
05dc762001-07-20Martin Stjernholm  while( i >= 10 )
77ef812001-06-30Per Hedbor  { b[ -j++ ] = '0'+(i%10); i /= 10; } b[ -j++ ] = '0'+(i%10); if( neg ) b[ -j++ ] = '-'; push_text( b-j+1 ); } return; case PIKE_T_FLOAT: /* Optimization, no need to check the resultstring for * unsafe characters. */
2a33f02002-07-02Per Hedbor  o_cast_to_string();
77ef812001-06-30Per Hedbor  return; default:
2a33f02002-07-02Per Hedbor  o_cast_to_string();
77ef812001-06-30Per Hedbor  case PIKE_T_STRING: break; } str = Pike_sp[-1].u.string; newlen = str->len; #define COUNT(T) { \ T *s = (T *)str->str; \ int i; \ for( i = 0; i<str->len; i++ ) \ switch( s[i] ) \ { \
8f77192004-01-27Henrik Grubbström (Grubba)  case 0: /* &#0; */ \ case '<': /* &lt; */ \
77ef812001-06-30Per Hedbor  case '>': newlen+=3; break;/* &gt; */ \
8f77192004-01-27Henrik Grubbström (Grubba)  case '&': /* &amp; */ \ case '"': /* &#34; */ \
77ef812001-06-30Per Hedbor  case '\'': newlen+=4;break;/* &#39; */ \ } \ } #define ADD(X) if(sizeof(X)-sizeof("")==4) ADD4(X); else ADD5(X) #define ADD4(X) ((d[0] = X[0]), (d[1] = X[1]), (d[2] = X[2]), (d[3] = X[3]),\ (d+=3)) #define ADD5(X) ((d[0] = X[0]), (d[1] = X[1]), (d[2] = X[2]), (d[3] = X[3]),\ (d[4] = X[4]), (d+=4)) #define REPLACE(T) { \ T *s = (T *)str->str; \ T *d = (T *)res->str; \ int i; \ for( i = 0; i<str->len; i++,s++,d++ ) \ switch( *s ) \ { \ case 0: ADD("&#0;"); break; \ case '&': ADD("&amp;"); break; \ case '<': ADD("&lt;"); break; \ case '>': ADD("&gt;"); break; \ case '"': ADD("&#34;"); break; \ case '\'':ADD("&#39;"); break; \ default: *d = *s; break; \ } \ } \ switch( str->size_shift ) {
8f77192004-01-27Henrik Grubbström (Grubba)  case 0: COUNT(p_wchar0); break; case 1: COUNT(p_wchar1); break; case 2: COUNT(p_wchar2); break;
77ef812001-06-30Per Hedbor  } if( newlen == str->len ) return; /* Already on stack. */ { struct pike_string *res = begin_wide_shared_string(newlen,str->size_shift); switch( str->size_shift ) {
8f77192004-01-27Henrik Grubbström (Grubba)  case 0: REPLACE(p_wchar0); break; case 1: REPLACE(p_wchar1); break; case 2: REPLACE(p_wchar2); break;
77ef812001-06-30Per Hedbor  } pop_stack(); push_string( low_end_shared_string( res ) ); } }
2d41d42001-10-02Per Hedbor 
ced8a32001-02-13Henrik Grubbström (Grubba) /*! @endmodule */
33805b2000-08-12Per Hedbor 
51ef5c2002-10-21Marcus Comstedt PIKE_MODULE_INIT
33805b2000-08-12Per Hedbor {
09959a2005-01-20Martin Nilsson  ADD_FUNCTION("make_http_headers", f_make_http_headers, tFunc(tMap(tStr,tOr(tStr,tArr(tStr))),tStr), 0 );
33805b2000-08-12Per Hedbor 
09959a2005-01-20Martin Nilsson  ADD_FUNCTION("http_decode_string", f_http_decode_string, tFunc(tStr,tStr), 0 );
33805b2000-08-12Per Hedbor 
09959a2005-01-20Martin Nilsson  ADD_FUNCTION("html_encode_string", f_html_encode_string, tFunc(tMix,tStr), 0 );
2d41d42001-10-02Per Hedbor 
33805b2000-08-12Per Hedbor  start_new_program(); ADD_STORAGE( struct header_buf );
18b0822002-10-14Henrik Grubbström (Grubba)  set_init_callback( f_hp_init );
2d41d42001-10-02Per Hedbor  set_exit_callback( f_hp_exit );
09959a2005-01-20Martin Nilsson  ADD_FUNCTION( "feed", f_hp_feed, tFunc(tStr,tArr(tOr(tStr,tMapping))), 0 ); ADD_FUNCTION( "create", f_hp_create, tFunc(tNone,tVoid), ID_STATIC );
33805b2000-08-12Per Hedbor  end_class( "HeaderParser", 0 ); }
51ef5c2002-10-21Marcus Comstedt PIKE_MODULE_EXIT
33805b2000-08-12Per Hedbor { }