//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2015 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)

//#define MPXPLAY_USE_DEBUGF 1
#define MPXPLAY_DEBUG_OUTPUT stdout
#define MPXPLAY_DEBUG_HEADER stdout
#define MPXPLAY_DEBUG_WARNING stderr
#define MPXPLAY_DEBUG_ERROR stderr
#define MPXPLAY_DEBUGOUT_READ NULL

#include "mpxplay.h"
#include "diskdriv.h"

unsigned long mpxplay_diskdrive_drvhttp_config = DRVFTP_HTTPDRIVE_FLAGS_DEFAULT_CONFIG;

#ifdef MPXPLAY_LINK_TCPIP

#include "tcpcomon.h"
#include "display\display.h"
#include "control\cntfuncs.h" // for mpxplay_control_keyboard_get_topfunc()
#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);
static void httpdrive_filter_parse_metadata(struct httpfile_info_s *htfi);

extern unsigned int mpxplay_signal_events;
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];
 snprintf(sout,(sizeof(sout)-1),"HTTP: %s",message);
 display_timed_message(sout);
 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "%s", sout);
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
 if(debug_fp)
  fprintf(debug_fp,"%s\n",sout);
#endif
}

static int drvhttp_convert_meta_string(struct httpfile_info_s *htfi, char *srcbuf, char *destbuf, unsigned int buflen)
{
 unsigned int src_texttype;
 if(pds_stricmp(srcbuf, "(null)") == 0) // workaround for some radio channels, got back "(null)" as a string result
  return 0;
 src_texttype = ((pds_strlen(srcbuf) != pds_utf8_strlen((unsigned char *)srcbuf))? MPXPLAY_TEXTCONV_TYPE_UTF8 : MPXPLAY_TEXTCONV_TYPE_CHAR) | MPXPLAY_TEXTCONV_TYPEFLAG_HTML;
 mpxplay_playlist_textconv_by_texttypes(MPXPLAY_TEXTCONV_TYPES_PUT(src_texttype,MPXPLAY_TEXTCONV_TYPE_MPXNATIVE), srcbuf, -1, destbuf, buflen);
 return pds_str_clean(destbuf);
}

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)
{
 //char *p;
 drvhttp_convert_meta_string(htfi, result, htfi->icy_name, sizeof(htfi->icy_name));
 /*p = pds_str_getwordn(htfi->icy_name, 3);
 if(p){
  *p = 0;
  p = pds_str_getwordn(htfi->icy_name, 2);
  if(!pds_strstri(p,"radio"))
   *p = 0;
 }*/
}

static void drvhttp_respondlinehand_icydescription(struct httpfile_info_s *htfi, char *result)
{
 if(!htfi->icy_name) // Icy-Name has higher priority
  drvhttp_convert_meta_string(htfi, result, htfi->icy_name, sizeof(htfi->icy_name));
}

static void drvhttp_respondlinehand_icymetaint(struct httpfile_info_s *htfi, char *result)
{
  htfi->metadata_interval = pds_atol(result);
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "Icy-Metaint: %d", htfi->metadata_interval);
}

static void drvhttp_respondlinehand_icygenre(struct httpfile_info_s *htfi, char *result)
{
 drvhttp_convert_meta_string(htfi, result, htfi->icy_genre, sizeof(htfi->icy_genre));
}

static void drvhttp_respondlinehand_icybitrate(struct httpfile_info_s *htfi, char *result)
{
  htfi->icy_bitrate = pds_atol(result);
}

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)
 {"Icy-Metaint:", drvhttp_respondlinehand_icymetaint}, // metadata_interval
 {"Icy-Genre:", drvhttp_respondlinehand_icygenre}, // icy genre
 {"Icy-Br:", drvhttp_respondlinehand_icybitrate}, // icy bitrate in kbits/s
 {"Session:", drvhttp_respondlinehand_session}, // rtsp session number
 {NULL,NULL}
};

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

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

 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++;

 if(pds_strlen(respond)){
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
  drvhttp_message_write_error(respond);
#else
  mpxplay_debugf(MPXPLAY_DEBUG_HEADER, "HTTP respond: %s", 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)
{
 struct ftpdrive_lowlevel_func_s *low_func;
 char *fn,*hostname,*portnum,*username,*password,strtmp[1024];

 low_func = httpdrive_drive_getlowfunc_by_name(htfi, pathname);
 if(!low_func){
  snprintf(strtmp, sizeof(strtmp), "No protocol for %s !", pathname);
  drvhttp_message_write_error(strtmp);
  return 0;
 }
 funcbit_smp_pointer_put(ftpi->lowfunc, low_func);

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

 tcpcomon_str_localname_to_remote(strtmp,pathname,sizeof(strtmp));
 drvhttp_message_write_error(strtmp);
 fn=pds_strchr(strtmp,'/');
 if(fn){
  *fn++=0; // cut filename from url
   pds_strncpy(htfi->singleftpfilename,fn,sizeof(htfi->singleftpfilename)-1);
   htfi->singleftpfilename[sizeof(htfi->singleftpfilename)-1] = 0;
 }else
  htfi->singleftpfilename[0]=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_int32_put(ftpi->socket_info_session.portnum,pds_atol(portnum));
 }
 if(!ftpi->socket_info_session.portnum)
  funcbit_smp_int32_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 int drvhttp_session_connect(struct ftpdrive_info_s *ftpi, struct httpfile_info_s *htfi)
{
 int retcode = MPXPLAY_ERROR_DISKDRIV_ERROR;
 char sout[256];

 funcbit_smp_disable(ftpi->flags,DRVFTP_FTPDRIVE_FLAG_CONNECTED);
 funcbit_smp_int32_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);
  mpxplay_debugf(MPXPLAY_DEBUG_WARNING, "drvhttp_session_connect connection_retry 0 ftpi:%8.8X", (unsigned int)ftpi);
  return MPXPLAY_ERROR_DISKDRIV_CONNECT_TIMEOUT;
 }
 funcbit_smp_int32_decrement(ftpi->connection_retry);
 ftpi->lastresptext[0]=0;

 snprintf(sout, sizeof(sout), "connecting to %s:%d ...", ftpi->servername, ftpi->socket_info_session.portnum);
 drvhttp_message_write_error(sout);

 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 (network error)!");
#endif
  return MPXPLAY_ERROR_DISKDRIV_ERROR_ADDRINFO;
 }

 if(!ftpi->lowfunc->socket_open(&ftpi->socket_info_session, DRVFTP_DEFAULT_RCVBUFSIZE)){
  drvhttp_message_write_error("couldn't open socket (internal error)!");
  return MPXPLAY_ERROR_DISKDRIV_SOCKET;
 }

 pds_smp_memcpy((char *)&ftpi->socket_info_session.conn_ip_addr,(char *)&ftpi->ip_remote,DRVFTP_IP4_LEN);
 retcode = ftpi->lowfunc->socket_connect(&ftpi->socket_info_session);
 if(retcode != MPXPLAY_ERROR_DISKDRIV_OK){
  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)){
  retcode = MPXPLAY_ERROR_DISKDRIV_CONNECT_SSL;
  pds_strcpy(ftpi->lastresptext,"SSL connect to server failed!");
  goto err_out_sc;
 }

 funcbit_smp_int32_put(ftpi->connection_retry,DRVFTP_DEFAULT_TIMEOUTRETRY_SESSION);
 funcbit_smp_disable(htfi->flags,DRVFTP_FTPFILE_FLAG_SEEK);
 funcbit_smp_int32_put(htfi->metadata_intrv_count, 0);

 return retcode;

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

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

 do{
  int len, end_resp, dummy_bytes;

  if(retry_server < DRVFTP_DEFAULT_REDIRECTRETRY_SESSION)
  {
     if(!retry_server || (!funcbit_test(htfi->flags, DRVFTP_FTPFILE_FLAG_CONNECTED) && (retcode != MPXPLAY_ERROR_DISKDRIV_ERROR_ADDRINFO) && (retcode != MPXPLAY_ERROR_DISKDRIV_SOCKET) && (retcode != MPXPLAY_ERROR_DISKDRIV_OK)))
      goto err_out_sl;
     pds_mdelay((retcode == MPXPLAY_ERROR_DISKDRIV_OK)? 0 : DRVFTP_DEFAULT_TIMEOUTMS_SHORTWAIT);  // redirection vs. socket/read error
  }
  retry_server--;

  retcode = drvhttp_session_connect(ftpi,htfi);
  if(retcode != MPXPLAY_ERROR_DISKDRIV_OK)
   continue;

  switch(htfi->subprotocol_type){
   case DRVHTTP_HTTPFILE_SUBPROTOCOL_HTTP:
    len = snprintf(strtmp, sizeof(strtmp), "GET /%s HTTP/1.1\r\n%sUser-Agent: Mpxplay/1.63\r\n%sHost: %s",
        htfi->singleftpfilename,
        ((mpxplay_diskdrive_drvhttp_config & DRVFTP_HTTPDRIVE_FLAG_HANDLE_METADATA)? "Icy-MetaData:1\r\n" : ""),
        ((htfi->flags & DRVFTP_FTPFILE_FLAG_NOKEEPALIVE)? "Connection: close\r\n" : ""),
        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=%lld-\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:
    retcode = MPXPLAY_ERROR_DISKDRIV_PROTOCOL;
    goto err_out_sl;
  }

  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){
   retcode = MPXPLAY_ERROR_DISKDRIV_SOCKET;
   pds_strcpy(ftpi->lastresptext,"HTTP send GET failed!");
   continue;
  }

  funcbit_disable(htfi->flags, DRVFTP_FTPFILE_FLAGS_RESET_AT_RECONNECT);
  htfi->remotefilename[0] = 0;
  htfi->read_buffer[0] = 0;
  htfi->readbuf_leftbytes = htfi->readbuf_readpos = 0;
  htfi->chunk_bytecount = htfi->chunk_size = 0;
  htfi->metadata_intrv_count = 0;
  htfi->metadata_length = htfi->metadata_read_pos = 0;
  htfi->filesize = 0;
  htfi->icy_bitrate = 0;
  timeout_at_read = 0;
  end_resp = dummy_bytes = 0;

  do{
   int received_bytes, processed_bytes;

   len = DRVHTTP_HTTPFILE_READBUF_SIZE - htfi->readbuf_leftbytes - 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_leftbytes], len);
     if(received_bytes > 0){
      htfi->readbuf_leftbytes += received_bytes;
      htfi->read_buffer[htfi->readbuf_leftbytes] = 0;
      timeout_at_read = 0;
     }
    }else if(received_bytes < 0){
     retcode = MPXPLAY_ERROR_DISKDRIV_SOCKET;
     break;
    }else{
     if(!timeout_at_read)
      timeout_at_read = pds_gettimem() + DRVFTP_DEFAULT_TIMEOUTMS_READ;
     else if(pds_gettimem() > timeout_at_read){
      retcode = MPXPLAY_ERROR_DISKDRIV_SOCKET;
      break;
     }
    }

    if(htfi->readbuf_leftbytes){
     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_leftbytes){
    processed_bytes = drvhttp_process_httprespondline(htfi,htfi->read_buffer); // process one respond line

    if(htfi->readbuf_leftbytes > processed_bytes)
     htfi->readbuf_leftbytes -= processed_bytes;
    else
     htfi->readbuf_leftbytes = 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_leftbytes > dummy_bytes){
       htfi->readbuf_leftbytes -= dummy_bytes;  // remove unused bytes
       pds_memcpy(&htfi->read_buffer[0], &htfi->read_buffer[dummy_bytes], htfi->readbuf_leftbytes);
      }else
       htfi->readbuf_leftbytes = 0;
     }
     break;
    }
    if(received_bytes < 0){
     retcode = MPXPLAY_ERROR_DISKDRIV_SOCKET;
     break;
    }
    if(funcbit_test(mpxplay_signal_events, MPXPLAY_SIGNALTYPE_DISKDRIVTERM) || funcbit_test(htfi->flags, DRVFTP_FTPFILE_FLAG_TERMINATE) || (!mpxplay_control_keyboard_get_topfunc() && pds_wipeout_by_extkey(KEY_ESC))){
     funcbit_enable(htfi->flags, DRVFTP_FTPFILE_FLAG_TERMINATE);
     retcode = MPXPLAY_ERROR_DISKDRIV_CONNECT_TERMINATED;
     goto err_out_sl;
    }
    tcpcommon_socket_sleep((ftpdrive_socket_t)ftpi->socket_info_session.socknum);
   }else if(htfi->readbuf_leftbytes)
    pds_memcpy(&htfi->read_buffer[0], &htfi->read_buffer[processed_bytes], htfi->readbuf_leftbytes); // consolidate buffer

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

  }while(1);

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

  if(retcode != MPXPLAY_ERROR_DISKDRIV_OK)
   continue;

  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])){
     retcode = MPXPLAY_ERROR_DISKDRIV_LOGIN;
     break;
    }
    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);
    retcode = MPXPLAY_ERROR_DISKDRIV_RESPONSE;
    break;
  }
  // try with a new connection (redirected or error)
 }while(1);

err_out_ok:

 funcbit_smp_enable(ftpi->flags,DRVFTP_FTPDRIVE_FLAG_CONNECTED);

 display_clear_timed_message();

 return MPXPLAY_ERROR_DISKDRIV_OK;

err_out_sl:
 ftpi->lowfunc->socket_close(&ftpi->socket_info_session,1);
 return retcode;
}

//-----------------------------------------------------------------------
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 = (struct httpfile_info_s *)drive_data;
 unsigned long arg2num;

 switch(funcnum){
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_CANBESTREAM:
   return TRUE;
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_CHKBUFBLOCKBYTES:
   return 8192;
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_PREREADBUFBYTES:
   return ((htfi && (htfi->flags&DRVFTP_FTPFILE_FLAG_LIVESTREAM))? 16384 : 32768);  // ??? (should be time based, not byte)
 }

 if(!htfi)
  return MPXPLAY_DISKDRIV_CFGERROR_INVALID_DRIVE;

 switch(funcnum){
  case MPXPLAY_DISKFILE_CFGFUNCNUM_CMD_TERMINATE:
   funcbit_enable(htfi->flags, DRVFTP_FTPFILE_FLAG_TERMINATE);
   funcbit_disable(htfi->flags,DRVFTP_FTPFILE_FLAG_READWAIT);
   funcbit_enable(htfi->ftpi_infos.socket_info_session.flags, DRVFTP_FTPFILE_FLAG_TERMINATE);  // TODO: add this to tcp API
   funcbit_enable(htfi->ftpi_infos.socket_info_filehand.flags, DRVFTP_FTPFILE_FLAG_TERMINATE);
   return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
  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_GET_CONTENTBITRATE:
   return htfi->icy_bitrate;
 }

 if(!argp1)
  return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;

 switch(funcnum){
  case MPXPLAY_DISKFILE_CFGFUNCNUM_SET_FILEBLOCKSIZE:
   funcbit_smp_int32_put(htfi->file_bufsize,(*((unsigned long *)argp1)));
   return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
  case MPXPLAY_DISKFILE_CFGFUNCNUM_SET_READWAIT:
   if(*((unsigned long *)argp1))
    funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_READWAIT);
   else
    funcbit_disable(htfi->flags,DRVFTP_FTPFILE_FLAG_READWAIT);
   return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
  case MPXPLAY_DISKFILE_CFGFUNCNUM_SET_NOKEEPALIVE:
   if(*((unsigned long *)argp1))
    funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_NOKEEPALIVE);
   else
    funcbit_disable(htfi->flags,DRVFTP_FTPFILE_FLAG_NOKEEPALIVE);
   return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
  case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENTTYPEEXT:
   *((char **)argp1) = (char *)htfi->content_type_fileext;
   return MPXPLAY_DISKDRIV_CFGERROR_GET_OK;
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_SET_CALLBACKPSIFN:
   htfi->control_cb = argp1;
   htfi->cb_data = argp2;  // can be NULL
   httpdrive_filter_parse_metadata(htfi); // !!! hack
   return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
 }

 if(!argp2)
  return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;

 arg2num = *((unsigned long *)argp2);
 if(!arg2num)
  return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;

 switch(funcnum){
  case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENT_ARTIST:
   pds_strncpy((char *)argp1, htfi->icy_name, arg2num);
   ((char *)argp1)[arg2num - 1] = 0;
   return MPXPLAY_DISKDRIV_CFGERROR_GET_OK;
  case MPXPLAY_DISKFILE_CFGFUNCNUM_GET_CONTENT_GENRE:
   pds_strncpy((char *)argp1, htfi->icy_genre, arg2num);
   ((char *)argp1)[arg2num - 1] = 0;
   return MPXPLAY_DISKDRIV_CFGERROR_GET_OK;
 }

 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, int *err_code)
{
 struct ftpdrive_info_s *ftpi;
 struct httpfile_info_s *htfi;
 int retcode = MPXPLAY_ERROR_DISKDRIV_ERROR;

 htfi = pds_calloc(1,sizeof(*htfi));
 if(!htfi)
  goto err_out_open;

 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_strncpy(htfi->openfilename, urlfilename, sizeof(htfi->openfilename) - 1);
 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);
 if(openmode & MPXPLAY_INFILE_OPENMODE_LOAD_PLAY)
  funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_PLAY);
 funcbit_smp_int32_put(ftpi->connection_retry,DRVFTP_DEFAULT_TIMEOUTRETRY_SESSION);

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

 htfi->read_buffer = (char *)pds_calloc(1, DRVHTTP_HTTPFILE_READBUF_SIZE);

 retcode = drvhttp_session_login(ftpi,htfi);
 if(retcode != MPXPLAY_ERROR_DISKDRIV_OK)
  goto err_out_open;

 funcbit_enable(htfi->flags, DRVFTP_FTPFILE_FLAG_CONNECTED);

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

 goto succes_out_open;

err_out_open:
 httpdrive_file_close(htfi);
 htfi = NULL;
succes_out_open:
 if(err_code)
  *err_code = retcode;
 return htfi;
}

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;
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "httpdrive_file_close %8.8X", (unsigned int)htfi);
  if(ftpi->lowfunc)
   ftpi->lowfunc->socket_close(&ftpi->socket_info_session, 1);
  if(htfi->read_buffer)
   pds_free(htfi->read_buffer);
  pds_free(htfi);
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
  /*if(debug_fp){
   fclose(debug_fp);
   debug_fp=NULL;
  }*/
#endif
 }
}

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

static long httpdrive_buffer_refill(struct httpfile_info_s *htfi, long num)
{
 struct ftpdrive_info_s *ftpi = &htfi->ftpi_infos;
 long bytes_read;

 if(htfi->readbuf_readpos >= DRVHTTP_HTTPFILE_READBUF_SIZE){  // these should not happen
  htfi->readbuf_readpos = 0;
  htfi->readbuf_leftbytes = 0;
 }else if(htfi->readbuf_leftbytes < 0)
  htfi->readbuf_leftbytes = 0;
 else if(htfi->readbuf_leftbytes > (DRVHTTP_HTTPFILE_READBUF_SIZE - htfi->readbuf_readpos)){
  htfi->readbuf_leftbytes = (DRVHTTP_HTTPFILE_READBUF_SIZE - htfi->readbuf_readpos);
 }

 if((htfi->readbuf_readpos > 0) && (htfi->readbuf_leftbytes > 0))
  pds_memcpy(htfi->read_buffer, &htfi->read_buffer[htfi->readbuf_readpos], htfi->readbuf_leftbytes);
 htfi->readbuf_readpos = 0;

 bytes_read = ftpi->lowfunc->bytes_buffered(&ftpi->socket_info_session);
 if(bytes_read > 0){
  long bufspace = (long)DRVHTTP_HTTPFILE_READBUF_SIZE - htfi->readbuf_leftbytes;
  if(bytes_read > bufspace)
   bytes_read = bufspace;
  if(bytes_read > 0) {
   bytes_read = ftpi->lowfunc->receive(&ftpi->socket_info_session, &htfi->read_buffer[htfi->readbuf_leftbytes], bytes_read);
   if(bytes_read > 0)
    htfi->readbuf_leftbytes += bytes_read;
   else{
    mpxplay_debugf(MPXPLAY_DEBUG_ERROR, "httpdrive_buffer_refill RECEIVE ERROR br:%d", bytes_read);
   }
  }
 }else if(bytes_read < 0){
  mpxplay_debugf(MPXPLAY_DEBUG_WARNING, "httpdrive_buffer_refill BYTES_BUFFERED ERROR br:%d tar:%d", bytes_read, (int)(htfi->timeout_at_read - pds_gettimem()));
 }

 return bytes_read;
}

static long httpdrive_filter_read_chunk(struct httpfile_info_s *htfi, long req_len)
{
 if(funcbit_test(htfi->flags, DRVFTP_FTPFILE_FLAG_CHUNKED)){
  long leftbytes;
  if(htfi->chunk_bytecount >= htfi->chunk_size) {
   char *p = &htfi->read_buffer[htfi->readbuf_readpos];
   long head_len;
   while((p[0] == 0x0d) && (p[1]==0x0a) && (htfi->readbuf_leftbytes >= 2)) {
    p += 2;
    htfi->readbuf_readpos += 2;
    htfi->readbuf_leftbytes -= 2;
   }
   leftbytes = htfi->readbuf_leftbytes;
   if(leftbytes < 3) // min  01h, 0dh, 0ah
    return 0;
   //mpxplay_debugf(MPXDEC_DEBUG_OUTPUT,"chunk1 %8.8X %8.8X lb:%d s:%d",*((unsigned long *)(p)),*((unsigned long *)(p+4)), leftbytes, pds_atol16(p));
   //mpxplay_debugf(MPXDEC_DEBUG_OUTPUT,"chunk2 %c%c%c%c %8.8X",p[0],p[1],p[2],p[3],*((unsigned long *)(p+4)));
   head_len = 0;
   do {
    if((p[0] == 0x0d) && (leftbytes > 1) && (p[1] == 0x0a))
     break;
    p++; head_len++;
   }while(--leftbytes);
   if(!leftbytes){ // no eol
    if(htfi->readbuf_leftbytes > 8){ // bad chunk -> seek
     mpxplay_debugf(MPXPLAY_DEBUG_WARNING,"httpdrive_filter_read_chunk readbuf_leftbytes: %d", htfi->readbuf_leftbytes);
     return MPXPLAY_ERROR_DISKDRIV_CHUNK;
    }
    return 0;
   }
   if(head_len > 8){ // bad chunk -> seek
    mpxplay_debugf(MPXPLAY_DEBUG_WARNING,"httpdrive_filter_read_chunk head_len: %d", head_len);
    return MPXPLAY_ERROR_DISKDRIV_CHUNK;
   }
   head_len += 2;
   htfi->chunk_size = pds_atol16(&htfi->read_buffer[htfi->readbuf_readpos]);
   htfi->chunk_bytecount = 0;
   //mpxplay_debugf(MPXDEC_DEBUG_OUTPUT,"chunk size: %d headlen:%d %8.8X", htfi->chunk_size, head_len, *((unsigned long *)&htfi->read_buffer[htfi->readbuf_readpos]));
   htfi->readbuf_readpos += head_len;
   htfi->readbuf_leftbytes -= head_len;
   if((htfi->chunk_size <= 0) && funcbit_test(htfi->flags,DRVFTP_FTPFILE_FLAG_NOKEEPALIVE) && (htfi->filesize == MPXPLAY_DISKDRIV_FILESIZE_UNKNOWN)) // no more chunk?
    return MPXPLAY_ERROR_DISKDRIV_EOF;
  }
  leftbytes = htfi->chunk_size - htfi->chunk_bytecount;
  if(req_len > leftbytes)
   req_len = leftbytes;
 }
 if(req_len > htfi->readbuf_leftbytes)
   req_len = htfi->readbuf_leftbytes;
 return req_len;
}

struct http_metaname_to_diskdrvcmd_s{
 const char *textname;
 const unsigned long cmd;
}http_meta_to_diskdrvcmds[]={
 {"StreamTitle", MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_TITLE},
 {NULL,0}
};

static void httpdrive_filter_parse_metadata(struct httpfile_info_s *htfi)
{
 char *curr = &htfi->metadata_stream_title[0], *next;
 char strtmp[512];

 if(!htfi->control_cb || !curr[0])
  return;

 do{
  struct http_metaname_to_diskdrvcmd_s *hmd;
  char *data, *end;
  unsigned int i;
  next = pds_strchr(curr, ';');
  if(next)
   *next++ = 0;
  data = NULL;
  i = 0;
  do{
   hmd = &http_meta_to_diskdrvcmds[i];
   if(!hmd->textname)
    break;
   data = pds_strstri(curr, (char *)hmd->textname);
   if(data){
    data += pds_strlen((char *)hmd->textname);
    break;
   }
   i++;
  }while(1);
  if(data){
   while((*data=='=') || (*data==' ') || (*data=='\'') || (*data=='\"'))
    data++;
   end = pds_strrchr(data,'\'');
   if(end)
    *end = 0;
   drvhttp_convert_meta_string(htfi, data, strtmp, sizeof(strtmp));
   htfi->control_cb(htfi->cb_data, hmd->cmd, htfi->openfilename, strtmp);
   mpxplay_debugf(MPXPLAY_DEBUGOUT_READ, "SEND %8.8X %8.8X \"%s\" -> \"%s\"", (unsigned long)htfi->control_cb, (unsigned long)htfi->cb_data, htfi->openfilename, strtmp);
  }
  curr = next;
 }while(curr);
 htfi->metadata_stream_title[0]=0;
}

static long httpdrive_filter_read_metadata(struct httpfile_info_s *htfi, long req_len)
{
 if(htfi->readbuf_leftbytes < 1)
  return 0;
 if(req_len > htfi->readbuf_leftbytes)
  req_len = htfi->readbuf_leftbytes;
 if(htfi->flags & DRVFTP_FTPFILE_FLAG_META_CHECK) {
  htfi->metadata_intrv_count = 0;
  htfi->metadata_length = (long)htfi->read_buffer[htfi->readbuf_readpos] * 16;
  htfi->metadata_read_pos = 0;
  htfi->readbuf_readpos++;
  htfi->readbuf_leftbytes--;
  htfi->chunk_bytecount++;
  funcbit_disable(htfi->flags, DRVFTP_FTPFILE_FLAG_META_CHECK);
  if(htfi->metadata_length){
   funcbit_enable(htfi->flags, DRVFTP_FTPFILE_FLAG_META_READ);
   if(htfi->control_cb)
    htfi->control_cb(htfi->cb_data, MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_CHG_BEG, htfi->openfilename, NULL);
  }
  req_len = 0;
 } else if(htfi->flags & DRVFTP_FTPFILE_FLAG_META_READ) {
  long left_meta_bytes = (htfi->metadata_length - htfi->metadata_read_pos);
  if(left_meta_bytes > req_len)
   left_meta_bytes = req_len;
  if(left_meta_bytes > 0) {
   long left_meta_space = sizeof(htfi->metadata_stream_title) - 4 - htfi->metadata_read_pos;
   if(left_meta_space > 0) {
    long clen = left_meta_bytes;
    if(clen > left_meta_space)
     clen = left_meta_space;
    pds_memcpy(&htfi->metadata_stream_title[htfi->metadata_read_pos], &htfi->read_buffer[htfi->readbuf_readpos], clen);
   }
   htfi->metadata_read_pos += left_meta_bytes;
  }
  if(htfi->metadata_read_pos >= htfi->metadata_length) {
   htfi->metadata_stream_title[min(htfi->metadata_read_pos, (sizeof(htfi->metadata_stream_title) - 4))] = 0;
   funcbit_disable(htfi->flags, DRVFTP_FTPFILE_FLAG_META_READ);
   mpxplay_debugf(MPXPLAY_DEBUGOUT_READ, "READ %8.8X %8.8X \"%s\"", (unsigned long)htfi->control_cb, (unsigned long)htfi->cb_data, htfi->metadata_stream_title);
   if(htfi->control_cb)
    htfi->control_cb(htfi->cb_data, MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_CHG_END, htfi->openfilename, NULL);
   httpdrive_filter_parse_metadata(htfi);
  }
  htfi->readbuf_readpos += left_meta_bytes;
  htfi->readbuf_leftbytes -= left_meta_bytes;
  htfi->chunk_bytecount += left_meta_bytes;
  req_len = 0;
 } else {
  if(htfi->metadata_interval && ((htfi->metadata_intrv_count + req_len) >= htfi->metadata_interval)) {
   req_len = (htfi->metadata_interval - htfi->metadata_intrv_count);
   funcbit_enable(htfi->flags, DRVFTP_FTPFILE_FLAG_META_CHECK);
  }
 }
 if(req_len > htfi->readbuf_leftbytes)
  req_len = htfi->readbuf_leftbytes;
 return req_len;
}

static long httpdrive_buffered_read(struct httpfile_info_s *htfi, char *ptr, long num)
{
 mpxp_int64_t timeout_at_read = pds_gettimem() + DRVFTP_DEFAULT_TIMEOUTMS_READ;
 long left_bytes = num;
 mpxplay_debugf(MPXPLAY_DEBUGOUT_READ, "httpdrive_buffered_read START htfi:%8.8X num:%d wait:%d", (unsigned int)htfi, num, ((htfi->flags&DRVFTP_FTPFILE_FLAG_READWAIT)? 1 : 0));
 while(left_bytes > 0) {
  long clen;
  if(funcbit_test(htfi->flags,DRVFTP_FTPFILE_FLAG_TERMINATE))
   return MPXPLAY_ERROR_DISKDRIV_CONNECT_TERMINATED;
  clen = httpdrive_filter_read_chunk(htfi, left_bytes);
  if(clen < 0)
   return clen;
  if(clen > 0)
   clen = httpdrive_filter_read_metadata(htfi, clen);
  if(clen > 0) {
   pds_memcpy(ptr, &htfi->read_buffer[htfi->readbuf_readpos], clen);
   ptr += clen;
   left_bytes -= clen;
   htfi->readbuf_readpos += clen;
   htfi->readbuf_leftbytes -= clen;
   htfi->metadata_intrv_count += clen;
   htfi->chunk_bytecount += clen;
  } else {
   clen = httpdrive_buffer_refill(htfi, left_bytes);
   if(clen <= 0) {
    if((clen < 0) || !(htfi->flags&DRVFTP_FTPFILE_FLAG_READWAIT)){
     if(left_bytes == num){
      //mpxplay_debugf(MPXPLAY_DEBUG_ERROR, "httpdrive_buffered_read ERROR htfi:%8.8X clen:%d rw:%d", (unsigned int)htfi, clen, ((htfi->flags&DRVFTP_FTPFILE_FLAG_READWAIT)? 1 : 0));
      return clen;
     }
     break;
    }
    if(pds_gettimem() >= timeout_at_read){
     if(left_bytes == num){
      mpxplay_debugf(MPXPLAY_DEBUG_ERROR, "httpdrive_buffered_read ERROR TIMEOUT htfi:%8.8X clen:%d", (unsigned int)htfi, clen);
      return MPXPLAY_ERROR_DISKDRIV_TIMEOUT_READ;
     }
     break;
    }
    pds_threads_sleep(MPXPLAY_THREADS_SHORTTASKSLEEP);
   }
  }
 }
 mpxplay_debugf(MPXPLAY_DEBUGOUT_READ, "httpdrive_buffered_read END htfi:%8.8X got:%d", (unsigned int)htfi, (num - left_bytes));
 return (num - left_bytes);
}

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;
 char sout[128];
 if(!htfi)
  return MPXPLAY_ERROR_DISKDRIV_ERROR;
 if(funcbit_test(htfi->flags, DRVFTP_FTPFILE_FLAG_TERMINATE))
  return MPXPLAY_ERROR_DISKDRIV_CONNECT_TERMINATED;
 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) != MPXPLAY_ERROR_DISKDRIV_OK){
   mpxplay_debugf(MPXPLAY_DEBUG_ERROR, "httpdrive_file_read drvhttp_session_login failed");
   return MPXPLAY_ERROR_FILEHAND_CANTREAD;
  }

 bytes_read = httpdrive_buffered_read(htfi, ptr, num);
#ifdef MPXPLAY_DRVFTP_DEBUGFILE
//  fwrite(ptr,1,bytes_read,debug_fp);
#endif

 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((bytes_read != MPXPLAY_ERROR_DISKDRIV_EOF) && ftpi->connection_retry && !funcbit_test(htfi->flags,DRVFTP_FTPFILE_FLAG_NOKEEPALIVE)){
   if(!htfi->timeout_at_read && (bytes_read==0))
    htfi->timeout_at_read = pds_gettimem() + ((htfi->flags & DRVFTP_FTPFILE_FLAG_PLAY)? DRVFTP_DEFAULT_TIMEOUTMS_READ : DRVFTP_DEFAULT_TIMEOUTMS_LONGWAIT);
   else if((bytes_read<0) || (pds_gettimem()>htfi->timeout_at_read)){
    funcbit_enable(htfi->flags,DRVFTP_FTPFILE_FLAG_SEEK);
    htfi->timeout_at_read = 0;
    snprintf(sout, sizeof(sout), "file connection lost (err:%d), reconnect ...", bytes_read);
    drvhttp_message_write_error(sout);
   }
  }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
