mirror of
https://github.com/xroche/httrack.git
synced 2026-08-02 14:01:43 +03:00
Compare commits
2 Commits
fix-915-st
...
fix-911-da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca7c30666a | ||
|
|
1d8c429545 |
7
.github/workflows/windows-build.yml
vendored
7
.github/workflows/windows-build.yml
vendored
@@ -265,20 +265,17 @@ jobs:
|
||||
# cause a false mismatch either.
|
||||
# footer-overflow and purge-longpath skip on Windows (need a path past MAX_PATH);
|
||||
# crange pending #581;
|
||||
# webdav-default, webdav-mime and webdav-overflow need a reapable background listener, which MSYS cannot give them;
|
||||
# webdav-default and webdav-mime need a reapable background listener, which MSYS cannot give them;
|
||||
# badmtime needs a filesystem that stores an mtime past gmtime's range;
|
||||
# single-file ends on a GUI half needing htsserver, which this job does not build;
|
||||
# update-304-leak needs a LeakSanitizer build, which MSVC has no equivalent of;
|
||||
# crash-symbolize and backtrace-empty need backtrace(), which Windows has no
|
||||
# equivalent of;
|
||||
# string-oom drives a helper binary that only the automake build produces.
|
||||
# equivalent of.
|
||||
expected_skips="01_engine-footer-overflow.test
|
||||
100_local-purge-longpath.test
|
||||
114_local-update-304-leak.test
|
||||
120_local-proxytrack-webdav-default.test
|
||||
143_engine-backtrace-empty.test
|
||||
147_local-proxytrack-webdav-overflow.test
|
||||
152_engine-string-oom.test
|
||||
48_local-crange-memresume.test
|
||||
71_local-crange-repaircache.test
|
||||
79_local-proxytrack-webdav-mime.test
|
||||
|
||||
@@ -829,14 +829,6 @@ static hts_boolean back_chunked_unterminated(const lien_back *const back) {
|
||||
return back->is_chunk && back->chunk_blocksize != -1 ? HTS_TRUE : HTS_FALSE;
|
||||
}
|
||||
|
||||
/* Past the terminating chunk, the line still owed is the optional trailer
|
||||
section (RFC 9112 7.1.2), read and discarded like a header block. */
|
||||
static hts_boolean back_in_chunk_trailers(const lien_back *const back) {
|
||||
return back->status == STATUS_CHUNK_CR && back->chunk_blocksize == -1
|
||||
? HTS_TRUE
|
||||
: HTS_FALSE;
|
||||
}
|
||||
|
||||
// objet (lien) téléchargé ou transféré depuis le cache
|
||||
//
|
||||
// fermer les paramètres de transfert,
|
||||
@@ -3498,10 +3490,6 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
else if (back[i].status == STATUS_CHUNK_WAIT || back[i].status == STATUS_CHUNK_CR) { // recevoir longueur chunk en hexa caractère par caractère
|
||||
// backuper pour lire dans le buffer chunk
|
||||
htsblk r;
|
||||
/* Block mode bounds the trailer section, which declares no length
|
||||
of its own, by HTS_LINE_BLOCK_SIZE. */
|
||||
const int chunk_read_mode =
|
||||
back_in_chunk_trailers(&back[i]) ? 0 : -1;
|
||||
|
||||
memcpy(&r, &(back[i].r), sizeof(htsblk));
|
||||
back[i].r.is_write = 0; // mémoire
|
||||
@@ -3512,7 +3500,7 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
back[i].r.is_file = 0;
|
||||
//
|
||||
// ligne par ligne
|
||||
retour_fread = http_xfread1(&(back[i].r), chunk_read_mode);
|
||||
retour_fread = http_xfread1(&(back[i].r), -1);
|
||||
// modifier et restaurer
|
||||
back[i].chunk_adr = back[i].r.adr; // adresse
|
||||
back[i].chunk_size = back[i].r.size; // taille taille chunk
|
||||
@@ -3650,22 +3638,12 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
}
|
||||
// Traitement des en têtes chunks ou en têtes
|
||||
if (back[i].status == STATUS_CHUNK_WAIT || back[i].status == STATUS_CHUNK_CR) { // réception taille chunk en hexa ( après les en têtes, peut ne pas
|
||||
const hts_boolean in_trailers = back_in_chunk_trailers(&back[i]);
|
||||
|
||||
/* A chunk-size or chunk-CRLF line closes on its first LF, the
|
||||
trailer section on the blank line ending it. Two LFs mean a
|
||||
blank line only because the reader drops every CR. */
|
||||
if (back[i].chunk_size > 0 &&
|
||||
back[i].chunk_adr[back[i].chunk_size - 1] == 10 &&
|
||||
(!in_trailers || back[i].chunk_size == 1 ||
|
||||
back[i].chunk_adr[back[i].chunk_size - 2] == 10)) {
|
||||
if (back[i].chunk_size > 0
|
||||
&& back[i].chunk_adr[back[i].chunk_size - 1] == 10) {
|
||||
int chunk_size = -1;
|
||||
char chunk_data[64];
|
||||
|
||||
if (in_trailers) {
|
||||
chunk_size =
|
||||
0; /* fields discarded, the blank line ends the body */
|
||||
} else if (back[i].chunk_size < 32) { // not too big
|
||||
if (back[i].chunk_size < 32) { // pas trop gros
|
||||
char *chstrip = back[i].chunk_adr;
|
||||
|
||||
back[i].chunk_adr[back[i].chunk_size - 1] = '\0'; // octet nul
|
||||
@@ -3849,6 +3827,12 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Oops, trailers! */
|
||||
if (back[i].r.keep_alive_trailers) {
|
||||
/* fixme (not yet supported) */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3862,7 +3846,7 @@ void back_wait(struct_back * sback, httrackp * opt, cache_back * cache,
|
||||
// NO! xxback[i].chunk_blocksize = 0;
|
||||
}
|
||||
|
||||
} // chunk buffer holds a complete line
|
||||
} // taille buffer chunk > 1 && LF
|
||||
//
|
||||
} else if (back[i].status == STATUS_WAIT_HEADERS) { // en têtes (avant le chunk si il est présent)
|
||||
//
|
||||
|
||||
@@ -1954,7 +1954,7 @@ LLint http_xfread1(htsblk * r, int bufl) {
|
||||
|
||||
} else if (bufl == -2) { // force reserve
|
||||
if (r->adr == NULL) {
|
||||
r->adr = (char *) malloct(HTS_LINE_BLOCK_SIZE);
|
||||
r->adr = (char *) malloct(8192);
|
||||
r->size = 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -1969,11 +1969,11 @@ LLint http_xfread1(htsblk * r, int bufl) {
|
||||
nl = READ_INTERNAL_ERROR;
|
||||
count--;
|
||||
if (r->adr == NULL) {
|
||||
r->adr = (char *) malloct(HTS_LINE_BLOCK_SIZE);
|
||||
r->adr = (char *) malloct(8192);
|
||||
r->size = 0;
|
||||
}
|
||||
if (r->adr != NULL) {
|
||||
if (r->size < HTS_LINE_BLOCK_SIZE - 2) {
|
||||
if (r->size < 8190) {
|
||||
// lecture
|
||||
nl = hts_read(r, r->adr + r->size, 1);
|
||||
if (nl > 0) {
|
||||
|
||||
@@ -248,10 +248,6 @@ void treathead(t_cookie * cookie, const char *adr, const char *fil, htsblk * ret
|
||||
void treatfirstline(htsblk * retour, const char *rcvd);
|
||||
|
||||
// sous-fonctions
|
||||
/* Buffer http_xfread1() fills in its line modes, and so the ceiling on any
|
||||
blank-line-terminated block it reads: a header section or a chunk trailer
|
||||
section. Overrunning it fails the transfer. */
|
||||
#define HTS_LINE_BLOCK_SIZE 8192
|
||||
LLint http_xfread1(htsblk * r, int bufl);
|
||||
/* Cached resolver: fill out[0..count-1] with up to max addresses for iadr (in
|
||||
resolver order), returning the count (0 = does not resolve, negative-cached).
|
||||
|
||||
@@ -80,7 +80,6 @@ Please visit our Website: http://www.httrack.com
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
#ifndef _WIN32
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
@@ -7632,149 +7631,6 @@ static int st_rtrim(httrackp *opt, int argc, char **argv) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Format LEN bytes of EXPECTED into S as two arguments, and check what came
|
||||
back. HEAD and TAIL are scratch buffers of at least LEN+1 bytes. */
|
||||
static int strsprintf_case(String *s, const char *expected, size_t len,
|
||||
char *head, char *tail) {
|
||||
const size_t half = len / 2;
|
||||
|
||||
memcpy(head, expected, half);
|
||||
head[half] = '\0';
|
||||
memcpy(tail, expected + half, len - half);
|
||||
tail[len - half] = '\0';
|
||||
StringSprintf(*s, "%s%s", head, tail);
|
||||
return StringLength(*s) == len &&
|
||||
memcmp(StringBuff(*s), expected, len) == 0 &&
|
||||
StringBuff(*s)[len] == '\0';
|
||||
}
|
||||
|
||||
/* StringSprintf_ stores the terminator at buffer[ret], so its `ret < capacity`
|
||||
guard is off by one byte at the exact fill: an output whose length equals the
|
||||
capacity writes past the allocation (#836). The lengths that reach it are the
|
||||
capacities themselves, floored at 256 and doubling from there. */
|
||||
static int st_strsprintf(httrackp *opt, int argc, char **argv) {
|
||||
static const size_t caps[] = {256, 512, 1024, 2048};
|
||||
|
||||
enum { maxLen = 2100 };
|
||||
|
||||
char *expected = malloct(maxLen + 1);
|
||||
char *head = malloct(maxLen + 1);
|
||||
char *tail = malloct(maxLen + 1);
|
||||
String reused = STRING_EMPTY;
|
||||
size_t i, len;
|
||||
int err = 0;
|
||||
|
||||
(void) opt;
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
if (expected == NULL || head == NULL || tail == NULL) {
|
||||
printf("strsprintf self-test: FAIL (out of memory)\n");
|
||||
return 1;
|
||||
}
|
||||
for (i = 0; i < maxLen; i++)
|
||||
expected[i] = (char) ('a' + (i % 26));
|
||||
expected[maxLen] = '\0';
|
||||
|
||||
/* one call on a String whose capacity is pinned to the boundary, so len ==
|
||||
capacity is reached exactly once per boundary */
|
||||
for (i = 0; !err && i < sizeof(caps) / sizeof(caps[0]); i++) {
|
||||
for (len = caps[i] - 3; !err && len <= caps[i] + 3; len++) {
|
||||
String s = STRING_EMPTY;
|
||||
|
||||
StringRoomTotal(s, caps[i]);
|
||||
if (StringCapacity(s) != caps[i]) {
|
||||
printf(" FAIL: capacity %u pinned to %u\n", (unsigned) caps[i],
|
||||
(unsigned) StringCapacity(s));
|
||||
err = 1;
|
||||
} else if (!strsprintf_case(&s, expected, len, head, tail)) {
|
||||
printf(" FAIL: length %u at capacity %u\n", (unsigned) len,
|
||||
(unsigned) caps[i]);
|
||||
err = 1;
|
||||
}
|
||||
StringFree(s);
|
||||
}
|
||||
}
|
||||
|
||||
/* the same String reused: its capacity grows under it between calls, and a
|
||||
shorter output must not leave the previous one behind */
|
||||
for (len = 0; !err && len <= maxLen; len++) {
|
||||
if (!strsprintf_case(&reused, expected, len, head, tail)) {
|
||||
printf(" FAIL: growing length %u\n", (unsigned) len);
|
||||
err = 1;
|
||||
}
|
||||
}
|
||||
for (len = maxLen + 1; !err && len-- > 0;) {
|
||||
if (!strsprintf_case(&reused, expected, len, head, tail)) {
|
||||
printf(" FAIL: shrinking length %u\n", (unsigned) len);
|
||||
err = 1;
|
||||
}
|
||||
}
|
||||
StringFree(reused);
|
||||
|
||||
/* The give-up path: an argument libc cannot convert fails at every capacity,
|
||||
so the retry loop climbs to STRING_SPRINTF_MAX and then empties the
|
||||
String. Probe libc first -- a platform that formats an unpaired surrogate
|
||||
without faulting never reaches the path. */
|
||||
{
|
||||
static const wchar_t bad[] = {(wchar_t) 0xd800, 0};
|
||||
char probe[32];
|
||||
|
||||
if (snprintf(probe, sizeof(probe), "%ls", bad) < 0) {
|
||||
String s = STRING_EMPTY;
|
||||
|
||||
StringCopy(s, "leftover");
|
||||
StringSprintf(s, "%ls", bad);
|
||||
if (StringNotEmpty(s) || StringBuff(s) == NULL ||
|
||||
StringBuff(s)[0] != '\0') {
|
||||
printf(" FAIL: a failed conversion left %u bytes behind\n",
|
||||
(unsigned) StringLength(s));
|
||||
err = 1;
|
||||
}
|
||||
StringFree(s);
|
||||
} else { /* stderr: test 150 pins stdout to the one-line verdict */
|
||||
fprintf(stderr, " (skipped: this libc formats an unconvertible wide "
|
||||
"string)\n");
|
||||
}
|
||||
}
|
||||
|
||||
/* StringSprintf empties the String when it gives up, and the WebDAV
|
||||
enumeration pops the trailing '/' right after: on an empty String an
|
||||
unguarded pop would wrap the unsigned length and write off the end. */
|
||||
{
|
||||
String never = STRING_EMPTY;
|
||||
String cleared = STRING_EMPTY;
|
||||
|
||||
StringPopRight(never); /* never written to: buffer_ is still NULL */
|
||||
if (StringLength(never) != 0 || StringBuff(never) != NULL) {
|
||||
printf(" FAIL: pop on an unallocated String\n");
|
||||
err = 1;
|
||||
}
|
||||
StringClear(cleared);
|
||||
StringPopRight(cleared);
|
||||
if (StringLength(cleared) != 0 || StringBuff(cleared)[0] != '\0') {
|
||||
printf(" FAIL: pop on an emptied String\n");
|
||||
err = 1;
|
||||
}
|
||||
/* control: the guard must not swallow a pop that has a byte to drop */
|
||||
StringSprintf(cleared, "ab");
|
||||
StringPopRight(cleared);
|
||||
if (StringLength(cleared) != 1 || strcmp(StringBuff(cleared), "a") != 0) {
|
||||
printf(" FAIL: pop on a non-empty String\n");
|
||||
err = 1;
|
||||
}
|
||||
StringFree(never);
|
||||
StringFree(cleared);
|
||||
}
|
||||
|
||||
freet(expected);
|
||||
freet(head);
|
||||
freet(tail);
|
||||
|
||||
printf("strsprintf self-test: %s\n", err ? "FAIL" : "OK");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* Registry: name -> handler, with a usage hint and a one-line description. */
|
||||
/* ------------------------------------------------------------ */
|
||||
@@ -7829,8 +7685,6 @@ static const struct selftest_entry {
|
||||
{"hashtable", "<count|file>", "coucal hashtable stress test", st_hashtable},
|
||||
{"strsafe", "[overflow|overflow-buff|overflow-src [str]]",
|
||||
"bounded string-op self-test", st_strsafe},
|
||||
{"strsprintf", "", "StringSprintf grows to fit at every capacity boundary",
|
||||
st_strsprintf},
|
||||
{"copyopt", "", "copy_htsopt option-copy self-test", st_copyopt},
|
||||
{"lastchar", "",
|
||||
"last-char helpers never index before the buffer (#770, #781, #821)",
|
||||
|
||||
@@ -36,9 +36,6 @@ Please visit our Website: http://www.httrack.com
|
||||
#define HTS_STRINGS_DEFSTATIC
|
||||
|
||||
/* System definitions. */
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* GCC extension */
|
||||
@@ -94,6 +91,10 @@ struct String {
|
||||
|
||||
#define STRING_FREE(BUFF) free(BUFF)
|
||||
#endif
|
||||
#ifndef STRING_ASSERT
|
||||
#include <assert.h>
|
||||
#define STRING_ASSERT(EXP) assert(EXP)
|
||||
#endif
|
||||
|
||||
/** Initializer for an empty String (NULL buffer). Use to declare or reset. **/
|
||||
#define STRING_EMPTY {(char *) NULL, 0, 0}
|
||||
@@ -126,44 +127,27 @@ struct String {
|
||||
/** Byte POS positions from the end (read/write). POS==1 is the last byte. **/
|
||||
#define StringRightRW(BLK, POS) (StringBuffRW(BLK)[StringLength(BLK) - POS])
|
||||
|
||||
/** Drop the last byte and re-terminate. No-op on an empty String: the length
|
||||
is unsigned, so an unguarded pop would wrap it and write off the end. **/
|
||||
/** Drop the last byte and re-terminate. Undefined if the String is empty
|
||||
(no length check; would underflow). **/
|
||||
#define StringPopRight(BLK) \
|
||||
do { \
|
||||
if (StringLength(BLK) > 0) { \
|
||||
StringBuffRW(BLK)[--StringLength(BLK)] = '\0'; \
|
||||
} \
|
||||
StringBuffRW(BLK)[--StringLength(BLK)] = '\0'; \
|
||||
} while (0)
|
||||
|
||||
/** Terminate on allocation failure. The String API returns void throughout, so
|
||||
there is no channel to report it on, and continuing would hand the caller a
|
||||
NULL buffer to write into. **/
|
||||
HTS_STATIC void StringOom_(size_t size) {
|
||||
fprintf(stderr, "String: out of memory allocating %lu bytes\n",
|
||||
(unsigned long) size);
|
||||
fflush(stderr); /* abort() flushes nothing; Windows buffers a piped stderr */
|
||||
abort();
|
||||
}
|
||||
|
||||
/** What to do when an allocation of SIZE bytes fails; overridable. **/
|
||||
#ifndef STRING_OOM
|
||||
#define STRING_OOM(SIZE) StringOom_(SIZE)
|
||||
#endif
|
||||
|
||||
/** Grow so capacity_ >= CAPACITY (total bytes, including the NUL). May realloc
|
||||
(invalidating prior buffer pointers); aborts on OOM. Never shrinks. **/
|
||||
(invalidating prior buffer pointers); aborts via STRING_ASSERT on OOM.
|
||||
Never shrinks. **/
|
||||
#define StringRoomTotal(BLK, CAPACITY) \
|
||||
do { \
|
||||
const size_t capacity_ = (size_t) (CAPACITY); \
|
||||
while ((BLK).capacity_ < capacity_) { \
|
||||
const size_t newcap_ = (BLK).capacity_ < 16 ? 16 : (BLK).capacity_ * 2; \
|
||||
char *const buff_ = STRING_REALLOC((BLK).buffer_, newcap_); \
|
||||
\
|
||||
if (buff_ == NULL) { \
|
||||
STRING_OOM(newcap_); \
|
||||
if ((BLK).capacity_ < 16) { \
|
||||
(BLK).capacity_ = 16; \
|
||||
} else { \
|
||||
(BLK).capacity_ *= 2; \
|
||||
} \
|
||||
(BLK).buffer_ = buff_; \
|
||||
(BLK).capacity_ = newcap_; \
|
||||
(BLK).buffer_ = STRING_REALLOC((BLK).buffer_, (BLK).capacity_); \
|
||||
STRING_ASSERT((BLK).buffer_ != NULL); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
@@ -181,46 +165,6 @@ HTS_STATIC char *StringBuffN_(String *blk, int size) {
|
||||
return StringBuffRW(*blk);
|
||||
}
|
||||
|
||||
/** Ceiling on the pre-C99 doubling search below; BLK is emptied past it. **/
|
||||
#define STRING_SPRINTF_MAX ((size_t) 16 * 1024 * 1024)
|
||||
|
||||
/** Replace BLK's contents with the formatted output, growing to fit, so no
|
||||
fixed reserve has to bound an argument carrying remote input. An argument
|
||||
may not point into BLK's own buffer, which is reallocated here.
|
||||
Leaves BLK empty if the output cannot be produced (a conversion error, or
|
||||
past STRING_SPRINTF_MAX): callers must not assume a non-empty result. **/
|
||||
#define StringSprintf(BLK, ...) StringSprintf_(&(BLK), __VA_ARGS__)
|
||||
|
||||
HTS_STATIC HTS_PRINTF_FUN(2, 3) void StringSprintf_(String *blk,
|
||||
const char *fmt, ...) {
|
||||
size_t capacity = StringCapacity(*blk) > 256 ? StringCapacity(*blk) : 256;
|
||||
|
||||
for (;;) {
|
||||
va_list args;
|
||||
int ret;
|
||||
|
||||
StringRoomTotal(*blk, capacity);
|
||||
va_start(args, fmt);
|
||||
ret = vsnprintf(StringBuffRW(*blk), capacity, fmt, args);
|
||||
va_end(args);
|
||||
if (ret >= 0 && (size_t) ret < capacity) {
|
||||
StringBuffRW(*blk)[ret] = '\0';
|
||||
StringLength(*blk) = (size_t) ret;
|
||||
return;
|
||||
}
|
||||
if (ret >= 0) {
|
||||
capacity = (size_t) ret + 1; /* C99 said what it needs */
|
||||
} else if (capacity < STRING_SPRINTF_MAX) {
|
||||
capacity *= 2; /* pre-C99 msvcrt only says "too small" */
|
||||
} else {
|
||||
/* a conversion error returns -1 too, and no capacity ever fixes that */
|
||||
StringBuffRW(*blk)[0] = '\0';
|
||||
StringLength(*blk) = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Zero the fields (NULL buffer, no allocation). Use on an uninitialized
|
||||
String only; does NOT free an existing buffer (use StringFree to reset
|
||||
an owned one), so calling it on a live String leaks. **/
|
||||
|
||||
@@ -548,37 +548,29 @@ static void proxytrack_add_DAV_Item(String * item, String * buff,
|
||||
name = "Default Document for the Folder";
|
||||
}
|
||||
|
||||
/* The path lands here twice and escapexml() expands '&' fivefold, so no
|
||||
fixed reserve bounds it (#836). */
|
||||
StringSprintf(
|
||||
*item,
|
||||
"<response xmlns=\"DAV:\">\r\n"
|
||||
"<href>/webdav%s%s</href>\r\n"
|
||||
"<propstat>\r\n"
|
||||
"<prop>\r\n"
|
||||
"<displayname>%s</displayname>\r\n"
|
||||
"<iscollection>%d</iscollection>\r\n"
|
||||
"<haschildren>%d</haschildren>\r\n"
|
||||
"<isfolder>%d</isfolder>\r\n"
|
||||
"<resourcetype>%s</resourcetype>\r\n"
|
||||
"<creationdate>%d-%02d-%02dT%02d:%02d:%02dZ</creationdate>\r\n"
|
||||
"<getlastmodified>%s</getlastmodified>\r\n"
|
||||
"<supportedlock></supportedlock>\r\n"
|
||||
"<lockdiscovery/>\r\n"
|
||||
"<getcontenttype>%s</getcontenttype>\r\n"
|
||||
"<getcontentlength>%d</getcontentlength>\r\n"
|
||||
"<isroot>%d</isroot>\r\n"
|
||||
"</prop>\r\n"
|
||||
"<status>HTTP/1.1 200 OK</status>\r\n"
|
||||
"</propstat>\r\n"
|
||||
"</response>\r\n",
|
||||
/* */
|
||||
(StringBuff(*buff)[0] == '/') ? "" : "/", StringBuff(*buff), name,
|
||||
isDir ? 1 : 0, isDir ? 1 : 0, isDir ? 1 : 0,
|
||||
isDir ? "<collection/>" : "", timetm->tm_year + 1900,
|
||||
timetm->tm_mon + 1, timetm->tm_mday, timetm->tm_hour, timetm->tm_min,
|
||||
timetm->tm_sec, tms, isDir ? "httpd/unix-directory" : mime, (int) size,
|
||||
isRoot ? 1 : 0);
|
||||
StringRoom(*item, 1024);
|
||||
sprintf(StringBuffRW(*item),
|
||||
"<response xmlns=\"DAV:\">\r\n" "<href>/webdav%s%s</href>\r\n"
|
||||
"<propstat>\r\n" "<prop>\r\n" "<displayname>%s</displayname>\r\n"
|
||||
"<iscollection>%d</iscollection>\r\n"
|
||||
"<haschildren>%d</haschildren>\r\n" "<isfolder>%d</isfolder>\r\n"
|
||||
"<resourcetype>%s</resourcetype>\r\n"
|
||||
"<creationdate>%d-%02d-%02dT%02d:%02d:%02dZ</creationdate>\r\n"
|
||||
"<getlastmodified>%s</getlastmodified>\r\n"
|
||||
"<supportedlock></supportedlock>\r\n" "<lockdiscovery/>\r\n"
|
||||
"<getcontenttype>%s</getcontenttype>\r\n"
|
||||
"<getcontentlength>%d</getcontentlength>\r\n"
|
||||
"<isroot>%d</isroot>\r\n" "</prop>\r\n"
|
||||
"<status>HTTP/1.1 200 OK</status>\r\n" "</propstat>\r\n"
|
||||
"</response>\r\n",
|
||||
/* */
|
||||
(StringBuff(*buff)[0] == '/') ? "" : "/", StringBuff(*buff), name,
|
||||
isDir ? 1 : 0, isDir ? 1 : 0, isDir ? 1 : 0,
|
||||
isDir ? "<collection/>" : "", timetm->tm_year + 1900,
|
||||
timetm->tm_mon + 1, timetm->tm_mday, timetm->tm_hour,
|
||||
timetm->tm_min, timetm->tm_sec, tms,
|
||||
isDir ? "httpd/unix-directory" : mime, (int) size, isRoot ? 1 : 0);
|
||||
StringLength(*item) = (int) strlen(StringBuff(*item));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,8 +721,11 @@ static PT_Element proxytrack_process_DAV_Request(PT_Indexes indexes,
|
||||
}
|
||||
|
||||
/* Form response */
|
||||
StringSprintf(response, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
|
||||
"<multistatus xmlns=\"DAV:\">\r\n");
|
||||
StringRoom(response, 1024);
|
||||
sprintf(StringBuffRW(response),
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"
|
||||
"<multistatus xmlns=\"DAV:\">\r\n");
|
||||
StringLength(response) = (int) strlen(StringBuff(response));
|
||||
/* */
|
||||
|
||||
/* Root */
|
||||
@@ -744,6 +739,7 @@ static PT_Element proxytrack_process_DAV_Request(PT_Indexes indexes,
|
||||
if (depth > 0) {
|
||||
time_t timestampRep = (time_t) - 1;
|
||||
const char *prefix = StringBuff(url);
|
||||
unsigned int prefixLen = (unsigned int) strlen(prefix);
|
||||
char **list = PT_Enumerate(indexes, prefix, 0);
|
||||
|
||||
if (list != NULL) {
|
||||
@@ -756,13 +752,15 @@ static PT_Element proxytrack_process_DAV_Request(PT_Indexes indexes,
|
||||
int thisIsDir = (hts_lastchar(thisUrl) == '/') ? 1 : 0;
|
||||
|
||||
/* Item URL */
|
||||
StringSprintf(itemUrl, "/%s/%s", prefix, thisUrl);
|
||||
if (!StringNotEmpty(itemUrl)) { /* formatting gave up: unnameable */
|
||||
continue;
|
||||
}
|
||||
if (thisIsDir) { /* drop the trailing '/' */
|
||||
StringPopRight(itemUrl);
|
||||
}
|
||||
StringRoom(itemUrl,
|
||||
thisUrlLen + prefixLen + sizeof("/webdav/") + 1);
|
||||
StringClear(itemUrl);
|
||||
sprintf(StringBuffRW(itemUrl), "/%s/%s", prefix, thisUrl);
|
||||
if (!thisIsDir)
|
||||
StringLength(itemUrl) = (int) strlen(StringBuff(itemUrl));
|
||||
else
|
||||
StringLength(itemUrl) = (int) strlen(StringBuff(itemUrl)) - 1;
|
||||
StringBuffRW(itemUrl)[StringLength(itemUrl)] = '\0';
|
||||
|
||||
if (thisIsDir == isDir) {
|
||||
size_t size = 0;
|
||||
@@ -829,8 +827,6 @@ static PT_Element proxytrack_process_DAV_Request(PT_Indexes indexes,
|
||||
strcpybuff(elt->msg, "Multi-Status");
|
||||
StringFree(response);
|
||||
|
||||
fprintf(stderr, "RESPONSE:\n%s\n", elt->adr);
|
||||
|
||||
return elt;
|
||||
}
|
||||
return NULL;
|
||||
@@ -1039,19 +1035,17 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
const char *options = "GET, HEAD, OPTIONS, POST, PROPFIND, TRACE" ", MKCOL, DELETE, PUT"; /* Not supported */
|
||||
|
||||
msgCode = HTTP_OK;
|
||||
StringSprintf(headers,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"DAV: 1, 2\r\n"
|
||||
"MS-Author-Via: DAV\r\n"
|
||||
"Cache-Control: private\r\n"
|
||||
"Allow: %s\r\n",
|
||||
msgCode, GetHttpMessage(msgCode), options);
|
||||
StringRoom(headers, 8192);
|
||||
sprintf(StringBuffRW(headers),
|
||||
"HTTP/1.1 %d %s\r\n" "DAV: 1, 2\r\n" "MS-Author-Via: DAV\r\n"
|
||||
"Cache-Control: private\r\n" "Allow: %s\r\n", msgCode,
|
||||
GetHttpMessage(msgCode), options);
|
||||
StringLength(headers) = (int) strlen(StringBuff(headers));
|
||||
} else if (strcasecmp(command, "propfind") == 0) {
|
||||
if (davDepth > 1) {
|
||||
msgCode = 403;
|
||||
msgError = "DAV Depth Limit Forbidden";
|
||||
} else {
|
||||
fprintf(stderr, "DEBUG: DAV-DATA=<%s>\n", StringBuff(davRequest));
|
||||
listRequest = 2; /* propfind */
|
||||
}
|
||||
} else if (strcasecmp(command, "mkcol") == 0
|
||||
@@ -1144,9 +1138,11 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
proxytrack_process_DAV_Request(indexes, StringBuff(url),
|
||||
davDepth)) != NULL) {
|
||||
msgCode = element->statuscode;
|
||||
StringSprintf(davHeaders, "DAV: 1, 2\r\n"
|
||||
"MS-Author-Via: DAV\r\n"
|
||||
"Cache-Control: private\r\n");
|
||||
StringRoom(davHeaders, 1024);
|
||||
sprintf(StringBuffRW(davHeaders),
|
||||
"DAV: 1, 2\r\n" "MS-Author-Via: DAV\r\n"
|
||||
"Cache-Control: private\r\n");
|
||||
StringLength(davHeaders) = (int) strlen(StringBuff(davHeaders));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1165,46 +1161,45 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
}
|
||||
}
|
||||
if (element != NULL) {
|
||||
/* lifted out of the format: a directive inside a macro argument list
|
||||
is undefined, and MSVC rejects it */
|
||||
#ifndef NO_WEBDAV
|
||||
const char *const davPart = StringBuff(davHeaders);
|
||||
#else
|
||||
const char *const davPart = "";
|
||||
#endif
|
||||
|
||||
msgCode = element->statuscode;
|
||||
StringSprintf(
|
||||
headers,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"%s"
|
||||
"Content-Type: %s%s%s%s\r\n"
|
||||
"%s%s%s"
|
||||
"%s%s%s"
|
||||
"%s%s%s",
|
||||
/* */
|
||||
msgCode, element->msg, davPart,
|
||||
/* Content-type: foo; [ charset=bar ] */
|
||||
hts_effective_mime(element->contenttype),
|
||||
((element->charset[0]) ? "; charset=\"" : ""), element->charset,
|
||||
((element->charset[0]) ? "\"" : ""),
|
||||
/* location */
|
||||
((element->location != NULL && element->location[0])
|
||||
? "Location: "
|
||||
: ""),
|
||||
((element->location != NULL && element->location[0])
|
||||
? element->location
|
||||
: ""),
|
||||
((element->location != NULL && element->location[0]) ? "\r\n"
|
||||
: ""),
|
||||
/* last-modified */
|
||||
((element->lastmodified[0]) ? "Last-Modified: " : ""),
|
||||
((element->lastmodified[0]) ? element->lastmodified : ""),
|
||||
((element->lastmodified[0]) ? "\r\n" : ""),
|
||||
/* etag */
|
||||
((element->etag[0]) ? "ETag: " : ""),
|
||||
((element->etag[0]) ? element->etag : ""),
|
||||
((element->etag[0]) ? "\r\n" : ""));
|
||||
StringRoom(headers, 8192);
|
||||
sprintf(StringBuffRW(headers),
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
#ifndef NO_WEBDAV
|
||||
"%s"
|
||||
#endif
|
||||
"Content-Type: %s%s%s%s\r\n"
|
||||
"%s%s%s"
|
||||
"%s%s%s"
|
||||
"%s%s%s",
|
||||
/* */
|
||||
msgCode, element->msg,
|
||||
#ifndef NO_WEBDAV
|
||||
/* DAV */
|
||||
StringBuff(davHeaders),
|
||||
#endif
|
||||
/* Content-type: foo; [ charset=bar ] */
|
||||
hts_effective_mime(element->contenttype),
|
||||
((element->charset[0]) ? "; charset=\"" : ""),
|
||||
element->charset, ((element->charset[0]) ? "\"" : ""),
|
||||
/* location */
|
||||
((element->location != NULL && element->location[0])
|
||||
? "Location: "
|
||||
: ""),
|
||||
((element->location != NULL && element->location[0])
|
||||
? element->location
|
||||
: ""),
|
||||
((element->location != NULL && element->location[0]) ? "\r\n"
|
||||
: ""),
|
||||
/* last-modified */
|
||||
((element->lastmodified[0]) ? "Last-Modified: " : ""),
|
||||
((element->lastmodified[0]) ? element->lastmodified : ""),
|
||||
((element->lastmodified[0]) ? "\r\n" : ""),
|
||||
/* etag */
|
||||
((element->etag[0]) ? "ETag: " : ""),
|
||||
((element->etag[0]) ? element->etag : ""),
|
||||
((element->etag[0]) ? "\r\n" : ""));
|
||||
StringLength(headers) = (int) strlen(StringBuff(headers));
|
||||
} else {
|
||||
/* No query string, no ending / : check the the <url>/ page */
|
||||
if (StringLength(url) > 0
|
||||
@@ -1214,32 +1209,29 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
StringCat(urlRedirect, "/");
|
||||
if (PT_LookupIndex(indexes, StringBuff(urlRedirect))) {
|
||||
msgCode = 301; /* Moved Permanently */
|
||||
StringSprintf(headers,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Content-Type: text/html\r\n"
|
||||
"Location: %s\r\n",
|
||||
/* */
|
||||
msgCode, GetHttpMessage(msgCode),
|
||||
StringBuff(urlRedirect));
|
||||
StringRoom(headers, 8192);
|
||||
sprintf(StringBuffRW(headers),
|
||||
"HTTP/1.1 %d %s\r\n" "Content-Type: text/html\r\n"
|
||||
"Location: %s\r\n",
|
||||
/* */
|
||||
msgCode, GetHttpMessage(msgCode), StringBuff(urlRedirect)
|
||||
);
|
||||
StringLength(headers) = (int) strlen(StringBuff(headers));
|
||||
/* */
|
||||
StringSprintf(
|
||||
output,
|
||||
"<html"
|
||||
">" PROXYTRACK_COMMENT_HEADER DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES
|
||||
"<head>"
|
||||
"<title>ProxyTrack - Page has moved</title>"
|
||||
"</head>\r\n"
|
||||
"<body>"
|
||||
"<h3>The correct location is:</h3><br />"
|
||||
"<b><a href=\"%s\">%s</a></b><br />"
|
||||
"<br />"
|
||||
"<br />\r\n"
|
||||
"<i>Generated by ProxyTrack " PROXYTRACK_VERSION
|
||||
", (C) Xavier Roche and other contributors</i>"
|
||||
"\r\n"
|
||||
"</body>"
|
||||
"</header>",
|
||||
StringBuff(urlRedirect), StringBuff(urlRedirect));
|
||||
StringRoom(output,
|
||||
1024 + sizeof(PROXYTRACK_COMMENT_HEADER) +
|
||||
sizeof(DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES));
|
||||
sprintf(StringBuffRW(output),
|
||||
"<html>" PROXYTRACK_COMMENT_HEADER
|
||||
DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES "<head>"
|
||||
"<title>ProxyTrack - Page has moved</title>" "</head>\r\n"
|
||||
"<body>" "<h3>The correct location is:</h3><br />"
|
||||
"<b><a href=\"%s\">%s</a></b><br />" "<br />" "<br />\r\n"
|
||||
"<i>Generated by ProxyTrack " PROXYTRACK_VERSION
|
||||
", (C) Xavier Roche and other contributors</i>" "\r\n"
|
||||
"</body>" "</header>", StringBuff(urlRedirect),
|
||||
StringBuff(urlRedirect));
|
||||
StringLength(output) = (int) strlen(StringBuff(output));
|
||||
}
|
||||
}
|
||||
if (msgCode == 0) {
|
||||
@@ -1260,29 +1252,25 @@ static void proxytrack_process_HTTP(PT_Indexes indexes, T_SOC soc_c) {
|
||||
} else if (msgError == NULL) {
|
||||
msgError = GetHttpMessage(msgCode);
|
||||
}
|
||||
StringSprintf(headers,
|
||||
"HTTP/1.1 %d %s\r\n"
|
||||
"Content-type: text/html\r\n",
|
||||
msgCode, msgError);
|
||||
StringSprintf(
|
||||
output,
|
||||
"<html"
|
||||
">" PROXYTRACK_COMMENT_HEADER DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES
|
||||
"<head>"
|
||||
"<title>ProxyTrack - HTTP Proxy Error %d</title>"
|
||||
"</head>\r\n"
|
||||
"<body>"
|
||||
"<h3>A proxy error has occurred while processing the "
|
||||
"request.</h3><br />"
|
||||
"<b>Error HTTP %d: <i>%s</i></b><br />"
|
||||
"<br />"
|
||||
"<br />\r\n"
|
||||
"<i>Generated by ProxyTrack " PROXYTRACK_VERSION
|
||||
", (C) Xavier Roche and other contributors</i>"
|
||||
"\r\n"
|
||||
"</body>"
|
||||
"</html>",
|
||||
msgCode, msgCode, msgError);
|
||||
StringRoom(headers, 256);
|
||||
sprintf(StringBuffRW(headers),
|
||||
"HTTP/1.1 %d %s\r\n" "Content-type: text/html\r\n", msgCode,
|
||||
msgError);
|
||||
StringLength(headers) = (int) strlen(StringBuff(headers));
|
||||
StringRoom(output,
|
||||
1024 + sizeof(PROXYTRACK_COMMENT_HEADER) +
|
||||
sizeof(DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES));
|
||||
sprintf(StringBuffRW(output),
|
||||
"<html>" PROXYTRACK_COMMENT_HEADER
|
||||
DISABLE_IE_FRIENDLY_HTTP_ERROR_MESSAGES "<head>"
|
||||
"<title>ProxyTrack - HTTP Proxy Error %d</title>" "</head>\r\n"
|
||||
"<body>"
|
||||
"<h3>A proxy error has occurred while processing the request.</h3><br />"
|
||||
"<b>Error HTTP %d: <i>%s</i></b><br />" "<br />" "<br />\r\n"
|
||||
"<i>Generated by ProxyTrack " PROXYTRACK_VERSION
|
||||
", (C) Xavier Roche and other contributors</i>" "\r\n" "</body>"
|
||||
"</html>", msgCode, msgCode, msgError);
|
||||
StringLength(output) = (int) strlen(StringBuff(output));
|
||||
}
|
||||
{
|
||||
char tmp[20 + 1]; /* 2^64 = 18446744073709551616 */
|
||||
|
||||
@@ -50,7 +50,31 @@ freeport() {
|
||||
proxyport=$(freeport)
|
||||
icpport=$(freeport)
|
||||
|
||||
proxytrack "127.0.0.1:$proxyport" "127.0.0.1:$icpport" "$dir/in.arc" >"$dir/pt.log" 2>&1 &
|
||||
: >"$dir/pt.log" # the drainer below creates it asynchronously
|
||||
# stdout to a file is fully buffered and SIGTERM never flushes it, so a leak on
|
||||
# stdout would be lost; a pty line-buffers it. The exec keeps $! on proxytrack.
|
||||
"$python" -c '
|
||||
import os, pty, sys
|
||||
master, slave = pty.openpty()
|
||||
if os.fork() == 0:
|
||||
os.close(slave)
|
||||
with open(sys.argv[1], "wb", 0) as log:
|
||||
while True:
|
||||
try:
|
||||
data = os.read(master, 65536)
|
||||
except OSError:
|
||||
break
|
||||
if not data:
|
||||
break
|
||||
log.write(data)
|
||||
os._exit(0)
|
||||
os.close(master)
|
||||
os.dup2(slave, 1)
|
||||
os.dup2(slave, 2)
|
||||
if slave > 2:
|
||||
os.close(slave)
|
||||
os.execvp(sys.argv[2], sys.argv[2:])
|
||||
' "$dir/pt.log" proxytrack "127.0.0.1:$proxyport" "127.0.0.1:$icpport" "$dir/in.arc" &
|
||||
ptpid=$!
|
||||
waited=0
|
||||
until grep -qE "HTTP Proxy installed on|Unable to (initialize a temporary server|create the server)" "$dir/pt.log"; do
|
||||
@@ -73,8 +97,10 @@ grep -q "HTTP Proxy installed on" "$dir/pt.log" || {
|
||||
}
|
||||
|
||||
propfind() {
|
||||
curl -s -i --max-time 30 -X PROPFIND -H "Depth: 1" \
|
||||
"http://127.0.0.1:$proxyport/webdav/example.com/$1"
|
||||
local path=$1
|
||||
shift
|
||||
curl -s -i --max-time 30 -X PROPFIND -H "Depth: 1" "$@" \
|
||||
"http://127.0.0.1:$proxyport/webdav/example.com/$path"
|
||||
}
|
||||
|
||||
resp=$(propfind page.html) || {
|
||||
@@ -118,4 +144,44 @@ grep -q '^HTTP/1\.[01] 207 ' <<<"$resp" || {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Leftover traces echoed the client's body and the generated response (#911).
|
||||
# The padding spills the canary past any stdio buffer, newline or not.
|
||||
sentinel=PT911-DAV-BODY-CANARY
|
||||
pad=$(printf '%8192s' '')
|
||||
resp=$(propfind "" -H 'Expect:' --data-binary "$sentinel${pad// /x}") || {
|
||||
echo "FAIL: proxytrack died serving a PROPFIND carrying a body"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
grep -q '^HTTP/1\.[01] 207 ' <<<"$resp" || {
|
||||
echo "FAIL: expected 207 Multi-Status on the canary listing"
|
||||
printf '%s\n' "$resp"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# The 207 is what makes the absence below mean something: a depth-rejected
|
||||
# PROPFIND is logged 403 without ever reaching the traces. It is logged after
|
||||
# them, so waiting on it also fences the drainer's copy.
|
||||
waited=0
|
||||
until test "$(grep -ci ' \* log: HTTP [^ ]* 207 [0-9]* propfind ' "$dir/pt.log" || true)" -ge 3; do
|
||||
test "$waited" -lt 50 || {
|
||||
echo "FAIL: fewer than three PROPFINDs were answered 207, so the checks below prove nothing"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
sleep 0.1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
|
||||
# Neither what the client sent nor the generated body is echoed anywhere; the
|
||||
# two literals name the exact regression on top.
|
||||
log=$(<"$dir/pt.log")
|
||||
for marker in "$sentinel" 'Default Document for the Folder' 'DEBUG: DAV-DATA' '^RESPONSE:'; do
|
||||
if grep -q "$marker" <<<"$log"; then
|
||||
echo "FAIL: a PROPFIND echoed '$marker' into proxytrack's output"
|
||||
printf '%s\n' "$log"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "OK: PROPFIND on an exact cache entry lists its default document and survives"
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# escapexml() expands '&' fivefold, so an unauthenticated PROPFIND path of
|
||||
# escaped ampersands used to overflow the fixed DAV item reserve (#836). The
|
||||
# Depth: 1 listing at the end covers the enumeration branch's own item URLs.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
# shellcheck source=tests/testlib.sh
|
||||
. "$top_srcdir/tests/testlib.sh"
|
||||
|
||||
python=$(find_python) || {
|
||||
echo "python3 missing, skipping"
|
||||
exit 77
|
||||
}
|
||||
command -v curl >/dev/null 2>&1 || {
|
||||
echo "curl missing, skipping"
|
||||
exit 77
|
||||
}
|
||||
# MSYS cannot reap a native listener, and the orphan wedges the suite (#595).
|
||||
if is_windows; then
|
||||
echo "windows: cannot reap a backgrounded proxytrack, skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
dir=$(mktemp -d)
|
||||
ptpid=
|
||||
cleanup() {
|
||||
stop_server "$ptpid"
|
||||
rm -rf "$dir"
|
||||
}
|
||||
trap 'set +e; cleanup' EXIT
|
||||
trap 'set +e; cleanup; exit 1' INT TERM HUP
|
||||
|
||||
printf 'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 5\r\n\r\n' >"$dir/hdr"
|
||||
printf 'hello' >"$dir/body"
|
||||
alen=$(($(wc -c <"$dir/hdr") + $(wc -c <"$dir/body")))
|
||||
{
|
||||
printf 'filedesc://t.arc 0.0.0.0 20250101000000 text/plain 200 - - 0 t.arc 9\n'
|
||||
printf '2 0 test\n'
|
||||
printf '\n\n'
|
||||
printf 'http://example.com/page.html 0.0.0.0 20250101000000 text/html 200 - - 0 t.arc %d\n' "$alen"
|
||||
cat "$dir/hdr" "$dir/body"
|
||||
# a record begins on the newline following the previous one's data
|
||||
printf '\n'
|
||||
# gives the Depth: 1 listing below a child directory as well as a child file
|
||||
printf 'http://example.com/sub/deep.html 0.0.0.0 20250101000000 text/html 200 - - 0 t.arc %d\n' "$alen"
|
||||
cat "$dir/hdr" "$dir/body"
|
||||
} >"$dir/in.arc"
|
||||
|
||||
freeport() {
|
||||
"$python" -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1",0)); print(s.getsockname()[1]); s.close()'
|
||||
}
|
||||
proxyport=$(freeport)
|
||||
icpport=$(freeport)
|
||||
|
||||
proxytrack "127.0.0.1:$proxyport" "127.0.0.1:$icpport" "$dir/in.arc" >"$dir/pt.log" 2>&1 &
|
||||
ptpid=$!
|
||||
waited=0
|
||||
until grep -qE "HTTP Proxy installed on|Unable to (initialize a temporary server|create the server)" "$dir/pt.log"; do
|
||||
kill -0 "$ptpid" 2>/dev/null || {
|
||||
echo "FAIL: proxytrack exited before listening"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
test "$waited" -lt 50 || {
|
||||
echo "FAIL: proxytrack never announced its listen port"
|
||||
exit 1
|
||||
}
|
||||
sleep 0.1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
grep -q "HTTP Proxy installed on" "$dir/pt.log" || {
|
||||
echo "FAIL: proxytrack failed to bind"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
|
||||
propfind() {
|
||||
curl -s -i --max-time 30 -X PROPFIND -H "Depth: 0" \
|
||||
"http://127.0.0.1:$proxyport/webdav/x/$1"
|
||||
}
|
||||
|
||||
# Amplification, not raw length, clears the old 1024-byte reserve: 900 '&'
|
||||
# become 4500 bytes, written twice, all under the 1024-byte request-line cap.
|
||||
amps=$("$python" -c "print('&' * 900)")
|
||||
resp=$(propfind "$amps") || {
|
||||
echo "FAIL: proxytrack died on an escapexml-amplified PROPFIND"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
grep -q '^HTTP/1\.[01] 207 ' <<<"$resp" || {
|
||||
echo "FAIL: expected 207 Multi-Status for the amplified path"
|
||||
head -c 512 <<<"$resp"
|
||||
exit 1
|
||||
}
|
||||
# Count what came back: a clipped path answers 207 too. The run appears in
|
||||
# the href and again in the displayname.
|
||||
got=$({ grep -o '&' <<<"$resp" || true; } | wc -l)
|
||||
test "$got" -eq 1800 || {
|
||||
echo "FAIL: 900 ampersands went in, $got came back escaped, wanted 1800"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Unescaped, so the bound is shown to be on the written length, not on '&'.
|
||||
plain=$("$python" -c "print('a' * 900)")
|
||||
resp=$(propfind "$plain") || {
|
||||
echo "FAIL: proxytrack died on a 900-byte plain PROPFIND path"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
grep -q "<href>/webdav/x/$plain</href>" <<<"$resp" || {
|
||||
echo "FAIL: a 900-byte path did not come back whole"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Depth: 1 is the only route into the enumeration branch, which builds each
|
||||
# item URL from the prefix and the child name. A child directory is enumerated
|
||||
# with a trailing '/' that has to come back off, or its name is lost and the
|
||||
# entry lists as the folder's default document.
|
||||
resp=$(curl -s -i --max-time 30 -X PROPFIND -H "Depth: 1" \
|
||||
"http://127.0.0.1:$proxyport/webdav/example.com/") || {
|
||||
echo "FAIL: proxytrack died on a Depth: 1 listing"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
grep -q '^HTTP/1\.[01] 207 ' <<<"$resp" || {
|
||||
echo "FAIL: expected 207 Multi-Status for the Depth: 1 listing"
|
||||
printf '%s\n' "$resp"
|
||||
exit 1
|
||||
}
|
||||
for want in '<href>/webdav/example\.com</href>' \
|
||||
'<href>/webdav/example\.com/page\.html</href>' \
|
||||
'<href>/webdav/example\.com/sub</href>' \
|
||||
'<displayname>sub</displayname>'; do
|
||||
grep -q "$want" <<<"$resp" || {
|
||||
echo "FAIL: the Depth: 1 listing is missing $want"
|
||||
printf '%s\n' "$resp"
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
responses=$(grep -c '<response ' <<<"$resp" || true)
|
||||
test "$responses" -eq 3 || {
|
||||
echo "FAIL: expected the root, the file and the directory, got $responses responses"
|
||||
printf '%s\n' "$resp"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# The overflow killed the listener, so a later request is the liveness proof.
|
||||
resp=$(propfind "") || {
|
||||
echo "FAIL: proxytrack died before the follow-up listing"
|
||||
cat "$dir/pt.log"
|
||||
exit 1
|
||||
}
|
||||
grep -q '^HTTP/1\.[01] 207 ' <<<"$resp" || {
|
||||
echo "FAIL: expected 207 Multi-Status on the follow-up listing"
|
||||
printf '%s\n' "$resp"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "OK: an escapexml-amplified PROPFIND path is served whole and does not overflow"
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A trailer section after the terminating chunk (RFC 9112 7.1.2, #855) is
|
||||
# discarded, bounded by HTS_LINE_BLOCK_SIZE (huge.html), and junk where a data
|
||||
# chunk's own CRLF belongs stays a framing error (bogus.html). eof.html pins the
|
||||
# deliberate leniency: past a complete body, an unterminated section still lands.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
bash "$top_srcdir/tests/local-crawl.sh" \
|
||||
--log-not-found 'Invalid chunk.*chunktrail/(one|many|none|file|eof)' \
|
||||
--log-not-found 'illegal chunk CRLF.*chunktrail/(one|many|none|file|huge|eof)' \
|
||||
--file-matches 'chunktrail/one.html' 'CHUNKTRAIL-ONE' \
|
||||
--file-matches 'chunktrail/many.html' 'CHUNKTRAIL-MANY' \
|
||||
--file-matches 'chunktrail/none.html' 'CHUNKTRAIL-NONE' \
|
||||
--file-matches 'chunktrail/file.bin' 'CHUNKTRAIL-BIN' \
|
||||
--file-min-bytes 'chunktrail/file.bin' 16384 \
|
||||
--file-matches 'chunktrail/eof.html' 'CHUNKTRAIL-EOF' \
|
||||
--cache-found '/chunktrail/one.html' \
|
||||
--cache-found '/chunktrail/many.html' \
|
||||
--cache-found '/chunktrail/file.bin' \
|
||||
--log-found 'Interrupted transfer.*chunktrail/huge\.html' \
|
||||
--not-found 'chunktrail/huge.html' \
|
||||
--cache-not-found '/chunktrail/huge.html' \
|
||||
--log-found 'illegal chunk CRLF.*chunktrail/bogus\.html' \
|
||||
--not-found 'chunktrail/bogus.html' \
|
||||
--cache-not-found '/chunktrail/bogus.html' \
|
||||
httrack 'BASEURL/chunktrail/index.html'
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# StringSprintf writes the terminator at buffer[ret], so a `ret <= capacity`
|
||||
# guard overflows by one byte whenever the output exactly fills the capacity
|
||||
# (#836). The proxytrack crawl tests never land on a capacity boundary.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${top_srcdir:=..}"
|
||||
|
||||
# Resolve httrack before any cd: PATH may carry a build-relative entry.
|
||||
bin=$(command -v httrack) || {
|
||||
echo "FAIL: httrack not found on PATH" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
out=$("$bin" -#test=strsprintf) || fail "httrack -#test=strsprintf exited non-zero: $out"
|
||||
test "$out" = "strsprintf self-test: OK" || fail "expected 'strsprintf self-test: OK', got: $out"
|
||||
|
||||
exit 0
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# A String that cannot grow must stop the process loudly rather than carry on
|
||||
# with a NULL buffer and a capacity already bumped. assert() is compiled out of
|
||||
# the MSVC Release build, so the check cannot lean on it (#915). The failure is
|
||||
# injected through a realloc stub: asking a real allocator for a size it should
|
||||
# refuse is a guess about the machine, not a test.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ulimit -c 0 # a deliberate abort must not litter the box with cores
|
||||
|
||||
fail() {
|
||||
echo "FAIL: $1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Unset means this is not an automake run (the Windows job runs the scripts
|
||||
# directly), so the helper does not exist. Set but missing is a build problem,
|
||||
# not a skip, or the assertions below would pass vacuously.
|
||||
if [ -z "${STRINGOOM_BIN:-}" ]; then
|
||||
echo "STRINGOOM_BIN unset, no automake environment, skipping" >&2
|
||||
exit 77
|
||||
fi
|
||||
[ -r "$STRINGOOM_BIN" ] || fail "$STRINGOOM_BIN was not built"
|
||||
|
||||
# Control first: with the stub allocating for real, growth must not reach the
|
||||
# failure path at all.
|
||||
out=$("$STRINGOOM_BIN" grow) || fail "grow case exited non-zero: $out"
|
||||
test "$out" = "grow: OK" || fail "expected 'grow: OK', got: $out"
|
||||
|
||||
# A failed realloc must reach the handler with the size it asked for, and leave
|
||||
# the String untouched rather than half-grown.
|
||||
out=$("$STRINGOOM_BIN" hook) || fail "hook case exited non-zero: $out"
|
||||
test "$out" = "hook: OK" || fail "expected 'hook: OK', got: $out"
|
||||
|
||||
# Same on a String that already owns a buffer: the live pointer must survive.
|
||||
out=$("$STRINGOOM_BIN" keep) || fail "keep case exited non-zero: $out"
|
||||
test "$out" = "keep: OK" || fail "expected 'keep: OK', got: $out"
|
||||
|
||||
# The shipped handler prints and aborts, so don't let set -e trip on it.
|
||||
err=$("$STRINGOOM_BIN" abort 2>&1) || true
|
||||
case "$err" in
|
||||
*"NOT aborted"*)
|
||||
fail "the failed allocation was not caught: $err"
|
||||
;;
|
||||
*"String: out of memory"*) ;;
|
||||
*)
|
||||
fail "expected the String out-of-memory abort, got: $err"
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -36,7 +36,6 @@ TESTS_ENVIRONMENT += THREADATTRFAIL_LA=$(abs_builddir)/libthreadattrfail.la
|
||||
TESTS_ENVIRONMENT += THREADATTRFAIL_LIB=$(abs_builddir)/$(LT_CV_OBJDIR)/libthreadattrfail.so
|
||||
TESTS_ENVIRONMENT += NOBACKTRACE_LA=$(abs_builddir)/libnobacktrace.la
|
||||
TESTS_ENVIRONMENT += NOBACKTRACE_LIB=$(abs_builddir)/$(LT_CV_OBJDIR)/libnobacktrace.so
|
||||
TESTS_ENVIRONMENT += STRINGOOM_BIN=$(abs_builddir)/stringoom$(EXEEXT)
|
||||
|
||||
# rename() interposer for 01_engine-renameover.test; -rpath is what makes
|
||||
# libtool build a shared module rather than a static-only convenience library.
|
||||
@@ -54,12 +53,6 @@ libthreadattrfail_la_LIBADD = $(DL_LIBS)
|
||||
libnobacktrace_la_SOURCES = nobacktrace.c
|
||||
libnobacktrace_la_LDFLAGS = -module -avoid-version -rpath $(abs_builddir)
|
||||
|
||||
# realloc stub for 152_engine-string-oom.test; links nothing, so it stays a
|
||||
# plain binary rather than a libtool wrapper script.
|
||||
check_PROGRAMS = stringoom
|
||||
stringoom_SOURCES = stringoom.c
|
||||
stringoom_CPPFLAGS = -I$(top_srcdir)/src
|
||||
|
||||
TEST_EXTENSIONS = .test
|
||||
# Run each .test through bash instead of execve()ing it. This lets "make check"
|
||||
# work when the source tree sits on a noexec filesystem (the driver would
|
||||
|
||||
@@ -1692,112 +1692,6 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
pass
|
||||
self.close_connection = True
|
||||
|
||||
# #855: a trailer section after the terminating zero-length chunk.
|
||||
def send_chunked_trailed(self, body, trailers, ctype="text/html; charset=utf-8"):
|
||||
self.protocol_version = "HTTP/1.1"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", ctype)
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
if self.command == "HEAD":
|
||||
return
|
||||
try:
|
||||
half = len(body) // 2
|
||||
for piece in (body[:half], body[half:]):
|
||||
self.wfile.write(b"%X\r\n" % len(piece) + piece + b"\r\n")
|
||||
self.wfile.write(
|
||||
b"0\r\n" + b"".join(t + b"\r\n" for t in trailers) + b"\r\n"
|
||||
)
|
||||
self.wfile.flush()
|
||||
except OSError:
|
||||
pass
|
||||
self.close_connection = True
|
||||
|
||||
def route_chunktrail_index(self):
|
||||
self.send_html(
|
||||
'\t<a href="one.html">one</a>\n'
|
||||
'\t<a href="many.html">many</a>\n'
|
||||
'\t<a href="none.html">none</a>\n'
|
||||
'\t<a href="huge.html">huge</a>\n'
|
||||
'\t<a href="bogus.html">bogus</a>\n'
|
||||
'\t<a href="eof.html">eof</a>\n'
|
||||
'\t<a href="file.bin">file</a>\n'
|
||||
)
|
||||
|
||||
def route_chunktrail_one(self):
|
||||
self.send_chunked_trailed(
|
||||
b"<html><body><p>CHUNKTRAIL-ONE</p></body></html>", [b"X-Foo: bar"]
|
||||
)
|
||||
|
||||
# Spans several reads: longer than the reader's 256 bytes per call.
|
||||
def route_chunktrail_many(self):
|
||||
self.send_chunked_trailed(
|
||||
b"<html><body><p>CHUNKTRAIL-MANY</p></body></html>",
|
||||
[b"X-Field-%02d: %s" % (n, b"v" * 40) for n in range(20)],
|
||||
)
|
||||
|
||||
# Control: same body, no trailer section.
|
||||
def route_chunktrail_none(self):
|
||||
self.send_chunked_trailed(
|
||||
b"<html><body><p>CHUNKTRAIL-NONE</p></body></html>", []
|
||||
)
|
||||
|
||||
# Past HTS_LINE_BLOCK_SIZE: the discard stays bounded, so the transfer drops.
|
||||
def route_chunktrail_huge(self):
|
||||
self.send_chunked_trailed(
|
||||
b"<html><body><p>CHUNKTRAIL-HUGE</p></body></html>",
|
||||
[b"X-Bloat-%04d: %s" % (n, b"w" * 100) for n in range(160)],
|
||||
)
|
||||
|
||||
# Only the terminating chunk opens the section, so junk in a data chunk's
|
||||
# own CRLF stays a framing error.
|
||||
def route_chunktrail_bogus(self):
|
||||
self.protocol_version = "HTTP/1.1"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
if self.command == "HEAD":
|
||||
return
|
||||
try:
|
||||
body = b"<html><body><p>CHUNKTRAIL-BOGUS</p></body></html>"
|
||||
self.wfile.write(b"%X\r\n" % len(body) + body + b"X-Foo: bar\r\n")
|
||||
self.wfile.write(b"0\r\n\r\n")
|
||||
self.wfile.flush()
|
||||
except OSError:
|
||||
pass
|
||||
self.close_connection = True
|
||||
|
||||
# Body complete, then EOF before the blank line closing the section. The
|
||||
# terminating chunk already arrived, so the payload stands.
|
||||
def route_chunktrail_eof(self):
|
||||
self.protocol_version = "HTTP/1.1"
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.send_header("Transfer-Encoding", "chunked")
|
||||
self.send_header("Connection", "close")
|
||||
self.end_headers()
|
||||
if self.command == "HEAD":
|
||||
return
|
||||
try:
|
||||
body = b"<html><body><p>CHUNKTRAIL-EOF</p></body></html>"
|
||||
self.wfile.write(b"%X\r\n" % len(body) + body + b"\r\n")
|
||||
self.wfile.write(b"0\r\nX-Checksum: deadbeef\r\n")
|
||||
self.wfile.flush()
|
||||
except OSError:
|
||||
pass
|
||||
self.close_connection = True
|
||||
|
||||
# The direct-to-disk path, where the body never sits in memory.
|
||||
def route_chunktrail_file(self):
|
||||
self.send_chunked_trailed(
|
||||
b"CHUNKTRAIL-BIN\n" + b"\x41\x42\x43\xfd" * 4096,
|
||||
[b"X-Checksum: 0badc0de", b"X-Done: 1"],
|
||||
"application/octet-stream",
|
||||
)
|
||||
|
||||
# Aborts the chunked body with an RST, so the read fails rather than seeing a
|
||||
# clean EOF and the transfer is already in error before the framing check.
|
||||
def route_chunktrunc_reset(self):
|
||||
@@ -2444,14 +2338,6 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
"/chunktrunc/stay.html": route_chunktrunc_stay,
|
||||
"/chunktrunc/hostile.html": route_chunktrunc_hostile,
|
||||
"/chunktrunc/reset.bin": route_chunktrunc_reset,
|
||||
"/chunktrail/index.html": route_chunktrail_index,
|
||||
"/chunktrail/one.html": route_chunktrail_one,
|
||||
"/chunktrail/many.html": route_chunktrail_many,
|
||||
"/chunktrail/none.html": route_chunktrail_none,
|
||||
"/chunktrail/huge.html": route_chunktrail_huge,
|
||||
"/chunktrail/bogus.html": route_chunktrail_bogus,
|
||||
"/chunktrail/eof.html": route_chunktrail_eof,
|
||||
"/chunktrail/file.bin": route_chunktrail_file,
|
||||
"/errpage/index.html": route_errpage_index,
|
||||
"/errpage/good.html": route_errpage_good,
|
||||
"/errpage/missing.html": route_errpage_missing,
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
/* ------------------------------------------------------------ */
|
||||
/*
|
||||
HTTrack Website Copier, Offline Browser for Windows and Unix
|
||||
Copyright (C) 1998 Xavier Roche and other contributors
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Please visit our Website: http://www.httrack.com
|
||||
*/
|
||||
|
||||
/* Drives StringRoomTotal's allocation-failure path through a realloc stub:
|
||||
asking a real allocator for a size it should refuse is a guess about the
|
||||
machine, not a test (#915). One case per run, named by argv[1]. */
|
||||
|
||||
#include <setjmp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static int alloc_fails = 0;
|
||||
static int oom_jumps = 0;
|
||||
static int oom_calls = 0;
|
||||
static size_t oom_size = 0;
|
||||
static jmp_buf oom_jump;
|
||||
|
||||
static char *test_realloc(char *buff, size_t size) {
|
||||
if (alloc_fails) {
|
||||
return NULL;
|
||||
}
|
||||
return (char *) realloc(buff, size);
|
||||
}
|
||||
|
||||
/* Declared before the include: StringBuffN_ and StringSprintf_ expand
|
||||
STRING_OOM inside the header itself. */
|
||||
static void test_oom(size_t size);
|
||||
|
||||
#define STRING_REALLOC(BUFF, SIZE) test_realloc(BUFF, SIZE)
|
||||
#define STRING_FREE(BUFF) free(BUFF)
|
||||
#define STRING_OOM(SIZE) test_oom(SIZE)
|
||||
|
||||
#include "htsstrings.h"
|
||||
|
||||
/* Either returns to the caller, leaving the String observable, or runs the
|
||||
shipped handler. */
|
||||
static void test_oom(size_t size) {
|
||||
oom_calls++;
|
||||
oom_size = size;
|
||||
if (oom_jumps) {
|
||||
longjmp(oom_jump, 1);
|
||||
}
|
||||
StringOom_(size);
|
||||
}
|
||||
|
||||
/* File scope, so longjmp cannot leave them indeterminate. */
|
||||
static String room = STRING_EMPTY;
|
||||
static const char *kept = NULL;
|
||||
|
||||
/* Control: with the stub allocating for real, nothing must reach the handler.
|
||||
Without this, a stub stuck in failing mode would "prove" every case. */
|
||||
static int grow_case(void) {
|
||||
StringRoomTotal(room, 100);
|
||||
if (oom_calls != 0) {
|
||||
printf("grow: FAIL (handler ran %d times)\n", oom_calls);
|
||||
return 1;
|
||||
}
|
||||
if (StringBuff(room) == NULL || StringCapacity(room) < 100) {
|
||||
printf("grow: FAIL (capacity %u)\n", (unsigned) StringCapacity(room));
|
||||
return 1;
|
||||
}
|
||||
StringFree(room);
|
||||
printf("grow: OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hook_case(void) {
|
||||
alloc_fails = oom_jumps = 1;
|
||||
if (setjmp(oom_jump) == 0) {
|
||||
StringRoomTotal(room, 100);
|
||||
printf("hook: FAIL (grew through a failing allocator)\n");
|
||||
return 1;
|
||||
}
|
||||
if (StringBuff(room) != NULL || StringCapacity(room) != 0 ||
|
||||
StringLength(room) != 0) {
|
||||
printf("hook: FAIL (capacity %u, buffer %s)\n",
|
||||
(unsigned) StringCapacity(room),
|
||||
StringBuff(room) == NULL ? "null" : "set");
|
||||
return 1;
|
||||
}
|
||||
if (oom_size != 16) { /* the first doubling, and the size we report */
|
||||
printf("hook: FAIL (reported %u bytes, expected 16)\n",
|
||||
(unsigned) oom_size);
|
||||
return 1;
|
||||
}
|
||||
printf("hook: OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The one the old code got wrong: a failed realloc must not overwrite the live
|
||||
buffer with NULL nor bump the capacity past what was allocated. */
|
||||
static int keep_case(void) {
|
||||
StringCopy(room, "abc");
|
||||
kept = StringBuff(room);
|
||||
alloc_fails = oom_jumps = 1;
|
||||
if (setjmp(oom_jump) == 0) {
|
||||
StringRoomTotal(room, 1000);
|
||||
printf("keep: FAIL (grew through a failing allocator)\n");
|
||||
return 1;
|
||||
}
|
||||
if (StringBuff(room) != kept || StringCapacity(room) != 16 ||
|
||||
StringLength(room) != 3 || strcmp(StringBuff(room), "abc") != 0) {
|
||||
printf("keep: FAIL (buffer %s, capacity %u)\n",
|
||||
StringBuff(room) == kept ? "kept" : "moved",
|
||||
(unsigned) StringCapacity(room));
|
||||
return 1;
|
||||
}
|
||||
alloc_fails = 0;
|
||||
StringFree(room);
|
||||
printf("keep: OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Runs the shipped handler, which must print and abort. */
|
||||
static int abort_case(void) {
|
||||
alloc_fails = 1;
|
||||
StringRoomTotal(room, 100);
|
||||
printf("abort: NOT aborted\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *const mode = argc > 1 ? argv[1] : "";
|
||||
|
||||
if (strcmp(mode, "grow") == 0) {
|
||||
return grow_case();
|
||||
} else if (strcmp(mode, "hook") == 0) {
|
||||
return hook_case();
|
||||
} else if (strcmp(mode, "keep") == 0) {
|
||||
return keep_case();
|
||||
} else if (strcmp(mode, "abort") == 0) {
|
||||
return abort_case();
|
||||
}
|
||||
fprintf(stderr, "usage: %s grow|hook|keep|abort\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
@@ -218,8 +218,4 @@ TESTS += 143_engine-backtrace-empty.test
|
||||
TESTS += 144_engine-datadir.test
|
||||
TESTS += 145_webhttrack-datadir.test
|
||||
TESTS += 146_bash-shell.test
|
||||
TESTS += 147_local-proxytrack-webdav-overflow.test
|
||||
TESTS += 149_local-chunked-trailer.test
|
||||
TESTS += 148_engine-spoolname.test
|
||||
TESTS += 150_engine-strsprintf.test
|
||||
TESTS += 152_engine-string-oom.test
|
||||
|
||||
Reference in New Issue
Block a user