/*
** 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
*/

#include <windows.h>
#include "jnetlib.h"
#include "webserver.h"

class WS_ItemList 
{
  public:
    WS_ItemList() { m_size=0; m_list=0; }
    ~WS_ItemList() { ::free(m_list); }

    void *Add(void *i);
    void *Get(int w);
    void Del(int idx);
    int GetSize(void) { return m_size; }
  protected:
    void **m_list;
    int m_size;
};

class WS_conInst
{
public:
  WS_conInst(JNL_Connection *c, int which_port);
  ~WS_conInst();

  // these will be used by WebServerBaseClass::onConnection yay
  JNL_HTTPServ m_serv;
  IPageGenerator *m_pagegen;


  int m_port; // port this came in on
  time_t m_connect_time;
};



void *WS_ItemList::Add(void *i)
{
  if (!m_list || !(m_size&31))
  {
    m_list=(void**)::realloc(m_list,sizeof(void*)*(m_size+32));
  }
  m_list[m_size++]=i;
  return i;
}
void *WS_ItemList::Get(int w) { if (w >= 0 && w < m_size) return m_list[w]; return NULL; }

void WS_ItemList::Del(int idx)
{
  if (m_list && idx >= 0 && idx < m_size)
  {
    m_size--;
    if (idx != m_size) ::memcpy(m_list+idx,m_list+idx+1,sizeof(void *)*(m_size-idx));
    if (!(m_size&31)&&m_size) // resize down
    {
      m_list=(void**)::realloc(m_list,sizeof(void*)*m_size);
    }
  }
}


WS_conInst::WS_conInst(JNL_Connection *c, int which_port) : m_serv(c), m_port(which_port)
{
  m_pagegen=0;
  time(&m_connect_time);
}

WS_conInst::~WS_conInst()
{
  delete m_pagegen;
}

WebServerBaseClass::~WebServerBaseClass()
{
  int x;
  for (x = 0; x < m_connections->GetSize(); x ++)
  {
    delete (WS_conInst *)m_connections->Get(x);
  }
  delete m_connections;
  for (x = 0; x < m_listeners->GetSize(); x ++)
  {
    delete (JNL_Listen *)m_listeners->Get(x);
  }
  delete m_listeners;
}

WebServerBaseClass::WebServerBaseClass()
{
  m_connections=new WS_ItemList;
  m_listeners=new WS_ItemList;
  m_listener_rot=0;
  m_timeout_s=15;
  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 long which_interface)
{
  int ffree=-1;
  removeListenPort(port);

  JNL_Listen *p=new JNL_Listen(port,which_interface);
  m_listeners->Add((void *)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_Listen *p=(JNL_Listen *)m_listeners->Get(x);
    if (p->port()==port)
    {
      delete p;
      m_listeners->Del(x);
      break;
    }
  }
}

void WebServerBaseClass::removeListenIdx(int idx)
{
  if (idx >= 0 && idx < m_listeners->GetSize())
  {
    JNL_Listen *p=(JNL_Listen *)m_listeners->Get(idx);
    delete p;
    m_listeners->Del(idx);
  }
}

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

void WebServerBaseClass::attachConnection(JNL_Connection *con, int port)
{
  m_connections->Add((void*)new WS_conInst(con,port));
}

void WebServerBaseClass::run(void)
{
  int nl;
  if (m_connections->GetSize() < m_max_con && (nl=m_listeners->GetSize()))
  {
    JNL_Listen *l=(JNL_Listen *)m_listeners->Get(m_listener_rot++ % nl);
    JNL_Connection *c=l->get_connect();
    if (c)
    {
      attachConnection(c,l->port());
    }
  }
  int x;
  for (x = 0; x < m_connections->GetSize(); x ++)
  {
    if (run_connection((WS_conInst *)m_connections->Get(x)))
    {
      delete ((WS_conInst *)m_connections->Get(x));
      m_connections->Del(x--);
    }
  }
}

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) 
    {
      return !con->m_serv.bytes_inqueue();
    }
    char buf[16384];
    int l=con->m_serv.bytes_cansend();
    if (l > 0)
    {
      if (l > sizeof(buf))l=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
      {
        return !con->m_serv.bytes_inqueue();
      }
      if (l>0)
        con->m_serv.write_bytes(buf,l);
    }
    return 0;
  }
  return 1; // we're done by this point
}



void WebServerBaseClass::url_encode(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(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<='z') r-='a'-10;
			  else if (r>='A'&&r<='Z') 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(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(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(char *auth_header, char *out, int out_len)//returns 0 on unknown auth, 1 on basic
{
  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;
}