Subversion Repositories SvarDOS

Rev

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

Rev 725 Rev 726
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-2022 MATEUSZ VISTE, ALL RIGHTS RESERVED.
6
 * COPYRIGHT (C) 2016-2022 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 "20220216"
43
#define PVER "20220216"
44
#define PDATE "2021-2022"
44
#define PDATE "2021-2022"
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, 6);  /* "        pkgnet checkup" */
83
  putsnls(1, 6);  /* "        pkgnet checkup" */
84
  puts("");
84
  puts("");
85
  putsnls(1, 7);  /* "actions:" */
85
  putsnls(1, 7);  /* "actions:" */
86
  puts("");
86
  puts("");
87
  putsnls(1, 8);  /* "search   - asks remote repository for the list of matching packages" */
87
  putsnls(1, 8);  /* "search   - asks remote repository for the list of matching packages" */
88
  putsnls(1, 9);  /* "pull     - downloads package into current directory" */
88
  putsnls(1, 9);  /* "pull     - downloads package into current directory" */
89
  putsnls(1, 10); /* "checkup  - lists updates available for your system" */
89
  putsnls(1, 10); /* "checkup  - lists updates available for your system" */
90
  puts("");
90
  puts("");
91
  printf("Watt32 kernel: %s", net_engine());
91
  printf("Watt32 kernel: %s", net_engine());
92
  puts("");
92
  puts("");
93
}
93
}
94
 
94
 
95
 
95
 
96
/* parses command line arguments and fills outfname and url accordingly
96
/* parses command line arguments and fills outfname and url accordingly
97
 * returns 0 on success, non-zero otherwise */
97
 * returns 0 on success, non-zero otherwise */
98
static int parseargv(int argc, char * const *argv, char *outfname, char *url, int *ispost) {
98
static int parseargv(int argc, char * const *argv, char *outfname, char *url, int *ispost) {
99
  const char *lang = getenv("LANG");
99
  const char *lang = getenv("LANG");
100
  if (lang == NULL) lang = "";
100
  if (lang == NULL) lang = "";
101
  *outfname = 0;
101
  *outfname = 0;
102
  *url = 0;
102
  *url = 0;
103
  *ispost = 0;
103
  *ispost = 0;
104
  if ((argc == 3) && (strcasecmp(argv[1], "search") == 0)) {
104
  if ((argc == 3) && (strcasecmp(argv[1], "search") == 0)) {
105
    sprintf(url, "/repo/?a=search&p=%s&lang=%s", argv[2], lang);
105
    sprintf(url, "/repo/?a=search&p=%s&lang=%s", argv[2], lang);
106
  } else if ((argc == 3) && (strcasecmp(argv[1], "pull") == 0)) {
106
  } else if ((argc == 3) && (strcasecmp(argv[1], "pull") == 0)) {
107
    unsigned short i;
107
    unsigned short i;
108
    sprintf(url, "/repo/?a=pull&p=%s&lang=%s", argv[2], lang);
108
    sprintf(url, "/repo/?a=pull&p=%s&lang=%s", argv[2], lang);
109
    /* copy argv[2] into outfname, but stop at first '-' or null terminator
109
    /* copy argv[2] into outfname, but stop at first '-' or null terminator
110
     * this trims any '-version' part in filename to respect 8+3 */
110
     * this trims any '-version' part in filename to respect 8+3 */
111
    for (i = 0; (argv[2][i] != 0) && (argv[2][i] != '-') && (i < 8); i++) {
111
    for (i = 0; (argv[2][i] != 0) && (argv[2][i] != '-') && (i < 8); i++) {
112
      outfname[i] = argv[2][i];
112
      outfname[i] = argv[2][i];
113
    }
113
    }
114
    /* add the svp extension to filename */
114
    /* add the svp extension to filename */
115
    strcpy(outfname + i, ".svp");
115
    strcpy(outfname + i, ".svp");
116
  } else if ((argc == 2) && (strcasecmp(argv[1], "checkup") == 0)) {
116
  } else if ((argc == 2) && (strcasecmp(argv[1], "checkup") == 0)) {
117
    sprintf(url, "/repo/?a=checkup&lang=%s", lang);
117
    sprintf(url, "/repo/?a=checkup&lang=%s", lang);
118
    *ispost = 1;
118
    *ispost = 1;
119
  } else {
119
  } else {
120
    help();
120
    help();
121
    return(-1);
121
    return(-1);
122
  }
122
  }
123
  return(0);
123
  return(0);
124
}
124
}
125
 
125
 
126
 
126
 
127
static int htget_headers(unsigned char *buffer, size_t buffersz, struct net_tcpsocket *sock, int *httpcode, int *ischunked)  {
127
static int htget_headers(unsigned char *buffer, size_t buffersz, struct net_tcpsocket *sock, int *httpcode, int *ischunked)  {
128
  unsigned char *buffptr = buffer;
128
  unsigned char *buffptr = buffer;
129
  unsigned short bufflen = 0;
129
  unsigned short bufflen = 0;
130
  int byteread;
130
  int byteread;
131
  time_t starttime = time(NULL);
131
  time_t starttime = time(NULL);
132
  for (;;) {
132
  for (;;) {
133
    byteread = net_recv(sock, buffptr, buffersz - (bufflen + 1)); /* -1 because I will append a NULL terminator */
133
    byteread = net_recv(sock, buffptr, buffersz - (bufflen + 1)); /* -1 because I will append a NULL terminator */
134
 
134
 
135
    if (byteread > 0) { /* got data */
135
    if (byteread > 0) { /* got data */
136
      int hdlen;
136
      int hdlen;
137
      bufflen += byteread;
137
      bufflen += byteread;
138
      buffptr += byteread;
138
      buffptr += byteread;
139
      buffer[bufflen] = 0;
139
      buffer[bufflen] = 0;
140
      hdlen = detecthttpheadersend(buffer);
140
      hdlen = detecthttpheadersend(buffer);
141
      if (hdlen > 0) { /* full headers - parse http code and continue processing */
141
      if (hdlen > 0) { /* full headers - parse http code and continue processing */
142
        int i;
142
        int i;
143
        buffer[hdlen - 1] = 0;
143
        buffer[hdlen - 1] = 0;
144
        /* find the first space (HTTP/1.1 200 OK) */
144
        /* find the first space (HTTP/1.1 200 OK) */
145
        for (i = 0; i < 16; i++) {
145
        for (i = 0; i < 16; i++) {
146
          if ((buffer[i] == ' ') || (buffer[i] == 0)) break;
146
          if ((buffer[i] == ' ') || (buffer[i] == 0)) break;
147
        }
147
        }
148
        if (buffer[i] != ' ') return(-1);
148
        if (buffer[i] != ' ') return(-1);
149
        *httpcode = atoi((char *)(buffer + i + 1));
149
        *httpcode = atoi((char *)(buffer + i + 1));
150
        /* switch all headers to low-case so it is easier to parse them */
150
        /* switch all headers to low-case so it is easier to parse them */
151
        for (i = 0; i < hdlen; i++) if ((buffer[i] >= 'A') && (buffer[i] <= 'Z')) buffer[i] += ('a' - 'A');
151
        for (i = 0; i < hdlen; i++) if ((buffer[i] >= 'A') && (buffer[i] <= 'Z')) buffer[i] += ('a' - 'A');
152
        /* look out for chunked transfer encoding */
152
        /* look out for chunked transfer encoding */
153
        {
153
        {
154
        char *lineptr = strstr((char *)buffer, "\ntransfer-encoding:");
154
        char *lineptr = strstr((char *)buffer, "\ntransfer-encoding:");
155
        if (lineptr != NULL) {
155
        if (lineptr != NULL) {
156
          lineptr += 19;
156
          lineptr += 19;
157
          /* replace nearest \r, \n or 0 by 0 */
157
          /* replace nearest \r, \n or 0 by 0 */
158
          for (i = 0; ; i++) {
158
          for (i = 0; ; i++) {
159
            if ((lineptr[i] == '\r') || (lineptr[i] == '\n') || (lineptr[i] == 0)) {
159
            if ((lineptr[i] == '\r') || (lineptr[i] == '\n') || (lineptr[i] == 0)) {
160
              lineptr[i] = 0;
160
              lineptr[i] = 0;
161
              break;
161
              break;
162
            }
162
            }
163
          }
163
          }
164
          /* do I see the 'chunked' word? */
164
          /* do I see the 'chunked' word? */
165
          if (strstr((char *)lineptr, "chunked") != NULL) *ischunked = 1;
165
          if (strstr((char *)lineptr, "chunked") != NULL) *ischunked = 1;
166
        }
166
        }
167
        }
167
        }
168
        /* rewind the buffer */
168
        /* rewind the buffer */
169
        bufflen -= hdlen;
169
        bufflen -= hdlen;
170
        memmove(buffer, buffer + hdlen, bufflen);
170
        memmove(buffer, buffer + hdlen, bufflen);
171
        return(bufflen); /* move to body processing now */
171
        return(bufflen); /* move to body processing now */
172
      }
172
      }
173
 
173
 
174
    } else if (byteread < 0) { /* abort on error */
174
    } else if (byteread < 0) { /* abort on error */
175
      return(-2); /* unexpected end of connection (while waiting for all http headers) */
175
      return(-2); /* unexpected end of connection (while waiting for all http headers) */
176
 
176
 
177
    } else { /* else no data received - look for timeout and release a cpu cycle */
177
    } else { /* else no data received - look for timeout and release a cpu cycle */
178
      if (time(NULL) - starttime > 20) return(-3); /* TIMEOUT! */
178
      if (time(NULL) - starttime > 20) return(-3); /* TIMEOUT! */
179
      _asm int 28h; /* release a CPU cycle */
179
      _asm int 28h; /* release a CPU cycle */
180
    }
180
    }
181
  }
181
  }
182
}
182
}
183
 
183
 
184
 
184
 
185
/* provides body data of the POST query for checkup actions
185
/* provides body data of the POST query for checkup actions
186
   fills buff with data and returns data length.
186
   fills buff with data and returns data length.
187
   must be called repeateadly until zero-lengh is returned */
187
   must be called repeateadly until zero-lengh is returned */
188
static unsigned short checkupdata(char *buff) {
188
static unsigned short checkupdata(char *buff) {
189
  static char *dosdir = NULL;
189
  static char *dosdir = NULL;
190
  static DIR *dp;
190
  static DIR *dp;
191
  static struct dirent *ep;
191
  static struct dirent *ep;
192
 
192
 
193
  /* make sure I know %DOSDIR% */
193
  /* make sure I know %DOSDIR% */
194
  if (dosdir == NULL) {
194
  if (dosdir == NULL) {
195
    dosdir = getenv("DOSDIR");
195
    dosdir = getenv("DOSDIR");
196
    if ((dosdir == NULL) || (dosdir[0] == 0)) {
196
    if ((dosdir == NULL) || (dosdir[0] == 0)) {
197
      putsnls(9, 0); /* "ERROR: %DOSDIR% not set" */
197
      putsnls(9, 0); /* "ERROR: %DOSDIR% not set" */
198
      return(0);
198
      return(0);
199
    }
199
    }
200
  }
200
  }
201
 
201
 
202
  /* if first call, open the package directory */
202
  /* if first call, open the package directory */
203
  if (dp == NULL) {
203
  if (dp == NULL) {
204
    sprintf(buff, "%s\\packages", dosdir);
204
    sprintf(buff, "%s\\packages", dosdir);
205
    dp = opendir(buff);
205
    dp = opendir(buff);
206
    if (dp == NULL) {
206
    if (dp == NULL) {
207
      putsnls(9, 1); /* "ERROR: Could not access %DOSDIR%\\packages directory" */
207
      putsnls(9, 1); /* "ERROR: Could not access %DOSDIR%\\packages directory" */
208
      return(0);
208
      return(0);
209
    }
209
    }
210
  }
210
  }
211
 
211
 
212
  for (;;) {
212
  for (;;) {
213
    int tlen;
213
    int tlen;
214
    char ver[20];
214
    char ver[20];
215
    ep = readdir(dp);   /* readdir() result must never be freed (statically allocated) */
215
    ep = readdir(dp);   /* readdir() result must never be freed (statically allocated) */
216
    if (ep == NULL) {   /* no more entries */
216
    if (ep == NULL) {   /* no more entries */
217
      closedir(dp);
217
      closedir(dp);
218
      return(0);
218
      return(0);
219
    }
219
    }
220
 
220
 
221
    tlen = strlen(ep->d_name);
221
    tlen = strlen(ep->d_name);
222
    if (tlen < 4) continue; /* files must be at least 5 bytes long ("x.lst") */
222
    if (tlen < 4) continue; /* files must be at least 5 bytes long ("x.lst") */
223
    if (strcasecmp(ep->d_name + tlen - 4, ".lst") != 0) continue;  /* if not an .lst file, skip it silently */
223
    if (strcasecmp(ep->d_name + tlen - 4, ".lst") != 0) continue;  /* if not an .lst file, skip it silently */
224
    ep->d_name[tlen - 4] = 0; /* trim out the ".lst" suffix */
224
    ep->d_name[tlen - 4] = 0; /* trim out the ".lst" suffix */
225
 
225
 
226
    /* load the metadata from %DOSDIR\APPINFO\*.lsm */
226
    /* load the metadata from %DOSDIR\APPINFO\*.lsm */
227
    sprintf(buff, "%s\\appinfo\\%s.lsm", dosdir, ep->d_name);
227
    sprintf(buff, "%s\\appinfo\\%s.lsm", dosdir, ep->d_name);
228
    readlsm(buff, ver, sizeof(ver));
228
    readlsm(buff, ver, sizeof(ver));
229
 
229
 
230
    return(sprintf(buff, "%s\t%s\n", ep->d_name, ver));
230
    return(sprintf(buff, "%s\t%s\n", ep->d_name, ver));
231
  }
231
  }
232
}
232
}
233
 
233
 
234
 
234
 
235
/* fetch http data from ipaddr using url
235
/* fetch http data from ipaddr using url
236
 * write result to file outfname if not null, or print to stdout otherwise
236
 * write result to file outfname if not null, or print to stdout otherwise
237
 * fills bsum with the BSD sum of the data
237
 * fills bsum with the BSD sum of the data
238
 * is ispost is non-zero, then the request is a POST and its body data is
238
 * is ispost is non-zero, then the request is a POST and its body data is
239
 * obtained through repeated calls to checkupdata()
239
 * obtained through repeated calls to checkupdata()
240
 * returns the length of data obtained, or neg value on error */
240
 * returns the length of data obtained, or neg value on error */
241
static long htget(const char *ipaddr, const char *url, const char *outfname, unsigned short *bsum, int ispost, unsigned char *buffer, size_t buffersz) {
241
static long htget(const char *ipaddr, const char *url, const char *outfname, unsigned short *bsum, int ispost, unsigned char *buffer, size_t buffersz) {
242
  struct net_tcpsocket *sock;
242
  struct net_tcpsocket *sock;
243
  time_t lastactivity, lastprogressoutput = 0;
243
  time_t lastactivity, lastprogressoutput = 0;
244
  int httpcode = -1, ischunked = 0;
244
  int httpcode = -1, ischunked = 0;
245
  int byteread;
245
  int byteread;
246
  long flen = 0, lastflen = 0;
246
  long flen = 0, lastflen = 0;
247
  FILE *fd = NULL;
247
  FILE *fd = NULL;
248
 
248
 
249
  /* unchunk state variable is using a little part of the supplied buffer */
249
  /* unchunk state variable is using a little part of the supplied buffer */
250
  struct unchunk_state *unchstate = (void *)buffer;
250
  struct unchunk_state *unchstate = (void *)buffer;
251
  buffer += sizeof(*unchstate);
251
  buffer += sizeof(*unchstate);
252
  buffersz -= sizeof(*unchstate);
252
  buffersz -= sizeof(*unchstate);
253
  memset(unchstate, 0, sizeof(*unchstate));
253
  memset(unchstate, 0, sizeof(*unchstate));
254
 
254
 
255
  sock = net_connect(ipaddr, 80);
255
  sock = net_connect(ipaddr, 80);
256
  if (sock == NULL) {
256
  if (sock == NULL) {
257
    printf(svarlang_strid(0x0902), HOSTADDR); /* "ERROR: failed to connect to " HOSTADDR */
257
    printf(svarlang_strid(0x0902), HOSTADDR); /* "ERROR: failed to connect to " HOSTADDR */
258
    puts("");
258
    puts("");
259
    goto SHITQUIT;
259
    goto SHITQUIT;
260
  }
260
  }
261
 
261
 
262
  /* wait for net_connect() to actually connect */
262
  /* wait for net_connect() to actually connect */
263
  for (;;) {
263
  for (;;) {
264
    int connstate = net_isconnected(sock);
264
    int connstate = net_isconnected(sock);
265
    if (connstate > 0) break;
265
    if (connstate > 0) break;
266
    if (connstate < 0) {
266
    if (connstate < 0) {
267
      printf(svarlang_strid(0x0902), HOSTADDR); /* "ERROR: failed to connect to " HOSTADDR */
267
      printf(svarlang_strid(0x0902), HOSTADDR); /* "ERROR: failed to connect to " HOSTADDR */
268
      puts("");
268
      puts("");
269
      goto SHITQUIT;
269
      goto SHITQUIT;
270
    }
270
    }
271
    _asm int 28h;  /* DOS idle */
271
    _asm int 28h;  /* DOS idle */
272
  }
272
  }
273
 
273
 
274
  /* socket is connected - send the http request (MUST be HTTP/1.0 because I do not support chunked transfers!) */
274
  /* socket is connected - send the http request (MUST be HTTP/1.0 because I do not support chunked transfers!) */
275
  if (ispost) {
275
  if (ispost) {
276
    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);
276
    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);
277
  } else {
277
  } else {
278
    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);
278
    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);
279
  }
279
  }
280
 
280
 
281
  if (net_send(sock, buffer, strlen((char *)buffer)) != (int)strlen((char *)buffer)) {
281
  if (net_send(sock, buffer, strlen((char *)buffer)) != (int)strlen((char *)buffer)) {
282
    putsnls(9, 3); /* "ERROR: failed to send a HTTP query to remote server" */
282
    putsnls(9, 3); /* "ERROR: failed to send a HTTP query to remote server" */
283
    goto SHITQUIT;
283
    goto SHITQUIT;
284
  }
284
  }
285
 
285
 
286
  /* send chunked data for POST queries */
286
  /* send chunked data for POST queries */
287
  if (ispost) {
287
  if (ispost) {
288
    unsigned short blen;
288
    unsigned short blen;
289
    int hlen;
289
    int hlen;
290
    char *hbuf = (char *)buffer + buffersz - 16;
290
    char *hbuf = (char *)buffer + buffersz - 16;
291
    do {
291
    do {
292
      blen = checkupdata((char *)buffer);
292
      blen = checkupdata((char *)buffer);
293
      if (blen == 0) { /* last item contains the message trailer */
293
      if (blen == 0) { /* last item contains the message trailer */
294
        hlen = sprintf(hbuf, "0\r\n");
294
        hlen = sprintf(hbuf, "0\r\n");
295
      } else {
295
      } else {
296
        hlen = sprintf(hbuf, "%X\r\n", blen);
296
        hlen = sprintf(hbuf, "%X\r\n", blen);
297
      }
297
      }
298
      if (net_send(sock, hbuf, hlen) != hlen) {
298
      if (net_send(sock, hbuf, hlen) != hlen) {
299
        putsnls(9, 4); /* "ERROR: failed to send POST data to remote server" */
299
        putsnls(9, 4); /* "ERROR: failed to send POST data to remote server" */
300
        goto SHITQUIT;
300
        goto SHITQUIT;
301
      }
301
      }
302
      /* add trailing CR/LF to buffer as required by chunked mode */
302
      /* add trailing CR/LF to buffer as required by chunked mode */
303
      buffer[blen++] = '\r';
303
      buffer[blen++] = '\r';
304
      buffer[blen++] = '\n';
304
      buffer[blen++] = '\n';
305
      if (net_send(sock, buffer, blen) != blen) {
305
      if (net_send(sock, buffer, blen) != blen) {
306
        putsnls(9, 4); /* "ERROR: failed to send POST data to remote server" */
306
        putsnls(9, 4); /* "ERROR: failed to send POST data to remote server" */
307
        goto SHITQUIT;
307
        goto SHITQUIT;
308
      }
308
      }
309
    } while (blen != 2);
309
    } while (blen != 2);
310
  }
310
  }
311
 
311
 
312
  /* receive and process HTTP headers */
312
  /* receive and process HTTP headers */
313
  byteread = htget_headers(buffer, buffersz, sock, &httpcode, &ischunked);
313
  byteread = htget_headers(buffer, buffersz, sock, &httpcode, &ischunked);
314
 
314
 
315
  /* transmission error? */
315
  /* transmission error? */
316
  if (byteread < 0) {
316
  if (byteread < 0) {
317
    printf(svarlang_strid(0x0905), byteread); /* "ERROR: TCP communication error #%d" */
317
    printf(svarlang_strid(0x0905), byteread); /* "ERROR: TCP communication error #%d" */
318
    puts("");
318
    puts("");
319
    goto SHITQUIT;
319
    goto SHITQUIT;
320
  }
320
  }
321
 
321
 
322
  /* open destination file if required and if no server-side error occured */
322
  /* open destination file if required and if no server-side error occured */
323
  if ((httpcode >= 200) && (httpcode <= 299) && (*outfname != 0)) {
323
  if ((httpcode >= 200) && (httpcode <= 299) && (*outfname != 0)) {
324
    fd = fopen(outfname, "wb");
324
    fd = fopen(outfname, "wb");
325
    if (fd == NULL) {
325
    if (fd == NULL) {
326
      printf(svarlang_strid(0x0906), outfname); /* "ERROR: failed to create file %s" */
326
      printf(svarlang_strid(0x0906), outfname); /* "ERROR: failed to create file %s" */
327
      puts("");
327
      puts("");
328
      goto SHITQUIT;
328
      goto SHITQUIT;
329
    }
329
    }
330
  }
330
  }
331
 
331
 
332
  /* read body of the answer (and chunk-decode it if needed) */
332
  /* read body of the answer (and chunk-decode it if needed) */
333
  lastactivity = time(NULL);
333
  lastactivity = time(NULL);
334
  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 */
334
  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 */
335
 
335
 
336
    if (byteread > 0) { /* got data */
336
    if (byteread > 0) { /* got data */
337
      lastactivity = time(NULL);
337
      lastactivity = time(NULL);
338
 
338
 
339
      /* unpack data if server transmits in chunked mode */
339
      /* unpack data if server transmits in chunked mode */
340
      if (ischunked) byteread = unchunk(buffer, byteread, unchstate);
340
      if (ischunked) byteread = unchunk(buffer, byteread, unchstate);
341
 
341
 
342
      /* if downloading to file, write stuff to disk */
342
      /* if downloading to file, write stuff to disk */
343
      if (fd != NULL) {
343
      if (fd != NULL) {
344
        int i;
344
        int i;
345
        if (fwrite(buffer, 1, byteread, fd) != byteread) {
345
        if (fwrite(buffer, 1, byteread, fd) != byteread) {
346
          printf(svarlang_strid(0x0907), outfname, flen); /* "ERROR: failed to write data to file %s after %ld bytes" */
346
          printf(svarlang_strid(0x0907), outfname, flen); /* "ERROR: failed to write data to file %s after %ld bytes" */
347
          puts("");
347
          puts("");
348
          break;
348
          break;
349
        }
349
        }
350
        flen += byteread;
350
        flen += byteread;
351
        /* update progress once a sec */
351
        /* update progress once a sec */
352
        if (lastprogressoutput != lastactivity) {
352
        if (lastprogressoutput != lastactivity) {
353
          lastprogressoutput = lastactivity;
353
          lastprogressoutput = lastactivity;
354
          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 */
354
          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 */
355
          lastflen = flen;
355
          lastflen = flen;
356
          fflush(stdout); /* avoid console buffering */
356
          fflush(stdout); /* avoid console buffering */
357
        }
357
        }
358
        /* update the bsd sum */
358
        /* update the bsd sum */
359
        for (i = 0; i < byteread; i++) {
359
        for (i = 0; i < byteread; i++) {
360
          /* rotr16 */
360
          /* rotr16 */
361
          unsigned short bsumlsb = *bsum & 1;
361
          unsigned short bsumlsb = *bsum & 1;
362
          *bsum >>= 1;
362
          *bsum >>= 1;
363
          *bsum |= (bsumlsb << 15);
363
          *bsum |= (bsumlsb << 15);
364
          *bsum += buffer[i];
364
          *bsum += buffer[i];
365
        }
365
        }
366
      } else { /* otherwise dump to screen */
366
      } else { /* otherwise dump to screen */
367
        buffer[byteread] = 0;
367
        buffer[byteread] = 0;
368
        printf("%s", buffer);
368
        printf("%s", buffer);
369
      }
369
      }
370
 
370
 
371
    } else if (byteread < 0) { /* end of connection */
371
    } else if (byteread < 0) { /* end of connection */
372
      break;
372
      break;
373
 
373
 
374
    } else { /* check for timeout (byteread == 0) */
374
    } else { /* check for timeout (byteread == 0) */
375
      if (time(NULL) - lastactivity > 20) { /* TIMEOUT! */
375
      if (time(NULL) - lastactivity > 20) { /* TIMEOUT! */
376
        putsnls(9, 8); /* "ERROR: Timeout while waiting for data" */
376
        putsnls(9, 8); /* "ERROR: Timeout while waiting for data" */
377
        goto SHITQUIT;
377
        goto SHITQUIT;
378
      }
378
      }
379
      /* waiting for packets - release a CPU cycle in the meantime */
379
      /* waiting for packets - release a CPU cycle in the meantime */
380
      _asm int 28h;
380
      _asm int 28h;
381
    }
381
    }
382
  }
382
  }
383
 
383
 
384
  goto ALLGOOD;
384
  goto ALLGOOD;
385
 
385
 
386
  SHITQUIT:
386
  SHITQUIT:
387
  flen = -1;
387
  flen = -1;
388
 
388
 
389
  ALLGOOD:
389
  ALLGOOD:
390
  if (fd != NULL) fclose(fd);
390
  if (fd != NULL) fclose(fd);
391
  net_close(sock);
391
  net_close(sock);
392
  return(flen);
392
  return(flen);
393
}
393
}
394
 
394
 
395
 
395
 
396
/* checks if file exists, returns 0 if not, non-zero otherwise */
396
/* checks if file exists, returns 0 if not, non-zero otherwise */
397
static int fexists(const char *fname) {
397
static int fexists(const char *fname) {
398
  FILE *fd = fopen(fname, "rb");
398
  FILE *fd = fopen(fname, "rb");
399
  if (fd == NULL) return(0);
399
  if (fd == NULL) return(0);
400
  fclose(fd);
400
  fclose(fd);
401
  return(1);
401
  return(1);
402
}
402
}
403
 
403
 
404
 
404
 
405
int main(int argc, char **argv) {
405
int main(int argc, char **argv) {
406
  unsigned short bsum = 0;
406
  unsigned short bsum = 0;
407
  long flen;
407
  long flen;
408
  int ispost; /* is the request a POST? */
408
  int ispost; /* is the request a POST? */
409
 
409
 
410
  struct {
410
  struct {
411
    unsigned char buffer[5000];
411
    unsigned char buffer[5000];
412
    char ipaddr[64];
412
    char ipaddr[64];
413
    char url[64];
413
    char url[64];
414
    char outfname[16];
414
    char outfname[16];
415
  } *mem;
415
  } *mem;
416
 
416
 
417
  svarlang_autoload("PKGNET");
417
  svarlang_autoload("PKGNET");
418
 
418
 
419
  /* allocate memory */
419
  /* allocate memory */
420
  mem = malloc(sizeof(*mem));
420
  mem = malloc(sizeof(*mem));
421
  if (mem == NULL) {
421
  if (mem == NULL) {
422
    putsnls(9, 9); /* "ERROR: out of memory" */
422
    putsnls(9, 9); /* "ERROR: out of memory" */
423
    return(1);
423
    return(1);
424
  }
424
  }
425
 
425
 
426
  /* parse command line arguments */
426
  /* parse command line arguments */
427
  if (parseargv(argc, argv, mem->outfname, mem->url, &ispost) != 0) return(1);
427
  if (parseargv(argc, argv, mem->outfname, mem->url, &ispost) != 0) return(1);
428
 
428
 
429
  /* if outfname requested, make sure that file does not exist yet */
429
  /* if outfname requested, make sure that file does not exist yet */
430
  if ((mem->outfname[0] != 0) && (fexists(mem->outfname))) {
430
  if ((mem->outfname[0] != 0) && (fexists(mem->outfname))) {
431
    printf(svarlang_strid(0x090A), mem->outfname); /* "ERROR: file %s already exists" */
431
    printf(svarlang_strid(0x090A), mem->outfname); /* "ERROR: file %s already exists" */
432
    puts("");
432
    puts("");
433
    return(1);
433
    return(1);
434
  }
434
  }
435
 
435
 
436
  /* init network stack */
436
  /* init network stack */
437
  if (net_init() != 0) {
437
  if (net_init() != 0) {
438
    putsnls(9, 11); /* "ERROR: Network subsystem initialization failed" */
438
    putsnls(9, 11); /* "ERROR: Network subsystem initialization failed" */
439
    return(1);
439
    return(1);
440
  }
440
  }
441
 
441
 
442
  puts(""); /* required because watt-32 likes to print out garbage sometimes ("configuring through DHCP...") */
442
  puts(""); /* required because watt-32 likes to print out garbage sometimes ("configuring through DHCP...") */
443
 
443
 
444
  if (net_dnsresolve(mem->ipaddr, HOSTADDR) != 0) {
444
  if (net_dnsresolve(mem->ipaddr, HOSTADDR) != 0) {
445
    putsnls(9, 12); /* "ERROR: DNS resolution failed" */
445
    putsnls(9, 12); /* "ERROR: DNS resolution failed" */
446
    return(1);
446
    return(1);
447
  }
447
  }
448
 
448
 
449
  flen = htget(mem->ipaddr, mem->url, mem->outfname, &bsum, ispost, mem->buffer, sizeof(mem->buffer));
449
  flen = htget(mem->ipaddr, mem->url, mem->outfname, &bsum, ispost, mem->buffer, sizeof(mem->buffer));
450
  if (flen < 1) return(1);
450
  if (flen < 1) return(1);
451
 
451
 
452
  if (mem->outfname[0] != 0) {
452
  if (mem->outfname[0] != 0) {
453
    /* print bsum, size, filename */
453
    /* print bsum, size, filename */
454
    printf(svarlang_strid(0x0200), flen >> 10, mem->outfname, bsum); /* Downloaded %ld KiB into %s (BSUM: %04X) */
454
    printf(svarlang_strid(0x0200), flen >> 10, mem->outfname, bsum); /* Downloaded %ld KiB into %s (BSUM: %04X) */
455
    puts("");
455
    puts("");
456
  }
456
  }
457
 
457
 
458
  return(0);
458
  return(0);
459
}
459
}
460
 
460