/**
 * Copyright (c) 2015, Jozef Stefan Institute, Quintelligence d.o.o. and contributors
 * All rights reserved.
 *
 * This source code is licensed under the FreeBSD license found in the
 * LICENSE file in the root directory of this source tree.
 */

/////////////////////////////////////////////////
// Http-General

// general strings
const TStr THttp::HttpStr="http";
const TStr THttp::SlashStr="/";
const TStr THttp::ColonStr=":";

// fields names
const TStr THttp::ContTypeFldNm="Content-Type";
const TStr THttp::ContLenFldNm="Content-Length";
const TStr THttp::HostFldNm="Host";
const TStr THttp::AcceptRangesFldNm="Accept-Ranges";
const TStr THttp::CacheCtrlFldNm="Cache-Control";
const TStr THttp::AcceptFldNm="Accept";
const TStr THttp::SrvFldNm="Server";
const TStr THttp::ConnFldNm="Connection";
const TStr THttp::FetchIdFldNm="FetchId";
const TStr THttp::LocFldNm="Location";
const TStr THttp::SetCookieFldNm="Set-Cookie";
const TStr THttp::CookieFldNm="Cookie";
const TStr THttp::ResponseTimeNm = "X-Response-Time";

// content-type field-values
const TStr THttp::TextFldVal="text/";
const TStr THttp::TextPlainFldVal="text/plain";
const TStr THttp::TextHtmlFldVal="text/html";
const TStr THttp::TextXmlFldVal="text/xml";
const TStr THttp::TextWmlFldVal="text/vnd.wap.wml";
const TStr THttp::TextJavaScriptFldVal="text/javascript";
const TStr THttp::TextCssFldVal="text/css";
const TStr THttp::ImageIcoFldVal="image/x-icon";
const TStr THttp::ImagePngFldVal="image/png";
const TStr THttp::ImageGifFldVal="image/gif";
const TStr THttp::ImageJpgFldVal="image/jpg";
const TStr THttp::AppOctetFldVal="application/octet-stream";
const TStr THttp::AppSoapXmlFldVal="application/soap+xml";
const TStr THttp::AppW3FormFldVal="application/x-www-form-urlencoded";
const TStr THttp::AppJSonFldVal = "application/json";
const TStr THttp::ConnKeepAliveFldVal="keep-alive";

// file extensions
bool THttp::IsHtmlFExt(const TStr& FExt){
  TStr UcFExt=FExt.GetUc();
  return ((UcFExt==TFile::HtmlFExt.GetUc())||(UcFExt==TFile::HtmFExt.GetUc()));
}

bool THttp::IsGifFExt(const TStr& FExt){
  return (FExt.GetUc()==TFile::GifFExt.GetUc());
}

// port number
const int THttp::DfPortN=80;

// status codes
const int THttp::OkStatusCd=200;
const int THttp::RedirStatusCd=300;
const int THttp::BadRqStatusCd=400;
const int THttp::ErrStatusCd=400;
const int THttp::ErrNotFoundStatusCd=404;
const int THttp::InternalErrStatusCd=500;

TStr THttp::GetReasonPhrase(const int& StatusCd){
  switch (StatusCd){
    case 200: return "OK";
    case 201: return "Created";
    case 202: return "Accepted";
    case 204: return "No Content";
    case 300: return "Multiple Choices";
    case 301: return "Moved Permanently";
    case 302: return "Moved Temporarily";
    case 304: return "Not Modified";
    case 400: return "Bad Request";
    case 401: return "Unauthorized";
    case 403: return "Forbidden";
    case 404: return "Not Found";
    case 500: return "Internal Server Error";
    case 501: return "Not Implemented";
    case 502: return "Bad Gateway";
    case 503: return "Service Unavailable";
    default: return TStr("Unknown Status Code ")+TInt::GetStr(StatusCd);
  }
}

// method names
const TStr THttp::GetMethodNm="GET";
const TStr THttp::HeadMethodNm="HEAD";
const TStr THttp::PostMethodNm="POST";
const TStr THttp::PutMethodNm="PUT";
const TStr THttp::PatchMethodNm="PATCH";
const TStr THttp::OptionsMethodNm="OPTIONS";
const TStr THttp::DeleteMethodNm="DELETE";
const TStr THttp::UndefMethodNm="UndefinedMethod";

/////////////////////////////////////////////////
// Http-Chars
typedef enum {hpctUndef, hpctAlpha, hpctDigit, hpctCtl, hpctSpec} THttpChTy;

class THttpChDef{
private:
  TIntV ChTyV;
  TChV LcChV;
  void SetLcCh(const TStr& Str);
  void SetChTy(const THttpChTy& ChTy, const char& Ch);
  void SetChTy(const THttpChTy& ChTy, const TStr& Str);
public:
  THttpChDef();

  THttpChDef& operator=(const THttpChDef&){Fail; return *this;}

  int GetChTy(const char& Ch){return ChTyV[Ch-TCh::Mn];}
  bool IsAlpha(const char& Ch){return ChTyV[Ch-TCh::Mn]==int(hpctAlpha);}
  bool IsDigit(const char& Ch){return ChTyV[Ch-TCh::Mn]==int(hpctDigit);}
  bool IsCtl(const char& Ch){return ChTyV[Ch-TCh::Mn]==int(hpctCtl);}
  bool IsLws(const char& Ch){
    return (Ch==' ')||(Ch==TCh::TabCh)||(Ch==TCh::CrCh)||(Ch==TCh::LfCh);}
  bool IsText(const char& Ch){return !IsCtl(Ch)||IsLws(Ch);}
  bool IsSpec(const char& Ch){
    return (ChTyV[Ch-TCh::Mn]==int(hpctSpec))||(Ch==9)||(Ch==32);}
  bool IsCr(const char& Ch){return Ch==13;}
  bool IsLf(const char& Ch){return Ch==10;}
  bool IsSp(const char& Ch){return Ch==32;}
  bool IsHt(const char& Ch){return Ch==9;}
  bool IsDQuote(const char& Ch){return Ch=='"';}

  char GetLcCh(const char& Ch){return LcChV[Ch-TCh::Mn];}
  TStr GetLcStr(const TStr& Str);
};

void THttpChDef::SetChTy(const THttpChTy& ChTy, const char& Ch){
  IAssert(ChTyV[Ch-TCh::Mn]==int(hpctUndef)); ChTyV[Ch-TCh::Mn]=TInt(ChTy);}

void THttpChDef::SetChTy(const THttpChTy& ChTy, const TStr& Str){
  for (int ChN=0; ChN<Str.Len(); ChN++){SetChTy(ChTy, Str[ChN]);}}

void THttpChDef::SetLcCh(const TStr& Str){
  for (int ChN=1; ChN<Str.Len(); ChN++){LcChV[Str[ChN]-TCh::Mn]=TCh(Str[0]);}}

THttpChDef::THttpChDef():
  ChTyV(TCh::Vals), LcChV(TCh::Vals){

  // Character-Types
  ChTyV.PutAll(TInt(hpctUndef));
  SetChTy(hpctAlpha, "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
  SetChTy(hpctAlpha, "abcdefghijklmnopqrstuvwxyz");
  SetChTy(hpctDigit, "0123456789");
  for (char Ch=0; Ch<=31; Ch++){SetChTy(hpctCtl, Ch);}
  SetChTy(hpctCtl, 127);
  SetChTy(hpctSpec, "()<>@,;:\\\"/[]?={}"); // +char(9)+char(32)

  // Lower-Case
  {for (int Ch=TCh::Mn; Ch<=TCh::Mx; Ch++){LcChV[Ch-TCh::Mn]=TCh(char(Ch));}}
  SetLcCh("aA"); SetLcCh("bB"); SetLcCh("cC"); SetLcCh("dD"); SetLcCh("eE");
  SetLcCh("fF"); SetLcCh("gG"); SetLcCh("hH"); SetLcCh("iI"); SetLcCh("jJ");
  SetLcCh("kK"); SetLcCh("lL"); SetLcCh("mM"); SetLcCh("nN"); SetLcCh("oO");
  SetLcCh("pP"); SetLcCh("qQ"); SetLcCh("rR"); SetLcCh("sS"); SetLcCh("tT");
  SetLcCh("uU"); SetLcCh("vV"); SetLcCh("wW"); SetLcCh("xX"); SetLcCh("yY");
  SetLcCh("zZ");
}

TStr THttpChDef::GetLcStr(const TStr& Str){
  TChA LcStr;
  for (int ChN=0; ChN<Str.Len(); ChN++){LcStr+=GetLcCh(Str[ChN]);}
  return LcStr;
}

/////////////////////////////////////////////////
// Http-Exception
typedef enum {
  heUnexpectedEof, hePeriodExpected, heTokenExpected, heInvalidToken,
  heTSpecExpected, heInvalidTSpec, heNumExpected, heInvalidNumPlaces,
  heCrLfExpected, heMethodNmExpected, heUrlEmpty, heBadUrl,
  heBadSearchStr} THttpExCd;

class THttpEx{
private:
  THttpExCd HttpExCd;
public:
  THttpEx(const THttpExCd& _HttpExCd): HttpExCd(_HttpExCd){}

  const THttpExCd& GetHttpExCd() const { return HttpExCd; }
};

/////////////////////////////////////////////////
// Http-Lexical
class THttpLx{
private:
  static THttpChDef ChDef;
  PSIn SIn;
  //TChA ChStack;
  TBoolChS EofChPrS;
  char Ch;
  bool AtEof;
  TMem SfMem;
public:
  THttpLx(const PSIn& _SIn):
    SIn(_SIn), EofChPrS(), Ch(' '), AtEof(false), SfMem(50000){
    GetFirstCh();}

  THttpLx& operator=(const THttpLx&){Fail; return *this;}

  // basic
  bool Eof(){return AtEof;}
  int Len(){return EofChPrS.Len()+SIn->Len();}
  char GetFirstCh();
  char GetCh();
  void GetRest();
  void PutCh(const char& _Ch){
    EofChPrS.Push(TBoolChPr(AtEof, Ch)); Ch=_Ch; AtEof=false; SfMem.Pop();}
  void ClrMemSf(){SfMem.Clr();}
  TMem& GetMemSf(){return SfMem;}

  // http request
  THttpRqMethod GetRqMethod();
  PUrl GetUrl();
  TStr GetUrlStr();
  // http response
  bool IsRespStatusLn();
  TStr GetRespReasonPhrase();
  // spacing
  void GetWs();
  bool IsLws();
  void GetLws();
  bool IsCrLf();
  void GetCrLf();
  // tokens
  void GetPeriod();
  TStr GetToken(const TStr& ExpectStr=TStr());
  TStr GetSpec(const TStr& ExpectStr=TStr());
  int GetInt(const int& RqPlaces=-1);
  TStr GetFldVal();

  static TStr GetNrStr(const TStr& Str){return ChDef.GetLcStr(Str);}
};
THttpChDef THttpLx::ChDef;

char THttpLx::GetFirstCh(){
  if (SIn->Eof()){
    if (AtEof){throw THttpEx(heUnexpectedEof);}
    AtEof=true; return 0;
  } else {
    Ch=SIn->GetCh(); return Ch;
  }
}

char THttpLx::GetCh(){
  if (EofChPrS.Empty()){
    if (SIn->Eof()){
      if (AtEof){throw THttpEx(heUnexpectedEof);}
      AtEof=true; SfMem+=Ch; Ch=TCh::NullCh; return Ch;
    } else {
      SfMem+=Ch; Ch=SIn->GetCh(); return Ch;
    }
  } else {
    SfMem+=Ch;
    AtEof=EofChPrS.Top().Val1; Ch=EofChPrS.Top().Val2; EofChPrS.Pop();
    return Ch;
  }
}

void THttpLx::GetRest(){
  while ((!SIn->Eof())&&(!EofChPrS.Empty())){GetCh();}
  if (!SIn->Eof()){SfMem+=Ch;}
  TMem RestMem; TMem::LoadMem(SIn, RestMem);
  SfMem+=RestMem;
}

THttpRqMethod THttpLx::GetRqMethod(){
  TChA MethodNm;
  while (!Eof() && ChDef.IsAlpha(Ch)){
    MethodNm+=Ch; GetCh();}
  THttpRqMethod Method=hrmUndef;
  if (MethodNm==THttp::GetMethodNm){Method=hrmGet;}
  else if (MethodNm==THttp::HeadMethodNm){Method=hrmHead;}
  else if (MethodNm==THttp::PostMethodNm){Method=hrmPost;}
  else if (MethodNm==THttp::PutMethodNm){Method=hrmPut;}
  else if (MethodNm==THttp::PatchMethodNm){Method=hrmPatch;}
  else if (MethodNm==THttp::OptionsMethodNm){Method=hrmOptions;}
  else if (MethodNm==THttp::DeleteMethodNm){Method=hrmDelete;}
  if (Method==hrmUndef){throw THttpEx(heMethodNmExpected);}
  return Method;
}

PUrl THttpLx::GetUrl(){
  TChA UrlChA;
  while ((!Eof())&&(!ChDef.IsSp(Ch))){
    UrlChA+=Ch; GetCh();}
  if (UrlChA.Empty()){
    throw THttpEx(heUrlEmpty);}
  static TStr LocalBaseUrlStr="http://localhost/";
  PUrl Url=PUrl(new TUrl(UrlChA, LocalBaseUrlStr));
  if (!Url->IsOk()){
    throw THttpEx(heBadUrl);}
  return Url;
}

TStr THttpLx::GetUrlStr(){
  TChA UrlChA;
  while ((!Eof())&&(!ChDef.IsSp(Ch))){
    UrlChA+=Ch; GetCh();}
  if (UrlChA.Empty()){
    throw THttpEx(heUrlEmpty);}
  return UrlChA;
}

bool THttpLx::IsRespStatusLn(){
  static const TChA MouldChA="http/N.N NNN ";
  TChA TestChA(MouldChA);
  int TestLen=TestChA.Len();
  if (1+Len()<TestLen){return false;}
  TestChA.PutCh(0, ChDef.GetLcCh(Ch));
  {for (int ChN=1; ChN<TestLen; ChN++){
    TestChA.PutCh(ChN, ChDef.GetLcCh(GetCh()));}}
  {for (int ChN=1; ChN<TestLen; ChN++){
    PutCh(TestChA[TestLen-ChN-1]);}}
  {for (int ChN=0; ChN<MouldChA.Len(); ChN++){
    if (MouldChA[ChN]=='N'){
      if (!ChDef.IsDigit(TestChA[ChN])){return false;}
    } else {
      if (MouldChA[ChN]!=TestChA[ChN]){return false;}
    }
  }}
  return true;
}

TStr THttpLx::GetRespReasonPhrase(){
  GetLws();
  TChA RPStr;
  while (!Eof()&&ChDef.IsText(Ch)&&(Ch!=TCh::CrCh)&&(Ch!=TCh::LfCh)){
    RPStr+=Ch; GetCh();}
  return RPStr;
}

void THttpLx::GetWs(){
  while (!Eof()&&((Ch==' ')||(Ch==TCh::TabCh))){GetCh();}
}

bool THttpLx::IsLws(){
  if ((Ch==' ')||(Ch==TCh::TabCh)){
    return true;
  } else
  if (Ch==TCh::CrCh){
    GetCh();
    if (Ch==TCh::LfCh){
      GetCh(); bool Ok=(Ch==' ')||(Ch==TCh::TabCh);
      PutCh(TCh::LfCh); PutCh(TCh::CrCh); return Ok;
    } else {
      PutCh(TCh::CrCh); return false;
    }
  } else
  if (Ch==TCh::LfCh){
    GetCh(); bool Ok=(Ch==' ')||(Ch==TCh::TabCh);
    PutCh(TCh::LfCh); return Ok;
  } else {
    return false;
  }
}

void THttpLx::GetLws(){
  forever {
    while ((Ch==' ')||(Ch==TCh::TabCh)){GetCh();}
    if (Ch==TCh::CrCh){
      GetCh();
      if (Ch==TCh::LfCh){
        GetCh();
        if ((Ch==' ')||(Ch==TCh::TabCh)){GetCh();}
        else {PutCh(TCh::LfCh); PutCh(TCh::CrCh); break;}
      } else {
        PutCh(TCh::CrCh); break;
      }
    } else
    if (Ch==TCh::LfCh){
      GetCh();
      if ((Ch==' ')||(Ch==TCh::TabCh)){GetCh();}
      else {PutCh(TCh::LfCh); break;}
    } else {
      break;
    }
  }
}

bool THttpLx::IsCrLf(){
  if (Ch==TCh::CrCh){
    GetCh(); bool Ok=(Ch==TCh::LfCh); PutCh(TCh::CrCh); return Ok;
  } else
  if (Ch==TCh::LfCh){
    return true;
  } else {
    return false;
  }
}

void THttpLx::GetCrLf(){
  if (Ch==TCh::CrCh){
    GetCh();
    if (Ch==TCh::LfCh){GetCh();} else {throw THttpEx(heCrLfExpected);}
  } else
  if (Ch==TCh::LfCh){
    GetCh();
  } else {
    throw THttpEx(heCrLfExpected);
  }
}

void THttpLx::GetPeriod(){
  GetWs();
  if (Ch!='.'){throw THttpEx(hePeriodExpected);}
  GetCh();
}

TStr THttpLx::GetToken(const TStr& ExpectStr){
  GetLws();
  TChA TokenStr;
  while (!Eof() && !ChDef.IsCtl(Ch) && !ChDef.IsSpec(Ch)){
    TokenStr+=Ch; GetCh();}
  if (TokenStr.Empty()){throw THttpEx(heTokenExpected);}
  if (!ExpectStr.Empty()){
    if (GetNrStr(ExpectStr)!=GetNrStr(TokenStr)){
      throw THttpEx(heInvalidToken);}
  }
  return TokenStr;
}

TStr THttpLx::GetSpec(const TStr& ExpectStr){
  GetLws();
  if (!ChDef.IsSpec(Ch)){throw THttpEx(heTSpecExpected);}
  TStr SpecStr(Ch); GetCh();
  if (!ExpectStr.Empty()){
    if (ExpectStr!=SpecStr){throw THttpEx(heInvalidTSpec);}}
  return SpecStr;
}

int THttpLx::GetInt(const int& RqPlaces){
  GetLws();
  if (!ChDef.IsDigit(Ch)){throw THttpEx(heNumExpected);}
  int Int=0; int CurPlaces=0;
  do {Int=Int*10+Ch-'0'; CurPlaces++; GetCh();
  } while ((CurPlaces<RqPlaces)&&(ChDef.IsDigit(Ch)));
  if (RqPlaces!=-1){
    if (CurPlaces!=RqPlaces){throw THttpEx(heInvalidNumPlaces);}}
  return Int;
}

TStr THttpLx::GetFldVal(){
  TChA FldValStr;
  do {
    GetLws();
    while (!Eof()&&ChDef.IsText(Ch)&&(Ch!=TCh::CrCh)&&(Ch!=TCh::LfCh)){
      FldValStr+=Ch; GetCh();}
    if (IsLws()){FldValStr+=' ';}
  } while (IsLws());
  return FldValStr;
}

/////////////////////////////////////////////////
// Http-Character-Returner
class THttpChRet{
  PSIn SIn;
  int Chs, ChN;
  THttpExCd HttpExCd;
public:
  THttpChRet(const PSIn& _SIn, const THttpExCd& _HttpExCd):
    SIn(_SIn), Chs(SIn->Len()), ChN(0), HttpExCd(_HttpExCd){}
  THttpChRet& operator=(const THttpChRet&){Fail; return *this;}
  bool Eof(){return ChN==Chs;}
  char GetCh(){
    if (ChN>=Chs){throw THttpEx(HttpExCd);}
    ChN++; return SIn->GetCh();}
};

/////////////////////////////////////////////////
// Http-Request
void THttpRq::ParseSearch(const TStr& SearchStr){
  PSIn SIn=TStrIn::New(SearchStr, false);
  THttpChRet ChRet(SIn, heBadSearchStr);
  try {
  // check empty search string
  if (ChRet.Eof()){return;}
  // require '?' at the beginning
  if (ChRet.GetCh()!='?'){
    throw THttpEx(heBadSearchStr);}
  // parse key=val{&...} pairs
  TChA KeyNm; TChA ValStr;
  while (!ChRet.Eof()){
    char Ch; KeyNm.Clr(); ValStr.Clr();
    // key
    while ((Ch=ChRet.GetCh())!='='){
      switch (Ch){
        case '%':{
          char Ch1=ChRet.GetCh();
          if (!TCh::IsHex(Ch1)) { throw THttpEx(heBadSearchStr); }
          char Ch2=ChRet.GetCh();
          if (!TCh::IsHex(Ch2)) { throw THttpEx(heBadSearchStr); }
          if (Ch1 == 0 && Ch2 == 0) throw THttpEx(heBadSearchStr);
          KeyNm.AddCh(char(16*TCh::GetHex(Ch1)+TCh::GetHex(Ch2)));} break;
        case '+': KeyNm.AddCh(' '); break;
        case '&': throw THttpEx(heBadSearchStr);
        default: KeyNm.AddCh(Ch);
      }
    }
    // equals
    if (Ch!='='){
      throw THttpEx(heBadSearchStr);}
    // value
    while ((!ChRet.Eof())&&((Ch=ChRet.GetCh())!='&')){
      switch (Ch){
        case '%':{
          char Ch1=ChRet.GetCh();
          if (!TCh::IsHex(Ch1)) { throw THttpEx(heBadSearchStr); }
          char Ch2=ChRet.GetCh();
          if (!TCh::IsHex(Ch2)) { throw THttpEx(heBadSearchStr); }
          if (Ch1 == 0 && Ch2 == 0) throw THttpEx(heBadSearchStr);
          ValStr.AddCh(char(16*TCh::GetHex(Ch1)+TCh::GetHex(Ch2)));} break;
        case '+': ValStr.AddCh(' '); break;
        case '&': throw THttpEx(heBadSearchStr);
        default: ValStr.AddCh(Ch);
      }
    }
    // save key-value pair
    UrlEnv->AddToKeyVal(KeyNm, ValStr);
    }
  }
  catch (const THttpEx&){Ok=false;}
}

void THttpRq::ParseHttpRq(const PSIn& SIn){
  THttpLx Lx(SIn);
  // initial status
  Ok=false;
  CompleteP=false;
  // request-line
  Method=Lx.GetRqMethod();
  Lx.GetWs();
  //Url=Lx.GetUrl();
  TStr UrlStr=Lx.GetUrlStr();
  Lx.GetWs();
  Lx.GetToken(THttp::HttpStr); Lx.GetSpec(THttp::SlashStr);
  MajorVerN=Lx.GetInt(1); Lx.GetPeriod(); MinorVerN=Lx.GetInt(1);
  Lx.GetCrLf();
  // header fields & values
  while ((!Lx.Eof())&&(!Lx.IsCrLf())){
    TStr FldNm=Lx.GetToken(); Lx.GetSpec(THttp::ColonStr);
    TStr FldVal=Lx.GetFldVal();
    Lx.GetCrLf();
    TStr NrFldNm=THttpLx::GetNrStr(FldNm);
    FldNmToValH.AddDat(NrFldNm, FldVal);
  }
  // separator CrLf
  if (!Lx.IsCrLf()){return;} // to avoid exceptions
  Lx.GetCrLf();
  // header & body strings
  HdStr=Lx.GetMemSf().GetAsStr();
  Lx.ClrMemSf();
  Lx.GetRest();
  BodyMem=Lx.GetMemSf();
  // completeness
  int ContLen=GetFldVal(THttp::ContLenFldNm).GetInt(-1);
  if (ContLen==-1){
    // if not content-len is given we assume http-request is ok
    CompleteP=true;
  } else {
    if (ContLen<=BodyMem.Len()){
      // if we read enough data, we claim completeness
      CompleteP=true;
      BodyMem.Trunc(ContLen);
    } else {
      // if we read not enough data we claim incompleteness
      CompleteP=false;
    }
  }
  // url
  if (CompleteP){
    const TStr LocalBaseUrlStr="http://localhost/";
    Url=TUrl::New(UrlStr, LocalBaseUrlStr);
    if (!Url->IsOk()){
      throw THttpEx(heBadUrl);}
  }
  if (Url.Empty()) {
	  throw THttpEx(heBadUrl);
  }
  // search string
  TStr SearchStr;
  if (Method==hrmGet){
	  ParseSearch(Url->GetSearchStr());
  }
  else if ((Method == hrmPost) && (
	  (!IsFldNm(THttp::ContTypeFldNm)) ||
	  (GetFldVal(THttp::ContTypeFldNm).SearchStr(THttp::TextHtmlFldVal) >= 0) ||		// content-type can also contain "charset=utf-8"
	  (GetFldVal(THttp::ContTypeFldNm).SearchStr(THttp::AppW3FormFldVal) >= 0))) {
	  ParseSearch(TStr("?") + BodyMem.GetAsStr());
  }
  // if json object then parse the object values
  else if (Method == hrmPost && GetFldVal(THttp::ContTypeFldNm).SearchStr(THttp::AppJSonFldVal) >= 0) {
	  bool Ok; TStr MsgStr;
	  PJsonVal Json = TJsonVal::GetValFromStr(BodyMem.GetAsStr(), Ok, MsgStr);
	  if (Ok && Json->IsObj()) {
		  for (int N = 0; N < Json->GetObjKeys(); N++) {
			  const TStr Key = Json->GetObjKey(N);
			  PJsonVal ValJson = Json->GetObjKey(Key);
			  if (ValJson->IsStr()) UrlEnv->AddToKeyVal(Key, ValJson->GetStr());	  // don't put strings into additional ""
			  else if (ValJson->IsArr()) {
				  for (int K = 0; K < ValJson->GetArrVals(); K++) {
					  if (ValJson->GetArrVal(K)->IsStr()) UrlEnv->AddToKeyVal(Key, ValJson->GetArrVal(K)->GetStr());	  // don't put strings into additional ""
					  else	UrlEnv->AddToKeyVal(Key, ValJson->GetArrVal(K)->SaveStr());
				  }
			  }
			  else	UrlEnv->AddToKeyVal(Key, ValJson->SaveStr());
		  }
	  }
  }
  // at this point ok=true
  Ok=true;
}

THttpRq::THttpRq(const PSIn& SIn):
  Ok(false), MajorVerN(0), MinorVerN(0), Method(hrmUndef),
  FldNmToValH(), UrlEnv(TUrlEnv::New()),
  HdStr(), BodyMem(){
  try {
    ParseHttpRq(SIn);
  }
  catch (const THttpEx&){
      Ok=false;
  }
  catch (PExcept E) {
      Ok = false;
  }
}

THttpRq::THttpRq(
 const THttpRqMethod& _Method, const PUrl& _Url,
 const TStr& ContTypeFldVal, const TMem& _BodyMem, const int& FetchId):
  Ok(false),
  MajorVerN(1), MinorVerN(0),
  Method(_Method),
  Url(_Url),
  FldNmToValH(),
  UrlEnv(TUrlEnv::New()),
  HdStr(), BodyMem(_BodyMem){
  // compose head-http-request
  TChA HdChA;
  if (Url->IsOk()){
    TStr AbsPath=Url->GetPathStr()+Url->GetSearchStr();
    HdChA+=GetMethodNm(); HdChA+=' '; HdChA+=AbsPath; HdChA+=" HTTP/1.0\r\n";
  }
  // add content-type
  if (!ContTypeFldVal.Empty()){
    FldNmToValH.AddDat(THttpLx::GetNrStr(THttp::ContTypeFldNm), ContTypeFldVal);
    HdChA+=THttpLx::GetNrStr(THttp::ContTypeFldNm); HdChA+=": ";
    HdChA+=ContTypeFldVal; HdChA+="\r\n";
  }
  // add host
  if (Url->IsOk()){
    TStr HostNm=Url->GetHostNm();
    FldNmToValH.AddDat(THttpLx::GetNrStr(THttp::HostFldNm), HostNm);
    HdChA+=THttpLx::GetNrStr(THttp::HostFldNm); HdChA+=": ";
    HdChA+=HostNm; HdChA+="\r\n";
    ParseSearch(Url->GetSearchStr());
  }
  // add fetch-id
  if (Url->IsOk()&&(FetchId!=-1)){
    TStr FetchIdStr=TInt::GetStr(FetchId);
    FldNmToValH.AddDat(THttpLx::GetNrStr(THttp::FetchIdFldNm), FetchIdStr);
    HdChA+=THttpLx::GetNrStr(THttp::FetchIdFldNm); HdChA+=": ";
    HdChA+=FetchIdStr; HdChA+="\r\n";
  }
  // finish head-http-request
  if (Url->IsOk()){
    HdChA+="\r\n";
    HdStr=HdChA;
  }
  // set http-request ok
  Ok=true;
}

const TStr& THttpRq::GetMethodNm() const {
  switch (Method){
    case hrmGet: return THttp::GetMethodNm;
    case hrmHead: return THttp::HeadMethodNm;
    case hrmPost: return THttp::PostMethodNm;
    case hrmPut: return THttp::PutMethodNm;
    case hrmPatch: return THttp::PatchMethodNm;
    case hrmOptions: return THttp::OptionsMethodNm;
    case hrmDelete: return THttp::DeleteMethodNm;
    default: return  THttp::UndefMethodNm;
  }
}

bool THttpRq::IsFldNm(const TStr& FldNm) const {
  return FldNmToValH.IsKey(THttpLx::GetNrStr(FldNm));
}

TStr THttpRq::GetFldVal(const TStr& FldNm) const {
  TStr NrFldNm=THttpLx::GetNrStr(FldNm);
  if (FldNmToValH.IsKey(NrFldNm)){
    return FldNmToValH.GetDat(NrFldNm);
  } else {
    return TStr();
  }
}

bool THttpRq::IsFldVal(const TStr& FldNm, const TStr& FldVal) const {
  return THttpLx::GetNrStr(FldVal)==THttpLx::GetNrStr(GetFldVal(FldNm));
}


void THttpRq::AddFldVal(const TStr& FldNm, const TStr& FldVal){
  TStr NrFldNm=THttpLx::GetNrStr(FldNm);
  FldNmToValH.AddDat(NrFldNm, FldVal);
}

const TStrStrH& THttpRq::GetFldValH() const {
	return FldNmToValH;
}

TStr THttpRq::GetStr() const {
  TChA ChA;
  ChA+=GetMethodNm(); ChA+=' ';
  ChA+=Url->GetUrlStr(); ChA+=' ';
  ChA+="HTTP/1.0\r\n";
  for (int FldN=0; FldN<FldNmToValH.Len(); FldN++){
    ChA+=FldNmToValH.GetKey(FldN); ChA+=": ";
    ChA+=FldNmToValH[FldN]; ChA+="\r\n";
  }
  if (!BodyMem.Empty()) {
    ChA+=THttp::ContLenFldNm; ChA+=": ";
    ChA+=TInt::GetStr(BodyMem.Len()); ChA+="\r\n";
  }
  ChA+="\r\n";
  ChA+=BodyMem.GetAsStr();
  return ChA;
}

bool THttpRq::IsMobileUsrAgent() const {
	TStr UserAgentStr = GetUsrAgent();

	if (UserAgentStr.IsStrIn("Tablet")) {
		return false;
	} else if (UserAgentStr.IsStrIn("Mobile")) {		//this should also cover Android
		return true;
	} else if (UserAgentStr.IsStrIn("iPhone") || UserAgentStr.IsStrIn("iPod")) {
		return true;
	} else if (UserAgentStr.IsStrIn("BlackBerry")) {
		return true;
	} else if (UserAgentStr.IsStrIn("Symbian")) {
		return true;
	} else if (UserAgentStr.IsStrIn("Opera Mini")) {
		return true;
	} else if (UserAgentStr.IsStrIn("Windows Phone")) {
		return true;
	}
	return false;
}

bool THttpRq::IsTabletUsrAgent() const {
	TStr UserAgentStr = GetUsrAgent();

	if (!IsMobileUsrAgent()) {
		if (UserAgentStr.IsStrIn("Android")) {
			return true;
		} else if (UserAgentStr.IsStrIn("iPad")) {
			return true;
		} else if (UserAgentStr.IsStrIn("Tablet")) {
			return true;
		}
	}
	return false;
}

TUserAgentDeviceType THttpRq::GetUsrAgentDeviceType() const {
	if (IsMobileUsrAgent())
		return uadtMobile;
	else if (IsTabletUsrAgent())
		return uadtTablet;
	else return uadtDesktop;
}

TUserAgentOSType THttpRq::GetUsrAgentOSType() const {
	TStr UserAgentStr = GetUsrAgent();

	if (UserAgentStr.IsStrIn("Windows NT"))
		return uaotWindows;
	if (UserAgentStr.IsStrIn("iPhone OS"))
		return uaotIOS;
	if (UserAgentStr.IsStrIn("Android"))
		return uaotAndroid;
	if (UserAgentStr.IsStrIn("Linux"))
		return uaotLinux;
	if (UserAgentStr.IsStrIn("Mac OS X"))
		return uaotMacOSX;
	if (UserAgentStr.IsStrIn("Symbian"))
		return uaotSymbian;
	if (UserAgentStr.IsStrIn("BlackBerry"))
		return uadtBlackBerry;

	// I'll return windows by default since it's the most common
	return uaotWindows;
}

/////////////////////////////////////////////////
// Http-Response
void THttpResp::AddHdFld(const TStr& FldNm, const TStr& FldVal, TChA& HdChA){
  TStr NrFldNm=THttpLx::GetNrStr(FldNm);
  FldNmToValVH.AddDat(NrFldNm).Add(FldVal);
  HdChA+=FldNm; HdChA+=": "; HdChA+=FldVal; HdChA+="\r\n";
}

void THttpResp::ParseHttpResp(const PSIn& SIn){
  THttpLx Lx(SIn);
  if (Lx.Eof()){
    // no content
    MajorVerN=0; MinorVerN=9; StatusCd=204;
    HdStr = TStr();
    BodyMem = TStr();
  } else {
    if (Lx.IsRespStatusLn()){
      // status-line
      Lx.GetToken(THttp::HttpStr); Lx.GetSpec(THttp::SlashStr);
      MajorVerN=Lx.GetInt(1); Lx.GetPeriod(); MinorVerN=Lx.GetInt(1);
      StatusCd=Lx.GetInt(3);
      ReasonPhrase=Lx.GetRespReasonPhrase();
      Lx.GetCrLf();
      // header fields & values
      while (!Lx.IsCrLf()){
        TStr FldNm=Lx.GetToken(); Lx.GetSpec(THttp::ColonStr);
        TStr FldVal=Lx.GetFldVal();
        Lx.GetCrLf();
        TStr NrFldNm=THttpLx::GetNrStr(FldNm);
        FldNmToValVH.AddDat(NrFldNm).Add(FldVal);
      }
      // separator CrLf
      Lx.GetCrLf();
      // header & body strings
      HdStr=Lx.GetMemSf().GetAsStr();
      Lx.ClrMemSf();
      Lx.GetRest();
      BodyMem=Lx.GetMemSf();
    } else {
      // old fashion format
      MajorVerN=0; MinorVerN=9; StatusCd=200;
      HdStr = TStr();
      Lx.ClrMemSf();
      Lx.GetRest();
      BodyMem=Lx.GetMemSf();
    }
  }
  Ok=true;
}

THttpResp::THttpResp(const int& _StatusCd, const TStr& ContTypeVal,
 const bool& CacheCtrlP, const PSIn& BodySIn, const TStr LocStr,
 const int& ResponseTimeMs, const TStrKdV& CustomHdrV):
  Ok(true), MajorVerN(1), MinorVerN(0), StatusCd(_StatusCd), ReasonPhrase(),
  FldNmToValVH(20), HdStr(), BodyMem(){
  ReasonPhrase=THttp::GetReasonPhrase(StatusCd);
  TChA HdChA;
  // first line
  HdChA+="HTTP/"; HdChA+=TInt::GetStr(MajorVerN); HdChA+=".";
  HdChA+=TInt::GetStr(MinorVerN); HdChA+=' ';
  HdChA+=TInt::GetStr(StatusCd); HdChA+=' ';
  HdChA+=ReasonPhrase;
  HdChA+="\r\n";
  // header fields
  // server
  //AddHdFld(THttp::SrvFldNm, "Tralala", HdChA);
  if (!LocStr.Empty()){
    AddHdFld("Location", LocStr, HdChA);}
  if (!BodySIn.Empty()){
    // content-type
    AddHdFld(THttp::ContTypeFldNm, ContTypeVal, HdChA);
    // accept-ranges
    AddHdFld(THttp::AcceptRangesFldNm, "bytes", HdChA);
    // content-length
    TStr ContLenVal=TInt::GetStr(BodySIn->Len());
    AddHdFld(THttp::ContLenFldNm, ContLenVal, HdChA);
    // cache-control
    if (!CacheCtrlP){
      AddHdFld(THttp::CacheCtrlFldNm, "no-cache", HdChA);}
  }
  // add response time header
  AddHdFld(THttp::ResponseTimeNm, TInt::GetStr(ResponseTimeMs), HdChA);
  // add custom headers
  for (int N = 0; N < CustomHdrV.Len(); N++) {
      AddHdFld(CustomHdrV[N].Key, CustomHdrV[N].Dat, HdChA);
  }
  // header/body separator
  HdChA+="\r\n";
  // header/body data
  HdStr=HdChA;
  if (!BodySIn.Empty()){
    TMem::LoadMem(BodySIn, BodyMem);}
}

THttpResp::THttpResp(const PSIn& SIn):
  Ok(false), MajorVerN(0), MinorVerN(0), StatusCd(-1), ReasonPhrase(),
  FldNmToValVH(20), HdStr(), BodyMem(){
  try {
    ParseHttpResp(SIn);
  }
  catch (const THttpEx&){Ok=false;}
}

bool THttpResp::IsFldNm(const TStr& FldNm) const {
  return FldNmToValVH.IsKey(THttpLx::GetNrStr(FldNm));
}

TStr THttpResp::GetFldVal(const TStr& FldNm, const int& ValN) const {
  TStr NrFldNm=THttpLx::GetNrStr(FldNm);
  if (FldNmToValVH.IsKey(NrFldNm)){
    const TStrV& ValV=FldNmToValVH.GetDat(NrFldNm);
    if (ValV.Len()>0){return ValV[ValN];} else {return TStr();}
  } else {
    return TStr();
  }
}

void THttpResp::GetFldValV(const TStr& FldNm, TStrV& FldValV) const {
  TStr NrFldNm=THttpLx::GetNrStr(FldNm);
  if (FldNmToValVH.IsKey(NrFldNm)){
    FldValV=FldNmToValVH.GetDat(NrFldNm);
  } else {
    FldValV.Clr();
  }
}

bool THttpResp::IsFldVal(const TStr& FldNm, const TStr& FldVal) const {
  return THttpLx::GetNrStr(FldVal)==THttpLx::GetNrStr(GetFldVal(FldNm));
}

void THttpResp::AddFldVal(const TStr& FldNm, const TStr& FldVal){
  TStr NrFldNm=THttpLx::GetNrStr(FldNm);
  FldNmToValVH.AddDat(NrFldNm).Add(FldVal);
  if (HdStr.EndsWith("\r\n\r\n")){
    TChA HdChA=HdStr;
    HdChA.Pop(); HdChA.Pop();
    HdChA+=NrFldNm; HdChA+=": "; HdChA+=FldVal;
    HdChA+="\r\n\r\n";
    HdStr=HdChA;
  }
}

void THttpResp::GetCookieKeyValDmPathQuV(TStrQuV& CookieKeyValDmPathQuV){
  CookieKeyValDmPathQuV.Clr();
  TStrV CookieFldValV; GetFldValV(THttp::SetCookieFldNm, CookieFldValV);
  for (int CookieN=0; CookieN<CookieFldValV.Len(); CookieN++){
    TStr CookieFldVal=CookieFldValV[CookieN];
    TStrV KeyValStrV;
    CookieFldVal.SplitOnAllCh(';', KeyValStrV, true);
    TStrPrV KeyValPrV; TStr DmNm; TStr PathStr;
    for (int KeyValStrN=0; KeyValStrN<KeyValStrV.Len(); KeyValStrN++){
      TStr KeyValStr=KeyValStrV[KeyValStrN];
      TStr KeyNm; TStr ValStr;
      if (KeyValStr.IsChIn('=')){
        KeyValStrV[KeyValStrN].SplitOnCh(KeyNm, '=', ValStr);
		KeyNm = KeyNm.GetTrunc(); ValStr = ValStr.GetTrunc();
      } else {
        KeyNm=KeyValStr.GetTrunc();
      }
      if (KeyNm=="expires"){}
      else if (KeyNm=="domain"){DmNm=ValStr;}
      else if (KeyNm=="path"){PathStr=ValStr;}
      else if (KeyNm=="expires"){}
      else if (KeyNm=="secure"){}
      else if (KeyNm=="httponly"){}
      else if (!KeyNm.Empty()){
        KeyValPrV.Add(TStrPr(KeyNm, ValStr));
      }
    }
    for (int KeyValPrN=0; KeyValPrN<KeyValPrV.Len(); KeyValPrN++){
      TStr KeyNm=KeyValPrV[KeyValPrN].Val1;
      TStr ValStr=KeyValPrV[KeyValPrN].Val2;
      CookieKeyValDmPathQuV.Add(TStrQu(KeyNm, ValStr, DmNm, PathStr));
    }
  }
}

PSIn THttpResp::GetSIn() const {
  TMOut MOut(HdStr.Len()+BodyMem.Len());
  MOut.PutStr(HdStr); MOut.PutMem(BodyMem);
  return MOut.GetSIn();
}
