Subversion Repositories SvarDOS

Rev

Rev 885 | Rev 1131 | Go to most recent revision | Only display areas with differences | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

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