mirror of
https://github.com/xroche/httrack.git
synced 2026-07-17 14:19:48 +03:00
Compare commits
1 Commits
fix-urlpor
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6aabb3ba09 |
@@ -567,24 +567,29 @@ int optinclude_file(const char *name, int *argc, char **argv, char *x_argvblk,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get home directory, '.' if failed */
|
||||
/* Get home directory, '.' if unset or empty */
|
||||
/* example: /home/smith */
|
||||
const char *hts_gethome(void) {
|
||||
const char *home = getenv("HOME");
|
||||
|
||||
if (home)
|
||||
return home;
|
||||
else
|
||||
return ".";
|
||||
/* An empty $HOME would expand ~/foo into the absolute /foo */
|
||||
return strnotempty(home) ? home : ".";
|
||||
}
|
||||
|
||||
/* Convert ~/foo into /home/smith/foo */
|
||||
/* Convert ~/foo into /home/smith/foo (~user/ left alone: no getpwnam here) */
|
||||
void expand_home(String * str) {
|
||||
if (StringSub(*str, 1) == '~') {
|
||||
if (StringNotEmpty(*str) && StringSub(*str, 0) == '~' &&
|
||||
(StringLength(*str) == 1 || StringSub(*str, 1) == '/')) {
|
||||
char BIGSTK tempo[HTS_URLMAXSIZE * 2];
|
||||
const char *const home = hts_gethome();
|
||||
const size_t homelen = strlen(home);
|
||||
const size_t taillen = StringLength(*str) - 1;
|
||||
|
||||
strcpybuff(tempo, hts_gethome());
|
||||
strcatbuff(tempo, StringBuff(*str) + 1);
|
||||
StringCopy(*str, tempo);
|
||||
/* Leave untouched rather than abort() in strcatbuff on a huge $HOME */
|
||||
if (taillen < sizeof(tempo) && homelen < sizeof(tempo) - taillen) {
|
||||
strcpybuff(tempo, home);
|
||||
strcatbuff(tempo, StringBuff(*str) + 1);
|
||||
StringCopy(*str, tempo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,50 +402,6 @@ int dns_selftests(httrackp *opt) {
|
||||
deletesoc(s);
|
||||
}
|
||||
|
||||
/* A URL port outside 1..65535 must refuse the link, not fold into range and
|
||||
connect elsewhere (#614). *addr_count discriminates: 0 only if refused
|
||||
before the resolve, still 2 for one merely truncated or defaulted. */
|
||||
{
|
||||
/* an empty "dual.test:" means the default port (WHATWG, curl): keep it */
|
||||
static const char *const good[] = {"dual.test:1", "dual.test:80",
|
||||
"dual.test:8080", "dual.test:65535",
|
||||
"dual.test:080", "dual.test:"};
|
||||
/* 65616 and 4294967376 are load-bearing: both wrap to a plausible 80 */
|
||||
static const char *const bad[] = {
|
||||
"dual.test:0", "dual.test:65536", "dual.test:65616",
|
||||
"dual.test:99999", "dual.test:2147483648", "dual.test:4294967296",
|
||||
"dual.test:4294967376", "dual.test:-1", "dual.test:-23437",
|
||||
"dual.test:80x", "dual.test:+80", "dual.test: 80",
|
||||
"dual.test:0x50"};
|
||||
size_t k;
|
||||
|
||||
for (k = 0; k < sizeof(good) / sizeof(good[0]); k++) {
|
||||
htsblk r;
|
||||
int count = -1;
|
||||
T_SOC s;
|
||||
|
||||
hts_init_htsblk(&r);
|
||||
s = newhttp_addr(opt, good[k], &r, -1, 0, 0, &count);
|
||||
CHECK(count == 2); /* accepted: reached the resolve */
|
||||
if (s != INVALID_SOCKET)
|
||||
deletesoc(s);
|
||||
}
|
||||
|
||||
for (k = 0; k < sizeof(bad) / sizeof(bad[0]); k++) {
|
||||
htsblk r;
|
||||
int count = -1;
|
||||
T_SOC s;
|
||||
|
||||
hts_init_htsblk(&r);
|
||||
s = newhttp_addr(opt, bad[k], &r, -1, 0, 0, &count);
|
||||
CHECK(s == INVALID_SOCKET);
|
||||
CHECK(count == 0); /* refused before resolving, not a failed connect */
|
||||
CHECK(strstr(r.msg, "Invalid port") != NULL);
|
||||
if (s != INVALID_SOCKET)
|
||||
deletesoc(s);
|
||||
}
|
||||
}
|
||||
|
||||
/* Connect-fallback decision (consumer of the multi-address list): when a
|
||||
stuck connect should abandon the current address for the next one. */
|
||||
{
|
||||
|
||||
36
src/htslib.c
36
src/htslib.c
@@ -2167,18 +2167,15 @@ T_SOC newhttp_addr(httrackp *opt, const char *_iadr, htsblk *retour, int port,
|
||||
#endif
|
||||
|
||||
if (a != NULL) {
|
||||
// folding a nonsense port into 0..65535 crawls one neither the link nor
|
||||
// a port filter named; an empty "host:" just means the default (#614)
|
||||
if (a[1] != '\0' && !hts_parse_url_port(a + 1, &port)) {
|
||||
if (retour != NULL) {
|
||||
snprintf(retour->msg, sizeof(retour->msg), "Invalid port: %s",
|
||||
a + 1);
|
||||
}
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
int i = -1;
|
||||
|
||||
iadr2[0] = '\0';
|
||||
// the address itself, without the ":port"
|
||||
sscanf(a + 1, "%d", &i);
|
||||
if (i != -1) {
|
||||
port = (unsigned short int) i;
|
||||
}
|
||||
|
||||
// adresse véritable (sans :xx)
|
||||
strncatbuff(iadr2, iadr, (int) (a - iadr));
|
||||
resolve_host = iadr2;
|
||||
}
|
||||
@@ -3751,27 +3748,18 @@ static int proxy_default_port(const char *arg) {
|
||||
return hts_proxy_is_socks(arg) ? 1080 : 8080;
|
||||
}
|
||||
|
||||
hts_boolean hts_parse_url_port(const char *a, int *port) {
|
||||
// port "a" of -P argument "arg": digits fitting TCP's 1..65535, else the scheme
|
||||
// default. Not sscanf("%d"): past INT_MAX it wraps to a garbage port (#602)
|
||||
static int parse_proxy_port(const char *a, const char *arg) {
|
||||
char *end;
|
||||
long p;
|
||||
|
||||
if (!isdigit((unsigned char) *a)) // strtol would eat a sign or leading space
|
||||
return HTS_FALSE;
|
||||
return proxy_default_port(arg);
|
||||
p = strtol(a, &end, 10);
|
||||
if (*end != '\0' || p < 1 || p > 65535) // ERANGE lands out of range too
|
||||
return HTS_FALSE;
|
||||
*port = (int) p;
|
||||
return HTS_TRUE;
|
||||
}
|
||||
|
||||
// port "a" of -P argument "arg": digits fitting TCP's 1..65535, else the scheme
|
||||
// default. Not sscanf("%d"): past INT_MAX it wraps to a garbage port (#602)
|
||||
static int parse_proxy_port(const char *a, const char *arg) {
|
||||
int port;
|
||||
|
||||
if (!hts_parse_url_port(a, &port))
|
||||
return proxy_default_port(arg);
|
||||
return port;
|
||||
return (int) p;
|
||||
}
|
||||
|
||||
void hts_parse_proxy(const char *arg, char *name, size_t name_size, int *port) {
|
||||
|
||||
@@ -288,12 +288,6 @@ const char *strrchr_limit(const char *s, char c, const char *limit);
|
||||
char *jump_protocol(char *source);
|
||||
const char *jump_protocol_const(const char *source);
|
||||
|
||||
/* Parse a URL's port text "a" (after the ':', up to the end of the string):
|
||||
TRUE and *port set for a bare decimal in 1..65535, else FALSE and *port left
|
||||
alone. Not sscanf("%d"), which range-checks nothing and wraps past INT_MAX.
|
||||
*/
|
||||
hts_boolean hts_parse_url_port(const char *a, int *port);
|
||||
|
||||
/* Split a -P proxy argument "[scheme://][user:pass@]host[:port]" into the proxy
|
||||
host string (scheme and any user:pass kept, for later stripping and auth),
|
||||
written NUL-terminated into name[name_size] (truncated to fit), and the port
|
||||
|
||||
@@ -491,11 +491,14 @@ static int socks5_handshake_stream(httrackp *opt, socks5_stream *st,
|
||||
if ((unsigned char) host[i] < ' ')
|
||||
return socks5_fail(msg, msgsize, "SOCKS5: invalid origin host");
|
||||
}
|
||||
// the old range check ran after sscanf("%d") had wrapped a huge value into a
|
||||
// plausible port (#614). An empty "host:" stays refused here, unlike the
|
||||
// direct path, as it was before #614.
|
||||
if (portsep != NULL && !hts_parse_url_port(portsep + 1, &port))
|
||||
return socks5_fail(msg, msgsize, "SOCKS5: invalid origin port");
|
||||
if (portsep != NULL) {
|
||||
int p = -1;
|
||||
|
||||
sscanf(portsep + 1, "%d", &p);
|
||||
if (p <= 0 || p > 65535)
|
||||
return socks5_fail(msg, msgsize, "SOCKS5: invalid origin port");
|
||||
port = p;
|
||||
}
|
||||
if (link_has_authorization(proxy_name)) {
|
||||
if (!socks5_credentials(opt, proxy_name, user, sizeof(user), &userlen, pass,
|
||||
sizeof(pass), &passlen, msg, msgsize))
|
||||
|
||||
@@ -45,6 +45,7 @@ Please visit our Website: http://www.httrack.com
|
||||
#include "htscore.h"
|
||||
#include "htsdefines.h"
|
||||
#include "htslib.h"
|
||||
#include "htsalias.h"
|
||||
#include "htsparse.h"
|
||||
#include "htscache.h"
|
||||
#include "htscache_selftest.h"
|
||||
@@ -825,6 +826,21 @@ static int st_simplify(httrackp *opt, int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st_expandhome(httrackp *opt, int argc, char **argv) {
|
||||
String path = STRING_EMPTY;
|
||||
|
||||
(void) opt;
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "expandhome: needs a path\n");
|
||||
return 1;
|
||||
}
|
||||
StringCopy(path, argv[0]);
|
||||
expand_home(&path);
|
||||
printf("expanded=%s\n", StringBuff(path));
|
||||
StringFree(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int st_mime(httrackp *opt, int argc, char **argv) {
|
||||
char mime[256];
|
||||
|
||||
@@ -1453,25 +1469,6 @@ static int st_socks5(httrackp *opt, int argc, char **argv) {
|
||||
assertf(socks5_handshake_scripted(opt, "origin.test:8443", proxy, &io) == 1);
|
||||
assertf(memcmp(io.sent + io.sent_len - 2, "\x20\xfb", 2) == 0);
|
||||
|
||||
/* a bad origin port is refused before any byte goes out (#614). 4294967376 is
|
||||
the case the old range check could not see: it overflowed the sscanf("%d")
|
||||
into a plausible 80 and passed. 65616 would not prove anything here, since
|
||||
it fits an int and the old check already caught it. */
|
||||
{
|
||||
static const char *const bad[] = {"origin.test:4294967376",
|
||||
"origin.test:80x", "origin.test:+80",
|
||||
"origin.test: 80", "origin.test:8.0"};
|
||||
size_t k;
|
||||
|
||||
for (k = 0; k < sizeof(bad) / sizeof(bad[0]); k++) {
|
||||
len = socks5_reply(script, 0x01, v4, sizeof(v4));
|
||||
io.reply = script;
|
||||
io.reply_len = len;
|
||||
assertf(socks5_handshake_scripted(opt, bad[k], proxy, &io) == 0);
|
||||
assertf(io.sent_len == 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* credentials: split on the first colon of the escaped userinfo, so %3a stays
|
||||
inside the username and a colon in the password is not a delimiter */
|
||||
{
|
||||
@@ -3098,6 +3095,7 @@ static const struct selftest_entry {
|
||||
{"filterbounds", "", "matcher length/work caps reject hostile patterns",
|
||||
st_filterbounds},
|
||||
{"simplify", "<path>", "collapse ./ and ../ in a path", st_simplify},
|
||||
{"expandhome", "<path>", "expand a leading ~/ into $HOME", st_expandhome},
|
||||
{"stripquery", "", "--strip-query pattern/key stripping self-test",
|
||||
st_stripquery},
|
||||
{"urlhack", "", "-%u url-hack sub-flag (www/slash/query) self-test",
|
||||
|
||||
53
tests/01_engine-expandhome.test
Normal file
53
tests/01_engine-expandhome.test
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
|
||||
# SC2088: the literal, unexpanded '~' is the input under test.
|
||||
# shellcheck disable=SC2088
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ~ expansion for the -O base path (expand_home). $HOME is pinned so the cases
|
||||
# cannot depend on the caller's, but MSYS rewrites it into a Windows path
|
||||
# before the native httrack.exe reads it: ask the engine what it saw rather
|
||||
# than hardcoding the value.
|
||||
ask() {
|
||||
HOME=HTSHOME httrack -O /dev/null -#test=expandhome "$1"
|
||||
}
|
||||
exp() {
|
||||
test "$(ask "$1")" == "expanded=$2" || exit 1
|
||||
}
|
||||
|
||||
home=$(ask '~')
|
||||
home=${home#expanded=}
|
||||
|
||||
# or every case below is vacuous: '~' means expansion never fired, '.' means it
|
||||
# fell back to the default without ever reading $HOME
|
||||
test "$home" != '~' || exit 1
|
||||
test "$home" != '.' || exit 1
|
||||
|
||||
exp '~' "$home"
|
||||
exp '~/foo' "$home/foo"
|
||||
exp '~/foo/bar/#' "$home/foo/bar/#"
|
||||
exp '~/' "$home/"
|
||||
exp '~/../x' "$home/../x"
|
||||
|
||||
# ~user/ needs getpwnam: left alone rather than mangled into $HOME + "user/"
|
||||
exp '~smith/foo' '~smith/foo'
|
||||
exp '~root' '~root'
|
||||
|
||||
# only a leading '~' expands; anything else is a literal path component
|
||||
exp 'a~/x' 'a~/x'
|
||||
exp './~/x' './~/x'
|
||||
exp 'foo~bar' 'foo~bar'
|
||||
|
||||
# an unset or empty $HOME must not turn ~/foo into the absolute /foo
|
||||
test "$(env -u HOME httrack -O /dev/null -#test=expandhome '~/foo')" == "expanded=./foo" || exit 1
|
||||
test "$(HOME='' httrack -O /dev/null -#test=expandhome '~/foo')" == "expanded=./foo" || exit 1
|
||||
|
||||
# A $HOME past the 2 * HTS_URLMAXSIZE buffer is left alone: strcatbuff aborts
|
||||
# rather than truncates. Only meaningful where the engine sees the value we set:
|
||||
# MSYS rewrites HOME into a path of its own choosing, long or not.
|
||||
if test "$home" = HTSHOME; then
|
||||
long=$(printf '%04096d' 0 | tr '0' 'A')
|
||||
test "$(HOME="$long" httrack -O /dev/null -#test=expandhome '~/foo')" == "expanded=~/foo" || exit 1
|
||||
fi
|
||||
@@ -59,6 +59,7 @@ TESTS = \
|
||||
01_engine-pause.test \
|
||||
01_engine-rcfile.test \
|
||||
01_engine-reconcile.test \
|
||||
01_engine-expandhome.test \
|
||||
01_engine-fsize.test \
|
||||
01_engine-redirect.test \
|
||||
01_engine-relative.test \
|
||||
|
||||
Reference in New Issue
Block a user