/*
  Easy example NTRIP client for POSIX.
  $Id: ntripclient.c,v 1.33 2007/10/05 15:40:24 stuerze Exp $
  Copyright (C) 2003-2005 by Dirk Stoecker <soft@dstoecker.de>
    
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  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.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  or read http://www.gnu.org/licenses/gpl.txt
*/

#include <ctype.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <time.h>

#ifndef COMPILEDATE
#define COMPILEDATE " built " __DATE__
#endif

/* The string, which is send as agent in HTTP request */
#define AGENTSTRING "NTRIP NtripClientPOSIX"

#define MAXDATASIZE 1000 /* max number of bytes we can get at once */
#define ALARMTIME   (2*60)

/* CVS revision and version */
static char revisionstr[] = "$Revision: 1.33 $";
static char datestr[]     = "$Date: 2007/10/05 15:40:24 $";

enum MODE { HTTP = 1, RTSP = 2, NTRIP1 = 3, AUTO = 4, END };

struct Args
{
  const char *server;
  const char *port;
  const char *user;
  const char *proxyhost;
  const char *proxyport;
  const char *password;
  const char *nmea;
  const char *data;
  int         bitrate;
  int         mode;
};



/* option parsing */
#ifdef NO_LONG_OPTS
#define LONG_OPT(a)
#else
#define LONG_OPT(a) a
static struct option opts[] = {
{ "bitrate",    no_argument,       0, 'b'},
{ "data",       required_argument, 0, 'd'},
{ "server",     required_argument, 0, 's'},
{ "password",   required_argument, 0, 'p'},
{ "port",       required_argument, 0, 'r'},
{ "proxyport",  required_argument, 0, 'R'},
{ "proxyhost",  required_argument, 0, 'S'},
{ "user",       required_argument, 0, 'u'},
{ "nmea",       required_argument, 0, 'n'},
{ "mode",       required_argument, 0, 'M'},
{ "help",       no_argument,       0, 'h'},
{0,0,0,0}};
#endif
#define ARGOPT "-d:bhp:r:s:u:n:S:R:M:"

#ifdef __GNUC__
static __attribute__ ((noreturn)) void sighandler_alarm(
int sig __attribute__((__unused__)))
#else /* __GNUC__ */
static void sighandler_alarm(int sig)
#endif /* __GNUC__ */
{
  fprintf(stderr, "ERROR: more than %d seconds no activity\n", ALARMTIME);
  exit(1);
}

int stop = 0;
#ifdef __GNUC__
static void sighandler_int(int sig __attribute__((__unused__)))
#else /* __GNUC__ */
static void sighandler_alarm(int sig)
#endif /* __GNUC__ */
{
  alarm(2);
  stop = 1;
}

static const char *encodeurl(const char *req)
{
  char *h = "0123456789abcdef";
  static char buf[128];
  char *urlenc = buf;
  char *bufend = buf + sizeof(buf) - 3;

  while(*req && urlenc < bufend)
  {
    if(isalnum(*req) 
    || *req == '-' || *req == '_' || *req == '.')
      *urlenc++ = *req++;
    else
    {
      *urlenc++ = '%';
      *urlenc++ = h[*req >> 4];
      *urlenc++ = h[*req & 0x0f];
      *req++;
    }
  }
  *urlenc = 0;
  return buf;
}

static const char *geturl(const char *url, struct Args *args)
{
  static char buf[1000];
  static char *Buffer = buf;
  static char *Bufend = buf+sizeof(buf);
  char *h = "0123456789abcdef";

  if(strncmp("ntrip:", url, 6))
    return "URL must start with 'ntrip:'.";
  url += 6; /* skip ntrip: */

  if(*url != '@' && *url != '/')
  {
    /* scan for mountpoint */
    args->data = Buffer;
    if(*url != '?')
    {
       while(*url && *url != '@' &&  *url != ';' && *url != '/' && Buffer != Bufend)
         *(Buffer++) = *(url++);
    }
    else
    {
       while(*url && *url != '@' &&  *url != '/' && Buffer != Bufend) 
       {
          if(isalnum(*url) || *url == '-' || *url == '_' || *url == '.')
            *Buffer++ = *url++;
          else
          {
            *Buffer++ = '%';
            *Buffer++ = h[*url >> 4];
            *Buffer++ = h[*url & 0x0f];
            *url++;
          }      
       }
    }
    if(Buffer == args->data)
      return "Mountpoint required.";
    else if(Buffer >= Bufend-1)
      return "Parsing buffer too short.";
    *(Buffer++) = 0;
  }

  if(*url == '/') /* username and password */
  {
    ++url;
    args->user = Buffer;
    while(*url && *url != '@' && *url != ';' && *url != ':' && Buffer != Bufend)
      *(Buffer++) = *(url++);
    if(Buffer == args->user)
      return "Username cannot be empty.";
    else if(Buffer >= Bufend-1)
      return "Parsing buffer too short.";
    *(Buffer++) = 0;

    if(*url == ':') ++url;

    args->password = Buffer;
    while(*url && *url != '@' && *url != ';' && Buffer != Bufend)
      *(Buffer++) = *(url++);
    if(Buffer == args->password)
      return "Password cannot be empty.";
    else if(Buffer >= Bufend-1)
      return "Parsing buffer too short.";
    *(Buffer++) = 0;
  }

  if(*url == '@') /* server */
  {
    ++url;
    if(*url != '@' && *url != ':')
    {
      args->server = Buffer;
      while(*url && *url != '@' && *url != ':' && *url != ';' && Buffer != Bufend)
        *(Buffer++) = *(url++);
      if(Buffer == args->server)
        return "Servername cannot be empty.";
      else if(Buffer >= Bufend-1)
        return "Parsing buffer too short.";
      *(Buffer++) = 0;
    }

    if(*url == ':')
    {
      ++url;
      args->port = Buffer;
      while(*url && *url != '@' && *url != ';' && Buffer != Bufend)
        *(Buffer++) = *(url++);
      if(Buffer == args->port)
        return "Port cannot be empty.";
      else if(Buffer >= Bufend-1)
        return "Parsing buffer too short.";
      *(Buffer++) = 0;
    }

    if(*url == '@') /* proxy */
    {
      ++url;
      args->proxyhost = Buffer;
      while(*url && *url != ':' && *url != ';' && Buffer != Bufend)
        *(Buffer++) = *(url++);
      if(Buffer == args->proxyhost)
        return "Proxy servername cannot be empty.";
      else if(Buffer >= Bufend-1)
        return "Parsing buffer too short.";
      *(Buffer++) = 0;

      if(*url == ':')
      {
        ++url;
        args->proxyport = Buffer;
        while(*url && *url != ';' && Buffer != Bufend)
          *(Buffer++) = *(url++);
        if(Buffer == args->proxyport)
          return "Proxy port cannot be empty.";
        else if(Buffer >= Bufend-1)
          return "Parsing buffer too short.";
        *(Buffer++) = 0;
      }
    }
  }
  if(*url == ';') /* NMEA */
  {
    args->nmea = ++url;
    while(*url)
      ++url;
  }

  return *url ? "Garbage at end of server string." : 0;
}

static int getargs(int argc, char **argv, struct Args *args)
{
  int res = 1;
  int getoptr;
  char *a;
  int i = 0, help = 0;

  args->server = "www.euref-ip.net";
  args->port = "2101";
  args->user = "";
  args->password = "";
  args->nmea = 0;
  args->data = 0;
  args->bitrate = 0;
  args->proxyhost = 0;
  args->proxyport = "2101";
  args->mode = AUTO;
  help = 0;

  do
  {
#ifdef NO_LONG_OPTS
    switch((getoptr = getopt(argc, argv, ARGOPT)))
#else
    switch((getoptr = getopt_long(argc, argv, ARGOPT, opts, 0)))
#endif
    {
    case 's': args->server = optarg; break;
    case 'u': args->user = optarg; break;
    case 'p': args->password = optarg; break;
    case 'd':
       if(optarg && *optarg == '?') 
         args->data = encodeurl(optarg);
       else 
         args->data = optarg; 
     break;
    case 'n': args->nmea = optarg; break;
    case 'b': args->bitrate = 1; break;
    case 'h': help=1; break;
    case 'r': args->port = optarg; break;
    case 'S': args->proxyhost = optarg; break;
    case 'R': args->proxyport = optarg; break;
    case 'M':
      args->mode = 0;
      if (!strcmp(optarg,"n") || !strcmp(optarg,"ntrip1"))
        args->mode = NTRIP1;
      else if(!strcmp(optarg,"h") || !strcmp(optarg,"http"))
        args->mode = HTTP;
      else if(!strcmp(optarg,"r") || !strcmp(optarg,"rtsp"))
        args->mode = RTSP;
      else if(!strcmp(optarg,"a") || !strcmp(optarg,"auto"))
        args->mode = AUTO;
      else args->mode = atoi(optarg);
      if((args->mode == 0) || (args->mode >= END))
      {
        fprintf(stderr, "Mode %s unknown\n", optarg);
        res = 0;
      }
      break;
    case 1:
      {
        const char *err;
        if((err = geturl(optarg, args)))
        {
          fprintf(stderr, "%s\n\n", err);
          res = 0;
        }
      }
      break;
    case -1: break;
    }
  } while(getoptr != -1 && res);

  for(a = revisionstr+11; *a && *a != ' '; ++a)
    revisionstr[i++] = *a;
  revisionstr[i] = 0;
  datestr[0] = datestr[7];
  datestr[1] = datestr[8];
  datestr[2] = datestr[9];
  datestr[3] = datestr[10];
  datestr[5] = datestr[12];
  datestr[6] = datestr[13];
  datestr[8] = datestr[15];
  datestr[9] = datestr[16];
  datestr[4] = datestr[7] = '-';
  datestr[10] = 0;

  if(!res || help)
  {
    fprintf(stderr, "Version %s (%s) GPL" COMPILEDATE "\nUsage:\n%s -s server -u user ...\n"
    " -d " LONG_OPT("--data      ") "the requested data set\n"
    " -s " LONG_OPT("--server    ") "the server name or address\n"
    " -p " LONG_OPT("--password  ") "the login password\n"
    " -r " LONG_OPT("--port      ") "the server port number (default 2101)\n"
    " -u " LONG_OPT("--user      ") "the user name\n"
    " -n " LONG_OPT("--nmea      ") "NMEA string for sending to server\n"
    " -b " LONG_OPT("--bitrate   ") "output bitrate\n"
    " -S " LONG_OPT("--proxyhost ") "proxy name or address\n"
    " -R " LONG_OPT("--proxyport ") "proxy port, optional (default 2101)\n"
    " -M " LONG_OPT("--mode      ") "mode for data request\n"
    "     Valid modes are:\n"
    "     1, h, http     NTRIP Version 2.0 Caster in TCP/IP mode\n"
    "     2, r, rtsp     NTRIP Version 2.0 Caster in RTSP/RTP mode\n"
    "     3, n, ntrip1   NTRIP Version 1.0 Caster\n"
    "     4, a, auto     automatic detection (default)\n"
    "or using an URL:\n%s ntrip:data[/user[:password]][@[server][:port][@proxyhost[:proxyport]]][;nmea]\n"
    , revisionstr, datestr, argv[0], argv[0]);
    exit(1);
  }
  return res;
}

static const char encodingTable [64] = {
  'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
  'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
  'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
  'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};

/* does not buffer overrun, but breaks directly after an error */
/* returns the number of required bytes */
static int encode(char *buf, int size, const char *user, const char *pwd)
{
  unsigned char inbuf[3];
  char *out = buf;
  int i, sep = 0, fill = 0, bytes = 0;

  while(*user || *pwd)
  {
    i = 0;
    while(i < 3 && *user) inbuf[i++] = *(user++);
    if(i < 3 && !sep)    {inbuf[i++] = ':'; ++sep; }
    while(i < 3 && *pwd)  inbuf[i++] = *(pwd++);
    while(i < 3)         {inbuf[i++] = 0; ++fill; }
    if(out-buf < size-1)
      *(out++) = encodingTable[(inbuf [0] & 0xFC) >> 2];
    if(out-buf < size-1)
      *(out++) = encodingTable[((inbuf [0] & 0x03) << 4)
               | ((inbuf [1] & 0xF0) >> 4)];
    if(out-buf < size-1)
    {
      if(fill == 2)
        *(out++) = '=';
      else
        *(out++) = encodingTable[((inbuf [1] & 0x0F) << 2)
                 | ((inbuf [2] & 0xC0) >> 6)];
    }
    if(out-buf < size-1)
    {
      if(fill >= 1)
        *(out++) = '=';
      else
        *(out++) = encodingTable[inbuf [2] & 0x3F];
    }
    bytes += 4;
  }
  if(out-buf < size)
    *out = 0;
  return bytes;
}

int main(int argc, char **argv)
{
  struct Args args;

  setbuf(stdout, 0);
  setbuf(stdin, 0);
  setbuf(stderr, 0);
  signal(SIGALRM,sighandler_alarm);
  signal(SIGINT,sighandler_int);
  alarm(ALARMTIME);

  if(getargs(argc, argv, &args))
  {
    int sleeptime = 0;
    do
    {
      int sockfd, numbytes;  
      char buf[MAXDATASIZE];
      struct sockaddr_in their_addr; /* connector's address information */
      struct hostent *he;
      struct servent *se;
      const char *server, *port, *proxyserver = 0;
      char proxyport[6];
      char *b;
      long i;
      if(sleeptime)
      {
        sleep(sleeptime);
        sleeptime += 2;
      }
      else
      {
        sleeptime = 1;
      }
      alarm(ALARMTIME);
      if(args.proxyhost)
      {
        int p;
        if((i = strtol(args.port, &b, 10)) && (!b || !*b))
          p = i;
        else if(!(se = getservbyname(args.port, 0)))
        {
          fprintf(stderr, "Can't resolve port %s.", args.port);
          exit(1);
        }
        else
        {
          p = ntohs(se->s_port);
        }
        snprintf(proxyport, sizeof(proxyport), "%d", p);
        port = args.proxyport;
        proxyserver = args.server;
        server = args.proxyhost;
      }
      else
      {
        server = args.server;
        port = args.port;
      }
      memset(&their_addr, 0, sizeof(struct sockaddr_in));
      if((i = strtol(port, &b, 10)) && (!b || !*b))
        their_addr.sin_port = htons(i);
      else if(!(se = getservbyname(port, 0)))
      {
        fprintf(stderr, "Can't resolve port %s.", port);
        exit(1);
      }
      else
      {
        their_addr.sin_port = se->s_port;
      }
      if(!(he=gethostbyname(server)))
      {
        fprintf(stderr, "Server name lookup failed for '%s'.\n", server);
        exit(1);
      }
      if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
      {
        perror("socket");
        exit(1);
      }
      their_addr.sin_family = AF_INET;
      their_addr.sin_addr = *((struct in_addr *)he->h_addr);
      
      if(args.data && *args.data != '%' && args.mode == RTSP)
      {
        struct sockaddr_in local;
        int sockudp, localport;
        int cseq = 1;
        socklen_t len;

        if((sockudp = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
        {
          perror("socket");
          exit(1);
        }
        /* fill structure with local address information for UDP */
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;  	
        local.sin_port = htons(0);
        local.sin_addr.s_addr = htonl(INADDR_ANY); 
        len = sizeof(local);
        /* bind() in order to get a random RTP client_port */ 
        if((bind(sockudp, (struct sockaddr *)&local, len)) < 0)
        {
          perror("bind");
          exit(1);
        }
        if((getsockname(sockudp, (struct sockaddr*)&local, &len)) != -1) 
        {
          localport = ntohs(local.sin_port); 
        }
        else
        {
          perror("local access failed"); 
          exit(1);
        }
        if(connect(sockfd, (struct sockaddr *)&their_addr,
        sizeof(struct sockaddr)) == -1)
        {
          perror("connect");
          exit(1);
        }
        i=snprintf(buf, MAXDATASIZE-40, /* leave some space for login */
        "SETUP rtsp://%s%s%s/%s RTSP/1.0\r\n" 	        
        "CSeq: %d\r\n"		
        "Ntrip-Version: Ntrip/2.0\r\n"
        "Ntrip-Component: Ntripclient\r\n"
        "User-Agent: %s/%s\r\n"
        "Transport: RTP/GNSS;unicast;client_port=%u\r\n"
        "Authorization: Basic ",
        args.server, proxyserver ? ":" : "", proxyserver ? args.port : "",
        args.data, cseq++, AGENTSTRING, revisionstr, localport);
        if(i > MAXDATASIZE-40 || i < 0) /* second check for old glibc */
        {
          fprintf(stderr, "Requested data too long\n");
          exit(1);
        }
        i += encode(buf+i, MAXDATASIZE-i-4, args.user, args.password);
        if(i > MAXDATASIZE-4)
        {
          fprintf(stderr, "Username and/or password too long\n");
          exit(1);
        }
        buf[i++] = '\r';
        buf[i++] = '\n';
        buf[i++] = '\r';
        buf[i++] = '\n';
        if(args.nmea)
        {
          int j = snprintf(buf+i, MAXDATASIZE-i, "%s\r\n", args.nmea);
          if(j >= 0 && j < MAXDATASIZE-i)
            i += j;
          else
          {
            fprintf(stderr, "NMEA string too long\n");
            exit(1);
          }
        }
        if(send(sockfd, buf, (size_t)i, 0) != i)
        {
          perror("send");
          exit(1);
        }
        if((numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) != -1)
        {
          if(numbytes >= 17 && !strncmp(buf, "RTSP/1.0 200 OK\r\n", 17))
	  {
            int serverport = 0, session = 0;
            const char *portcheck = "server_port=";
            const char *sessioncheck = "session: ";
            int l = strlen(portcheck)-1;
            int j=0;
            for(i = 0; j != l && i < numbytes-l; ++i)
            {
              for(j = 0; j < l && tolower(buf[i+j]) == portcheck[j]; ++j)
                ;
            }
            if(i == numbytes-l)
            {
              fprintf(stderr, "No server port number found\n");
              exit(1);
            }
            else
            {
              i+=l;
              while(i < numbytes && buf[i] >= '0' && buf[i] <= '9')
                serverport = serverport * 10 + buf[i++]-'0';
              if(buf[i] != '\r' && buf[i] != ';')
              {
                fprintf(stderr, "Could not extract server port\n");
                exit(1);
              }
            }
            l = strlen(sessioncheck)-1;
            j=0;
            for(i = 0; j != l && i < numbytes-l; ++i)
            {
              for(j = 0; j < l && tolower(buf[i+j]) == sessioncheck[j]; ++j)
                ;
            }
            if(i == numbytes-l)
            {
              fprintf(stderr, "No session number found\n");
              exit(1);
            }
            else
            {
              i+=l;
              while(i < numbytes && buf[i] >= '0' && buf[i] <= '9')
                session = session * 10 + buf[i++]-'0';
              if(buf[i] != '\r')
              {
                fprintf(stderr, "Could not extract session number\n");
                exit(1);
              }
            }

            i = snprintf(buf, MAXDATASIZE,
            "PLAY rtsp://%s%s%s/%s RTSP/1.0\r\n"	        
            "CSeq: %d\r\n"
            "Session: %d\r\n"
            "\r\n", 
            args.server, proxyserver ? ":" : "", proxyserver ? args.port : "",
            args.data, cseq++, session);

            if(i > MAXDATASIZE || i < 0) /* second check for old glibc */
            {
              fprintf(stderr, "Requested data too long\n");
              exit(1);
            }
            if(send(sockfd, buf, (size_t)i, 0) != i)
            {
              perror("send");
              exit(1);
            }
            if((numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) != -1)
            {
              if(numbytes >= 17 && !strncmp(buf, "RTSP/1.0 200 OK\r\n", 17))
              {
                struct sockaddr_in addrRTP;
                /* fill structure with caster address information for UDP */
                memset(&addrRTP, 0, sizeof(addrRTP));
                addrRTP.sin_family = AF_INET;  
                addrRTP.sin_port   = htons(serverport);
                their_addr.sin_addr = *((struct in_addr *)he->h_addr);
                len = sizeof(addrRTP);
                int ts = 0;
                int sn = 0;
                int ssrc = 0;
                int init = 0;
                int u, v, w;
                while(!stop && (i = recvfrom(sockudp, buf, 1526, 0,
                (struct sockaddr*) &addrRTP, &len)) > 0)
                {
                  alarm(ALARMTIME);
                  if(i >= 12+1 && (unsigned char)buf[0] == (2 << 6) && buf[1] == 0x60)
                  {
                    u= ((unsigned char)buf[2]<<8)+(unsigned char)buf[3];
                    v = ((unsigned char)buf[4]<<24)+((unsigned char)buf[5]<<16)
                    +((unsigned char)buf[6]<<8)+(unsigned char)buf[7];
                    w = ((unsigned char)buf[8]<<24)+((unsigned char)buf[9]<<16)
                    +((unsigned char)buf[10]<<8)+(unsigned char)buf[11];

                    if(init)
                    {
                      if(u < -30000 && sn > 30000) sn -= 0xFFFF;
                      if(ssrc != w || ts > v)
                      {
                        fprintf(stderr, "Illegal UDP data received.\n");
                        exit(1);
                      }
                      if(u > sn) /* don't show out-of-order packets */
                        fwrite(buf+12, (size_t)i-12, 1, stdout);
                    }
                    sn = u; ts = v; ssrc = w; init = 1;
                  }
                  else
                  {
                    fprintf(stderr, "Illegal UDP header.\n");
                    exit(1);
                  }
                }
              }
              i = snprintf(buf, MAXDATASIZE,
              "TEARDOWN rtsp://%s%s%s/%s RTSP/1.0\r\n"	        
              "CSeq: %d\r\n"
              "Session: %d\r\n"
              "\r\n", 
              args.server, proxyserver ? ":" : "", proxyserver ? args.port : "",
              args.data, cseq++, session);

              if(i > MAXDATASIZE || i < 0) /* second check for old glibc */
              {
                fprintf(stderr, "Requested data too long\n");
                exit(1);
              }
              if(send(sockfd, buf, (size_t)i, 0) != i)
              {
                perror("send");
                exit(1);
              }
            }
            else
            {
              fprintf(stderr, "Could not start data stream.\n");
              exit(1);
            }
          }
          else
          {
            fprintf(stderr, "Could not setup initial control connection.\n");
            exit(1);
          }
        }
        else
        {
          perror("recv");
          exit(1);
        }
      }
      else
      {
        if(connect(sockfd, (struct sockaddr *)&their_addr,
        sizeof(struct sockaddr)) == -1)
        {
          perror("connect");
          exit(1);
        }
        if(!args.data)
        {
          i = snprintf(buf, MAXDATASIZE,
          "GET %s%s%s%s/ HTTP/1.0\r\n"
          "Host: %s\r\n%s"
          "User-Agent: %s/%s\r\n"
          "\r\n"
          , proxyserver ? "http://" : "", proxyserver ? proxyserver : "",
          proxyserver ? ":" : "", proxyserver ? proxyport : "",
          args.server, args.mode == NTRIP1 ? "" : "Ntrip-Version: Ntrip/2.0\r\n",
          AGENTSTRING, revisionstr);
        }
        else
        {
          i=snprintf(buf, MAXDATASIZE-40, /* leave some space for login */
          "GET %s%s%s%s/%s HTTP/1.0\r\n"
          "Host: %s\r\n%s"
          "User-Agent: %s/%s\r\n"
          "Authorization: Basic "
          , proxyserver ? "http://" : "", proxyserver ? proxyserver : "",
          proxyserver ? ":" : "", proxyserver ? proxyport : "",
          args.data, args.server,
          args.mode == NTRIP1 ? "" : "Ntrip-Version: Ntrip/2.0\r\n",
          AGENTSTRING, revisionstr);
          if(i > MAXDATASIZE-40 || i < 0) /* second check for old glibc */
          {
            fprintf(stderr, "Requested data too long\n");
            exit(1);
          }
          i += encode(buf+i, MAXDATASIZE-i-4, args.user, args.password);
          if(i > MAXDATASIZE-4)
          {
            fprintf(stderr, "Username and/or password too long\n");
            exit(1);
          }
          buf[i++] = '\r';
          buf[i++] = '\n';
          buf[i++] = '\r';
          buf[i++] = '\n';
          if(args.nmea)
          {
            int j = snprintf(buf+i, MAXDATASIZE-i, "%s\r\n", args.nmea);
            if(j >= 0 && j < MAXDATASIZE-i)
              i += j;
            else
            {
              fprintf(stderr, "NMEA string too long\n");
              exit(1);
            }
          }
        }
        if(send(sockfd, buf, (size_t)i, 0) != i)
        {
          perror("send");
          exit(1);
        }
        if(args.data && *args.data != '%')
        {
          int k = 0;
          int chunkymode = 0;
          int starttime = time(0);
          int lastout = starttime;
          int totalbytes = 0;
          int chunksize = 0;

          while(!stop && (numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) != -1)
          {
            alarm(ALARMTIME);
            if(!k)
            {
              if(numbytes > 17 && (!strncmp(buf, "HTTP/1.1 200 OK\r\n", 17)
              || !strncmp(buf, "HTTP/1.0 200 OK\r\n", 17)))
	      {
                const char *datacheck = "Content-Type: gnss/data\r\n";
                const char *chunkycheck = "Transfer-Encoding: chunked\r\n";
                int l = strlen(datacheck)-1;
                int j=0;
                for(i = 0; j != l && i < numbytes-l; ++i)
                {
                  for(j = 0; j < l && buf[i+j] == datacheck[j]; ++j)
                    ;
                }
                if(i == numbytes-l)
                {
                  fprintf(stderr, "No 'Content-Type: gnss/data' found\n");
                  exit(1);
                }
                l = strlen(chunkycheck)-1;
                j=0;
                for(i = 0; j != l && i < numbytes-l; ++i)
                {
                  for(j = 0; j < l && buf[i+j] == chunkycheck[j]; ++j)
                    ;
                }
                if(i < numbytes-l)
                  chunkymode = 1;
	      }
              else if(numbytes < 12 || strncmp("ICY 200 OK\r\n", buf, 12))
              {
                fprintf(stderr, "Could not get the requested data: ");
                for(k = 0; k < numbytes && buf[k] != '\n' && buf[k] != '\r'; ++k)
                {
                  fprintf(stderr, "%c", isprint(buf[k]) ? buf[k] : '.');
                }
                fprintf(stderr, "\n");
                exit(1);
              }
              else if(args.mode != NTRIP1)
              {
                fprintf(stderr, "NTRIP version 2 HTTP connection failed%s.\n",
                args.mode == AUTO ? ", falling back to NTRIP1" : "");
                if(args.mode == HTTP)
                  exit(1);
              }
              ++k;
            }
            else
            {
              sleeptime = 0;
              if(chunkymode)
              {
                int stop = 0;
                int pos = 0;
                while(!stop && pos < numbytes)
                {
                  switch(chunkymode)
                  {
                  case 1: /* reading number starts */
                    chunksize = 0;
                    ++chunkymode; /* no break */
                  case 2: /* during reading number */
                    i = buf[pos++];
                    if(i >= '0' && i <= '9') chunksize = chunksize*16+i-'0';
                    else if(i >= 'a' && i <= 'f') chunksize = chunksize*16+i-'a'+10;
                    else if(i >= 'A' && i <= 'F') chunksize = chunksize*16+i-'A'+10;
                    else if(i == '\r') ++chunkymode;
                    else stop = 1;
                    break;
                  case 3: /* scanning for return */
                    if(buf[pos++] == '\n') chunkymode = chunksize ? 4 : 1;
                    else stop = 1;
                    break;
                  case 4: /* output data */
                    i = numbytes-pos;
                    if(i > chunksize) i = chunksize;
                    fwrite(buf+pos, (size_t)i, 1, stdout);
                    totalbytes += i;
                    chunksize -= i;
                    pos += i;
                    if(!chunksize)
                      chunkymode = 1;
                    break;
                  }
                }
                if(stop)
                {
                  fprintf(stderr, "Error in chunky transfer encoding\n");
                  break;
                }
              }
              else
              {
                totalbytes += numbytes;
                fwrite(buf, (size_t)numbytes, 1, stdout);
              }
              fflush(stdout);
              if(totalbytes < 0) /* overflow */
              {
                totalbytes = 0;
                starttime = time(0);
                lastout = starttime;
              }
              if(args.bitrate)
              {
                int t = time(0);
                if(t > lastout + 60)
                {
                  lastout = t;
                  fprintf(stderr, "Bitrate is %dbyte/s (%d seconds accumulated).\n",
                  totalbytes/(t-starttime), t-starttime);
                }
              }
            }
          }
        }
        else
        {
          sleeptime = 0;
          while(!stop && (numbytes=recv(sockfd, buf, MAXDATASIZE-1, 0)) > 0)
          {
            alarm(ALARMTIME);
            fwrite(buf, (size_t)numbytes, 1, stdout);
          }
        }
        close(sockfd);
      }
    } while(args.data && *args.data != '%' && !stop);
  }
  return 0;
}