Subversion Repositories SvarDOS

Rev

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

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