Subversion Repositories SvarDOS

Rev

Rev 1185 | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1185 Rev 1186
1
/*
1
/*
2
 * pkgnet - pulls SvarDOS packages from the project's online repository
2
 * pkgnet - pulls SvarDOS packages from the project's online repository
3
 *
3
 *
4
 * PUBLISHED UNDER THE TERMS OF THE MIT LICENSE
4
 * PUBLISHED UNDER THE TERMS OF THE MIT LICENSE
5
 *
5
 *
6
 * COPYRIGHT (C) 2016-2023 MATEUSZ VISTE, ALL RIGHTS RESERVED.
6
 * COPYRIGHT (C) 2016-2023 MATEUSZ VISTE, ALL RIGHTS RESERVED.
7
 *
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining a
8
 * Permission is hereby granted, free of charge, to any person obtaining a
9
 * copy of this software and associated documentation files (the "Software"),
9
 * copy of this software and associated documentation files (the "Software"),
10
 * to deal in the Software without restriction, including without limitation
10
 * to deal in the Software without restriction, including without limitation
11
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
11
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12
 * and/or sell copies of the Software, and to permit persons to whom the
12
 * and/or sell copies of the Software, and to permit persons to whom the
13
 * Software is furnished to do so, subject to the following conditions:
13
 * Software is furnished to do so, subject to the following conditions:
14
 *
14
 *
15
 * The above copyright notice and this permission notice shall be included in
15
 * The above copyright notice and this permission notice shall be included in
16
 * all copies or substantial portions of the Software.
16
 * all copies or substantial portions of the Software.
17
 *
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24
 * DEALINGS IN THE SOFTWARE.
24
 * DEALINGS IN THE SOFTWARE.
25
 *
25
 *
26
 * http://svardos.org
26
 * http://svardos.org
27
 */
27
 */
28
 
28
 
29
#include <direct.h> /* opendir() and friends */
29
#include <direct.h> /* opendir() and friends */
30
#include <stdio.h>
30
#include <stdio.h>
31
#include <stdlib.h>
31
#include <stdlib.h>
32
#include <string.h>
32
#include <string.h>
33
#include <time.h>
33
#include <time.h>
34
 
34
 
35
#include "net.h"
35
#include "net.h"
36
#include "unchunk.h"
36
#include "unchunk.h"
37
 
37
 
38
#include "svarlang.lib\svarlang.h"
38
#include "svarlang.lib\svarlang.h"
39
 
39
 
40
#include "../../pkg/trunk/lsm.h"
40
#include "../../pkg/trunk/lsm.h"
41
 
41
 
42
 
42
 
43
#define PVER "20230208"
43
#define PVER "20230208"
44
#define PDATE "2021-2023"
44
#define PDATE "2021-2023"
45
 
45
 
46
#define HOSTADDR "svardos.org"
46
#define HOSTADDR "svardos.org"
47
 
47
 
48
 
48
 
49
/* convenience define that outputs nls strings to screen (followed by CR/LF) */
49
/* convenience define that outputs nls strings to screen (followed by CR/LF) */
50
#define putsnls(x,y) puts(svarlang_strid((x << 8) | y))
50
#define putsnls(x,y) puts(svarlang_strid((x << 8) | y))
51
 
51
 
52
 
52
 
53
/* returns length of all http headers, or 0 if uncomplete yet */
53
/* returns length of all http headers, or 0 if uncomplete yet */
54
static unsigned short detecthttpheadersend(const unsigned char *buff) {
54
static unsigned short detecthttpheadersend(const unsigned char *buff) {
55
  char lastbyteislf = 0;
55
  char lastbyteislf = 0;
56
  unsigned short i;
56
  unsigned short i;
57
  for (i = 0; buff[i] != 0; i++) {
57
  for (i = 0; buff[i] != 0; i++) {
58
    if (buff[i] == '\r') continue; /* ignore CR characters */
58
    if (buff[i] == '\r') continue; /* ignore CR characters */
59
    if (buff[i] != '\n') {
59
    if (buff[i] != '\n') {
60
      lastbyteislf = 0;
60
      lastbyteislf = 0;
61
      continue;
61
      continue;
62
    }
62
    }
63
    /* cur byte is LF -> if last one was also LF then this is an empty line, meaning headers are over */
63
    /* cur byte is LF -> if last one was also LF then this is an empty line, meaning headers are over */
64
    if (lastbyteislf == 0) {
64
    if (lastbyteislf == 0) {
65
      lastbyteislf = 1;
65
      lastbyteislf = 1;
66
      continue;
66
      continue;
67
    }
67
    }
68
    /* end of headers! return length of headers */
68
    /* end of headers! return length of headers */
69
    return(i + 1); /* add 1 to skip the current \n character */
69
    return(i + 1); /* add 1 to skip the current \n character */
70
  }
70
  }
71
  return(0);
71
  return(0);
72
}
72
}
73
 
73
 
74
 
74
 
75
static void help(void) {
75
static void help(void) {
76
  puts("pkgnet ver " PVER " -- Copyright (C) " PDATE " Mateusz Viste");
76
  puts("pkgnet ver " PVER " -- Copyright (C) " PDATE " Mateusz Viste");
77
  puts("");
77
  puts("");
78
  putsnls(1, 0);  /* "pkgnet is the SvarDOS package downloader" */
78
  putsnls(1, 0);  /* "pkgnet is the SvarDOS package downloader" */
79
  puts("");
79
  puts("");
80
  putsnls(1, 1);  /* "usage:  pkgnet search <term>" */
80
  putsnls(1, 1);  /* "usage:  pkgnet search <term>" */
81
  putsnls(1, 2);  /* "        pkgnet pull <package>" */
81
  putsnls(1, 2);  /* "        pkgnet pull <package>" */
82
  putsnls(1, 3);  /* "        pkgnet pull <package>-<version>" */
82
  putsnls(1, 3);  /* "        pkgnet pull <package>-<version>" */
83
  putsnls(1, 4);  /* "        pkgnet pullsrc <package>" */
83
  putsnls(1, 4);  /* "        pkgnet pullsrc <package>" */
84
  putsnls(1, 5);  /* "        pkgnet pullsrc <package>-<version>" */
84
  putsnls(1, 5);  /* "        pkgnet pullsrc <package>-<version>" */
85
  putsnls(1, 6);  /* "        pkgnet checkup" */
85
  putsnls(1, 6);  /* "        pkgnet checkup" */
86
  puts("");
86
  puts("");
87
  putsnls(1, 7);  /* "actions:" */
87
  putsnls(1, 7);  /* "actions:" */
88
  puts("");
88
  puts("");
89
  putsnls(1, 8);  /* "search   - asks remote repository for the list of matching packages" */
89
  putsnls(1, 8);  /* "search   - asks remote repository for the list of matching packages" */
90
  putsnls(1, 9);  /* "pull     - downloads package into current directory" */
90
  putsnls(1, 9);  /* "pull     - downloads package into current directory" */
91
  putsnls(1, 10); /* "pullsrc  - downloads source code archive for package" */
91
  putsnls(1, 10); /* "pullsrc  - downloads source code archive for package" */
92
  putsnls(1, 11); /* "checkup  - lists updates available for your system" */
92
  putsnls(1, 11); /* "checkup  - lists updates available for your system" */
93
  puts("");
93
  puts("");
94
  printf("Watt32 kernel: %s", net_engine());
94
  printf("Watt32 kernel: %s", net_engine());
95
  puts("");
95
  puts("");
96
}
96
}
97
 
97
 
98
 
98
 
99
/* parses command line arguments and fills outfname and url accordingly
99
/* parses command line arguments and fills outfname and url accordingly
100
 * returns 0 on success, non-zero otherwise */
100
 * returns 0 on success, non-zero otherwise */
101
static int parseargv(int argc, char * const *argv, char *outfname, char *url, int *ispost) {
101
static int parseargv(int argc, char * const *argv, char *outfname, char *url, int *ispost) {
102
  const char *lang = getenv("LANG");
102
  const char *lang = getenv("LANG");
103
  if (lang == NULL) lang = "";
103
  if (lang == NULL) lang = "";
104
  *outfname = 0;
104
  *outfname = 0;
105
  *url = 0;
105
  *url = 0;
106
  *ispost = 0;
106
  *ispost = 0;
107
  if ((argc == 3) && (strcasecmp(argv[1], "search") == 0)) {
107
  if ((argc == 3) && (strcasecmp(argv[1], "search") == 0)) {
108
    sprintf(url, "/repo/?a=search&p=%s&lang=%s", argv[2], lang);
108
    sprintf(url, "/repo/?a=search&p=%s&lang=%s", argv[2], lang);
109
  } else if ((argc == 3) && ((strcasecmp(argv[1], "pull") == 0) || (strcasecmp(argv[1], "pullsrc") == 0))) {
109
  } else if ((argc == 3) && ((strcasecmp(argv[1], "pull") == 0) || (strcasecmp(argv[1], "pullsrc") == 0))) {
110
    unsigned short i;
110
    unsigned short i;
111
    /* copy argv[2] into outfname, but stop at first '-' or null terminator
111
    /* copy argv[2] into outfname, but stop at first '-' or null terminator
112
     * this trims any '-version' part in filename to respect 8+3 */
112
     * this trims any '-version' part in filename to respect 8+3 */
113
    for (i = 0; (argv[2][i] != 0) && (argv[2][i] != '-') && (i < 8); i++) {
113
    for (i = 0; (argv[2][i] != 0) && (argv[2][i] != '-') && (i < 8); i++) {
114
      outfname[i] = argv[2][i];
114
      outfname[i] = argv[2][i];
115
    }
115
    }
116
    /* add the extension (svp or zip) to filename and compute url */
116
    /* add the extension (svp or zip) to filename and compute url */
117
    if (strcasecmp(argv[1], "pull") == 0) {
117
    if (strcasecmp(argv[1], "pull") == 0) {
118
      sprintf(url, "/repo/?a=pull&p=%s&lang=%s", argv[2], lang);
118
      sprintf(url, "/repo/?a=pull&p=%s&lang=%s", argv[2], lang);
119
      strcpy(outfname + i, ".svp");
119
      strcpy(outfname + i, ".svp");
120
    } else {
120
    } else {
121
      sprintf(url, "/repo/?a=pullsrc&p=%s&lang=%s", argv[2], lang);
121
      sprintf(url, "/repo/?a=pullsrc&p=%s&lang=%s", argv[2], lang);
122
      strcpy(outfname + i, ".zip");
122
      strcpy(outfname + i, ".zip");
123
    }
123
    }
124
  } else if ((argc == 2) && (strcasecmp(argv[1], "checkup") == 0)) {
124
  } else if ((argc == 2) && (strcasecmp(argv[1], "checkup") == 0)) {
125
    sprintf(url, "/repo/?a=checkup&lang=%s", lang);
125
    sprintf(url, "/repo/?a=checkup&lang=%s", lang);
126
    *ispost = 1;
126
    *ispost = 1;
127
  } else {
127
  } else {
128
    help();
128
    help();
129
    return(-1);
129
    return(-1);
130
  }
130
  }
131
  return(0);
131
  return(0);
132
}
132
}
133
 
133
 
134
 
134
 
135
static int htget_headers(unsigned char *buffer, size_t buffersz, struct net_tcpsocket *sock, int *httpcode, int *ischunked)  {
135
static int htget_headers(unsigned char *buffer, size_t buffersz, struct net_tcpsocket *sock, int *httpcode, int *ischunked)  {
136
  unsigned char *buffptr = buffer;
136
  unsigned char *buffptr = buffer;
137
  unsigned short bufflen = 0;
137
  unsigned short bufflen = 0;
138
  int byteread;
138
  int byteread;
139
  time_t starttime = time(NULL);
139
  time_t starttime = time(NULL);
140
  for (;;) {
140
  for (;;) {
141
    byteread = net_recv(sock, buffptr, buffersz - (bufflen + 1)); /* -1 because I will append a NULL terminator */
141
    byteread = net_recv(sock, buffptr, buffersz - (bufflen + 1)); /* -1 because I will append a NULL terminator */
142
 
142
 
143
    if (byteread > 0) { /* got data */
143
    if (byteread > 0) { /* got data */
144
      int hdlen;
144
      int hdlen;
145
      bufflen += byteread;
145
      bufflen += byteread;
146
      buffptr += byteread;
146
      buffptr += byteread;
147
      buffer[bufflen] = 0;
147
      buffer[bufflen] = 0;
148
      hdlen = detecthttpheadersend(buffer);
148
      hdlen = detecthttpheadersend(buffer);
149
      if (hdlen > 0) { /* full headers - parse http code and continue processing */
149
      if (hdlen > 0) { /* full headers - parse http code and continue processing */
150
        int i;
150
        int i;
151
        buffer[hdlen - 1] = 0;
151
        buffer[hdlen - 1] = 0;
152
        /* find the first space (HTTP/1.1 200 OK) */
152
        /* find the first space (HTTP/1.1 200 OK) */
153
        for (i = 0; i < 16; i++) {
153
        for (i = 0; i < 16; i++) {
154
          if ((buffer[i] == ' ') || (buffer[i] == 0)) break;
154
          if ((buffer[i] == ' ') || (buffer[i] == 0)) break;
155
        }
155
        }
156
        if (buffer[i] != ' ') return(-1);
156
        if (buffer[i] != ' ') return(-1);
157
        *httpcode = atoi((char *)(buffer + i + 1));
157
        *httpcode = atoi((char *)(buffer + i + 1));
158
        /* switch all headers to low-case so it is easier to parse them */
158
        /* switch all headers to low-case so it is easier to parse them */
159
        for (i = 0; i < hdlen; i++) if ((buffer[i] >= 'A') && (buffer[i] <= 'Z')) buffer[i] += ('a' - 'A');
159
        for (i = 0; i < hdlen; i++) if ((buffer[i] >= 'A') && (buffer[i] <= 'Z')) buffer[i] += ('a' - 'A');
160
        /* look out for chunked transfer encoding */
160
        /* look out for chunked transfer encoding */
161
        {
161
        {
162
        char *lineptr = strstr((char *)buffer, "\ntransfer-encoding:");
162
        char *lineptr = strstr((char *)buffer, "\ntransfer-encoding:");
163
        if (lineptr != NULL) {
163
        if (lineptr != NULL) {
164
          lineptr += 19;
164
          lineptr += 19;
165
          /* replace nearest \r, \n or 0 by 0 */
165
          /* replace nearest \r, \n or 0 by 0 */
166
          for (i = 0; ; i++) {
166
          for (i = 0; ; i++) {
167
            if ((lineptr[i] == '\r') || (lineptr[i] == '\n') || (lineptr[i] == 0)) {
167
            if ((lineptr[i] == '\r') || (lineptr[i] == '\n') || (lineptr[i] == 0)) {
168
              lineptr[i] = 0;
168
              lineptr[i] = 0;
169
              break;
169
              break;
170
            }
170
            }
171
          }
171
          }
172
          /* do I see the 'chunked' word? */
172
          /* do I see the 'chunked' word? */
173
          if (strstr((char *)lineptr, "chunked") != NULL) *ischunked = 1;
173
          if (strstr((char *)lineptr, "chunked") != NULL) *ischunked = 1;
174
        }
174
        }
175
        }
175
        }
176
        /* rewind the buffer */
176
        /* rewind the buffer */
177
        bufflen -= hdlen;
177
        bufflen -= hdlen;
178
        memmove(buffer, buffer + hdlen, bufflen);
178
        memmove(buffer, buffer + hdlen, bufflen);
179
        return(bufflen); /* move to body processing now */
179
        return(bufflen); /* move to body processing now */
180
      }
180
      }
181
 
181
 
182
    } else if (byteread < 0) { /* abort on error */
182
    } else if (byteread < 0) { /* abort on error */
183
      return(-2); /* unexpected end of connection (while waiting for all http headers) */
183
      return(-2); /* unexpected end of connection (while waiting for all http headers) */
184
 
184
 
185
    } else { /* else no data received - look for timeout and release a cpu cycle */
185
    } else { /* else no data received - look for timeout and release a cpu cycle */
186
      if (time(NULL) - starttime > 20) return(-3); /* TIMEOUT! */
186
      if (time(NULL) - starttime > 20) return(-3); /* TIMEOUT! */
187
      _asm int 28h; /* release a CPU cycle */
187
      _asm int 28h; /* release a CPU cycle */
188
    }
188
    }
189
  }
189
  }
190
}
190
}
191
 
191
 
192
 
192
 
193
/* provides body data of the POST query for checkup actions
193
/* provides body data of the POST query for checkup actions
194
   fills buff with data and returns data length.
194
   fills buff with data and returns data length.
195
   must be called repeateadly until zero-lengh is returned */
195
   must be called repeateadly until zero-lengh is returned */
196
static unsigned short checkupdata(char *buff) {
196
static unsigned short checkupdata(char *buff) {
197
  static char *dosdir = NULL;
197
  static char *dosdir = NULL;
198
  static DIR *dp;
198
  static DIR *dp;
199
  static struct dirent *ep;
199
  static struct dirent *ep;
200
 
200
 
201
  /* make sure I know %DOSDIR% */
201
  /* make sure I know %DOSDIR% */
202
  if (dosdir == NULL) {
202
  if (dosdir == NULL) {
203
    dosdir = getenv("DOSDIR");
203
    dosdir = getenv("DOSDIR");
204
    if ((dosdir == NULL) || (dosdir[0] == 0)) {
204
    if ((dosdir == NULL) || (dosdir[0] == 0)) {
205
      putsnls(9, 0); /* "ERROR: %DOSDIR% not set" */
205
      putsnls(9, 0); /* "ERROR: %DOSDIR% not set" */
206
      return(0);
206
      return(0);
207
    }
207
    }
208
  }
208
  }
209
 
209
 
210
  /* if first call, open the package directory */
210
  /* if first call, open the package directory */
211
  if (dp == NULL) {
211
  if (dp == NULL) {
212
    sprintf(buff, "%s\\packages", dosdir);
212
    sprintf(buff, "%s\\packages", dosdir);
213
    dp = opendir(buff);
213
    dp = opendir(buff);
214
    if (dp == NULL) {
214
    if (dp == NULL) {
215
      putsnls(9, 1); /* "ERROR: Could not access %DOSDIR%\\packages directory" */
215
      putsnls(9, 1); /* "ERROR: Could not access %DOSDIR%\\packages directory" */
216
      return(0);
216
      return(0);
217
    }
217
    }
218
  }
218
  }
219
 
219
 
220
  for (;;) {
220
  for (;;) {
221
    int tlen;
221
    int tlen;
222
    char ver[20];
222
    char ver[20];
223
    ep = readdir(dp);   /* readdir() result must never be freed (statically allocated) */
223
    ep = readdir(dp);   /* readdir() result must never be freed (statically allocated) */
224
    if (ep == NULL) {   /* no more entries */
224
    if (ep == NULL) {   /* no more entries */
225
      closedir(dp);
225
      closedir(dp);
226
      return(0);
226
      return(0);
227
    }
227
    }
228
 
228
 
229
    tlen = strlen(ep->d_name);
229
    tlen = strlen(ep->d_name);
230
    if (tlen < 4) continue; /* files must be at least 5 bytes long ("x.lst") */
230
    if (tlen < 4) continue; /* files must be at least 5 bytes long ("x.lst") */
231
    if (strcasecmp(ep->d_name + tlen - 4, ".lst") != 0) continue;  /* if not an .lst file, skip it silently */
231
    if (strcasecmp(ep->d_name + tlen - 4, ".lst") != 0) continue;  /* if not an .lst file, skip it silently */
232
    ep->d_name[tlen - 4] = 0; /* trim out the ".lst" suffix */
232
    ep->d_name[tlen - 4] = 0; /* trim out the ".lst" suffix */
233
 
233
 
234
    /* load the metadata from %DOSDIR\APPINFO\*.lsm */
234
    /* load the metadata from %DOSDIR\APPINFO\*.lsm */
235
    sprintf(buff, "%s\\appinfo\\%s.lsm", dosdir, ep->d_name);
235
    sprintf(buff, "%s\\appinfo\\%s.lsm", dosdir, ep->d_name);
236
    readlsm(buff, ver, sizeof(ver));
236
    readlsm(buff, ver, sizeof(ver));
237
 
237
 
238
    return(sprintf(buff, "%s\t%s\n", ep->d_name, ver));
238
    return(sprintf(buff, "%s\t%s\n", ep->d_name, ver));
239
  }
239
  }
240
}
240
}
241
 
241
 
242
 
242
 
243
/* fetch http data from ipaddr using url
243
/* fetch http data from ipaddr using url
244
 * write result to file outfname if not null, or print to stdout otherwise
244
 * write result to file outfname if not null, or print to stdout otherwise
245
 * fills bsum with the BSD sum of the data
245
 * fills bsum with the BSD sum of the data
246
 * is ispost is non-zero, then the request is a POST and its body data is
246
 * is ispost is non-zero, then the request is a POST and its body data is
247
 * obtained through repeated calls to checkupdata()
247
 * obtained through repeated calls to checkupdata()
248
 * returns the length of data obtained, or neg value on error */
248
 * returns the length of data obtained, or neg value on error */
249
static long htget(const char *ipaddr, const char *url, const char *outfname, unsigned short *bsum, int ispost, unsigned char *buffer, size_t buffersz, unsigned short tcpbuflen) {
249
static long htget(const char *ipaddr, const char *url, const char *outfname, unsigned short *bsum, int ispost, unsigned char *buffer, size_t buffersz, unsigned short tcpbuflen) {
250
  struct net_tcpsocket *sock;
250
  struct net_tcpsocket *sock;
251
  time_t lastactivity, lastprogressoutput = 0;
251
  time_t lastactivity, lastprogressoutput = 0;
252
  int httpcode = -1, ischunked = 0;
252
  int httpcode = -1, ischunked = 0;
253
  int byteread;
253
  int byteread;
254
  long flen = 0, lastflen = 0;
254
  long flen = 0, lastflen = 0;
255
  FILE *fd = NULL;
255
  FILE *fd = NULL;
256
 
256
 
257
  /* unchunk state variable is using a little part of the supplied buffer */
257
  /* unchunk state variable is using a little part of the supplied buffer */
258
  struct unchunk_state *unchstate = (void *)buffer;
258
  struct unchunk_state *unchstate = (void *)buffer;
259
  buffer += sizeof(*unchstate);
259
  buffer += sizeof(*unchstate);
260
  buffersz -= sizeof(*unchstate);
260
  buffersz -= sizeof(*unchstate);
261
  memset(unchstate, 0, sizeof(*unchstate));
261
  memset(unchstate, 0, sizeof(*unchstate));
262
 
262
 
263
  sock = net_connect(ipaddr, 80, tcpbuflen);
263
  sock = net_connect(ipaddr, 80, tcpbuflen);
264
  if (sock == NULL) {
264
  if (sock == NULL) {
265
    printf(svarlang_strid(0x0902), HOSTADDR); /* "ERROR: failed to connect to " HOSTADDR */
265
    printf(svarlang_strid(0x0902), HOSTADDR); /* "ERROR: failed to connect to " HOSTADDR */
266
    puts("");
266
    puts("");
267
    goto SHITQUIT;
267
    goto SHITQUIT;
268
  }
268
  }
269
 
269
 
270
  /* wait for net_connect() to actually connect */
270
  /* wait for net_connect() to actually connect */
271
  for (;;) {
271
  for (;;) {
272
    int connstate = net_isconnected(sock);
272
    int connstate = net_isconnected(sock);
273
    if (connstate > 0) break;
273
    if (connstate > 0) break;
274
    if (connstate < 0) {
274
    if (connstate < 0) {
275
      printf(svarlang_strid(0x0902), HOSTADDR); /* "ERROR: failed to connect to " HOSTADDR */
275
      printf(svarlang_strid(0x0902), HOSTADDR); /* "ERROR: failed to connect to " HOSTADDR */
276
      puts("");
276
      puts("");
277
      goto SHITQUIT;
277
      goto SHITQUIT;
278
    }
278
    }
279
    _asm int 28h;  /* DOS idle */
279
    _asm int 28h;  /* DOS idle */
280
  }
280
  }
281
 
281
 
282
  /* socket is connected - send the http request (MUST be HTTP/1.0 because I do not support chunked transfers!) */
282
  /* socket is connected - send the http request (MUST be HTTP/1.0 because I do not support chunked transfers!) */
283
  if (ispost) {
283
  if (ispost) {
284
    snprintf((char *)buffer, buffersz, "POST %s HTTP/1.1\r\nHOST: " HOSTADDR "\r\nUSER-AGENT: pkgnet/" PVER "\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n", url);
284
    snprintf((char *)buffer, buffersz, "POST %s HTTP/1.1\r\nHOST: " HOSTADDR "\r\nUSER-AGENT: pkgnet/" PVER "\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n", url);
285
  } else {
285
  } else {
286
    snprintf((char *)buffer, buffersz, "GET %s HTTP/1.1\r\nHOST: " HOSTADDR "\r\nUSER-AGENT: pkgnet/" PVER "\r\nConnection: close\r\n\r\n", url);
286
    snprintf((char *)buffer, buffersz, "GET %s HTTP/1.1\r\nHOST: " HOSTADDR "\r\nUSER-AGENT: pkgnet/" PVER "\r\nConnection: close\r\n\r\n", url);
287
  }
287
  }
288
 
288
 
289
  if (net_send(sock, buffer, strlen((char *)buffer)) != (int)strlen((char *)buffer)) {
289
  if (net_send(sock, buffer, strlen((char *)buffer)) != (int)strlen((char *)buffer)) {
290
    putsnls(9, 3); /* "ERROR: failed to send a HTTP query to remote server" */
290
    putsnls(9, 3); /* "ERROR: failed to send a HTTP query to remote server" */
291
    goto SHITQUIT;
291
    goto SHITQUIT;
292
  }
292
  }
293
 
293
 
294
  /* send chunked data for POST queries */
294
  /* send chunked data for POST queries */
295
  if (ispost) {
295
  if (ispost) {
296
    unsigned short blen;
296
    unsigned short blen;
297
    int hlen;
297
    int hlen;
298
    char *hbuf = (char *)buffer + buffersz - 16;
298
    char *hbuf = (char *)buffer + buffersz - 16;
299
    do {
299
    do {
300
      blen = checkupdata((char *)buffer);
300
      blen = checkupdata((char *)buffer);
301
      if (blen == 0) { /* last item contains the message trailer */
301
      if (blen == 0) { /* last item contains the message trailer */
302
        hlen = sprintf(hbuf, "0\r\n");
302
        hlen = sprintf(hbuf, "0\r\n");
303
      } else {
303
      } else {
304
        hlen = sprintf(hbuf, "%X\r\n", blen);
304
        hlen = sprintf(hbuf, "%X\r\n", blen);
305
      }
305
      }
306
      if (net_send(sock, hbuf, hlen) != hlen) {
306
      if (net_send(sock, hbuf, hlen) != hlen) {
307
        putsnls(9, 4); /* "ERROR: failed to send POST data to remote server" */
307
        putsnls(9, 4); /* "ERROR: failed to send POST data to remote server" */
308
        goto SHITQUIT;
308
        goto SHITQUIT;
309
      }
309
      }
310
      /* add trailing CR/LF to buffer as required by chunked mode */
310
      /* add trailing CR/LF to buffer as required by chunked mode */
311
      buffer[blen++] = '\r';
311
      buffer[blen++] = '\r';
312
      buffer[blen++] = '\n';
312
      buffer[blen++] = '\n';
313
      if (net_send(sock, buffer, blen) != blen) {
313
      if (net_send(sock, buffer, blen) != blen) {
314
        putsnls(9, 4); /* "ERROR: failed to send POST data to remote server" */
314
        putsnls(9, 4); /* "ERROR: failed to send POST data to remote server" */
315
        goto SHITQUIT;
315
        goto SHITQUIT;
316
      }
316
      }
317
    } while (blen != 2);
317
    } while (blen != 2);
318
  }
318
  }
319
 
319
 
320
  /* receive and process HTTP headers */
320
  /* receive and process HTTP headers */
321
  byteread = htget_headers(buffer, buffersz, sock, &httpcode, &ischunked);
321
  byteread = htget_headers(buffer, buffersz, sock, &httpcode, &ischunked);
322
 
322
 
323
  /* transmission error? */
323
  /* transmission error? */
324
  if (byteread < 0) {
324
  if (byteread < 0) {
325
    printf(svarlang_strid(0x0905), byteread); /* "ERROR: TCP communication error #%d" */
325
    printf(svarlang_strid(0x0905), byteread); /* "ERROR: TCP communication error #%d" */
326
    puts("");
326
    puts("");
327
    goto SHITQUIT;
327
    goto SHITQUIT;
328
  }
328
  }
329
 
329
 
330
  /* open destination file if required and if no server-side error occured */
330
  /* open destination file if required and if no server-side error occured */
331
  if ((httpcode >= 200) && (httpcode <= 299) && (*outfname != 0)) {
331
  if ((httpcode >= 200) && (httpcode <= 299) && (*outfname != 0)) {
332
    fd = fopen(outfname, "wb");
332
    fd = fopen(outfname, "wb");
333
    if (fd == NULL) {
333
    if (fd == NULL) {
334
      printf(svarlang_strid(0x0906), outfname); /* "ERROR: failed to create file %s" */
334
      printf(svarlang_strid(0x0906), outfname); /* "ERROR: failed to create file %s" */
335
      puts("");
335
      puts("");
336
      goto SHITQUIT;
336
      goto SHITQUIT;
337
    }
337
    }
338
  }
338
  }
339
 
339
 
340
  /* read body of the answer (and chunk-decode it if needed) */
340
  /* read body of the answer (and chunk-decode it if needed) */
341
  lastactivity = time(NULL);
341
  lastactivity = time(NULL);
342
  for (;; byteread = net_recv(sock, buffer, buffersz - 1)) { /* read 1 byte less because I need to append a NULL terminator when printf'ing the content */
342
  for (;; byteread = net_recv(sock, buffer, buffersz - 1)) { /* read 1 byte less because I need to append a NULL terminator when printf'ing the content */
343
 
343
 
344
    if (byteread > 0) { /* got data */
344
    if (byteread > 0) { /* got data */
345
      lastactivity = time(NULL);
345
      lastactivity = time(NULL);
346
 
346
 
347
      /* unpack data if server transmits in chunked mode */
347
      /* unpack data if server transmits in chunked mode */
348
      if (ischunked) byteread = unchunk(buffer, byteread, unchstate);
348
      if (ischunked) byteread = unchunk(buffer, byteread, unchstate);
349
 
349
 
350
      /* if downloading to file, write stuff to disk */
350
      /* if downloading to file, write stuff to disk */
351
      if (fd != NULL) {
351
      if (fd != NULL) {
352
        int i;
352
        int i;
353
        if (fwrite(buffer, 1, byteread, fd) != byteread) {
353
        if (fwrite(buffer, 1, byteread, fd) != byteread) {
354
          printf(svarlang_strid(0x0907), outfname, flen); /* "ERROR: failed to write data to file %s after %ld bytes" */
354
          printf(svarlang_strid(0x0907), outfname, flen); /* "ERROR: failed to write data to file %s after %ld bytes" */
355
          puts("");
355
          puts("");
356
          break;
356
          break;
357
        }
357
        }
358
        flen += byteread;
358
        flen += byteread;
359
        /* update progress once a sec */
359
        /* update progress once a sec */
360
        if (lastprogressoutput != lastactivity) {
360
        if (lastprogressoutput != lastactivity) {
361
          lastprogressoutput = lastactivity;
361
          lastprogressoutput = lastactivity;
362
          printf("%ld KiB (%ld KiB/s)     \r", flen >> 10, (flen >> 10) - (lastflen >> 10)); /* trailing spaces are meant to avoid leaving garbage on screen if speed goes from, say, 1000 KiB/s to 9 KiB/s */
362
          printf("%ld KiB (%ld KiB/s)     \r", flen >> 10, (flen >> 10) - (lastflen >> 10)); /* trailing spaces are meant to avoid leaving garbage on screen if speed goes from, say, 1000 KiB/s to 9 KiB/s */
363
          lastflen = flen;
363
          lastflen = flen;
364
          fflush(stdout); /* avoid console buffering */
364
          fflush(stdout); /* avoid console buffering */
365
        }
365
        }
366
        /* update the bsd sum */
366
        /* update the bsd sum */
367
        for (i = 0; i < byteread; i++) {
367
        for (i = 0; i < byteread; i++) {
368
          /* rotr16 */
368
          /* rotr16 */
369
          unsigned short bsumlsb = *bsum & 1;
369
          unsigned short bsumlsb = *bsum & 1;
370
          *bsum >>= 1;
370
          *bsum >>= 1;
371
          *bsum |= (bsumlsb << 15);
371
          *bsum |= (bsumlsb << 15);
372
          *bsum += buffer[i];
372
          *bsum += buffer[i];
373
        }
373
        }
374
      } else { /* otherwise dump to screen */
374
      } else { /* otherwise dump to screen */
375
        buffer[byteread] = 0;
375
        buffer[byteread] = 0;
376
        printf("%s", buffer);
376
        printf("%s", buffer);
377
      }
377
      }
378
 
378
 
379
    } else if (byteread < 0) { /* end of connection */
379
    } else if (byteread < 0) { /* end of connection */
380
      break;
380
      break;
381
 
381
 
382
    } else { /* check for timeout (byteread == 0) */
382
    } else { /* check for timeout (byteread == 0) */
383
      if (time(NULL) - lastactivity > 20) { /* TIMEOUT! */
383
      if (time(NULL) - lastactivity > 20) { /* TIMEOUT! */
384
        putsnls(9, 8); /* "ERROR: Timeout while waiting for data" */
384
        putsnls(9, 8); /* "ERROR: Timeout while waiting for data" */
385
        goto SHITQUIT;
385
        goto SHITQUIT;
386
      }
386
      }
387
      /* waiting for packets - release a CPU cycle in the meantime */
387
      /* waiting for packets - release a CPU cycle in the meantime */
388
      _asm int 28h;
388
      _asm int 28h;
389
    }
389
    }
390
  }
390
  }
391
 
391
 
392
  goto ALLGOOD;
392
  goto ALLGOOD;
393
 
393
 
394
  SHITQUIT:
394
  SHITQUIT:
395
  flen = -1;
395
  flen = -1;
396
 
396
 
397
  ALLGOOD:
397
  ALLGOOD:
398
  if (fd != NULL) fclose(fd);
398
  if (fd != NULL) fclose(fd);
399
  net_close(sock);
399
  net_close(sock);
400
  return(flen);
400
  return(flen);
401
}
401
}
402
 
402
 
403
 
403
 
404
/* checks if file exists, returns 0 if not, non-zero otherwise */
404
/* checks if file exists, returns 0 if not, non-zero otherwise */
405
static int fexists(const char *fname) {
405
static int fexists(const char *fname) {
406
  FILE *fd = fopen(fname, "rb");
406
  FILE *fd = fopen(fname, "rb");
407
  if (fd == NULL) return(0);
407
  if (fd == NULL) return(0);
408
  fclose(fd);
408
  fclose(fd);
409
  return(1);
409
  return(1);
410
}
410
}
411
 
411
 
412
 
412
 
413
int main(int argc, char **argv) {
413
int main(int argc, char **argv) {
414
  unsigned short bsum = 0;
414
  unsigned short bsum = 0;
415
  long flen;
415
  long flen;
416
  unsigned short tcpbufsz = (16 * 1024);
416
  unsigned short tcpbufsz = (16 * 1024);
417
  int ispost; /* is the request a POST? */
417
  int ispost; /* is the request a POST? */
418
 
418
 
419
  struct {
419
  struct {
420
    unsigned char buffer[5000];
420
    unsigned char buffer[5000];
421
    char ipaddr[64];
421
    char ipaddr[64];
422
    char url[64];
422
    char url[64];
423
    char outfname[16];
423
    char outfname[16];
424
  } *mem;
424
  } *mem;
425
 
425
 
426
  svarlang_autoload("PKGNET");
426
  svarlang_autoload("PKGNET");
427
 
427
 
428
  /* look for PKGNETBUFSZ env var to size up the buffer (default=5000 bytes) */
428
  /* look for PKGNETBUFSZ env var to size up the buffer (default=5000 bytes) */
429
  {
429
  {
430
    const char *ptr = getenv("PKGNETBUFSZ");
430
    const char *ptr = getenv("PKGNETBUFSZ");
431
    if (ptr != NULL) {
431
    if (ptr != NULL) {
432
      long newsz = atol(ptr);
432
      long newsz = atol(ptr);
433
      if ((newsz >= 0) && (newsz < 65500)) {
433
      if ((newsz >= 0) && (newsz < 65500)) {
434
        tcpbufsz = newsz;
434
        tcpbufsz = newsz;
435
        printf("WILL USE CUSTOM TCP BUFF SIZE = %u\r\n", tcpbufsz);
435
        printf("WILL USE CUSTOM TCP BUFF SIZE = %u\r\n", tcpbufsz);
436
      }
436
      }
437
    }
437
    }
438
  }
438
  }
439
 
439
 
440
  /* allocate memory */
440
  /* allocate memory */
441
  mem = malloc(sizeof(*mem));
441
  mem = malloc(sizeof(*mem));
442
  if (mem == NULL) {
442
  if (mem == NULL) {
443
    putsnls(9, 9); /* "ERROR: out of memory" */
443
    putsnls(9, 9); /* "ERROR: out of memory" */
444
    return(1);
444
    return(1);
445
  }
445
  }
446
 
446
 
447
  /* parse command line arguments */
447
  /* parse command line arguments */
448
  if (parseargv(argc, argv, mem->outfname, mem->url, &ispost) != 0) return(1);
448
  if (parseargv(argc, argv, mem->outfname, mem->url, &ispost) != 0) return(1);
449
 
449
 
450
  /* if outfname requested, make sure that file does not exist yet */
450
  /* if outfname requested, make sure that file does not exist yet */
451
  if ((mem->outfname[0] != 0) && (fexists(mem->outfname))) {
451
  if ((mem->outfname[0] != 0) && (fexists(mem->outfname))) {
452
    printf(svarlang_strid(0x090A), mem->outfname); /* "ERROR: file %s already exists" */
452
    printf(svarlang_strid(0x090A), mem->outfname); /* "ERROR: file %s already exists" */
453
    puts("");
453
    puts("");
454
    return(1);
454
    return(1);
455
  }
455
  }
456
 
456
 
457
  /* init network stack */
457
  /* init network stack */
458
  if (net_init() != 0) {
458
  if (net_init() != 0) {
459
    putsnls(9, 11); /* "ERROR: Network subsystem initialization failed" */
459
    putsnls(9, 11); /* "ERROR: Network subsystem initialization failed" */
460
    return(1);
460
    return(1);
461
  }
461
  }
462
 
462
 
463
  puts(""); /* required because watt-32 likes to print out garbage sometimes ("configuring through DHCP...") */
463
  puts(""); /* required because watt-32 likes to print out garbage sometimes ("configuring through DHCP...") */
464
 
464
 
465
  if (net_dnsresolve(mem->ipaddr, HOSTADDR, 2) != 0) {
465
  if (net_dnsresolve(mem->ipaddr, HOSTADDR, 2) != 0) {
466
    putsnls(9, 12); /* "ERROR: DNS resolution failed" */
466
    putsnls(9, 12); /* "ERROR: DNS resolution failed" */
467
    return(1);
467
    return(1);
468
  }
468
  }
469
 
469
 
470
  flen = htget(mem->ipaddr, mem->url, mem->outfname, &bsum, ispost, mem->buffer, sizeof(mem->buffer), tcpbufsz);
470
  flen = htget(mem->ipaddr, mem->url, mem->outfname, &bsum, ispost, mem->buffer, sizeof(mem->buffer), tcpbufsz);
471
  if (flen < 1) return(1);
471
  if (flen < 1) return(1);
472
 
472
 
473
  if (mem->outfname[0] != 0) {
473
  if (mem->outfname[0] != 0) {
474
    /* print bsum, size, filename */
474
    /* print bsum, size, filename */
475
    printf(svarlang_strid(0x0200), flen >> 10, mem->outfname, bsum); /* Downloaded %ld KiB into %s (BSUM: %04X) */
475
    printf(svarlang_strid(0x0200), flen >> 10, mem->outfname, bsum); /* Downloaded %ld KiB into %s (BSUM: %04X) */
476
    puts("");
476
    puts("");
477
  }
477
  }
478
 
478
 
479
  return(0);
479
  return(0);
480
}
480
}
481
 
481