unit urosx;
{$IFNDEF LINUX}
{$DEFINE PROXY_ENABLED_ROS}
{$ENDIF}

{$IFDEF FPC}
  {$MODE Delphi}
{$ENDIF}

interface

uses
  Classes, SysUtils,
  {$IFDEF PROXY_ENABLED_ROS}
   httpsend,
  {$ELSE}
  {$IFDEF WINDOWS}
  Winsock,
  {$ELSE}
  unix,
  netdb,
  sockets,
  {$ENDIF}
  {$ENDIF}
  StrUtils,
//  synacode, el EncodeUrl no me funcionó para los INSERT en la base.
//  uglobsharedmem,
  uDataSetGenerico;


type
  TResultadoQuery = class(TDataSetGenerico)
    private
      procedure call_ros_constructor_helper(orden: String; paramStr: string);
    public
      constructor CreateQuery(sql: string );
      constructor CreateExec( sql: string );
  end;


type
  TDAOfByte = array of byte;

const
  MSK_USUARIO_COMUN= 1; // tiene permisos para bajar.
  MSK_USUARIO_ADMIN= 2; // tiene permisos para subir.
  MSK_USUARIO_AGENTE = 4; // puede leer la información restringida a los agentes.
  MSK_USUARIO_ROOT=8; // YO


var
// en caso de ocurrir en un error, se guarda en esta variable
// el mensaje de error correspondiente.
  ultimoError: string;

// estas varibles queda fijadas una vez que el usuario se logea correctamente
  usuario_loginok: boolean;
  usuario_tipo: integer;
  usuario_nid: string;
  usuario_email: string;
  usuario_EsAdmin, usuario_EsRoot: boolean;

// servidor http
  ipfija: string; // si se inicializa no se consulta la ip
  host: string; // se usa si ipfija='' para buscar la ip
  puerto: integer; // por defecto es 80

  // se usan solo si está en modo  PROXY_ENABLED_ROS
  proxy_host, proxy_port, proxy_user, proxy_pass: string;


// si el resultado es true se logró ejecutar con exito.
// si es false, en ultimoError está el mensaje de error.
function sql_exec( sql: string ): boolean;

// Si la consulta tiene éxito se retorna el resultado
// si falla la consulta, se retorna NIL y en UltimoError está el mensaje de error.
function sql_query( sql: string ): TResultadoQuery;

// hace la consulta y retorna un string con el resultado
// si es error retorna '' El string vacío.
// Esta llamada es últil para funciones que retornan un sólo valor
function sql_func( sql: string ): string;

// hace la consulta y retorna el record (FICHA) correspondiente
// si da error retorna nil. El usuario debe llamar al FREE de la ficha
// Es útil para las consultas en las que se quiere trabajar sobre un único record
function sql_ficha( sql: string ): TDataRecord;

// retorna el siguiente valor de la tabla sequencias
function sql_nextnid( nombre: string ): integer;

// Retorna un string con la hora del servidor de bases de datos
// Simplemente ejecuta sql_func( 'SELECT now() ' );
function sql_now: string;

// Solicita la ejecución de la orden xo al demonio ROS pasando la lista de parámetros
function fros(xo: String; const paramNames, paramValues: array of String): String;

function ros_simsee_mail(const email, asunto, texto: String): boolean;
function ros_mail(const email, asunto, texto, replayto: String): boolean;

// retona la ip y el puerto visible desde el exterior.
function ros_getmyipandport( var ip: shortstring; var port: word ): boolean;


// calcula el checksum de un buffer de bytes y retorna un string de 16 caracteres
// con el resultado en formato hexadecimal
function checksum( pbuff: pointer; nBytes: cardinal ): shortstring;

function escapeChars(const s: string): string; overload;

function URLEncode(Str: string): string;
function URLDecode(Str: string): string;


//funciones auxileares para armado de los SQL de Insert y Update
procedure AppendToInsertStr( var str_nombres, str_valores: string; const nombre, valor: string; encomille: boolean = true );
procedure AppendToUpdateStr( var str_update: string; const nombre, valor: string; encomille: boolean = true );


type
  ConsultaSQLException = class(Exception)
    Constructor Create(const msg: String);
  end;

//Retorna un string formado por un número de 4 dígitos al azar
function gen_codigo: String;

// encripta el string s con un método propio. El resultado
// tiene el doble de largo que el original y es alfanumerio.
function barullar( s: string ): string;

// dado un string barullado con barullar lo desenbarulla
function desbarullar( s: string ): string;

{$IFNDEF PROXY_ENABLED_ROS}

function GetIpByName( hostName: string ): string;
// retorna el nombre de la maquina local.
function GetLocalHostName: string;
function IP4StrToCardinal(const AIpAddress : shortstring ) : cardinal;

// abre un socket cliente --
function rosx_cliOpen(var sock: TSocket; hostId: cardinal; port : integer ): boolean;
function rosx_cliClose(var sock: TSocket ): boolean;
{$ENDIF}

//  estas funciones suponen que Buff existe y tiene largo nbytes
function BuffToHexStr( var buff; nbytes: integer ): string;
procedure HexStrToBuff( var buff; nbytes: integer; hexStr: string );

implementation


function digHexToInt( c: char ): integer;
begin
  case c of
    '0'..'9': result:= ord(c) - ord('0');
    'A'..'F': result:= 10+ ord( c ) - ord( 'A' );
    else
      raise Exception.Create('uros.digHexToInt: caracter inválido "' + c + '"');
  end;
end;


function HexToInt( s: string ): integer;
var
  res, k: integer;
//  c: char;
begin
  res:= 0;
  for k:= 1 to length( s ) do
    res:= res * 16 + digHexToInt( s[k] );
  result:= res;
end;


type
  TBuffer_de_bytes = packed array[0..10240-1] of byte;

function BuffToHexStr( var buff; nbytes: integer ): string;
var
  res: string;
  k: integer;
  b: byte;
begin
  res:= '';
  for k:= 0 to nbytes-1 do
  begin
    b:= TBuffer_de_bytes( buff )[k];
    res:= res+ IntToHex( b, 2 );
  end;
  result:= res;
end;

procedure HexStrToBuff( var buff; nbytes: integer; hexStr: string );
var
  k: integer;
  n: integer;
  b: byte;
  s2: string;
begin
  n:= length( hexStr );
  if n <> nbytes * 2 then
     raise Exception.Create( 'HexStrToBuff .. nbytes * 2 <> length( hexStr ) ' );

  for k:= 0 to nbytes-1 do
  begin
    s2:= copy( hexStr, k * 2 +1 , 2 );
    b:= HexToInt( s2 );
    TBuffer_de_bytes( buff )[k]:= b;
  end;
end;


function gen_codigo: String;
var
  res: String;
  i: Integer;
begin
  res:= '';
  randomize();
  for i:= 0 to 3 do
    res:= res + IntToStr(Random(9)+1);
  result:= res;
end;

function barullar( s: string ): string;
var
  rs: string;
  k: integer;
  b, c: byte;
  dr, dl: byte;
begin
  c:= 33;
  dr:= 31 mod 8;
  dl:= 8 - dr;

  for k:= 1 to length( s ) do
  begin
    b:= ord( s[k] );
    b:= (( b shr dr) + ( b shl dl)) mod 256;
    b:= b xor c;
    c:= b;
    rs:= rs + IntToHex(b, 2 );
  end;
  result:= rs;
end;

function desbarullar( s: string ): string;
var
  rs: string;
  i: integer;
  c, cs, b: byte;
  n: integer;
  dr, dl: byte;
begin
  c:= 33;
  dl:= 31 mod 8;
  dr:= 8 - dl;

//  i:= 0;
  n:= length( s ) div 2;
  for i:= 1 to n do
  begin
    b:= HexToInt( copy( s, (i-1)*2+1, 2 ) );
    cs:= b;
    b:= b xor c;
    b:= ((b shl dl) + ( b shr dr)) mod 256;
    rs:= rs+chr( b );
    c:= cs;
  end;
  result:= rs;
end;


function sql_nextnid( nombre: string ): integer;
begin
  result:= StrToInt( fros('next_nid', ['tabla'], [nombre] ));
end;

function sql_exec( sql: string ): boolean;
var
  queryRes: TResultadoQuery;
begin

//system.writeln('sql_exec:', sql );

  try
    queryRes:= TResultadoQuery.CreateExec(sql);
    queryRes.Free;
    result:= true;
  except
    on E: Exception do
    begin
      ultimoError:= E.Message;
      result:= false;
    end;
  end;
end;

function sql_query( sql: string ): TResultadoQuery;
var
  queryRes: TResultadoQuery;
begin
  try
    queryRes:= TResultadoQuery.CreateQuery(sql);
    result:= queryRes;
  except
    on E: Exception do
    begin
      ultimoError:= E.Message;
      result:= nil;
    end;
  end;
end;


// hace la consulta y retorna un string con el resultado
// si es error retorna '' El string vacío.
// Esta llamada es últil para funciones que retornan un sólo valor
function sql_func( sql: string ): string;
var
  ds: TResultadoQuery;
  r: TDataRecord;
begin
  ds:= sql_query( sql );
  if (ds <> nil) and (ds.nrows > 0 ) then
  begin
    r:= ds.first;
    result:= r.GetByIdAsString(0);
    ds.Free;
  end
  else
    result:= '';
end;

// hace la consulta y retorna el record (FICHA) correspondiente
// si da error retorna nil. El usuario debe llamar al FREE de la ficha
// Es útil para las consultas en las que se quiere trabajar sobre un único record
function sql_ficha( sql: string ): TDataRecord;
var
  ds: TResultadoQuery;
  r: TDataRecord;
begin
  ds:= sql_query( sql );
  if (ds <> nil) and ( ds.nrows > 0 ) then
  begin
    r:= ds.first;
    r.freePadre:= true; // le indicamos a la ficha que en su FREE libere los resultados
    result:= r;
  end
  else
    result:= NIL;
end;

function sql_now: String;
begin
  result:= sql_func( 'SELECT now() ');
end;


function escapeChars(const s: string): string;
const
  escapables = [#0, '''', '"', #13, #10, #9, '\'];
var
  ir, iw: integer;
  cnt: integer;
  res: string;
begin
  cnt:= 0;
  for ir:= 1 to length( s ) do
    if s[ir] in escapables then inc( cnt );
  setlength( res, length( s ) + cnt );
  iw:= 1;
  for ir:= 1 to length( s ) do
  begin
    if s[ir] in escapables then
    begin
      res[iw]:='\';
      inc( iw );
    end;
    res[iw]:= s[ir];
    inc( iw );
  end;
  result:= res;
end;


function URLEncode(Str: string): string;
var
  i, j: integer;
  res, s2: string;
begin
//  result:= synacode.EncodeURL( str );
  setlength( res, length( str ) * 3 );
  j:= 1;
  for i:= 1 to Length(Str) do
    if Str[i] in ['A'..'Z','a'..'z','0'..'9','-','_','.'] then
    begin
      Res[j]:= Str[ i ];
      inc( j );
    end
    else
    begin
      Res[j]:= '%'; inc(j);
      s2:=  IntToHex(Ord(Str[ i ]),2);
      Res[j]:= s2[1]; inc(j);
      Res[j]:= s2[2]; inc(j);
    end;
  setlength( res, j-1 );
  result:= res;
end;

function URLDecode(Str: string): string;
var
  i: integer;
  res: string;
  j, k : integer;
  s2: string;
  c: char;
  n: integer;
begin
//  result:= synacode.DecodeURL( Str );
  n:= length( str );
  setlength( res, n );
  j:= 1;
  k:= 1;
  while k <= n do
  begin
    c:= str[k]; inc( k );
    if ( c = '%' ) then
    begin
      s2:= '$'+copy( str, k, 2 ); inc( k, 2 );
      if not TryStrToInt( s2, i) then
      begin
        result:= '';
        exit;
      end;
      res[j]:= chr( i );
    end
    else
      res[j]:= c;
    inc( j);
  end;
  setlength( res, j-1 );
  result:= res;
end;

function checksum( pbuff: pointer; nBytes: cardinal ): shortstring;
type
 TLARB = packed array[0..1024*1024*1024] of byte;
var
  res: int64;
  p: ^TLARB;
  k: cardinal;
  b: byte;
//  ress: shortstring;
begin
  p:= pbuff;
  res:= 0;
  for k:= 0 to nBytes-1 do
  begin
    b:= p^[k];
    res:= ( ( res shr 1) + ( res shl 63) ) xor b;
  end;
  result:= inttohex( res, 16 );
end;

{$IFNDEF PROXY_ENABLED_ROS}
function GetIpByName( hostName: string ): string;
var
{$IFDEF WINDOWS}
  HEnt: pHostEnt;
{$ELSE}
  Hent: THostEntry;
{$ENDIF}
  i: Integer;
  IPaddr: string;
  name: string;
  flgFrom_hosts: boolean;

begin
  IpAddr:= '';

  {$IFDEF WINDOWS}
  name:= HostName+#0;
  HEnt := GetHostByName( @name[1]);
  if HEnt = NIL then
      raise Exception.Create('GetIpByName: error en GetHostByName(' + hostName + '). WSAGetLastError= ' + IntToStr(WSAGetLastError));
  for i := 0 to HEnt^.h_length - 1  do
   IPaddr :=
    Concat(IPaddr,
    IntToStr(Ord(HEnt^.h_addr_list^[i])) + '.');

  {$ELSE}
  flgFrom_hosts:= true;
  if not GetHostByName( hostName, HEnt ) then
  begin
   flgFrom_hosts:= false;
   if not ResolveHostByName( hostName, HEnt ) then
    raise Exception.Create('GetIpByName: error en GetHostByName(' + hostName + ').');
  end;

  if flgFrom_hosts then
  begin
    for i := 4 downto 1 do
     IPaddr :=
         Concat(IPaddr,
         IntToStr(Ord(HEnt.addr.s_bytes[i])) + '.');
  end
  else
  begin
    for i := 1 to 4 do
     IPaddr :=
         Concat(IPaddr,
         IntToStr(Ord(HEnt.addr.s_bytes[i])) + '.');
  end;
  {$ENDIF}


  SetLength(IPaddr, Length(IPaddr) - 1);
  result:= IpAddr;
end;

function GetLocalHostName: string;
{$IFDEF WINDOWS}
type
  Name = array[0..100] of Char;
  PName = ^Name;
var
  HName: PName;
begin
  New(HName);
  if GetHostName(HName^, SizeOf(Name)) = 0 then
    result:= StrPas(HName^)
  else
    result:= '';
  Dispose(HName);
end;
{$ELSE}
begin
  result:= unix.GetHostName;
end;
{$ENDIF}

function IP4StrToCardinal(const AIpAddress : shortstring ) : cardinal;
var Retvar,i : longword;
    sData,sSeg : string;
begin
  Retvar := 0;
  sData := trim(AIpAddress);
  while sData <> '' do begin
    Retvar:= Retvar shl 8;
    i := pos('.',sData);
    if i <> 0 then begin
      sSeg := copy(sData,1,i - 1);
      sData := copy(sData,i+1,length(sData));
    end
    else begin
      sSeg := sData;
      sData := '';
    end;
    Retvar := Retvar + (longword(StrToIntDef(sSeg,0)) );
  end;
  Result := Retvar;
end;


function rosx_cliClose(var sock: TSocket ): boolean;
var
  res: longint; // longint
begin
  res:= closeSocket( sock );
  result:= res = 0;
end;

function rosx_cliOpen(var sock: TSocket; hostId: cardinal; port : integer ): boolean;
var
  v, addr, y: sockaddr_in;
  k: integer;
begin
  addr.sin_family:= PF_INET;
  addr.sin_port:= htons(port); // ShortHostToNet(port);

  {$IFDEF LINUX}
  addr.sin_addr.S_addr:=HostTonet(longint( hostId )); // htonl( longInt( hostId )); //
  {$ELSE}
  addr.sin_addr.S_addr:= htonl( u_long (hostId)); //HostTonet(longint(h.IPAddress));
  {$ENDIF}

  for k:= 0 to 7 do
  {$IFDEF LINUX}
    addr.xpad[k]:= #0;
  {$ELSE}
    addr.sin_zero[k]:= chr(0);
  {$ENDIF}

{$IFDEF LINUX}
  sock := fpSocket(PF_INET, SOCK_STREAM, 0);
  if sock = -1 then
{$ELSE}
  sock := Socket(PF_INET, SOCK_STREAM, 0);
  if WSAGetLastError <> 0 then
{$ENDIF}
  begin
    ultimoError:= 'cliOpen: Error creando socket.';
    result:= false;
    exit;
  end;

  {$IFDEF LINUX}
  if fpConnect(sock, @addr, sizeOf( addr )) <> 0 then
  begin
    ultimoError:= 'cliOpen: Error conectando socket, socket= ' + IntToStr(sock);
    rosx_cliClose(sock);
    result:= false;
    Exit;
  end;
  {$ELSE}
  if Connect(sock, addr, sizeOf( addr )) <> 0 then
  begin
    ultimoError:= 'cliOpen: Error conectando socket, WSAGetLastError= ' + IntToStr(WSAGetLastError) + ', socket= ' + IntToStr(sock);
    rosx_cliClose(sock);
    result:= false;
    Exit;
  end;
  {$ENDIF}
  result:= true;
end;

Type
  TBufBytes = packed array[0..1024*100] of byte;

function cliSendAll( s: TSocket; var Buf; len: integer ): boolean;
var
  total: integer;        // cuántos bytes hemos enviado
  bytesleft: integer; // cuántos se han quedado pendientes
  n: integer;
begin
  total:= 0;
  bytesleft:= len;

  while(total < len) do
  begin
{$IFDEF LINUX}
    n:= fpsend(s, @TBufBytes(buf)[total], bytesleft, 0);
{$ELSE}
    n:= send(s, TBufBytes(buf)[total], bytesleft, 0);
{$ENDIF}
    if (n = -1) then break;
    total:= total + n;
    bytesleft:= bytesleft - n;
  end;
  result:=  len = total;
end;

function cliWrite( s: TSocket; const r: string ): boolean;
var
  ts: string;
begin
  ts:= r;
  result:= cliSendAll( s, ts[1], length( ts ) );
end;


function cliWriteln( s: TSocket; const r: string ): boolean;
var
  ts: string;
begin
  ts:= r+#10;
  result:= cliSendAll( s, ts[1], length( ts ) );
end;

function cliReadln_shortString( var sock: TSocket; var buf: ShortString; var rln: boolean ): boolean;
var
  kw: integer;
  tam: integer;
  nleidos: integer;
  socketCerrado: boolean;
begin
  rln:= false;
  tam:= 255;
  kw:= 1;
  buf[0]:=#0;
  socketCerrado:= false;
//  nleidos:= 0;
  while (
    (tam >0 )
//    and ( buf[kw-1] <> #13 )
    and (buf[kw-1] <> #10))
    and not socketCerrado do
  begin
{$IFDEF LINUX}
    nleidos:= fprecv( sock, @buf[kw], 1, 0 );
{$ELSE}
    nleidos:= recv( sock, buf[kw], 1, 0 );
{$ENDIF}
    if nleidos > 0 then
    begin
      kw:= kw + nleidos;
      tam:= tam - nleidos;
    end
    else
      socketCerrado:= true;
  end;

  nleidos:= kw-1;

  while (nleidos > 0) and (( buf[nleidos]=#13) or ( buf[nleidos]=#10) ) do
  begin
    dec( nleidos );
     rln:= true;
  end;
  buf[0]:= chr(nleidos);
  result:= not socketCerrado;
end;


function cliReadln( var sock: TSocket; var res: String ): boolean;
var
  ress: ShortString;
  lecturaOk: boolean;
  rln: boolean;
begin
  res:= '';
  repeat
    ress:= '';
    lecturaOk:= cliReadln_ShortString( sock, ress, rln );
    res:= res+ ress;
  until ( rln or not LecturaOk );
  result:= lecturaOk;
end;
{$ENDIF}

{$IFDEF PROXY_ENABLED_ROS}
function ProxyHttpPostURL(const URL, URLData: string; const Data: TStream): Boolean;
var
  HTTP: THTTPSend;
begin
  HTTP := THTTPSend.Create;
  try
    HTTP.ProxyHost := proxy_host;
    HTTP.ProxyPort := proxy_port;
    HTTP.ProxyUser := proxy_user;
    HTTP.ProxyPass := proxy_pass;
    HTTP.Document.Write(Pointer(URLData)^, Length(URLData));
    HTTP.MimeType := 'application/x-www-form-urlencoded';
    Result := HTTP.HTTPMethod('POST', URL);
    Data.CopyFrom(HTTP.Document, 0);
  finally
    HTTP.Free;
  end;
end;

function ros(xo: String; paramStr: string): TStringList;
var
  url: string;
  rh, uri, rb: string;
  Content_Length: cardinal;
  buff: string;
  ipStr: string;
  lineaInicioRes, lineaFinRes, error: String;
  res: TStringList;
  k, j: integer;
  st: TMemoryStream;
  buscando: Boolean;

function getpal: string;
begin
  if res.count > 0 then
  begin
    result:= res[0];
    res.delete( 0 );
  end
  else
    result:= '';
end;

begin

  if ipfija = '' then
    url:= 'http://'+host
  else
    url:= 'http://'+ ipFija;

  uri:= '/simsee/ros/rosx.php';

  url:= url+uri;

  if paramStr <> '' then
    rb:= 'xo=' + xo + '&' + paramStr
  else
    rb:= 'xo=' + xo;

  lineaInicioRes:= '+inicio_' + xo;
  lineaFinRes:= '+fin_' + xo;

  st:=TMemoryStream.Create;
  try
    ProxyHTTPpostURL( url, rb, st);
    st.Seek(0,soFromBeginning);
    res:= TStringList.Create;
    res.LoadFromStream(st);
  finally
    st.Free;
  end;

  buff:= getpal;
  buscando:= ( buff <>  lineaInicioRes);
  while ( res.count > 0 ) and buscando do
  begin
    if ( buff =  lineaInicioRes) then
      buscando:= false
    else
     buff:= getpal;
  end;

  if buscando then
  begin
    res.free;
    raise ConsultaSQLException.Create('TResultadoQuery.ros: error, no se recibió ' + lineaInicioRes);
  end;

  buff:= getpal;
  if buff = '+error' then
  begin
    error:= '';
    while (  res.count > 0 ) and ( buff <> lineaFinRes) do
    begin
      buff:= getpal;
      error:= error + buff + #13#10;
    end;
    res.Free;
    raise ConsultaSQLException.Create('TResultadoQuery.ros: ' + error);
  end;

  buscando := buff <> lineaFinRes;
  k:= 0;
  while ( buscando ) and ( k < res.count ) do
  begin
     if res[k] = lineaFinRes then
       buscando:= false
     else
       inc( k );
  end;
  assert( k < res.count, 'uros.ros: error no se recibió ' + lineaFinRes + ' donde se esperaba.');
  // borramos el final
  while res.count > k do res.delete( k );
  result:= res;
end;


{$ELSE}

function ros(xo: String; paramStr: string): TStringList;
var
  ip: cardinal;
  s: TSocket;
  rh, uri, rb: string;
  Content_Length: cardinal;
  buff: string;
  ipStr: string;
  lineaInicioRes, lineaFinRes, error: String;
  res: TStringList;
begin

  if ipfija = '' then
  begin
//  writeln(' obteniendo ip' );
    ipStr:= GetIPByName(host);
//  writeln( 'ipStr: ', ipStr );
  end
  else
    ipStr:= ipfija;

  ip:= IP4StrToCardinal( IpStr );

// writeln(' conectand con ... '+ipStr+' port: '+IntToStr( puerto ) );

  if not rosx_cliopen( s, ip, puerto ) then
  begin
    raise Exception.Create(
      'No pude abrir el socket. El servidor: '
      + ipStr +' en el puerto: '+IntToStr( puerto )
      + ' no contesto la solicitud. Ultimo Error: '+UltimoError);
  end;
  uri:= '/simsee/ros/rosx.php';
  if paramStr <> '' then
    rb:= 'xo=' + xo + '&' + paramStr
  else
    rb:= 'xo=' + xo;
  Content_Length:= length( rb );
  rh :=   'POST '+uri+' HTTP/1.0'+#10
          +'Host: '+host+#10
          +'User-Agent: PostIt'+#10
          +'Content-Type: application/x-www-form-urlencoded'+#10
      		+'Content-Length: '+IntToStr( Content_Length )+#10
          +#10
          + rb+#10;
//  writeln(' enviando POST' );
  cliWrite( s, rh );
//  writeln(' esperando linea ' );

  lineaInicioRes:= '+inicio_' + xo;
  lineaFinRes:= '+fin_' + xo;

  while (cliReadln( s, buff )) and
        (buff <> lineaInicioRes) do
  begin
//    writeln( buff );
    //Ignoro lo leído
  end;

  if buff <> lineaInicioRes then
  begin
    rosx_cliClose(s);
    raise ConsultaSQLException.Create('TResultadoQuery.ros: error, no se recibió ' + lineaInicioRes);
  end;

  cliReadln( s, buff );
  if buff = '+error' then
  begin
    error:= '';
    while (cliReadln( s, buff )) and
          (buff <> lineaFinRes) do
      error:= error + buff + #13#10;
    rosx_cliClose(s);
    raise ConsultaSQLException.Create('TResultadoQuery.ros: ' + error);
  end;

  res:= TStringList.Create;
  while cliReadln( s, buff ) and (buff <> lineaFinRes) do
  begin
    res.Add(buff);
  end;
  rosx_cliClose( s );
  assert(buff = lineaFinRes, 'uros.ros: error no se recibió ' + lineaFinRes + ' donde se esperaba.');
  result:= res;
end;

{$ENDIF}

function fros_str(xo: String; paramStr: string): String;
var
  rosRes: TStringList;
  res: String;
begin
  rosRes:= ros(xo, paramStr);
//  assert((rosRes.Count = 1), 'uros.fros: se llamo a fros pero la orden devolvió mas de un resultado');
  res:= rosRes[0];
  rosRes.Free;
  result:= res;
end;

function fros(xo: String; const paramNames, paramValues: array of String): String;
var
  paramString: string;
  i: Integer;
begin
  assert(Length(paramNames) = Length(paramValues));
  if Length(paramNames) > 0 then
  begin
    paramString:= paramNames[0] + '=' + urlencode( paramValues[0]);
    for i:= 1 to high(paramNames) - 1 do
      paramString:= paramString + '&' + paramNames[i] + '=' + urlencode(paramValues[i]);
  end
  else
    paramString:= '';
  result:= fros_str(xo, paramString);
end;

function ros_getmyipandport( var ip: shortstring; var port: word ): boolean;
var
  rosRes: TStringList;
  res: boolean;

begin
  res:= true;
  try
    rosRes:= ros('getmyipandport','');
    ip:= rosRes[0];
    port:= StrToInt( rosRes[1] );
    rosRes.Free;
  Except
    on e: ConsultaSQLException do
    begin
      ip:= '';
      port:= 0;
      res:= false;
      ultimoError:= e.Message;
    end;
  end;
  result:= res;
end;


function ros_mail(const email, asunto, texto, replayto: String): boolean;
var
  params: String;
  rosRes: TStringList;
  res: boolean;

begin
  res:= true;

  params:= 'email=' + URLEncode(email)
            + '&asunto=' + URLEncode(asunto)
            + '&texto=' + URLEncode(texto)
            + '&replayto=' + URLEncode(replayto);
  try
    rosRes:= ros('mail', params);
    rosRes.Free;
  Except
    on e: ConsultaSQLException do
    begin
      res:= false;
      ultimoError:= e.Message;
    end;
  end;
  result:= res;
end;


function ros_simsee_mail(const email, asunto, texto: String): boolean;
var
  params: String;
  rosRes: TStringList;
  res: boolean;
  cuerpo: string;

begin
  res:= true;

  cuerpo:= texto +#13#10 +'-------------------------'+#13#10
            +'Atentamente, el equipo de SimSEE.';

  params:= 'email=' + URLEncode(email) + '&asunto=' + URLEncode(asunto) + '&texto=' + URLEncode(cuerpo);
  try
    rosRes:= ros('mail', params);
    rosRes.Free;
  Except
    on e: ConsultaSQLException do
    begin
      res:= false;
      ultimoError:= e.Message;
    end;
  end;
  result:= res;
end;

Constructor TResultadoQuery.CreateQuery( sql: string );
begin
  call_ros_constructor_helper('query', sql);
end;

constructor TResultadoQuery.CreateExec( sql: string );
begin
  call_ros_constructor_helper('exec',  sql);
end;


procedure TResultadoQuery.call_ros_constructor_helper(orden: String; paramStr: string);
var
  rh, uri, rb: string;
  Content_Length: cardinal;
  buff: string;
  ipStr: string;
  lst_res: TStringList;
  lineaInicioRes, lineaFinRes, error: String;
  i, j: Integer;
  nfilas, ncampos: Integer;
  k: integer;

begin
  lst_res:= ros( orden, 'sql=' + URLEncode(paramStr) );

  if lst_res.count = 0 then
  begin
    inherited Create(0, 0);
  end
  else
  begin
    k:= 0;

    nfilas:= StrToInt( lst_res[k] );
    inc( k );
    ncampos:= StrToInt(lst_res[k] );
    inc( k );
    inherited Create(nfilas, ncampos);

    for j:= 0 to nfields - 1 do
    begin
      descripcionDeCampos[j].tipo:= lst_res[k];
      inc( k );
    end;
    for j:= 0 to nfields - 1 do
    begin
      descripcionDeCampos[j].nombre:= lst_res[k];
      inc( k );
    end;

    for i:= 0 to nrows - 1 do
    begin
      resultados[i]:= TDataRecord.Create(self);
      for j:= 0 to nfields - 1 do
      begin
        resultados[i].SetValById(j, URLDecode( lst_res[k] ));
        inc( k );
      end;
    end;
  end;
  lst_res.Free;
end;


Constructor ConsultaSQLException.Create(const msg: String);
begin
  inherited Create(msg);
end;

{$IFNDEF PROXY_ENABLED_ROS}
{$IFDEF WINDOWS}
procedure inicializar_WinSocket;
var
  wsadata : twsadata;
begin
  WSAStartUp( 2*16+2 ,wsadata);
end;
procedure finalizar_WinSocket;
begin
  WSACleanUp;
end;
{$ENDIF}
{$ENDIF}

//funciones auxileares para armado de los SQL de Insert y Update
procedure AppendToInsertStr( var str_nombres, str_valores: string; const nombre, valor: string; encomille: boolean = true );
begin
  if str_nombres <> '' then
  begin
   str_nombres:= str_nombres+', ';
   str_valores:= str_valores+', ';
  end;
  str_nombres:= str_nombres+nombre;
  if encomille then
    str_valores:= str_valores+''''+valor+''''
  else
    str_valores:= str_valores+valor;
end;

procedure AppendToUpdateStr( var str_update: string; const nombre, valor: string; encomille: boolean = true );
begin
  if str_update <> '' then
    str_update:= str_update+', ';
  str_update:= str_update+ nombre+' = ';
  if encomille then
    str_update:= str_update+''''+ valor+ ''''
  else
    str_update:= str_update+ valor;
end;


initialization
  usuario_loginok:= false;
  usuario_tipo:= 0;
  usuario_nid:='0';
  usuario_EsAdmin:= false;

  {$IFDEF INSIDE_CLUSTER_FING}
  ipfija := '192.168.242.1'; // nodo01 del Cluster
  host:= '';
  puerto := 2281;
  {$ELSE}
  ipfija:= ''; // si se inicializa no se consulta la ip
  host:= 'iie.fing.edu.uy'; // se usa si ipfija='' para buscar la ip
  puerto:= 80; // por defecto es 80
  {$ENDIF}

  proxy_host:= '';
  proxy_port:= '';
  proxy_user:= '';
  proxy_pass:= '';

{$IFNDEF PROXY_ENABLED_ROS}
{$IFDEF WINDOWS}
  inicializar_WinSocket;
{$ENDIF}
{$ENDIF}
finalization
{$IFNDEF PROXY_ENABLED_ROS}
{$IFDEF WINDOWS}
  finalizar_WinSocket;
{$ENDIF}
{$ENDIF}
end.
