/*
** JNetLib
** Copyright (C) 2000-2003 Nullsoft, Inc.
** Author: Justin Frankel
** File: webserver.cpp - Generic simple webserver baseclass
** License: see jnetlib.h
** see test.cpp for an example of how to use this class
*/

#ifdef _WIN32
#include <windows.h>
#endif
#include "jnetlib.h"
#include "webserver.h"


WebServerBaseClass::~WebServerBaseClass()
{
  m_connections.Empty(true);
  m_listeners.Empty(true);
}

WebServerBaseClass::WebServerBaseClass()
{
  m_listener_rot=0;
  m_timeout_s=30;
  m_max_con=100;
}


void WebServerBaseClass::setMaxConnections(int max_con)
{
  m_max_con=max_con;
}

void WebServerBaseClass::setRequestTimeout(int timeout_s)
{
  m_timeout_s=timeout_s;
}

int WebServerBaseClass::addListenPort(int port, unsigned int which_interface)
{
  removeListenPort(port);

  JNL_IListen *p=new JNL_Listen(port,which_interface);
  m_listeners.Add(p);
  if (p->is_error()) return -1;
  return 0;
}

void WebServerBaseClass::removeListenPort(int port)
{
  int x;
  for (x = 0; x < m_listeners.GetSize(); x ++)
  {
    JNL_IListen *p=m_listeners.Get(x);
    if (p->port()==port)
    {
      m_listeners.Delete(x,true);
      break;
    }
  }
}

void WebServerBaseClass::removeListenIdx(int idx)
{
  m_listeners.Delete(idx,true);
}

int WebServerBaseClass::getListenPort(int idx, int *err)
{
  JNL_IListen *p=m_listeners.Get(idx);
  if (p)
  {
    if (err) *err=p->is_error();
    return p->port();
  }
  return 0;
}

void WebServerBaseClass::attachConnection(JNL_IConnection *con, int port)
{
  m_connections.Add(new WS_conInst(con,port));
}

void WebServerBaseClass::run(void)
{
  int nl;
  if (m_connections.GetSize() < m_max_con && (nl=m_listeners.GetSize()))
  {
    JNL_IListen *l=m_listeners.Get(m_listener_rot++ % nl);
    JNL_IConnection *c=l->get_connect();
    if (c)
    {
//      char buf[512];
//      sprintf(buf,"got new connection at %.3f",GetTickCount()/1000.0);
//      OutputDebugString(buf);
      attachConnection(c,l->port());
    }
  }
  int x;
  for (x = 0; x < m_connections.GetSize(); x ++)
  {
    WS_conInst *ci = m_connections.Get(x);
    int rv=0;
    for (int y = 0; y < 4 && !(rv=run_connection(ci)); y ++); // keep latency down

    if (rv==-1)
    {
      if (ci->m_serv.want_keepalive_reset())
      {
        time(&ci->m_connect_time);
        delete ci->m_pagegen;
        ci->m_pagegen=0;
        continue;
      }
    }

    if (rv>0)
    {
      m_connections.Delete(x--,true);
    }
  }
}

int WebServerBaseClass::run_connection(WS_conInst *con)
{
  int s=con->m_serv.run();
  if (s < 0)
  {
    // m_serv.geterrorstr()
    return 1;
  }
  if (s < 2)
  {
    // return 1 if we timed out
    return time(NULL)-con->m_connect_time > m_timeout_s;    
  }
  if (s < 3)
  {
    con->m_pagegen=onConnection(&con->m_serv,con->m_port);
    return 0;
  }
  if (s < 4)
  {
    if (!con->m_pagegen) 
    {
      if (con->m_serv.canKeepAlive()) return -1;

      return !con->m_serv.bytes_inqueue();
    }
    char buf[16384];
    int l=con->m_serv.bytes_cansend();
    if (l > 0)
    {
      if (l > (int)sizeof(buf)) l=(int)sizeof(buf);
      l=con->m_pagegen->GetData(buf,l);
      if (l < (con->m_pagegen->IsNonBlocking() ? 0 : 1)) // if nonblocking, this is l < 0, otherwise it's l<1
      {
        if (con->m_serv.canKeepAlive()) 
        {
          con->m_serv.write_bytes("",0);
          return -1;
        }
        return !con->m_serv.bytes_inqueue();
      }
      if (l>0)
        con->m_serv.write_bytes(buf,l);
    }
    return l > 0 ? 0 : -2; // -2 = no more data to send, but all is well
  }
  if (con->m_serv.canKeepAlive()) return -1;
  return 1; // we're done by this point
}



void WebServerBaseClass::url_encode(const char *in, char *out, int max_out)
{
  while (*in && max_out > 4)
  {
    if ((*in >= 'A' && *in <= 'Z')||
	      (*in >= 'a' && *in <= 'z')||
	      (*in >= '0' && *in <= '9')|| *in == '.' || *in == '_' || *in == '-') 
    {
      *out++=*in++;
      max_out--;
    }
    else
	  {
  	  int i=*in++;
      *out++ = '%';
      int b=(i>>4)&15;
      if (b < 10) *out++='0'+b;
      else *out++='A'+b-10;
      b=i&15;
      if (b < 10) *out++='0'+b;
      else *out++='A'+b-10;
      max_out-=3;
	  }
  }
  *out=0;
}


void WebServerBaseClass::url_decode(const char *in, char *out, int maxlen)
{
  while (*in && maxlen>1)
  {
    if (*in == '+') 
    {
      in++;
      *out++=' ';
    }
	  else if (*in == '%' && in[1] != '%' && in[1])
	  {
		  int a=0;
		  int b=0;
		  for ( b = 0; b < 2; b ++)
		  {
			  int r=in[1+b];
			  if (r>='0'&&r<='9') r-='0';
			  else if (r>='a'&&r<='f') r-='a'-10;
			  else if (r>='A'&&r<='F') r-='A'-10;
			  else break;
			  a*=16;
			  a+=r;
		  }
		  if (b < 2) *out++=*in++;
		  else { *out++=a; in += 3;}
	  }
	  else *out++=*in++;
	  maxlen--;
  }
  *out=0;
}





void WebServerBaseClass::base64decode(const char *src, char *dest, int destsize)
{
  int accum=0;
  int nbits=0;
  while (*src)
  {
    int x=0;
    char c=*src++;
    if (c >= 'A' && c <= 'Z') x=c-'A';
    else if (c >= 'a' && c <= 'z') x=c-'a' + 26;
    else if (c >= '0' && c <= '9') x=c-'0' + 52;
    else if (c == '+') x=62;
    else if (c == '/') x=63;
    else break;

    accum <<= 6;
    accum |= x;
    nbits += 6;   

    while (nbits >= 8)
    {
      if (--destsize<=0) break;
      nbits-=8;
      *dest++ = (char)((accum>>nbits)&0xff);
    }

  }
  *dest=0;
}

void WebServerBaseClass::base64encode(const char *in, char *out)
{
  char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  int shift = 0;
  int accum = 0;

  while (*in)
  {
    if (*in)
    {
      accum <<= 8;
      shift += 8;
      accum |= *in++;
    }
    while ( shift >= 6 )
    {
      shift -= 6;
      *out++ = alphabet[(accum >> shift) & 0x3F];
    }
  }
  if (shift == 4)
  {
    *out++ = alphabet[(accum & 0xF)<<2];
    *out++='=';  
  }
  else if (shift == 2)
  {
    *out++ = alphabet[(accum & 0x3)<<4];
    *out++='=';  
    *out++='=';  
  }

  *out++=0;
}

int WebServerBaseClass::parseAuth(const char *auth_header, char *out, int out_len)//returns 0 on unknown auth, 1 on basic
{
  const char *authstr=auth_header;
  *out=0;
  if (!auth_header || !*auth_header) return 0;
  while (*authstr == ' ') authstr++;
  if (strnicmp(authstr,"basic ",6)) return 0;
  authstr+=6;
  while (*authstr == ' ') authstr++;
  base64decode(authstr,out,out_len);
  return 1;
}
