cgi_cgi.c

Go to the documentation of this file.
00001 /******************************************************************************/
00002 /* cgi_cgi.c © Copyright 1998-2005 by Raosoft Inc. All Rights Reserved.       */
00003 /*                                                                            */
00004 /* You may use and modify this file for your own use, but may not distribute  */
00005 /* it or derivative works without the prior written consent of Raosoft, Inc.  */
00006 /*                                                                            */
00007 /* If you choose to share your modifications with Raosoft, Inc. the company   */
00008 /* will attempt to incorporate them into future versions of this file.        */
00009 /*                                                                            */
00010 /* This software is provided "as is," and Raosoft makes no warranty, express  */
00011 /* or implied, of fitness for a particular application. Every measure has been*/
00012 /* taken to anticipate risks inherent to computer networks, but we cannot     */
00013 /* guarantee safety or reliability of this program in every situation.        */
00014 /*                                                                            */
00015 /******************************************************************************/
00016 
00017 #include "cgi.h"
00018 
00019 #ifndef VPWSCGI
00020 
00021 #ifdef EBCDIC
00022 static unsigned char ebcdic2ascii[] = {
00023 0,  1,  2,  3, 55, 45, 46, 47, 22,  5, 21, 11, 12, 13, 14,
00024 15,16, 17, 18, 19, 60, 61, 50, 38, 24, 25, 63, 39, 28, 29, 30,
00025 31,64, 90,127,123, 91,108, 80,125, 77, 93, 92, 78,107, 96,
00026 75,97,240,241,242,243,244,245,246,247,248,249,122,
00027 94,76,126,110,111,124,193,194,195,196,197,198,199,200,201,209,210,211,212,213,
00028 214,215,216,217,226,227,228,229,230,231,232,233,173,224,189,
00029 95,109,121,129,130,131,132,133,134,135,136,137,145,146,147,148,149,150,151,152,
00030 153,162,163,164,165,166,167,168,169,192,79,208,161,
00031 7,32, 33, 34, 35, 36, 37,  6, 23, 40, 41, 42, 43, 44,  9, 10,27,48, 49,
00032 26, 51, 52, 53, 54,  8, 56, 57, 58, 59,  4,
00033 20,62,255,65,170,74,177,159,178,106,181,187,180,154,138,176,202,175,188,144,
00034 143,234,250,190,160,182,179,157,218,155,139,183,184,185,171,100,101,
00035 98,102,99,103,158,104,116,113,114,115,120,117,118,119,172,105,237,238,235,
00036 239,236,191,128,253,254,251,252,186,174,
00037 89,68, 69, 66, 70, 67, 71,156, 72, 84, 81, 82, 83, 88, 85,
00038 86,87,140,73,205,206,203,207,204,225,112,221,222,219,220,141,142,223
00039 };
00040 
00041 static unsigned char ascii2ebcdic[] = {
00042 0,1,2,3,236,9,202,127,226,210,211,11,12,13,14,169,16,17,18,19,
00043 239,197,8,203,24,25,220,216,28,29,30,31,183,184,185,187,196,10,23,27,
00044 204,205,207,208,209,5,6,7,217,218,22,221,222,223,224,4,227,229,233,235,
00045 176,177,158,26,32,201,131,132,133,160,242,134,135,164,213,46,60,40,43,179,
00046 38,130,136,137,138,161,140,139,141,225,33,36,42,41,59,94,45,47,178,142,
00047 180,181,182,143,128,165,124,44,37,95,62,63,186,144,188,189,190,243,192,193,
00048 194,96,58,35,64,39,61,34,195,97,98,99,100,101,102,103,104,105,174,175,
00049 198,199,200,241,248,106,107,108,109,110,111,112,113,114,166,167,145,206,146,15,
00050 230,126,115,116,117,118,119,120,121,122,173,168,212,91,214,215,155,156,157,250,
00051 159,21,20,172,171,252,170,254,228,93,191,231,123,65,66,67,68,69,70,71,
00052 72,73,232,147,148,149,162,237,125,74,75,76,77,78,79,80,81,82,238,150,
00053 129,151,163,152,92,240,83,84,85,86,87,88,89,90,253,245,153,247,246,249,
00054 48,49,50,51,52,53,54,55,56,57,219,251,154,244,234,255
00055 };
00056 #endif
00057 
00058 void Translate(char*s,unsigned char* list)
00059 {
00060   unsigned char * x = (unsigned char*)s;
00061   while (*x) {*x = list[*x]; x++;}
00062 }
00063 
00064 void LogConnection(char* program,char* host)
00065 {
00066  char Date[14],Time[14];
00067 
00068  GetTime(Date,Time,0);
00069  LogMessage("Run\tPROGRAM=");
00070  LogMessage(program);
00071  LogMessage(",HOST=");
00072  LogMessage(host);
00073  LogMessage(",DATE=");
00074  LogMessage(Date);
00075  LogMessage(",TIME=");
00076  LogMessage(Time);
00077  LogMessage("\n");
00078 }
00079 
00080 #ifdef WINCGI
00081 
00082 char WinCGIFile[1024];
00083 
00084 int InitWindowsCGI(char * setupfile,STREAM htmlin, STREAM htmlout)
00085 {
00086  char file[256];
00087 
00088  if (setupfile)
00089    strcpy(WinCGIFile,setupfile);
00090  else
00091    {
00092     *WinCGIFile = 0;
00093    }
00094 
00095  htmlin->t = htmlout->t = NULL;
00096 
00097 #ifdef CONSOLECGI
00098  htmlin->f = stdin;
00099  htmlout->f = stdout;
00100 #else
00101  htmlin->f = NULL;
00102  htmlout->f = NULL;
00103 #endif
00104 
00105  if (!*WinCGIFile) return 0;
00106 
00107  if (GetPrivateProfileString("System","Output File",NULLSTR,file,256,WinCGIFile))
00108  {
00109   htmlout->f = CGIFOPEN(file,"wt");
00110  }
00111 
00112  if (GetPrivateProfileString("System","Content File",NULLSTR,file,256,WinCGIFile))
00113  {
00114   htmlin->f = CGIFOPEN(file,"rt");
00115  }
00116 
00117 #ifdef CONSOLECGI
00118  if (!htmlin->f) htmlin->f = stdin;
00119  if (!htmlout->f) htmlout->f = stdout;
00120 #endif
00121  if (!htmlout->f && !htmlin->f) return 0;
00122 
00123  return 1;
00124 }
00125 
00126 void DoneWindowsCGI(STREAM htmlin, STREAM htmlout)
00127 {
00128 #ifdef CONSOLECGI
00129  if (htmlin->f) if (htmlin->f != stdin)    CGIFCLOSE(htmlin->f);
00130  if (htmlout->f) if (htmlout->f != stdout) CGIFCLOSE(htmlout->f);
00131 #else
00132  if (htmlin->f)    CGIFCLOSE(htmlin->f);
00133  if (htmlout->f) CGIFCLOSE(htmlout->f);
00134 #endif
00135 }
00136 #endif
00137 
00138 #ifndef ISAPICGI
00139 #if defined(WINCGI)
00140 int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
00141                     LPSTR lpszCmdLine, int nShow)
00142 {
00143  int argc;
00144  char *argv[128];
00145  int IsCGI = 1; /* Programs can only be invoked through CGI */
00146 #else
00147 int main(int argc, char *argv[])
00148 {
00149 #ifdef FASTCGI
00150  int fcgiCount = 0;
00151  FCGISTREAM chtmlin,chtmlerr,chtmlout;
00152  STREAM htmlerr;
00153 #else
00154  EZSSTREAM ehtmlin, ehtmlout;
00155 #endif /* FASTCGI */
00156 #endif /* WINCGI */
00157  CGINameValue * Params;
00158  int i;
00159  STREAM htmlin;
00160  STREAM htmlout;
00161  char * host;
00162 
00163 #ifdef FASTCGI
00164  htmlin=&chtmlin;
00165  htmlout=&chtmlout;
00166  htmlerr=&chtmlerr;
00167  htmlin->envp = htmlout->envp = htmlerr->envp = NULL;
00168  while (FCGX_Accept(&htmlin->strp, &htmout->strp, &htmlerr->strp, &htmlout.envp) >= 0)
00169  {
00170    htmlin->envp = htmlerr->envp = htmlout->envp;
00171    fcgiCount++;
00172  }
00173 #else
00174  htmlin = &ehtmlin; htmlout = &ehtmlout;
00175 #ifdef WINCGI
00176   argc = SplitArgs(lpszCmdLine, argv,128);
00177   if (!InitWindowsCGI(argv[1],htmlin,htmlout)) return 1;
00178 #else /* assume UNIX, or at least win32 console. ISAPI never got this far. */
00179 
00180 #ifdef XP_WIN
00181 swapchars(argv[0],'/','\\'); /* Apache 1.3.6 needs this */
00182 setmode(fileno(stdout),O_BINARY); /* Apache hack to be able to send images */
00183 #endif
00184 
00185  htmlin->t = NULL;
00186  htmlin->f = stdin;
00187 
00188  htmlout->t = NULL;
00189  htmlout->f = stdout;
00190 #endif
00191 #endif
00192 
00193   switch ( CheckCGISendMethod(htmlin) )
00194   {
00195    case 0: Params = ReadCGIPostData(htmlin,64);  break;
00196    case 1: Params = ReadCGIGetData(htmlin,64); break;
00197    case 2: Params = 0; break;
00198    default:Params = argc ? ReadPairedValues(argc-1,argv+1,64) : 0;
00199   };
00200 
00201  host = Params != NULL ? GetFieldValue(Params,"HOST") : "unknown";
00202 
00203 #ifndef CGI_FAST
00204  LogStartup(htmlout,argv[0]);
00205  LogConnection(argv[0],host);
00206  if (Params) LogTrx(Params);
00207 #endif
00208 
00209  if (!Params) Params = NewNVP(1);
00210 
00211  i = CGImain(argv[0],Params,htmlout);
00212 
00213  if (i)
00214  {
00215         char file[MAXPATH];
00216         ExpandLocalPath(argv[0],file,"error.html",NULLSTR);
00217         HTMLWriteFile(htmlout,file);
00218  }
00219 
00220  if (i == 101) /* security violation */
00221   {
00222    HTMLWrite(htmlout,"<P>Error: Access denied.\n");
00223    HTMLWrite(htmlout,"<!-- Raosoft CGI " RAOSOFT_CGI_VERSION " -->");
00224    LogMessage(",ERROR=Access denied");
00225   }
00226  else if (i != 0)
00227   {
00228    char err[128];
00229    HTMLPrintf(htmlout,
00230    "<P><A href=http://www.raosoft.com/help/cgi/ezs/error.html#s%d>Error %d</a>\n",i,i);
00231    HTMLWrite(htmlout,"<!-- Raosoft CGI " RAOSOFT_CGI_VERSION " -->");
00232    sprintf(err,"\nCGImain returned %d",i);
00233    LogError(err);
00234 #ifndef CGI_FAST
00235    LogError("\nCGI parameters");
00236    for (i=0;Params[i].name; i++)
00237    {
00238       if (!Params[i].name[0]) continue;
00239        LogError("\n");LogError(Params[i].name);LogError("=");LogError(Params[i].value);
00240    }
00241    LogError("\nEnvironment\nContent_type=");
00242    LogError(GetEnvironment(htmlout,"CONTENT_TYPE"));
00243    LogError("\nQUERY_STRING=");
00244    LogError(GetEnvironment(htmlout,"QUERY_STRING"));
00245    LogError("\nREQUEST_METHOD=");
00246    LogError(GetEnvironment(htmlout,"REQUEST_METHOD"));
00247    LogError("\nCONTENT_LENGTH=");
00248    LogError(GetEnvironment(htmlout,"CONTENT_LENGTH"));
00249 #endif
00250   }
00251 
00252 #ifdef WINCGI
00253  DoneWindowsCGI(htmlin,htmlout);
00254 #endif
00255  if (Params) DeleteNVP(Params);
00256 
00257  return 0;
00258 }
00259 #endif
00260 
00261 #ifndef ISAPICGI
00262 int SendCGIHeader(STREAM htmlout,char * header)
00263 {
00264  if (header != NULL)
00265   if (strlen(header))
00266   { HTMLWrite(htmlout,header); return 1;}
00267  HTMLWrite(htmlout,CGI_DEFAULT_HEADER);
00268  return 1;
00269 }
00270 #endif
00271 
00272 /* CGI data translation */
00273 #define NEWALLOCSIZE 128
00274 
00275 #ifdef FASTCGI
00276 #define NEXTCHAR(s) FCGX_GetChar(s->strp)
00277 #define TESTEOF(s) FCGX_HasSeenEOF(s->strp)
00278 #else
00279 #define NEXTCHAR(s) fgetc(s->f)
00280 #define TESTEOF(s) feof(s->f)
00281 #endif
00282 
00283 #define MAXSIZE 65535
00284 #ifndef ISAPICGI
00285 /* only works on FILE streams */
00286 char *fmakeword(STREAM in, char stop, int *cl) {
00287     int wsize;
00288     char *word;
00289     int ll;
00290 
00291     wsize = NEWALLOCSIZE;
00292     ll=0;
00293     word = (char *) CGIMALLOC(sizeof(char) * (wsize + 1));
00294 
00295     while(1) {
00296         word[ll] = (char)NEXTCHAR(in);
00297         if(ll==wsize) {
00298             word[ll+1] = 0;
00299             wsize+=NEWALLOCSIZE;
00300             if (wsize > MAXSIZE)
00301              break;
00302             else
00303             {
00304              char* W2 = (char *)realloc(word,sizeof(char)*(wsize+1));
00305              if (!W2) break;
00306              word = W2;
00307             }
00308         }
00309         --(*cl);
00310         if((word[ll] == stop) || (TESTEOF(in)) || (!(*cl))) {
00311             if(word[ll] != stop) ll++;
00312             word[ll] = '\0';
00313             return word; /* success */
00314         }
00315         ++ll;
00316     }
00317 /* error recovery */
00318     free(word);
00319     return 0;
00320 }
00321 
00322 #endif
00323 
00324 /* we can't save tabs in a database, so convert to spaces */
00325 
00326 #ifndef ISAPICGI
00327 #ifdef WINCGI
00328 char WINCGIenv[256];
00329 #endif
00330 
00331 /* from cgi_util.c */
00332 extern int CGILogLevel;
00333 
00334 char * GetEnvironment(STREAM htmlout,char* key)
00335 {
00336 #ifdef WINCGI
00337  GetPrivateProfileString("System",key,NULLSTR,WINCGIenv,256,WinCGIFile);
00338  return WINCGIenv;
00339 #elif defined(FASTCGI)
00340  return FCGX_GetParam(key,htmlout->envp);
00341 #else
00342  return getenv(key);
00343 #endif
00344 }
00345 
00346 void SetupCGIData(STREAM htmlin,CGINameValue *data)
00347 {
00348 /* Date/Time format: 19960214 124253 */
00349  char Date[14],Time[14];
00350  char * host;
00351  char temp[256];
00352  temp[0]=0;
00353 
00354  /*RenameField(data,"","HOST");   */
00355 
00356 #ifdef WINCGI
00357  GetPrivateProfileString("System","Remote Address",NULLSTR,temp,256,WinCGIFile);
00358  SetFieldValue(data,"HOST",temp);
00359 #else
00360  host = GetEnvironment(htmlin,"REMOTE_ADDR");
00361  if (host == NULL || host[0] == 0) host = GetEnvironment(htmlin,"HTTP_ADDR");
00362  if (host == NULL || host[0] == 0) host = GetEnvironment(htmlin,"REMOTE_HOST");
00363  if (host == NULL || host[0] == 0) host = GetEnvironment(htmlin,"HTTP_HOST");
00364  if (host == NULL || host[0] == 0) host = "unknown";
00365  SetFieldValue(data,"HOST",host);
00366 #endif
00367 
00368  GetTime(Date,Time,0);
00369 /* RenameField(data,"","DATE");  */
00370  SetFieldValue(data,"DATE",Date);
00371 
00372 /* RenameField(data,"","TIME");   */
00373  SetFieldValue(data,"TIME",Time);
00374 }
00375 
00376 CGINameValue* ReadCGIGetData(STREAM htmlin,int extra)
00377 {
00378  char * c;
00379  CGINameValue* data;
00380  int x;
00381 #ifdef WINCGI
00382  c = CGIMALLOC(65535);
00383  GetPrivateProfileString("CGI","Query String",NULLSTR,c,65535,WinCGIFile);
00384 #else
00385  c = GetEnvironment(htmlin,"QUERY_STRING");
00386 #endif
00387 
00388  if (c && *c)
00389   {
00390 #ifdef EBCDIC
00391     Translate(c,ascii2ebcdic);
00392 #endif
00393    /*2005 this is really old code, before web browsers were standardized.
00394    if (!strchr(c,'&')) data = ReadPairedString(c,'/',3+extra);
00395    else */
00396    data = ReadPairedString(c,'&',3+extra);
00397   }
00398  else
00399  {
00400 #ifdef WINCGI
00401   GetPrivateProfileString("CGI","Logical Path",NULLSTR,c,65535,WinCGIFile);
00402 #else
00403   c = GetEnvironment(htmlin,"PATH_INFO");
00404 #endif
00405   if (c &&*c)
00406    {
00407 #ifdef EBCDIC
00408     Translate(c,ascii2ebcdic);
00409 #endif
00410     if (*c=='/') c++;
00411     if (!strchr(c,'&')) data = ReadPairedString(c,'/',3+extra);
00412     else data = ReadPairedString(c,'&',3+extra);
00413    }
00414   else return 0;
00415  }
00416 
00417  if (!data) return 0;
00418  SetupCGIData(htmlin,data);
00419 
00420 #ifndef WINCGI
00421  for (x = 3+extra; data[x].name; x++)
00422   {
00423     swapchars(data[x].name,'+',' '); /*plus to space*/
00424     swapchars(data[x].value,'+',' '); /*plus to space*/
00425     ExpandUrl(data[x].name);
00426     ExpandUrl(data[x].value);
00427 
00428     if (strstr(data[x].name,"+AAA"))
00429     {
00430       UTF7toUTF8(data[x].name);
00431       UTF7toUTF8(data[x].value);
00432     }
00433     else if (ContainsANSIChars(data[x].value))
00434     {
00435      char* c = data[x].value;
00436      data[x].value = ANSItoUTF8(c);
00437      free(c);
00438     }
00439      swapchars(data[x].name,'\t',' '); /*remove tabs*/
00440      swapchars(data[x].name,'\r',' '); /*remove carriage-returns*/
00441      swapchars(data[x].name,'\n',' '); /*remove linefeeds*/
00442      swapchars(data[x].value,'\t',' '); /*remove tabs*/
00443      swapchars(data[x].value,'\r',' '); /*remove carriage-returns*/
00444      swapchars(data[x].value,'\n',' '); /*remove linefeeds*/
00445   }
00446 #endif
00447 
00448 #ifdef WINCGI
00449  CGIFREE(c);
00450 #endif
00451  return data;
00452 }
00453 
00454 #ifdef WINCGI
00455 CGINameValue* ReadCGIPostData(STREAM htmlin,int extra)
00456 {
00457 CGINameValue * data = 0;
00458  data = ReadINIFileSection(WinCGIFile,"Form Literal",3 + extra);
00459   if (data)
00460     SetupCGIData(htmlin,data);
00461  return data;
00462 }
00463 #else
00464 
00465 #define MAX_ENTRIES 1000
00466 CGINameValue* ReadCGIPostData(STREAM htmlin,int reserve)
00467 {int cl;
00468  register int x;
00469  char * c;
00470  int maxsize = MAX_ENTRIES + reserve;
00471  int growcount = 0; /* that should do the trick! */
00472  #ifdef EBCDIC
00473  char stopstr[2];
00474  #endif
00475 
00476  CGINameValue* data;
00477 
00478  #ifdef EBCDIC
00479  stopstr[0]='&';
00480  stopstr[1]=0;
00481  Translate(stopstr,ebcdic2ascii);
00482  #endif
00483 
00484  c = getenv("CONTENT_LENGTH");
00485  cl = c ? atoi(c) : 0;
00486 
00487  if (cl == 0) return NULL;
00488  if (!htmlin->f) return NULL;
00489  /* Read the host information */
00490 
00491  data = NewNVP(maxsize);
00492 
00493  for (x=0; x<reserve; x++)
00494   {
00495    data[x].name=strdup(NULLSTR);
00496    data[x].value=strdup(NULLSTR);
00497   }
00498 
00499  SetupCGIData(htmlin,data);
00500  x=reserve;
00501 
00502  while((cl > 0) && (!feof(htmlin->f)) && (growcount  < 64 ))
00503  {
00504  /* Convert the CGI data back to ASCII, read from stdin */
00505      if (x == maxsize) /* running out of space? grow the list */
00506      {
00507       CGINameValue* newdata = NewNVP(maxsize+ MAX_ENTRIES);
00508       if (newdata == NULL) return data;
00509       memcpy(newdata,data,maxsize * sizeof(CGINameValue));
00510       CGIFREE(data);
00511       data = newdata;
00512       maxsize += MAX_ENTRIES;
00513       growcount++;
00514      }
00515 
00516 #ifdef EBCDIC
00517 /* Translate binary data to EBCDIC text before doing anything. */
00518      c = fmakeword(htmlin,stopstr[0],&cl);
00519 #else
00520      c = fmakeword(htmlin,'&',&cl);
00521 #endif
00522 
00523 #ifdef EBCDIC
00524      Translate(c,ascii2ebcdic);
00525 #endif
00526 
00527      if (!c) {DeleteNVP(data); return NULL;}
00528      swapchars(c,'+',' '); /*plus to space*/
00529      ExpandUrl(c);      /*converts %xx to characters*/
00530      /* swapchars(c,'\t',' '); remove tabs*/
00531      data[x].name = c;
00532 
00533      c = strchr(c,'=');
00534      if ((c) && (*c))
00535       {
00536        *c++ = 0;
00537        data[x].value = strdup(c);
00538       }
00539      else
00540       {
00541        data[x].value = strdup(NULLSTR);
00542       }
00543 
00544      if (strstr(data[x].name,"+AAA"))
00545      {
00546       UTF7toUTF8(data[x].name);
00547       UTF7toUTF8(data[x].value);
00548      } /*application/x-www-form-urlencoded*/
00549      else if (ContainsANSIChars(data[x].value))
00550      {
00551       char* c = data[x].value;
00552       data[x].value = ANSItoUTF8(c);
00553       free(c);
00554      }
00555 
00556      swapchars(data[x].name,'\t',' '); /*remove tabs*/
00557      swapchars(data[x].name,'\r',' '); /*remove carriage-returns*/
00558      swapchars(data[x].name,'\n',' '); /*remove linefeeds*/
00559      swapchars(data[x].value,'\t',' '); /*remove tabs*/
00560      swapchars(data[x].value,'\r',' '); /*remove carriage-returns*/
00561      swapchars(data[x].value,'\n',' '); /*remove linefeeds*/
00562      /*not all compilers reliably increment x after for loops*/
00563      x++;
00564  }
00565  return data;
00566 } /* end of ReadCGIData */
00567 
00568 #endif
00569 
00570 /* 0 = POST, 1 = GET, 2 = POST_ERROR, 3=CONSOLE */
00571 int CheckCGISendMethod(STREAM htmlin)
00572 {
00573 #ifdef WINCGI
00574  char c[256],host[256];
00575  if (!*WinCGIFile) return 3;
00576  GetPrivateProfileString("CGI","Request Method",NULLSTR,c,256,WinCGIFile);
00577 #else
00578  char * c;
00579 
00580  c = GetEnvironment(htmlin,"REQUEST_METHOD");
00581 #endif
00582 
00583  if (!c) return 3; /* not cgi */
00584 
00585 if (c && (stricmp(c,"GET") == 0))
00586    {
00587     return 1;
00588    }
00589 
00590 #ifndef WINCGI
00591 if (c && (stricmp(c,"POST") == 0))
00592    {
00593      c =  getenv("CONTENT_TYPE");
00594 
00595     if(c && strnicmp(c,"application/x-www-form-urlencoded",33))
00596      {
00597       return 2;
00598      }
00599     return 0;
00600    }
00601 return 0;
00602 #else
00603 return 0;
00604 #endif
00605 }
00606 #endif
00607 
00608 #endif /* VPWSCGI */


Raosoft, Inc.
Raosoft EZReport, EZSurvey, InterForm, RapidReport, Raosoft, and SurveyWin are registered trademarks of Raosoft, Inc. Page contents © 1996-2007 by Raosoft, Inc. You may use and modify this file for your own use, but may not distribute it or derivative works without the prior written consent of Raosoft, Inc. This software is provided "as is," and Raosoft makes no warranty, express or implied, of fitness for a particular application. Every measure has been taken to anticipate risks inherent to computer networks, but we cannot guarantee safety or reliability of this program in every situation.
Tel: 206-525-4025 (US) Email: raosoft@raosoft.com
http://www.raosoft.com/