//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2012 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  This program is distributed in the hope that it will be useful,       *
//*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *
//*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function: http file/stream handling (http client)

#include "mpxplay.h"

#ifdef MPXPLAY_LINK_TCPIP

#include "diskdriv.h"
#include "tcpcomon.h"
#include "display\display.h"
#include "control\cntfuncs.h" // for mpxplay_control_keyboard_get_topfunc()
#include <malloc.h>
#include <inttypes.h>

#ifdef MPXPLAY_DRVFTP_DEBUGFILE
#ifdef MPXPLAY_WIN32
#include <winsock.h>
#endif
extern FILE *debug_fp;
#endif

struct http_respond_line_handler_s{
 const char *resptype;
 void (*handler)(struct httpfile_info_s *htfi, char *data);
};

static struct ftpdrive_lowlevel_func_s *httpdrive_drive_getlowfunc_by_name(struct httpfile_info_s *htfi,char *name);
static void httpdrive_file_close(void *filehand_data);

extern ftpdrive_lowlevel_func_s HTTPDRV_lowlevel_funcs;

static ftpdrive_lowlevel_func_s *ALL_http_lowlevel_funcs[]={
 &HTTPDRV_lowlevel_funcs,  // 0.
 NULL
};

static struct mime_to_extension_s {
 const char *file_extension;
 const char *stream_mime;
} mime2extension[] = {
 {"AAC", "audio/aac"},
 {"AAC", "audio/aacp"},
 {"ASF", "audio/x-ms-wax"},
 {"ASF", "audio/x-ms-wma"},
 {"ASF", "video/x-ms-asf"},
 {"ASF", "video/x-ms-afs"},
 {"ASF", "video/x-ms-wmv"},
 {"ASF", "video/x-ms-wma"},
 {"ASF", "application/x-mms-framed"},
 {"ASF", "application/vnd.ms.wms-hdr.asfv1"},
 {"AVI", "video/x-msvideo"}, // ???
 {"FLV", "video/x-flv"},
 {"MP3", "audio/mpeg"},
 {"MP3", "audio/x-mpeg"},
 {"MP3", "audio/mp3"},
 {"MP3", "audio/x-mp3"},
 {"MP3", "audio/mpeg3"},
 {"MP3", "audio/x-mpeg3"},
 {"MP3", "audio/mpg"},
 {"MP3", "audio/x-mpg"},
 {"MP3", "audio/x-mpegaudio"},
 {"MPG", "video/mpeg"},
 {"MPG", "video/x-mpeg"},
 {"MPG", "video/x-mpeg2"},
 {"MOV", "video/quicktime"},
 {"NSV", "video/nsv"},
 {"NSV", "misc/ultravox"},
 {"OGG", "application/ogg"},
 {"OGG", "application/x-ogg"},
 {NULL,NULL}
};

static void drvhttp_message_write_error(char *message)
{
 char sout[1024];
 //pds_textdisplay_printf(message);
 snprintf(sout,(sizeof(sout)-1),"HTTP: %s",message);
 display_timed_message(sout);
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
 if(debug_fp)
  fprintf(debug_fp,"%s\n",sout);
#endif
}

static void drvhttp_respondlinehand_icy(struct httpfile_info_s *htfi, char *result)
{
 htfi->ftpi_infos.lastrespcode = pds_atol(result); // error code
}

static void drvhttp_respondlinehand_http(struct httpfile_info_s *htfi, char *result)
{
 while(*result && *result!=' ') // skip (HTTP/)N.N
  result++;
 while(*result==' ') // skip spaces
  result++;
 htfi->ftpi_infos.lastrespcode = pds_atol(result); // error code
 if(htfi->ftpi_infos.lastrespcode == 206)
  funcbit_enable(htfi->flags, DRVFTP_FTPFILE_FLAG_PARTIALCONTENT);
}

static void drvhttp_respondlinehand_server(struct httpfile_info_s *htfi, char *result)
{
 if((pds_strnicmp(result,"gvs 1.0",7)==0) || (pds_strnicmp(result,"MakeMKV",7)==0))
  funcbit_disable(htfi->flags,DRVFTP_FTPFILE_FLAG_NONSEEKABLE);
}

static void drvhttp_respondlinehand_status(struct httpfile_info_s *htfi, char *result)
{
 htfi->ftpi_infos.lastrespcode = pds_atol(result); // error code
}

static void drvhttp_respondlinehand_location(struct httpfile_info_s *htfi, char *result)
{
 if(!htfi->remotefilename[0]){
  pds_strncpy(htfi->remotefilename, result, sizeof(htfi->remotefilename)-1);
  htfi->remotefilename[sizeof(htfi->remotefilename)-1] = 0;
 }
}

static void drvhttp_respondlinehand_contenttype(struct httpfile_info_s *htfi, char *result)
{
 struct mime_to_extension_s *mte = &mime2extension[0];
 do{
  if(pds_stricmp(result,(char *)mte->stream_mime) == 0){
   htfi->content_type_fileext = mte->file_extension;
   break;
  }
  mte++;
 }while(mte->file_extension);
}

static void drvhttp_respondlinehand_contentlength(struct httpfile_info_s *htfi, char *result)
{
 if(!(htfi->flags&DRVFTP_FTPFILE_FLAG_PARTIALCONTENT))
  htfi->filesize = pds_atoi64(result); // filesize
}

static void drvhttp_respondlinehand_transferencoding(struct httpfile_info_s *htfi, char *result)
{
 if(pds_strnicmp(result,"chunked",7)==0)
  funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_CHUNKED);
}

static void drvhttp_respondlinehand_acceptranges(struct httpfile_info_s *htfi, char *result)
{
 if(pds_strnicmp(result,"bytes",5)==0)
  funcbit_disable(htfi->flags,DRVFTP_FTPFILE_FLAG_NONSEEKABLE);
}

static void drvhttp_respondlinehand_setcookie(struct httpfile_info_s *htfi, char *result)
{
 char *end = pds_strchr(result,';');
 if(end){
  *end = 0;
  pds_strncpy(htfi->cookiename, result, sizeof(htfi->cookiename));
  htfi->cookiename[sizeof(htfi->cookiename) - 1] = 0;
 }
}

static void drvhttp_respondlinehand_icyname(struct httpfile_info_s *htfi, char *result)
{
 pds_strncpy(htfi->icy_name, result, sizeof(htfi->icy_name));
 htfi->icy_name[sizeof(htfi->icy_name) - 1] = 0;
}

static void drvhttp_respondlinehand_icydescription(struct httpfile_info_s *htfi, char *result)
{
 if(!htfi->icy_name){ // Icy-Name has higher priority
  pds_strncpy(htfi->icy_name, result, sizeof(htfi->icy_name));
  htfi->icy_name[sizeof(htfi->icy_name) - 1] = 0;
 }
}

static void drvhttp_respondlinehand_session(struct httpfile_info_s *htfi, char *result)
{
 pds_strncpy(htfi->session_name, result, sizeof(htfi->session_name));
 htfi->session_name[sizeof(htfi->session_name) - 1] = 0;
}

static struct http_respond_line_handler_s http_respondline_handlers [] = {
 {"ICY ", drvhttp_respondlinehand_icy},  // ICY 200 OK (Shoutcast server)
 {"HTTP/", drvhttp_respondlinehand_http},  // HTTP/1.0 2nn OK (Icecast server)
 {"RTSP/", drvhttp_respondlinehand_http},  // RTSP/1.0 2nn OK
 {"Server:", drvhttp_respondlinehand_server}, // server type (to check seekable status)
 {"Status:", drvhttp_respondlinehand_status}, // Status: 302 Moved (redirected url)
 {"Location:", drvhttp_respondlinehand_location}, // redirected url address
 {"Content-Type:", drvhttp_respondlinehand_contenttype}, // stream type
 {"Content-Length:", drvhttp_respondlinehand_contentlength}, // file length (if not stream)
 {"Transfer-Encoding:", drvhttp_respondlinehand_transferencoding},
 {"Accept-Ranges:", drvhttp_respondlinehand_acceptranges}, // seekable?
 {"Set-Cookie:", drvhttp_respondlinehand_setcookie}, // cookie for the connection
 {"Icy-Name:", drvhttp_respondlinehand_icyname}, // usually the name of the radio
 {"Icy-Description:", drvhttp_respondlinehand_icydescription}, // or this (longer)
 {"Session:", drvhttp_respondlinehand_session}, // rtsp session number
 {NULL,NULL}
};

//----------------------------------------------------------------------

static unsigned int drvhttp_process_httprespondline(struct httpfile_info_s *htfi, char *respond)
{
 char *eol;
 struct http_respond_line_handler_s *rlh;

 eol = pds_strchr(respond, '\n'); // search for end of line
 if(!eol)
  return 0;

 eol[0] = 0;
 if((eol > respond) && (eol[-1] == '\r')){
  eol[-1] = 0;
 }else if(eol[1] == '\r'){
  eol[1] = 0;
  eol++;
 }
 eol++;

#ifdef MPXPLAY_DRVFTP_DEBUGFILE
 drvhttp_message_write_error(respond);
#endif

 rlh = &http_respondline_handlers[0];
 do{
  int len = pds_strlen((char *)rlh->resptype);
  if((len > 0) && (pds_strnicmp(respond, (char *)rlh->resptype, len) == 0)){ // search for required lines
   char *result = respond + len; // result of line (ie: mime type of content-type)
   while(*result == ' ')
    result++;
   pds_strcutspc(result);
   rlh->handler(htfi, result); // process result
   break;
  }
  rlh++;
 }while(rlh->resptype);

 return (unsigned int)(eol - respond);
}

//------------------------------------------------------------------------

static unsigned int drvhttp_extract_logininfos_from_path(struct ftpdrive_info_s *ftpi, struct httpfile_info_s *htfi, char *pathname)
{
 char *fn,*hostname,*portnum,*username,*password,strtmp[1024];

 funcbit_smp_pointer_put(ftpi->lowfunc,httpdrive_drive_getlowfunc_by_name(htfi,pathname));
 if(!ftpi->lowfunc){
  drvhttp_message_write_error("No lowfunc!");
  return 0;
 }

 hostname = pds_strchr(pathname,':'); // skip http:
 if(!hostname)
  return 0;
 pathname = hostname + 1;
 while((*pathname=='/') || (*pathname=='\\')) // skip /
  pathname++;

 tcpcomon_str_localname_to_remote(strtmp,pathname,sizeof(strtmp));
 drvhttp_message_write_error(strtmp);
 fn=pds_strchr(strtmp,'/');
 if(!fn){
  drvhttp_message_write_error(strtmp);
  return 0;
 }
 *fn++=0; // cut filename from url
 pds_strncpy(htfi->singleftpfilename,fn,sizeof(htfi->singleftpfilename)-1);
 htfi->singleftpfilename[sizeof(htfi->singleftpfilename)-1] = 0;

 hostname=pds_strrchr(strtmp,DRVFTP_PATHSEPARATOR_USER);
 if(hostname){
  *hostname++=0;
  username=&strtmp[0];
  password=pds_strchr(strtmp,DRVFTP_PATHSEPARATOR_PASSWORD);
  if(password)
   *password++=0;
  pds_strcpy(ftpi->username,username);
  pds_strcpy(ftpi->password,password);
 }else{
  hostname=&strtmp[0];
 }

 portnum=pds_strchr(hostname,DRVFTP_PATHSEPARATOR_PORTNUM);
 if(portnum){
  *portnum++=0;
  funcbit_smp_value_put(ftpi->socket_info_session.portnum,pds_atol(portnum));
 }
 if(!ftpi->socket_info_session.portnum)
  funcbit_smp_value_put(ftpi->socket_info_session.portnum, htfi->port_number_default);

 pds_strncpy(ftpi->servername,hostname,sizeof(ftpi->servername)-1);
 ftpi->servername[sizeof(ftpi->servername)-1] = 0;

 return 1;
}

//-----------------------------------------------------------------------
static unsigned int drvhttp_session_connect(struct ftpdrive_info_s *ftpi, struct httpfile_info_s *htfi)
{
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
 char sout[256];
#endif

 funcbit_smp_disable(ftpi->flags,DRVFTP_FTPDRIVE_FLAG_CONNECTED);
 funcbit_smp_value_put(ftpi->lastrespcode,0);

 if(htfi->flags&DRVFTP_FTPFILE_FLAG_NONSEEKABLE)
  funcbit_smp_filesize_put(htfi->filepos,0);

 ftpi->lowfunc->socket_close(&ftpi->socket_info_session,1);

 if(!ftpi->connection_retry){
  funcbit_smp_disable(htfi->flags,DRVFTP_FTPFILE_FLAG_SEEK);
  return 0;
 }
 funcbit_smp_value_decrement(ftpi->connection_retry);
 ftpi->lastresptext[0]=0;

#ifdef MPXPLAY_DRVFTP_DEBUGFILE
 snprintf(sout,sizeof(sout),"host:%s port:%d",ftpi->servername, ftpi->socket_info_session.portnum);
 drvhttp_message_write_error(sout);
#endif

 drvhttp_message_write_error("getting ip adresses...");

 if(!ftpi->lowfunc->addressinfo_init(&ftpi->socket_info_session,ftpi->servername,NULL,&ftpi->ip_remote[0])){
#if defined(MPXPLAY_DRVFTP_DEBUGFILE) && defined(MPXPLAY_WIN32)
  snprintf(sout,sizeof(sout),"getting IP failed with: %d",WSAGetLastError());
  drvhttp_message_write_error(sout);
#else
  drvhttp_message_write_error("getting IP address(es) failed!");
#endif
  return 0;
 }

 drvhttp_message_write_error("opening socket...");

 if(!ftpi->lowfunc->socket_open(&ftpi->socket_info_session, DRVFTP_DEFAULT_RCVBUFSIZE)){
  drvhttp_message_write_error("couldn't open socket!");
  return 0;
 }
 drvhttp_message_write_error("connecting to server...");

 pds_smp_memcpy(&ftpi->socket_info_session.conn_ip_addr,&ftpi->ip_remote,DRVFTP_IP4_LEN);
 if(!ftpi->lowfunc->socket_connect(&ftpi->socket_info_session)){
  pds_strcpy(ftpi->lastresptext,"no connection to the server!");
  goto err_out_sc;
 }
 if(ftpi->lowfunc->socket_ssl_connect && !ftpi->lowfunc->socket_ssl_connect(&ftpi->socket_info_session)){
  pds_strcpy(ftpi->lastresptext,"SSL connect to server failed!");
  goto err_out_sc;
 }

 funcbit_smp_value_put(ftpi->connection_retry,DRVFTP_DEFAULT_TIMEOUTRETRY_SESSION);

 funcbit_smp_disable(htfi->flags,DRVFTP_FTPFILE_FLAG_SEEK);

 drvhttp_message_write_error("connect ok");

 display_clear_timed_message();

 return 1;

err_out_sc:
 drvhttp_message_write_error(ftpi->lastresptext);
 ftpi->lowfunc->socket_close(&ftpi->socket_info_session,1);
 return 0;
}

static unsigned int drvhttp_session_login(struct ftpdrive_info_s *ftpi, struct httpfile_info_s *htfi)
{
 unsigned char retry_server = 8;
 mpxp_uint64_t timeout_at_read;
 char stmp[512], strtmp[2048];

 do{
  int len, end_resp, dummy_bytes;

  if(!(--retry_server))
   goto err_out_sc;

  if(!drvhttp_session_connect(ftpi,htfi))
   goto err_out_sc;

  switch(htfi->subprotocol_type){
   case DRVHTTP_HTTPFILE_SUBPROTOCOL_HTTP:
    //len = snprintf(strtmp, sizeof(strtmp), "GET /%s HTTP/1.0\r\nUser-Agent: Mpxplay/1.60\r\nConnection: close\r\nHost: %s",
    len = snprintf(strtmp, sizeof(strtmp), "GET /%s HTTP/1.1\r\nUser-Agent: Mpxplay/1.60\r\nHost: %s",
        htfi->singleftpfilename, ftpi->hostname);

    if(ftpi->host_portnum != 80){ // port number
     sprintf(stmp, ":%d", ftpi->host_portnum);
     len += pds_strcpy(&strtmp[len], stmp);
    }
    len += pds_strcpy(&strtmp[len],"\r\n"); // eol

    if(htfi->cookiename[0]){ // cookie info
     snprintf(stmp,sizeof(stmp), "Cookie: %s\r\n", htfi->cookiename);
     len += pds_strcpy(&strtmp[len], stmp);
    }

    if(!(htfi->flags&DRVFTP_FTPFILE_FLAG_NONSEEKABLE) && htfi->filepos){ // seek (start pos)
     sprintf(stmp, "Range: bytes=%"PRId64"-\r\n", (mpxp_int64_t)htfi->filepos);
     len += pds_strcpy(&strtmp[len], stmp);
    }
    break;

   case DRVHTTP_HTTPFILE_SUBPROTOCOL_RTSP:
    len = snprintf(strtmp, sizeof(strtmp), "%s %s RTSP/1.0\r\n",
         ((htfi->session_name[0])? "PLAY":"SETUP"), ftpi->hostname);

    snprintf(stmp,sizeof(stmp), "CSeq: %d\r\n", (++htfi->rtsp_cseq_number));
    len += pds_strcpy(&strtmp[len], stmp);

    if(htfi->session_name[0]){ // PLAY
     snprintf(stmp,sizeof(stmp), "Session: %s\r\n", htfi->session_name);
     len += pds_strcpy(&strtmp[len], stmp);
    }else // SETUP
     len += pds_strcpy(&strtmp[len], "Transport: RTP/AVP/TCP;interleaved=0-1\r\n");

    break;

   default:
    goto err_out_sc;
  }

  len += pds_strcpy(&strtmp[len],"\r\n"); // end of GET

#ifdef MPXPLAY_DRVFTP_DEBUGFILE
  drvhttp_message_write_error(strtmp);
#endif

  if(ftpi->lowfunc->send(&ftpi->socket_info_session, strtmp, len) <= 0){
   pds_strcpy(ftpi->lastresptext,"HTTP send GET failed!");
   goto err_out_sc;
  }

  funcbit_disable(htfi->flags, (DRVFTP_FTPFILE_FLAG_PARTIALCONTENT|DRVFTP_FTPFILE_FLAG_CHUNKED));
  htfi->remotefilename[0] = 0;
  htfi->read_buffer[0] = 0;
  htfi->readbuf_storedbytes = 0;
  timeout_at_read = 0;
  end_resp = dummy_bytes = 0;

  do{
   int received_bytes, processed_bytes;

   len = sizeof(htfi->read_buffer) - htfi->readbuf_storedbytes - 1;
   if(len <= 0)
    break;

   received_bytes = 0;

   if(!end_resp){
    received_bytes = ftpi->lowfunc->bytes_buffered(&ftpi->socket_info_session);
    if(received_bytes > 0){
     received_bytes = ftpi->lowfunc->receive(&ftpi->socket_info_session, &htfi->read_buffer[htfi->readbuf_storedbytes], len);
     if(received_bytes > 0){
      htfi->readbuf_storedbytes += received_bytes;
      htfi->read_buffer[htfi->readbuf_storedbytes] = 0;
      timeout_at_read = 0;
     }
    }else{
     if(!timeout_at_read)
      timeout_at_read = pds_gettimem() + DRVFTP_DEFAULT_TIMEOUTMS_READ;
     else if(pds_gettimem() > timeout_at_read)
      goto err_out_sc;
    }

    if(htfi->readbuf_storedbytes){
     char *p = pds_strstr(htfi->read_buffer, "\r\n\r\n"); // search for end of (status) response
     if(p)
      end_resp = sizeof("\r\n\r\n") - 1;
     else{
      p = pds_strstr(htfi->read_buffer, "\n\r\n\r");
      if(p)
       end_resp = sizeof("\n\r\n\r") -1 ;
      else{
       p = pds_strstr(htfi->read_buffer,"\n\n");
       if(p)
        end_resp = sizeof("\n\n") - 1;
      }
     }

     if(end_resp){ // found
      end_resp += (p - &htfi->read_buffer[0]);
      htfi->read_buffer[end_resp - 1] = 0;
      dummy_bytes = end_resp; // the unprocessed bytes from the http response
     }
    }
   }

   if(htfi->readbuf_storedbytes){
    processed_bytes = drvhttp_process_httprespondline(htfi,htfi->read_buffer); // process one respond line

    if(htfi->readbuf_storedbytes > processed_bytes)
     htfi->readbuf_storedbytes -= processed_bytes;
    else
     htfi->readbuf_storedbytes = 0;

    if(dummy_bytes > processed_bytes)
     dummy_bytes -= processed_bytes;
    else
     dummy_bytes = 0;
   }else
    processed_bytes = 0;

   if(!processed_bytes){ // cannot process new line
    if(end_resp){       // end of http respond
     if(dummy_bytes){    // unused bytes
      if(htfi->readbuf_storedbytes > dummy_bytes){
       htfi->readbuf_storedbytes -= dummy_bytes;  // remove unused bytes
       pds_memcpy(&htfi->read_buffer[0], &htfi->read_buffer[dummy_bytes], htfi->readbuf_storedbytes);
      }else
       htfi->readbuf_storedbytes = 0;
     }
     break;
    }
    if(received_bytes < 0) // ??? (socket error)
     goto err_out_sc;
    if((pds_look_extgetch() == KEY_ESC) && !mpxplay_control_keyboard_get_topfunc()){ // !!!
     pds_extgetch();
     goto err_out_sc;
    }
    tcpcommon_socket_sleep((ftpdrive_socket_t)ftpi->socket_info_session.socknum);
   }else if(htfi->readbuf_storedbytes)
    pds_memcpy(&htfi->read_buffer[0], &htfi->read_buffer[processed_bytes], htfi->readbuf_storedbytes); // consolidate buffer

   htfi->read_buffer[htfi->readbuf_storedbytes] = 0;

  }while(1);

#ifdef MPXPLAY_DRVFTP_DEBUGFILE
  drvhttp_message_write_error((char *)htfi->content_type_fileext);
#endif

  switch(htfi->ftpi_infos.lastrespcode){
   case 200: // OK, can read the stream
   case 206: // OK, partial content (after seek)
    goto err_out_ok;
   case 301: // moved permanently
   case 302:
   case 303:
   case 307: // redirected/moved url
    if(!drvhttp_extract_logininfos_from_path(ftpi, htfi, &htfi->remotefilename[0]))
     goto err_out_sc;
    if(htfi->ftpi_infos.lastrespcode == 301)
     pds_strcpy(ftpi->hostname, ftpi->servername);
    break;  // reconnect to the new url
   default:
    snprintf(strtmp,sizeof(strtmp),"HTTP GET failed with: %d code!",htfi->ftpi_infos.lastrespcode);
    drvhttp_message_write_error(strtmp);
    goto err_out_sc;
  }

 }while(1);

err_out_ok:

 funcbit_smp_enable(ftpi->flags,DRVFTP_FTPDRIVE_FLAG_CONNECTED);

 drvhttp_message_write_error("connect ok, playing...");

 display_clear_timed_message();

 return 1;

err_out_sc:
 ftpi->lowfunc->socket_close(&ftpi->socket_info_session,1);
 return 0;
}

//-----------------------------------------------------------------------
static struct ftpdrive_lowlevel_func_s *httpdrive_drive_getlowfunc_by_name(struct httpfile_info_s *htfi, char *name)
{
 if((pds_strlicmp(name,"mms:") == 0) || (pds_strlicmp(name,"rtsp:") == 0)){
  if(htfi){
   htfi->subprotocol_type = DRVHTTP_HTTPFILE_SUBPROTOCOL_RTSP;
   htfi->port_number_default = 554;
  }
  return (&HTTPDRV_lowlevel_funcs);
 }else{
  struct ftpdrive_lowlevel_func_s **lowfunc = &ALL_http_lowlevel_funcs[0];
  do{
   if(pds_strlicmp(name,(*lowfunc)->name) == 0){
    if(htfi)
     htfi->port_number_default = (*lowfunc)->def_portnum;
    return (*lowfunc);
   }
   lowfunc ++;
  }while(*lowfunc);
 }
 return NULL;
}

static unsigned int httpdrive_drive_check(char *pathname)
{
 if(httpdrive_drive_getlowfunc_by_name(NULL,pathname))
  return 1;
 return 0;
}

static long httpdrive_drive_config(void *drive_data,unsigned long funcnum,void *argp1,void *argp2)
{
 struct httpfile_info_s *htfi;

 switch(funcnum){
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_CHKBUFBLOCKBYTES:
   return 4096;
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_PREREADBUFBYTES:
   return 32768;  // ??? (should be time based, not byte)
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_REALLYFULLPATH:
   if(!argp1 || !argp2)
    return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
   tcpcomon_str_localname_to_remote((char *)argp1,(char *)argp2,MAX_PATHNAMELEN);
   return 1;
 }

 if(!drive_data)
  return MPXPLAY_DISKDRIV_CFGERROR_INVALID_DRIVE;
 htfi=drive_data;

 switch(funcnum){
  // htfi
  case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_ISNONSEEKABLE:
   return ((htfi->flags&DRVFTP_FTPFILE_FLAG_NONSEEKABLE)? 1:0);
  case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_ISLIVESTREAM:
   return ((htfi->flags&DRVFTP_FTPFILE_FLAG_LIVESTREAM)? 1:0);
  case MPXPLAY_DISKFILE_CFGFUNCNUM_SET_FILEBLOCKSIZE:
   if(!argp1)
    return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
   funcbit_smp_value_put(htfi->file_bufsize,(*((unsigned long *)argp1)));
   return 1;
  case MPXPLAY_DISKFILE_CFGFUNCNUM_SET_READWAIT:
   if(!argp1)
    return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
   if(*((unsigned long *)argp1))
    funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_READWAIT);
   else
    funcbit_disable(htfi->flags,DRVFTP_FTPFILE_FLAG_READWAIT);
   return 1;
  case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENTTYPEEXT:
   if(!argp1)
    return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
   *((char **)argp1) = (char *)htfi->content_type_fileext;
   return 1;
  case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENT_ARTIST:
   if(!argp1 || !argp2)
    return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
   pds_strncpy((char *)argp1,htfi->icy_name,*((unsigned long *)argp2));
   return 1;
  //case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENT_TITLE:
 }
 return MPXPLAY_DISKDRIV_CFGERROR_UNSUPPFUNC;
}

static unsigned int httpdrive_file_check(void *drivehand_data, char *pathname)
{
 if(httpdrive_drive_getlowfunc_by_name(NULL,pathname))
  return 1;
 return 0;
}

static void *httpdrive_file_open(void *drivehand_data,char *urlfilename,unsigned long openmode)
{
 struct ftpdrive_info_s *ftpi;
 struct httpfile_info_s *htfi;

 htfi = calloc(1,sizeof(*htfi));
 if(!htfi)
  return htfi;

 switch(openmode&(O_RDONLY|O_RDWR|O_WRONLY)){
  case O_RDONLY:funcbit_enable(htfi->opentype,DRVFTP_FTPFILE_OPENTYPE_READ);break;
  default:goto err_out_open; // only read is supported
 }

#ifdef MPXPLAY_DRVFTP_DEBUGFILE
 if(!debug_fp)
  debug_fp=fopen(MPXPLAY_DRVFTP_DEBUGFILE,"a+b");
 drvhttp_message_write_error("---- httpdrive_file_open ---------");
#endif

 ftpi = &htfi->ftpi_infos;

 mpxplay_tcpcommon_socket_reset(&ftpi->socket_info_session); // !!! must have

 if(!drvhttp_extract_logininfos_from_path(ftpi, htfi, urlfilename))
  goto err_out_open;
 pds_strcpy(ftpi->hostname, ftpi->servername); // for redirected urls
 ftpi->host_portnum = ftpi->socket_info_session.portnum;

 //funcbit_enable(ftpi->flags,DRVFTP_FTPDRIVE_FLAG_HTTP); // not used
 funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_READWAIT);
 funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_NONSEEKABLE);
 funcbit_smp_value_put(ftpi->connection_retry,DRVFTP_DEFAULT_TIMEOUTRETRY_SESSION);

 if(!ftpi->lowfunc->global_init())
  goto err_out_open;

 if(!drvhttp_session_login(ftpi,htfi))
  goto err_out_open;

 if(!htfi->filesize){
  funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_LIVESTREAM);
  htfi->filesize = 0x7eeeeeee;
 }

 return htfi;

err_out_open:
 httpdrive_file_close(htfi);
 return NULL;
}

static void httpdrive_file_close(void *filehand_data)
{
 struct httpfile_info_s *htfi = filehand_data;
 if(htfi){
  struct ftpdrive_info_s *ftpi = &htfi->ftpi_infos;
  ftpi->lowfunc->socket_close(&ftpi->socket_info_session, 1);
  free(htfi);
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
  /*if(debug_fp){
   fclose(debug_fp);
   debug_fp=NULL;
  }*/
#endif
 }
}

static long httpdrive_file_read(void *filehand_data,char *ptr,unsigned int num)
{
 struct httpfile_info_s *htfi = filehand_data;
 struct ftpdrive_info_s *ftpi;
 long bytes_read = 0, clen = 0;

 if(!funcbit_test(htfi->opentype,DRVFTP_FTPFILE_OPENTYPE_READ) || !num)
  return bytes_read;

 if(!funcbit_test(htfi->flags, DRVFTP_FTPFILE_FLAG_LIVESTREAM) && htfi->filesize){
  if(htfi->filepos >= htfi->filesize)
   return bytes_read;
  if((htfi->filepos+num) >= htfi->filesize)
   num = htfi->filesize - htfi->filepos;
 }

 ftpi = &htfi->ftpi_infos;

 if(htfi->flags&DRVFTP_FTPFILE_FLAG_SEEK)
  if(!drvhttp_session_login(ftpi,htfi))
   return MPXPLAY_ERROR_FILEHAND_CANTREAD;

 if(htfi->readbuf_storedbytes){ // copy the left data from the read_buffer (some servers send the stream data in the same packet like the http response)
  clen = num;
  if(clen > htfi->readbuf_storedbytes)
   clen = htfi->readbuf_storedbytes;
  num -= clen;
  htfi->readbuf_storedbytes -= clen;
  pds_memcpy(ptr, htfi->read_buffer, clen);
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
//  fwrite(ptr,1,clen,debug_fp);
#endif
  ptr += clen;
  if(htfi->readbuf_storedbytes)
   pds_memcpy(htfi->read_buffer, &htfi->read_buffer[clen], htfi->readbuf_storedbytes);
 }

 if(num > 0){ // read from the socket
  if(htfi->flags&DRVFTP_FTPFILE_FLAG_READWAIT)
   bytes_read = ftpi->lowfunc->receive(&ftpi->socket_info_session,ptr,num);
  else{
   bytes_read = ftpi->lowfunc->bytes_buffered(&ftpi->socket_info_session);
   if(bytes_read>0)
    bytes_read = ftpi->lowfunc->receive(&ftpi->socket_info_session,ptr,num);
  }
 }

 if(bytes_read >= 0)
  bytes_read += clen; // buffer + socket read len
 else if(clen)
  bytes_read = clen;

 if(bytes_read>0){
  funcbit_smp_filesize_put(htfi->filepos, (htfi->filepos + bytes_read));
  htfi->timeout_at_read = 0;
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
//  fwrite(ptr,1,bytes_read,debug_fp);
#endif
 }else{ //data-reconnect at read-timeout
  if(ftpi->connection_retry && !funcbit_test(htfi->flags,DRVFTP_FTPFILE_FLAG_READWAIT)){
   if(!htfi->timeout_at_read && (bytes_read==0))
    htfi->timeout_at_read=pds_gettimem()+DRVFTP_DEFAULT_TIMEOUTMS_READ;
   else if((bytes_read<0) || (pds_gettimem()>htfi->timeout_at_read)){
    funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_SEEK);
    htfi->timeout_at_read = 0;
    drvhttp_message_write_error("file connection lost, reconnect ...");
   }
  }else
   htfi->timeout_at_read = 0;
 }

 return bytes_read;
}

static mpxp_filesize_t ftpdrive_file_tell(void *filehand_data)
{
 struct httpfile_info_s *htfi=filehand_data;
 return htfi->filepos;
}

static mpxp_filesize_t ftpdrive_file_seek(void *filehand_data,mpxp_filesize_t pos,int fromwhere)
{
 struct httpfile_info_s *htfi=filehand_data;
 mpxp_filesize_t newfilepos;
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
 char sout[100];
#endif

 switch(fromwhere){
  case SEEK_CUR:newfilepos=htfi->filepos+pos;break;
  case SEEK_END:newfilepos=htfi->filesize+pos;break;
  case SEEK_SET:
        default:newfilepos=pos;break;
 }

#ifdef MPXPLAY_DRVFTP_DEBUGFILE
 sprintf(sout,"!!! file_seek: pos:%d from:%d newfilepos:%d\r\n",pos,fromwhere,newfilepos);
 drvhttp_message_write_error(sout);
#endif

 if(newfilepos >= htfi->filesize) // !!!
  return MPXPLAY_ERROR_MPXINBUF_SEEK_EOF;

 if(newfilepos != htfi->filepos){
  if(!(htfi->flags&DRVFTP_FTPFILE_FLAG_NONSEEKABLE)){
   funcbit_smp_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_SEEK);
   funcbit_smp_filesize_put(htfi->filepos,newfilepos);
  }
 }

 return htfi->filepos;
}

static mpxp_filesize_t ftpdrive_file_length(void *filehand_data)
{
 struct httpfile_info_s *htfi=filehand_data;
 return htfi->filesize;
}

static int ftpdrive_file_eof(void *filehand_data)
{
 struct httpfile_info_s *htfi=filehand_data;
 if(!funcbit_test(htfi->ftpi_infos.flags, DRVFTP_FTPDRIVE_FLAG_CONNECTED))
  return 1;
 if(funcbit_test(htfi->flags, DRVFTP_FTPFILE_FLAG_LIVESTREAM))
  return 0;
 if(htfi->filepos >= htfi->filesize)
  return 1;
 return 0;
}

struct mpxplay_drivehand_func_s HTTPDRIVE_drivehand_funcs={
 "HTTPDRIVE",
 MPXPLAY_DRIVEHANDFUNC_INFOBIT_SLOWACCESS,
 &httpdrive_drive_config,
 &httpdrive_drive_check,
 NULL, // drive_connect
 NULL, // drive_unmount
 NULL, // findfirst
 NULL, // findnext
 NULL, // findclose
 NULL, // getcwd
 NULL, // chdir
 NULL,
 NULL,
 NULL,
 NULL,
 NULL, // r15
 NULL, // r16
 NULL, // r17
 NULL, // r18
 NULL, // r19
 NULL, // r20

 &httpdrive_file_check,
 &httpdrive_file_open,
 &httpdrive_file_close,
 &httpdrive_file_read,
 NULL, // file_write
 &ftpdrive_file_seek,
 &ftpdrive_file_tell,
 &ftpdrive_file_length,
 &ftpdrive_file_eof,
 NULL, // file_chsize
 NULL  // r31
};

#endif // MPXPLAY_LINK_TCPIP
