Subversion Repositories SvarDOS

Rev

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

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