diff --git a/goldlib/glibc/regex.cpp b/goldlib/glibc/regex.cpp new file mode 100644 index 0000000..39fe261 --- /dev/null +++ b/goldlib/glibc/regex.cpp @@ -0,0 +1,5066 @@ +/* Extended regular expression matching and search library, + version 0.12. + (Implements POSIX draft P1003.2/D11.2, except for some of the + internationalization features.) + Copyright (C) 1993, 94, 95, 96, 97, 98 Free Software Foundation, Inc. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* AIX requires this to be the first thing in the file. */ +#if defined _AIX && !defined REGEX_MALLOC + #pragma alloca +#endif + +#undef _GNU_SOURCE +#define _GNU_SOURCE + +#ifdef HAVE_CONFIG_H +# include +#endif + +#if defined STDC_HEADERS && !defined emacs +# include +#else +/* We need this for `regex.h', and perhaps for the Emacs include files. */ +# include +#endif + +#define WIDE_CHAR_SUPPORT (HAVE_WCTYPE_H && HAVE_WCHAR_H && HAVE_BTOWC) + +/* For platform which support the ISO C amendement 1 functionality we + support user defined character classes. */ +#if defined _LIBC || WIDE_CHAR_SUPPORT +/* Solaris 2.5 has a bug: must be included before . */ +# include +# include +#endif + +#ifdef _LIBC +/* We have to keep the namespace clean. */ +# define regfree(preg) __regfree (preg) +# define regexec(pr, st, nm, pm, ef) __regexec (pr, st, nm, pm, ef) +# define regcomp(preg, pattern, cflags) __regcomp (preg, pattern, cflags) +# define regerror(errcode, preg, errbuf, errbuf_size) \ + __regerror(errcode, preg, errbuf, errbuf_size) +# define re_set_registers(bu, re, nu, st, en) \ + __re_set_registers (bu, re, nu, st, en) +# define re_match_2(bufp, string1, size1, string2, size2, pos, regs, stop) \ + __re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop) +# define re_match(bufp, string, size, pos, regs) \ + __re_match (bufp, string, size, pos, regs) +# define re_search(bufp, string, size, startpos, range, regs) \ + __re_search (bufp, string, size, startpos, range, regs) +# define re_compile_pattern(pattern, length, bufp) \ + __re_compile_pattern (pattern, length, bufp) +# define re_set_syntax(syntax) __re_set_syntax (syntax) +# define re_search_2(bufp, st1, s1, st2, s2, startpos, range, regs, stop) \ + __re_search_2 (bufp, st1, s1, st2, s2, startpos, range, regs, stop) +# define re_compile_fastmap(bufp) __re_compile_fastmap (bufp) + +#define btowc __btowc +#endif + +/* This is for other GNU distributions with internationalized messages. */ +#if HAVE_LIBINTL_H || defined _LIBC +# include +#else +# define gettext(msgid) (msgid) +#endif + +#ifndef gettext_noop +/* This define is so xgettext can find the internationalizable + strings. */ +# define gettext_noop(String) String +#endif + +/* The `emacs' switch turns on certain matching commands + that make sense only in Emacs. */ +#ifdef emacs + +# include "lisp.h" +# include "buffer.h" +# include "syntax.h" + +#else /* not emacs */ + +/* If we are not linking with Emacs proper, + we can't use the relocating allocator + even if config.h says that we can. */ +# undef REL_ALLOC + +# if defined STDC_HEADERS || defined _LIBC +# include +# else +char *malloc (); +char *realloc (); +# endif + +/* When used in Emacs's lib-src, we need to get bzero and bcopy somehow. + If nothing else has been done, use the method below. */ +# ifdef INHIBIT_STRING_HEADER +# if !(defined HAVE_BZERO && defined HAVE_BCOPY) +# if !defined bzero && !defined bcopy +# undef INHIBIT_STRING_HEADER +# endif +# endif +# endif + +/* This is the normal way of making sure we have a bcopy and a bzero. + This is used in most programs--a few other programs avoid this + by defining INHIBIT_STRING_HEADER. */ +# ifndef INHIBIT_STRING_HEADER +# if defined HAVE_STRING_H || defined STDC_HEADERS || defined _LIBC +# include +# ifndef bzero +# ifndef _LIBC +# define bzero(s, n) (memset (s, '\0', n), (s)) +# else +# define bzero(s, n) __bzero (s, n) +# endif +# endif +# else +# include +# ifndef memcmp +# define memcmp(s1, s2, n) bcmp (s1, s2, n) +# endif +# ifndef memcpy +# define memcpy(d, s, n) (bcopy (s, d, n), (d)) +# endif +# endif +# endif + +/* isalpha etc. are used for the character classes. */ +#include + +/* Define the syntax stuff for \<, \>, etc. */ + +/* This must be nonzero for the wordchar and notwordchar pattern + commands in re_match_2. */ +# ifndef Sword +# define Sword 1 +# endif + +# ifdef SWITCH_ENUM_BUG +# define SWITCH_ENUM_CAST(x) ((int)(x)) +# else +# define SWITCH_ENUM_CAST(x) (x) +# endif + +/* How many characters in the character set. */ +# define CHAR_SET_SIZE 256 + +# ifdef SYNTAX_TABLE + +extern char *re_syntax_table; + +# else /* not SYNTAX_TABLE */ + +static char re_syntax_table[CHAR_SET_SIZE]; + +static void +init_syntax_once () +{ + register int c; + static int done = 0; + + if (done) + return; + + bzero (re_syntax_table, sizeof re_syntax_table); + +/* for (c = 'a'; c <= 'z'; c++) + re_syntax_table[c] = Sword; + + for (c = 'A'; c <= 'Z'; c++) + re_syntax_table[c] = Sword; + + for (c = '0'; c <= '9'; c++) + re_syntax_table[c] = Sword; */ + + for (c = 0; c < 256; c++) + re_syntax_table[c] = isxalnum(c) ? Sword : 0; + + re_syntax_table['_'] = Sword; + + done = 1; +} + +# endif /* not SYNTAX_TABLE */ + +# define SYNTAX(c) re_syntax_table[c] + +#endif /* not emacs */ + +/* Get the interface, including the syntax bits. */ +#include "regex.h" + +/* Jim Meyering writes: + + "... Some ctype macros are valid only for character codes that + isascii says are ASCII (SGI's IRIX-4.0.5 is one such system --when + using /bin/cc or gcc but without giving an ansi option). So, all + ctype uses should be through macros like ISPRINT... If + STDC_HEADERS is defined, then autoconf has verified that the ctype + macros don't need to be guarded with references to isascii. ... + Defining isascii to 1 should let any compiler worth its salt + eliminate the && through constant folding." + Solaris defines some of these symbols so we must undefine them first. */ + +#undef ISASCII +#if defined STDC_HEADERS || (!defined isascii && !defined HAVE_ISASCII) +# define ISASCII(c) ((c) < 256) +#else +# define ISASCII(c) isascii(c) +#endif + +#ifdef isblank +# define ISBLANK(c) (ISASCII (c) && isblank (c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif +#ifdef isgraph +# define ISGRAPH(c) (ISASCII (c) && isgraph (c)) +#else +# define ISGRAPH(c) (ISASCII (c) && isprint (c) && !isspace (c)) +#endif + +#undef ISPRINT +#define ISPRINT(c) (ISASCII (c) && isprint (c)) +#define ISDIGIT(c) (ISASCII (c) && isdigit (c)) +#define ISALNUM(c) (ISASCII (c) && isxalnum (c)) +#define ISALPHA(c) (ISASCII (c) && g_isalpha (c)) +#define ISCNTRL(c) (ISASCII (c) && iscntrl (c)) +#define ISLOWER(c) (ISASCII (c) && g_islower (c)) +#define ISPUNCT(c) (ISASCII (c) && ispunct (c)) +#define ISSPACE(c) (ISASCII (c) && isspace (c)) +#define ISUPPER(c) (ISASCII (c) && g_isupper (c)) +#define ISXDIGIT(c) (ISASCII (c) && isxdigit (c)) + +#ifndef NULL +# define NULL (void *)0 +#endif + +/* We remove any previous definition of `SIGN_EXTEND_CHAR', + since ours (we hope) works properly with all combinations of + machines, compilers, `char' and `unsigned char' argument types. + (Per Bothner suggested the basic approach.) */ +#undef SIGN_EXTEND_CHAR +#if __STDC__ +# define SIGN_EXTEND_CHAR(c) ((signed char) (c)) +#else /* not __STDC__ */ +/* As in Harbison and Steele. */ +# define SIGN_EXTEND_CHAR(c) ((((unsigned char) (c)) ^ 128) - 128) +#endif + +/* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we + use `alloca' instead of `malloc'. This is because using malloc in + re_search* or re_match* could cause memory leaks when C-g is used in + Emacs; also, malloc is slower and causes storage fragmentation. On + the other hand, malloc is more portable, and easier to debug. + + Because we sometimes use alloca, some routines have to be macros, + not functions -- `alloca'-allocated space disappears at the end of the + function it is called in. */ + +#ifdef REGEX_MALLOC + +# define REGEX_ALLOCATE malloc +# define REGEX_REALLOCATE(source, osize, nsize) realloc (source, nsize) +# define REGEX_FREE free + +#else /* not REGEX_MALLOC */ + +/* Emacs already defines alloca, sometimes. */ +# ifndef alloca + +/* Make alloca work the best possible way. */ +# ifdef __GNUC__ +# define alloca __builtin_alloca +# else /* not __GNUC__ */ +# if HAVE_ALLOCA_H +# include +# endif /* HAVE_ALLOCA_H */ +# if HAVE_MALLOC_H +# include +# endif /* HAVE_MALLOC_H */ +# endif /* not __GNUC__ */ + +# endif /* not alloca */ + +# define REGEX_ALLOCATE alloca + +/* Assumes a `char *destination' variable. */ +# define REGEX_REALLOCATE(source, osize, nsize) \ + (destination = (char *) alloca (nsize), \ + memcpy (destination, source, osize)) + +/* No need to do anything to free, after alloca. */ +# define REGEX_FREE(arg) ((void)0) /* Do nothing! But inhibit gcc warning. */ + +#endif /* not REGEX_MALLOC */ + +/* Define how to allocate the failure stack. */ + +#if defined REL_ALLOC && defined REGEX_MALLOC + +# define REGEX_ALLOCATE_STACK(size) \ + r_alloc (&failure_stack_ptr, (size)) +# define REGEX_REALLOCATE_STACK(source, osize, nsize) \ + r_re_alloc (&failure_stack_ptr, (nsize)) +# define REGEX_FREE_STACK(ptr) \ + r_alloc_free (&failure_stack_ptr) + +#else /* not using relocating allocator */ + +# ifdef REGEX_MALLOC + +# define REGEX_ALLOCATE_STACK malloc +# define REGEX_REALLOCATE_STACK(source, osize, nsize) realloc (source, nsize) +# define REGEX_FREE_STACK free + +# else /* not REGEX_MALLOC */ + +# define REGEX_ALLOCATE_STACK alloca + +# define REGEX_REALLOCATE_STACK(source, osize, nsize) \ + REGEX_REALLOCATE (source, osize, nsize) +/* No need to explicitly free anything. */ +# define REGEX_FREE_STACK(arg) + +# endif /* not REGEX_MALLOC */ +#endif /* not using relocating allocator */ + + +/* True if `size1' is non-NULL and PTR is pointing anywhere inside + `string1' or just past its end. This works if PTR is NULL, which is + a good thing. */ +#define FIRST_STRING_P(ptr) \ + (size1 && string1 <= (ptr) && (ptr) <= string1 + size1) + +/* (Re)Allocate N items of type T using malloc, or fail. */ +#define TALLOC(n, t) ((t *) malloc ((n) * sizeof (t))) +#define RETALLOC(addr, n, t) ((addr) = (t *) realloc (addr, (n) * sizeof (t))) +#define RETALLOC_IF(addr, n, t) \ + if (addr) RETALLOC((addr), (n), t); else (addr) = TALLOC ((n), t) +#define REGEX_TALLOC(n, t) ((t *) REGEX_ALLOCATE ((n) * sizeof (t))) + +#define BYTEWIDTH 8 /* In bits. */ + +#define STREQ(s1, s2) ((strcmp (s1, s2) == 0)) + +#undef MAX +#undef MIN +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static int re_match_2_internal(struct re_pattern_buffer *bufp, + const char *string1, int size1, + const char *string2, int size2, + int pos, + struct re_registers *regs, + int stop); + +/* These are the command codes that appear in compiled regular + expressions. Some opcodes are followed by argument bytes. A + command code can specify any interpretation whatsoever for its + arguments. Zero bytes may appear in the compiled regular expression. */ + +typedef enum +{ + no_op = 0, + + /* Succeed right away--no more backtracking. */ + succeed, + + /* Followed by one byte giving n, then by n literal bytes. */ + exactn, + + /* Matches any (more or less) character. */ + anychar, + + /* Matches any one char belonging to specified set. First + following byte is number of bitmap bytes. Then come bytes + for a bitmap saying which chars are in. Bits in each byte + are ordered low-bit-first. A character is in the set if its + bit is 1. A character too large to have a bit in the map is + automatically not in the set. */ + charset, + + /* Same parameters as charset, but match any character that is + not one of those specified. */ + charset_not, + + /* Start remembering the text that is matched, for storing in a + register. Followed by one byte with the register number, in + the range 0 to one less than the pattern buffer's re_nsub + field. Then followed by one byte with the number of groups + inner to this one. (This last has to be part of the + start_memory only because we need it in the on_failure_jump + of re_match_2.) */ + start_memory, + + /* Stop remembering the text that is matched and store it in a + memory register. Followed by one byte with the register + number, in the range 0 to one less than `re_nsub' in the + pattern buffer, and one byte with the number of inner groups, + just like `start_memory'. (We need the number of inner + groups here because we don't have any easy way of finding the + corresponding start_memory when we're at a stop_memory.) */ + stop_memory, + + /* Match a duplicate of something remembered. Followed by one + byte containing the register number. */ + duplicate, + + /* Fail unless at beginning of line. */ + begline, + + /* Fail unless at end of line. */ + endline, + + /* Succeeds if at beginning of buffer (if emacs) or at beginning + of string to be matched (if not). */ + begbuf, + + /* Analogously, for end of buffer/string. */ + endbuf, + + /* Followed by two byte relative address to which to jump. */ + jump, + + /* Same as jump, but marks the end of an alternative. */ + jump_past_alt, + + /* Followed by two-byte relative address of place to resume at + in case of failure. */ + on_failure_jump, + + /* Like on_failure_jump, but pushes a placeholder instead of the + current string position when executed. */ + on_failure_keep_string_jump, + + /* Throw away latest failure point and then jump to following + two-byte relative address. */ + pop_failure_jump, + + /* Change to pop_failure_jump if know won't have to backtrack to + match; otherwise change to jump. This is used to jump + back to the beginning of a repeat. If what follows this jump + clearly won't match what the repeat does, such that we can be + sure that there is no use backtracking out of repetitions + already matched, then we change it to a pop_failure_jump. + Followed by two-byte address. */ + maybe_pop_jump, + + /* Jump to following two-byte address, and push a dummy failure + point. This failure point will be thrown away if an attempt + is made to use it for a failure. A `+' construct makes this + before the first repeat. Also used as an intermediary kind + of jump when compiling an alternative. */ + dummy_failure_jump, + + /* Push a dummy failure point and continue. Used at the end of + alternatives. */ + push_dummy_failure, + + /* Followed by two-byte relative address and two-byte number n. + After matching N times, jump to the address upon failure. */ + succeed_n, + + /* Followed by two-byte relative address, and two-byte number n. + Jump to the address N times, then fail. */ + jump_n, + + /* Set the following two-byte relative address to the + subsequent two-byte number. The address *includes* the two + bytes of number. */ + set_number_at, + + wordchar, /* Matches any word-constituent character. */ + notwordchar, /* Matches any char that is not a word-constituent. */ + + wordbeg, /* Succeeds if at word beginning. */ + wordend, /* Succeeds if at word end. */ + + wordbound, /* Succeeds if at a word boundary. */ + notwordbound /* Succeeds if not at a word boundary. */ + +#ifdef emacs + ,before_dot, /* Succeeds if before point. */ + at_dot, /* Succeeds if at point. */ + after_dot, /* Succeeds if after point. */ + + /* Matches any character whose syntax is specified. Followed by + a byte which contains a syntax code, e.g., Sword. */ + syntaxspec, + + /* Matches any character whose syntax is not that specified. */ + notsyntaxspec +#endif /* emacs */ +} re_opcode_t; + +/* Common operations on the compiled pattern. */ + +/* Store NUMBER in two contiguous bytes starting at DESTINATION. */ + +#define STORE_NUMBER(destination, number) \ + { \ + (destination)[0] = (number) & 0377; \ + (destination)[1] = (number) >> 8; \ + } + +/* Same as STORE_NUMBER, except increment DESTINATION to + the byte after where the number is stored. Therefore, DESTINATION + must be an lvalue. */ + +#define STORE_NUMBER_AND_INCR(destination, number) \ + { \ + STORE_NUMBER (destination, number); \ + (destination) += 2; \ + } + +/* Put into DESTINATION a number stored in two contiguous bytes starting + at SOURCE. */ + +#define EXTRACT_NUMBER(destination, source) \ + { \ + (destination) = *(source) & 0377; \ + (destination) += SIGN_EXTEND_CHAR (*((source) + 1)) << 8; \ + } + +/* Same as EXTRACT_NUMBER, except increment SOURCE to after the number. + SOURCE must be an lvalue. */ + +#define EXTRACT_NUMBER_AND_INCR(destination, source) \ + { \ + EXTRACT_NUMBER (destination, source); \ + (source) += 2; \ + } + +/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can + also be assigned to arbitrarily: each pattern buffer stores its own + syntax, so it can be changed between regex compilations. */ +/* This has no initializer because initialized variables in Emacs + become read-only after dumping. */ +reg_syntax_t re_syntax_options; + + +/* Specify the precise syntax of regexps for compilation. This provides + for compatibility for various utilities which historically have + different, incompatible syntaxes. + + The argument SYNTAX is a bit mask comprised of the various bits + defined in regex.h. We return the old syntax. */ + +reg_syntax_t +re_set_syntax (reg_syntax_t syntax) +{ + reg_syntax_t ret = re_syntax_options; + + re_syntax_options = syntax; + return ret; +} +#ifdef _LIBC +weak_alias (__re_set_syntax, re_set_syntax) +#endif + +/* This table gives an error message for each of the error codes listed + in regex.h. Obviously the order here has to be same as there. + POSIX doesn't require that we do anything for REG_NOERROR, + but why not be nice? */ + +static const char *re_error_msgid[] = + { + gettext_noop ("Success"), /* REG_NOERROR */ + gettext_noop ("No match"), /* REG_NOMATCH */ + gettext_noop ("Invalid regular expression"), /* REG_BADPAT */ + gettext_noop ("Invalid collation character"), /* REG_ECOLLATE */ + gettext_noop ("Invalid character class name"), /* REG_ECTYPE */ + gettext_noop ("Trailing backslash"), /* REG_EESCAPE */ + gettext_noop ("Invalid back reference"), /* REG_ESUBREG */ + gettext_noop ("Unmatched [ or [^"), /* REG_EBRACK */ + gettext_noop ("Unmatched ( or \\("), /* REG_EPAREN */ + gettext_noop ("Unmatched \\{"), /* REG_EBRACE */ + gettext_noop ("Invalid content of \\{\\}"), /* REG_BADBR */ + gettext_noop ("Invalid range end"), /* REG_ERANGE */ + gettext_noop ("Memory exhausted"), /* REG_ESPACE */ + gettext_noop ("Invalid preceding regular expression"), /* REG_BADRPT */ + gettext_noop ("Premature end of regular expression"), /* REG_EEND */ + gettext_noop ("Regular expression too big"), /* REG_ESIZE */ + gettext_noop ("Unmatched ) or \\)"), /* REG_ERPAREN */ + }; + +/* Avoiding alloca during matching, to placate r_alloc. */ + +/* Define MATCH_MAY_ALLOCATE unless we need to make sure that the + searching and matching functions should not call alloca. On some + systems, alloca is implemented in terms of malloc, and if we're + using the relocating allocator routines, then malloc could cause a + relocation, which might (if the strings being searched are in the + ralloc heap) shift the data out from underneath the regexp + routines. + + Here's another reason to avoid allocation: Emacs + processes input from X in a signal handler; processing X input may + call malloc; if input arrives while a matching routine is calling + malloc, then we're scrod. But Emacs can't just block input while + calling matching routines; then we don't notice interrupts when + they come in. So, Emacs blocks input around all regexp calls + except the matching calls, which it leaves unprotected, in the + faith that they will not malloc. */ + +/* Normally, this is fine. */ +#define MATCH_MAY_ALLOCATE + +/* When using GNU C, we are not REALLY using the C alloca, no matter + what config.h may say. So don't take precautions for it. */ +#ifdef __GNUC__ +# undef C_ALLOCA +#endif + +/* The match routines may not allocate if (1) they would do it with malloc + and (2) it's not safe for them to use malloc. + Note that if REL_ALLOC is defined, matching would not use malloc for the + failure stack, but we would still use it for the register vectors; + so REL_ALLOC should not affect this. */ +#if (defined C_ALLOCA || defined REGEX_MALLOC) && defined emacs +# undef MATCH_MAY_ALLOCATE +#endif + + +/* Failure stack declarations and macros; both re_compile_fastmap and + re_match_2 use a failure stack. These have to be macros because of + REGEX_ALLOCATE_STACK. */ + + +/* Number of failure points for which to initially allocate space + when matching. If this number is exceeded, we allocate more + space, so it is not a hard limit. */ +#ifndef INIT_FAILURE_ALLOC +# define INIT_FAILURE_ALLOC 5 +#endif + +/* Roughly the maximum number of failure points on the stack. Would be + exactly that if always used MAX_FAILURE_ITEMS items each time we failed. + This is a variable only so users of regex can assign to it; we never + change it ourselves. */ + +#ifdef INT_IS_16BIT + +# if defined MATCH_MAY_ALLOCATE +/* 4400 was enough to cause a crash on Alpha OSF/1, + whose default stack limit is 2mb. */ +long int re_max_failures = 4000; +# else +long int re_max_failures = 2000; +# endif + +union fail_stack_elt +{ + unsigned char *pointer; + long int integer; +}; + +typedef union fail_stack_elt fail_stack_elt_t; + +typedef struct +{ + fail_stack_elt_t *stack; + unsigned long int size; + unsigned long int avail; /* Offset of next open position. */ +} fail_stack_type; + +#else /* not INT_IS_16BIT */ + +# if defined MATCH_MAY_ALLOCATE +/* 4400 was enough to cause a crash on Alpha OSF/1, + whose default stack limit is 2mb. */ +int re_max_failures = 20000; +# else +int re_max_failures = 2000; +# endif + +union fail_stack_elt +{ + unsigned char *pointer; + int integer; +}; + +typedef union fail_stack_elt fail_stack_elt_t; + +typedef struct +{ + fail_stack_elt_t *stack; + unsigned size; + unsigned avail; /* Offset of next open position. */ +} fail_stack_type; + +#endif /* INT_IS_16BIT */ + +#define FAIL_STACK_EMPTY() (fail_stack.avail == 0) +#define FAIL_STACK_PTR_EMPTY() (fail_stack_ptr->avail == 0) +#define FAIL_STACK_FULL() (fail_stack.avail == fail_stack.size) + + +/* Define macros to initialize and free the failure stack. + Do `return -2' if the alloc fails. */ + +#ifdef MATCH_MAY_ALLOCATE +# define INIT_FAIL_STACK() \ + { \ + fail_stack.stack = (fail_stack_elt_t *) \ + REGEX_ALLOCATE_STACK (INIT_FAILURE_ALLOC * sizeof (fail_stack_elt_t)); \ + if (fail_stack.stack == NULL) return -2; \ + fail_stack.size = INIT_FAILURE_ALLOC; \ + fail_stack.avail = 0; \ + } + +# define RESET_FAIL_STACK() REGEX_FREE_STACK (fail_stack.stack) +#else +# define INIT_FAIL_STACK() \ + do { \ + fail_stack.avail = 0; \ + } while (0) + +# define RESET_FAIL_STACK() +#endif + + +/* Double the size of FAIL_STACK, up to approximately `re_max_failures' items. + + Return 1 if succeeds, and 0 if either ran out of memory + allocating space for it or it was already too large. + + REGEX_REALLOCATE_STACK requires `destination' be declared. */ + +#define DOUBLE_FAIL_STACK(fail_stack) \ + ((fail_stack).size > (unsigned) (re_max_failures * MAX_FAILURE_ITEMS) \ + ? 0 \ + : ((fail_stack).stack = (fail_stack_elt_t *) \ + REGEX_REALLOCATE_STACK ((fail_stack).stack, \ + (fail_stack).size * sizeof (fail_stack_elt_t), \ + ((fail_stack).size << 1) * sizeof (fail_stack_elt_t)), \ + \ + (fail_stack).stack == NULL \ + ? 0 \ + : ((fail_stack).size <<= 1, \ + 1))) + + +/* Push pointer POINTER on FAIL_STACK. + Return 1 if was able to do so and 0 if ran out of memory allocating + space to do so. */ +#define PUSH_PATTERN_OP(POINTER, FAIL_STACK) \ + ((FAIL_STACK_FULL () \ + && !DOUBLE_FAIL_STACK (FAIL_STACK)) \ + ? 0 \ + : ((FAIL_STACK).stack[(FAIL_STACK).avail++].pointer = POINTER, \ + 1)) + +/* Push a pointer value onto the failure stack. + Assumes the variable `fail_stack'. Probably should only + be called from within `PUSH_FAILURE_POINT'. */ +#define PUSH_FAILURE_POINTER(item) \ + fail_stack.stack[fail_stack.avail++].pointer = (unsigned char *) (item) + +/* This pushes an integer-valued item onto the failure stack. + Assumes the variable `fail_stack'. Probably should only + be called from within `PUSH_FAILURE_POINT'. */ +#define PUSH_FAILURE_INT(item) \ + fail_stack.stack[fail_stack.avail++].integer = (item) + +/* Push a fail_stack_elt_t value onto the failure stack. + Assumes the variable `fail_stack'. Probably should only + be called from within `PUSH_FAILURE_POINT'. */ +#define PUSH_FAILURE_ELT(item) \ + fail_stack.stack[fail_stack.avail++] = (item) + +/* These three POP... operations complement the three PUSH... operations. + All assume that `fail_stack' is nonempty. */ +#define POP_FAILURE_POINTER() fail_stack.stack[--fail_stack.avail].pointer +#define POP_FAILURE_INT() fail_stack.stack[--fail_stack.avail].integer +#define POP_FAILURE_ELT() fail_stack.stack[--fail_stack.avail] + + +/* Push the information about the state we will need + if we ever fail back to it. + + Requires variables fail_stack, regstart, regend, reg_info, and + num_regs_pushed be declared. DOUBLE_FAIL_STACK requires `destination' + be declared. + + Does `return FAILURE_CODE' if runs out of memory. */ + +#define PUSH_FAILURE_POINT(pattern_place, string_place, failure_code) \ + { \ + char *destination; \ + /* Must be int, so when we don't save any registers, the arithmetic \ + of 0 + -1 isn't done as unsigned. */ \ + /* Can't be int, since there is not a shred of a guarantee that int \ + is wide enough to hold a value of something to which pointer can \ + be assigned */ \ + active_reg_t this_reg; \ + \ + /* Ensure we have enough space allocated for what we will push. */ \ + while (REMAINING_AVAIL_SLOTS < NUM_FAILURE_ITEMS) \ + { \ + if (!DOUBLE_FAIL_STACK (fail_stack)) \ + return failure_code; \ + } \ + \ + for (this_reg = lowest_active_reg; this_reg <= highest_active_reg; \ + this_reg++) \ + { \ + PUSH_FAILURE_POINTER (regstart[this_reg]); \ + PUSH_FAILURE_POINTER (regend[this_reg]); \ + PUSH_FAILURE_ELT (reg_info[this_reg].word); \ + } \ + \ + PUSH_FAILURE_INT (lowest_active_reg); \ + PUSH_FAILURE_INT (highest_active_reg); \ + PUSH_FAILURE_POINTER (pattern_place); \ + PUSH_FAILURE_POINTER (string_place); \ + } + +/* This is the number of items that are pushed and popped on the stack + for each register. */ +#define NUM_REG_ITEMS 3 + +/* Individual items aside from the registers. */ +#define NUM_NONREG_ITEMS 4 + +/* We push at most this many items on the stack. */ +/* We used to use (num_regs - 1), which is the number of registers + this regexp will save; but that was changed to 5 + to avoid stack overflow for a regexp with lots of parens. */ +#define MAX_FAILURE_ITEMS (5 * NUM_REG_ITEMS + NUM_NONREG_ITEMS) + +/* We actually push this many items. */ +#define NUM_FAILURE_ITEMS \ + (((0 \ + ? 0 : highest_active_reg - lowest_active_reg + 1) \ + * NUM_REG_ITEMS) \ + + NUM_NONREG_ITEMS) + +/* How many items can still be added to the stack without overflowing it. */ +#define REMAINING_AVAIL_SLOTS ((fail_stack).size - (fail_stack).avail) + + +/* Pops what PUSH_FAIL_STACK pushes. + + We restore into the parameters, all of which should be lvalues: + STR -- the saved data position. + PAT -- the saved pattern position. + LOW_REG, HIGH_REG -- the highest and lowest active registers. + REGSTART, REGEND -- arrays of string positions. + REG_INFO -- array of information about each subexpression. + + Also assumes the variables `fail_stack' and (if debugging), `bufp', + `pend', `string1', `size1', `string2', and `size2'. */ + +#define POP_FAILURE_POINT(str, pat, low_reg, high_reg, regstart, regend, reg_info) \ +{ \ + active_reg_t this_reg; \ + const unsigned char *string_temp; \ + \ + /* If the saved string location is NULL, it came from an \ + on_failure_keep_string_jump opcode, and we want to throw away the \ + saved NULL, thus retaining our current position in the string. */ \ + string_temp = POP_FAILURE_POINTER (); \ + if (string_temp != NULL) \ + str = (const char *) string_temp; \ + \ + pat = (unsigned char *) POP_FAILURE_POINTER (); \ + \ + /* Restore register info. */ \ + high_reg = (active_reg_t) POP_FAILURE_INT (); \ + low_reg = (active_reg_t) POP_FAILURE_INT (); \ + \ + for (this_reg = high_reg; this_reg >= low_reg; this_reg--) \ + { \ + reg_info[this_reg].word = POP_FAILURE_ELT (); \ + regend[this_reg] = (const char *) POP_FAILURE_POINTER (); \ + regstart[this_reg] = (const char *) POP_FAILURE_POINTER (); \ + } \ + \ + set_regs_matched_done = 0; \ +} /* POP_FAILURE_POINT */ + + + +/* Structure for per-register (a.k.a. per-group) information. + Other register information, such as the + starting and ending positions (which are addresses), and the list of + inner groups (which is a bits list) are maintained in separate + variables. + + We are making a (strictly speaking) nonportable assumption here: that + the compiler will pack our bit fields into something that fits into + the type of `word', i.e., is something that fits into one item on the + failure stack. */ + + +/* Declarations and macros for re_match_2. */ + +typedef union +{ + fail_stack_elt_t word; + struct + { + /* This field is one if this group can match the empty string, + zero if not. If not yet determined, `MATCH_NULL_UNSET_VALUE'. */ +#define MATCH_NULL_UNSET_VALUE 3 + unsigned match_null_string_p : 2; + unsigned is_active : 1; + unsigned matched_something : 1; + unsigned ever_matched_something : 1; + } bits; +} register_info_type; + +#define REG_MATCH_NULL_STRING_P(R) ((R).bits.match_null_string_p) +#define IS_ACTIVE(R) ((R).bits.is_active) +#define MATCHED_SOMETHING(R) ((R).bits.matched_something) +#define EVER_MATCHED_SOMETHING(R) ((R).bits.ever_matched_something) + + +/* Call this when have matched a real character; it sets `matched' flags + for the subexpressions which we are currently inside. Also records + that those subexprs have matched. */ +#define SET_REGS_MATCHED() \ + { \ + if (!set_regs_matched_done) \ + { \ + active_reg_t r; \ + set_regs_matched_done = 1; \ + for (r = lowest_active_reg; r <= highest_active_reg; r++) \ + { \ + MATCHED_SOMETHING (reg_info[r]) \ + = EVER_MATCHED_SOMETHING (reg_info[r]) \ + = 1; \ + } \ + } \ + } + +/* Registers are set to a sentinel when they haven't yet matched. */ +static char reg_unset_dummy; +#define REG_UNSET_VALUE (®_unset_dummy) +#define REG_UNSET(e) ((e) == REG_UNSET_VALUE) + +/* Subroutine declarations and macros for regex_compile. */ + +static reg_errcode_t regex_compile(const char *pattern, size_t size, + reg_syntax_t syntax, struct re_pattern_buffer *bufp); +static void store_op1(re_opcode_t op, unsigned char *loc, int arg); +static void store_op2(re_opcode_t op, unsigned char *loc, int arg1, int arg2); +static void insert_op1(re_opcode_t op, unsigned char *loc, + int arg, unsigned char *end); +static void insert_op2(re_opcode_t op, unsigned char *loc, + int arg1, int arg2, unsigned char *end); +static bool at_begline_loc_p(const char *pattern, const char *p, + reg_syntax_t syntax); +static bool at_endline_loc_p(const char *p, const char *pend, + reg_syntax_t syntax); +static reg_errcode_t compile_range(const char **p_ptr, const char *pend, + char *translate, reg_syntax_t syntax, + unsigned char *b); + +/* Fetch the next character in the uncompiled pattern---translating it + if necessary. Also cast from a signed character in the constant + string passed to us by the user to an unsigned char that we can use + as an array index (in, e.g., `translate'). */ +#ifndef PATFETCH +# define PATFETCH(c) \ + { \ + if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + if (translate) c = (unsigned char) translate[c]; \ + } +#endif + +/* Fetch the next character in the uncompiled pattern, with no + translation. */ +#define PATFETCH_RAW(c) \ + { \ + if (p == pend) return REG_EEND; \ + c = (unsigned char) *p++; \ + } + +/* Go backwards one character in the pattern. */ +#define PATUNFETCH p-- + + +/* If `translate' is non-null, return translate[D], else just D. We + cast the subscript to translate because some data is declared as + `char *', to avoid warnings when a string constant is passed. But + when we use a character as a subscript we must make it unsigned. */ +#ifndef TRANSLATE +# define TRANSLATE(d) \ + (translate ? (char) translate[(unsigned char) (d)] : (d)) +#endif + + +/* Macros for outputting the compiled pattern into `buffer'. */ + +/* If the buffer isn't allocated when it comes in, use this. */ +#define INIT_BUF_SIZE 32 + +/* Make sure we have at least N more bytes of space in buffer. */ +#define GET_BUFFER_SPACE(n) \ + while ((unsigned long) (b - bufp->buffer + (n)) > bufp->allocated) \ + EXTEND_BUFFER () + +/* Make sure we have one more byte of buffer space and then add C to it. */ +#define BUF_PUSH(c) \ + { \ + GET_BUFFER_SPACE (1); \ + *b++ = (unsigned char) (c); \ + } + + +/* Ensure we have two more bytes of buffer space and then append C1 and C2. */ +#define BUF_PUSH_2(c1, c2) \ + { \ + GET_BUFFER_SPACE (2); \ + *b++ = (unsigned char) (c1); \ + *b++ = (unsigned char) (c2); \ + } + + +/* As with BUF_PUSH_2, except for three bytes. */ +#define BUF_PUSH_3(c1, c2, c3) \ + { \ + GET_BUFFER_SPACE (3); \ + *b++ = (unsigned char) (c1); \ + *b++ = (unsigned char) (c2); \ + *b++ = (unsigned char) (c3); \ + } + + +/* Store a jump with opcode OP at LOC to location TO. We store a + relative address offset by the three bytes the jump itself occupies. */ +#define STORE_JUMP(op, loc, to) \ + store_op1 (op, loc, (int) ((to) - (loc) - 3)) + +/* Likewise, for a two-argument jump. */ +#define STORE_JUMP2(op, loc, to, arg) \ + store_op2 (op, loc, (int) ((to) - (loc) - 3), arg) + +/* Like `STORE_JUMP', but for inserting. Assume `b' is the buffer end. */ +#define INSERT_JUMP(op, loc, to) \ + insert_op1 (op, loc, (int) ((to) - (loc) - 3), b) + +/* Like `STORE_JUMP2', but for inserting. Assume `b' is the buffer end. */ +#define INSERT_JUMP2(op, loc, to, arg) \ + insert_op2 (op, loc, (int) ((to) - (loc) - 3), arg, b) + + +/* This is not an arbitrary limit: the arguments which represent offsets + into the pattern are two bytes long. So if 2^16 bytes turns out to + be too small, many things would have to change. */ +/* Any other compiler which, like MSC, has allocation limit below 2^16 + bytes will have to use approach similar to what was done below for + MSC and drop MAX_BUF_SIZE a bit. Otherwise you may end up + reallocating to 0 bytes. Such thing is not going to work too well. + You have been warned!! */ +#if defined _MSC_VER && !defined WIN32 +/* Microsoft C 16-bit versions limit malloc to approx 65512 bytes. + The REALLOC define eliminates a flurry of conversion warnings, + but is not required. */ +# define MAX_BUF_SIZE 65500L +# define REALLOC(p,s) realloc ((p), (size_t) (s)) +#else +# define MAX_BUF_SIZE (1L << 16) +# define REALLOC(p,s) realloc ((p), (s)) +#endif + +/* Extend the buffer by twice its current size via realloc and + reset the pointers that pointed into the old block to point to the + correct places in the new one. If extending the buffer results in it + being larger than MAX_BUF_SIZE, then flag memory exhausted. */ +#define EXTEND_BUFFER() \ + { \ + unsigned char *old_buffer = bufp->buffer; \ + if (bufp->allocated == MAX_BUF_SIZE) \ + return REG_ESIZE; \ + bufp->allocated <<= 1; \ + if (bufp->allocated > MAX_BUF_SIZE) \ + bufp->allocated = MAX_BUF_SIZE; \ + bufp->buffer = (unsigned char *) REALLOC (bufp->buffer, bufp->allocated); \ + if (bufp->buffer == NULL) \ + return REG_ESPACE; \ + /* If the buffer moved, move all the pointers into it. */ \ + if (old_buffer != bufp->buffer) \ + { \ + b = (b - old_buffer) + bufp->buffer; \ + begalt = (begalt - old_buffer) + bufp->buffer; \ + if (fixup_alt_jump) \ + fixup_alt_jump = (fixup_alt_jump - old_buffer) + bufp->buffer; \ + if (laststart) \ + laststart = (laststart - old_buffer) + bufp->buffer; \ + if (pending_exact) \ + pending_exact = (pending_exact - old_buffer) + bufp->buffer; \ + } \ + } + + +/* Since we have one byte reserved for the register number argument to + {start,stop}_memory, the maximum number of groups we can report + things about is what fits in that byte. */ +#define MAX_REGNUM 255 + +/* But patterns can have more than `MAX_REGNUM' registers. We just + ignore the excess. */ +typedef unsigned regnum_t; + + +/* Macros for the compile stack. */ + +/* Since offsets can go either forwards or backwards, this type needs to + be able to hold values from -(MAX_BUF_SIZE - 1) to MAX_BUF_SIZE - 1. */ +/* int may be not enough when sizeof(int) == 2. */ +typedef long pattern_offset_t; + +typedef struct +{ + pattern_offset_t begalt_offset; + pattern_offset_t fixup_alt_jump; + pattern_offset_t inner_group_offset; + pattern_offset_t laststart_offset; + regnum_t regnum; +} compile_stack_elt_t; + + +typedef struct +{ + compile_stack_elt_t *stack; + unsigned size; + unsigned avail; /* Offset of next open position. */ +} compile_stack_type; + + +#define INIT_COMPILE_STACK_SIZE 32 + +#define COMPILE_STACK_EMPTY (compile_stack.avail == 0) +#define COMPILE_STACK_FULL (compile_stack.avail == compile_stack.size) + +/* The next available element. */ +#define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail]) + + +/* Set the bit for character C in a list. */ +#define SET_LIST_BIT(c) \ + (b[((unsigned char) (c)) / BYTEWIDTH] \ + |= 1 << (((unsigned char) c) % BYTEWIDTH)) + + +/* Get the next unsigned number in the uncompiled pattern. */ +#define GET_UNSIGNED_NUMBER(num) \ + { if (p != pend) \ + { \ + PATFETCH (c); \ + while (ISDIGIT (c)) \ + { \ + if (num < 0) \ + num = 0; \ + num = num * 10 + c - '0'; \ + if (p == pend) \ + break; \ + PATFETCH (c); \ + } \ + } \ + } + +#if defined _LIBC || WIDE_CHAR_SUPPORT +/* The GNU C library provides support for user-defined character classes + and the functions from ISO C amendement 1. */ +# ifdef CHARCLASS_NAME_MAX +# define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX +# else +/* This shouldn't happen but some implementation might still have this + problem. Use a reasonable default value. */ +# define CHAR_CLASS_MAX_LENGTH 256 +# endif + +# ifdef _LIBC +# define IS_CHAR_CLASS(string) __wctype (string) +# else +# define IS_CHAR_CLASS(string) wctype (string) +# endif +#else +# define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */ + +# define IS_CHAR_CLASS(string) \ + (STREQ (string, "alpha") || STREQ (string, "upper") \ + || STREQ (string, "lower") || STREQ (string, "digit") \ + || STREQ (string, "alnum") || STREQ (string, "xdigit") \ + || STREQ (string, "space") || STREQ (string, "print") \ + || STREQ (string, "punct") || STREQ (string, "graph") \ + || STREQ (string, "cntrl") || STREQ (string, "blank")) +#endif + +#ifndef MATCH_MAY_ALLOCATE + +/* If we cannot allocate large objects within re_match_2_internal, + we make the fail stack and register vectors global. + The fail stack, we grow to the maximum size when a regexp + is compiled. + The register vectors, we adjust in size each time we + compile a regexp, according to the number of registers it needs. */ + +static fail_stack_type fail_stack; + +/* Size with which the following vectors are currently allocated. + That is so we can make them bigger as needed, + but never make them smaller. */ +static int regs_allocated_size; + +static const char ** regstart, ** regend; +static const char ** old_regstart, ** old_regend; +static const char **best_regstart, **best_regend; +static register_info_type *reg_info; +static const char **reg_dummy; +static register_info_type *reg_info_dummy; + +/* Make the register vectors big enough for NUM_REGS registers, + but don't make them smaller. */ + +static +regex_grow_registers (num_regs) + int num_regs; +{ + if (num_regs > regs_allocated_size) + { + RETALLOC_IF (regstart, num_regs, const char *); + RETALLOC_IF (regend, num_regs, const char *); + RETALLOC_IF (old_regstart, num_regs, const char *); + RETALLOC_IF (old_regend, num_regs, const char *); + RETALLOC_IF (best_regstart, num_regs, const char *); + RETALLOC_IF (best_regend, num_regs, const char *); + RETALLOC_IF (reg_info, num_regs, register_info_type); + RETALLOC_IF (reg_dummy, num_regs, const char *); + RETALLOC_IF (reg_info_dummy, num_regs, register_info_type); + + regs_allocated_size = num_regs; + } +} + +#endif /* not MATCH_MAY_ALLOCATE */ + +static bool group_in_compile_stack(compile_stack_type compile_stack, + regnum_t regnum); + +/* `regex_compile' compiles PATTERN (of length SIZE) according to SYNTAX. + Returns one of error codes defined in `regex.h', or zero for success. + + Assumes the `allocated' (and perhaps `buffer') and `translate' + fields are set in BUFP on entry. + + If it succeeds, results are put in BUFP (if it returns an error, the + contents of BUFP are undefined): + `buffer' is the compiled pattern; + `syntax' is set to SYNTAX; + `used' is set to the length of the compiled pattern; + `fastmap_accurate' is zero; + `re_nsub' is the number of subexpressions in PATTERN; + `not_bol' and `not_eol' are zero; + + The `fastmap' and `newline_anchor' fields are neither + examined nor set. */ + +/* Return, freeing storage we allocated. */ +#define FREE_STACK_RETURN(value) \ + return (free (compile_stack.stack), value) + +static reg_errcode_t +regex_compile (const char *pattern, + size_t size, + reg_syntax_t syntax, + struct re_pattern_buffer *bufp) +{ + /* We fetch characters from PATTERN here. Even though PATTERN is + `char *' (i.e., signed), we declare these variables as unsigned, so + they can be reliably used as array indices. */ + register unsigned char c, c1; + + /* A random temporary spot in PATTERN. */ + const char *p1; + + /* Points to the end of the buffer, where we should append. */ + register unsigned char *b; + + /* Keeps track of unclosed groups. */ + compile_stack_type compile_stack; + + /* Points to the current (ending) position in the pattern. */ + const char *p = pattern; + const char *pend = pattern + size; + + /* How to translate the characters in the pattern. */ + RE_TRANSLATE_TYPE translate = bufp->translate; + + /* Address of the count-byte of the most recently inserted `exactn' + command. This makes it possible to tell if a new exact-match + character can be added to that command or if the character requires + a new `exactn' command. */ + unsigned char *pending_exact = 0; + + /* Address of start of the most recently finished expression. + This tells, e.g., postfix * where to find the start of its + operand. Reset at the beginning of groups and alternatives. */ + unsigned char *laststart = 0; + + /* Address of beginning of regexp, or inside of last group. */ + unsigned char *begalt; + + /* Place in the uncompiled pattern (i.e., the {) to + which to go back if the interval is invalid. */ + const char *beg_interval; + + /* Address of the place where a forward jump should go to the end of + the containing expression. Each alternative of an `or' -- except the + last -- ends with a forward jump of this sort. */ + unsigned char *fixup_alt_jump = 0; + + /* Counts open-groups as they are encountered. Remembered for the + matching close-group on the compile stack, so the same register + number is put in the stop_memory as the start_memory. */ + regnum_t regnum = 0; + + /* Initialize the compile stack. */ + compile_stack.stack = TALLOC (INIT_COMPILE_STACK_SIZE, compile_stack_elt_t); + if (compile_stack.stack == NULL) + return REG_ESPACE; + + compile_stack.size = INIT_COMPILE_STACK_SIZE; + compile_stack.avail = 0; + + /* Initialize the pattern buffer. */ + bufp->syntax = syntax; + bufp->fastmap_accurate = 0; + bufp->not_bol = bufp->not_eol = 0; + + /* Set `used' to zero, so that if we return an error, the pattern + printer (for debugging) will think there's no pattern. We reset it + at the end. */ + bufp->used = 0; + + /* Always count groups, whether or not bufp->no_sub is set. */ + bufp->re_nsub = 0; + +#if !defined emacs && !defined SYNTAX_TABLE + /* Initialize the syntax table. */ + init_syntax_once (); +#endif + + if (bufp->allocated == 0) + { + if (bufp->buffer) + { /* If zero allocated, but buffer is non-null, try to realloc + enough space. This loses if buffer's address is bogus, but + that is the user's responsibility. */ + RETALLOC (bufp->buffer, INIT_BUF_SIZE, unsigned char); + } + else + { /* Caller did not allocate a buffer. Do it for them. */ + bufp->buffer = TALLOC (INIT_BUF_SIZE, unsigned char); + } + if (!bufp->buffer) FREE_STACK_RETURN (REG_ESPACE); + + bufp->allocated = INIT_BUF_SIZE; + } + + begalt = b = bufp->buffer; + + /* Loop through the uncompiled pattern until we're at the end. */ + while (p != pend) + { + PATFETCH (c); + + switch (c) + { + case '^': + { + if ( /* If at start of pattern, it's an operator. */ + p == pattern + 1 + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* Otherwise, depends on what's come before. */ + || at_begline_loc_p (pattern, p, syntax)) + { + BUF_PUSH (begline); + } + else + goto normal_char; + } + break; + + + case '$': + { + if ( /* If at end of pattern, it's an operator. */ + p == pend + /* If context independent, it's an operator. */ + || syntax & RE_CONTEXT_INDEP_ANCHORS + /* Otherwise, depends on what's next. */ + || at_endline_loc_p (p, pend, syntax)) + { + BUF_PUSH (endline); + } + else + goto normal_char; + } + break; + + + case '+': + case '?': + if ((syntax & RE_BK_PLUS_QM) + || (syntax & RE_LIMITED_OPS)) + goto normal_char; + handle_plus: + case '*': + /* If there is no previous pattern... */ + if (!laststart) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + FREE_STACK_RETURN (REG_BADRPT); + else if (!(syntax & RE_CONTEXT_INDEP_OPS)) + goto normal_char; + } + + { + /* Are we optimizing this jump? */ + bool keep_string_p = false; + + /* 1 means zero (many) matches is allowed. */ + char zero_times_ok = 0, many_times_ok = 0; + + /* If there is a sequence of repetition chars, collapse it + down to just one (the right one). We can't combine + interval operators with these because of, e.g., `a{2}*', + which should only match an even number of `a's. */ + + for (;;) + { + zero_times_ok |= c != '+'; + many_times_ok |= c != '?'; + + if (p == pend) + break; + + PATFETCH (c); + + if (c == '*' + || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?'))) + ; + + else if (syntax & RE_BK_PLUS_QM && c == '\\') + { + if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); + + PATFETCH (c1); + if (!(c1 == '+' || c1 == '?')) + { + PATUNFETCH; + PATUNFETCH; + break; + } + + c = c1; + } + else + { + PATUNFETCH; + break; + } + + /* If we get here, we found another repeat character. */ + } + + /* Star, etc. applied to an empty pattern is equivalent + to an empty pattern. */ + if (!laststart) + break; + + /* Now we know whether or not zero matches is allowed + and also whether or not two or more matches is allowed. */ + if (many_times_ok) + { /* More than one repetition is allowed, so put in at the + end a backward relative jump from `b' to before the next + jump we're going to put in below (which jumps from + laststart to after this jump). + + But if we are at the `*' in the exact sequence `.*\n', + insert an unconditional jump backwards to the ., + instead of the beginning of the loop. This way we only + push a failure point once, instead of every time + through the loop. */ + + /* Allocate the space for the jump. */ + GET_BUFFER_SPACE (3); + + /* We know we are not at the first character of the pattern, + because laststart was nonzero. And we've already + incremented `p', by the way, to be the character after + the `*'. Do we have to do something analogous here + for null bytes, because of RE_DOT_NOT_NULL? */ + if (TRANSLATE (*(p - 2)) == TRANSLATE ('.') + && zero_times_ok + && p < pend && TRANSLATE (*p) == TRANSLATE ('\n') + && !(syntax & RE_DOT_NEWLINE)) + { /* We have .*\n. */ + STORE_JUMP (jump, b, laststart); + keep_string_p = true; + } + else + /* Anything else. */ + STORE_JUMP (maybe_pop_jump, b, laststart - 3); + + /* We've added more stuff to the buffer. */ + b += 3; + } + + /* On failure, jump from laststart to b + 3, which will be the + end of the buffer after this jump is inserted. */ + GET_BUFFER_SPACE (3); + INSERT_JUMP (keep_string_p ? on_failure_keep_string_jump + : on_failure_jump, + laststart, b + 3); + pending_exact = 0; + b += 3; + + if (!zero_times_ok) + { + /* At least one repetition is required, so insert a + `dummy_failure_jump' before the initial + `on_failure_jump' instruction of the loop. This + effects a skip over that instruction the first time + we hit that loop. */ + GET_BUFFER_SPACE (3); + INSERT_JUMP (dummy_failure_jump, laststart, laststart + 6); + b += 3; + } + } + break; + + + case '.': + laststart = b; + BUF_PUSH (anychar); + break; + + + case '[': + { + bool had_char_class = false; + + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + /* Ensure that we have enough space to push a charset: the + opcode, the length count, and the bitset; 34 bytes in all. */ + GET_BUFFER_SPACE (34); + + laststart = b; + + /* We test `*p == '^' twice, instead of using an if + statement, so we only need one BUF_PUSH. */ + BUF_PUSH (*p == '^' ? charset_not : charset); + if (*p == '^') + p++; + + /* Remember the first position in the bracket expression. */ + p1 = p; + + /* Push the number of bytes in the bitmap. */ + BUF_PUSH ((1 << BYTEWIDTH) / BYTEWIDTH); + + /* Clear the whole map. */ + bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH); + + /* charset_not matches newline according to a syntax bit. */ + if ((re_opcode_t) b[-2] == charset_not + && (syntax & RE_HAT_LISTS_NOT_NEWLINE)) + SET_LIST_BIT ('\n'); + + /* Read in characters and ranges, setting map bits. */ + for (;;) + { + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + PATFETCH (c); + + /* \ might escape characters inside [...] and [^...]. */ + if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\') + { + if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); + + PATFETCH (c1); + SET_LIST_BIT (c1); + continue; + } + + /* Could be the end of the bracket expression. If it's + not (i.e., when the bracket expression is `[]' so + far), the ']' character bit gets set way below. */ + if (c == ']' && p != p1 + 1) + break; + + /* Look ahead to see if it's a range when the last thing + was a character class. */ + if (had_char_class && c == '-' && *p != ']') + FREE_STACK_RETURN (REG_ERANGE); + + /* Look ahead to see if it's a range when the last thing + was a character: if this is a hyphen not at the + beginning or the end of a list, then it's the range + operator. */ + if (c == '-' + && !(p - 2 >= pattern && p[-2] == '[') + && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^') + && *p != ']') + { + reg_errcode_t ret + = compile_range (&p, pend, translate, syntax, b); + if (ret != REG_NOERROR) FREE_STACK_RETURN (ret); + } + + else if (p[0] == '-' && p[1] != ']') + { /* This handles ranges made up of characters only. */ + reg_errcode_t ret; + + /* Move past the `-'. */ + PATFETCH (c1); + + ret = compile_range (&p, pend, translate, syntax, b); + if (ret != REG_NOERROR) FREE_STACK_RETURN (ret); + } + + /* See if we're at the beginning of a possible character + class. */ + + else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':') + { /* Leave room for the null. */ + char str[CHAR_CLASS_MAX_LENGTH + 1]; + + PATFETCH (c); + c1 = 0; + + /* If pattern is `[[:'. */ + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + for (;;) + { + PATFETCH (c); + if ((c == ':' && *p == ']') || p == pend) + break; + if (c1 < CHAR_CLASS_MAX_LENGTH) + str[c1++] = c; + else + /* This is in any case an invalid class name. */ + str[0] = '\0'; + } + str[c1] = '\0'; + + /* If isn't a word bracketed by `[:' and `:]': + undo the ending character, the letters, and leave + the leading `:' and `[' (but set bits for them). */ + if (c == ':' && *p == ']') + { +#if defined _LIBC || WIDE_CHAR_SUPPORT + bool is_lower = STREQ (str, "lower"); + bool is_upper = STREQ (str, "upper"); + wctype_t wt; + int ch; + + wt = IS_CHAR_CLASS (str); + if (wt == 0) + FREE_STACK_RETURN (REG_ECTYPE); + + /* Throw away the ] at the end of the character + class. */ + PATFETCH (c); + + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + for (ch = 0; ch < 1 << BYTEWIDTH; ++ch) + { +# ifdef _LIBC + if (__iswctype (__btowc (ch), wt)) + SET_LIST_BIT (ch); +# else + if (iswctype (btowc (ch), wt)) + SET_LIST_BIT (ch); +# endif + + if (translate && (is_upper || is_lower) + && (ISUPPER (ch) || ISLOWER (ch))) + SET_LIST_BIT (ch); + } + + had_char_class = true; +#else + int ch; + bool is_alnum = STREQ (str, "alnum"); + bool is_alpha = STREQ (str, "alpha"); + bool is_blank = STREQ (str, "blank"); + bool is_cntrl = STREQ (str, "cntrl"); + bool is_digit = STREQ (str, "digit"); + bool is_graph = STREQ (str, "graph"); + bool is_lower = STREQ (str, "lower"); + bool is_print = STREQ (str, "print"); + bool is_punct = STREQ (str, "punct"); + bool is_space = STREQ (str, "space"); + bool is_upper = STREQ (str, "upper"); + bool is_xdigit = STREQ (str, "xdigit"); + + if (!IS_CHAR_CLASS (str)) + FREE_STACK_RETURN (REG_ECTYPE); + + /* Throw away the ] at the end of the character + class. */ + PATFETCH (c); + + if (p == pend) FREE_STACK_RETURN (REG_EBRACK); + + for (ch = 0; ch < 1 << BYTEWIDTH; ch++) + { + /* This was split into 3 if's to + avoid an arbitrary limit in some compiler. */ + if ( (is_alnum && ISALNUM (ch)) + || (is_alpha && ISALPHA (ch)) + || (is_blank && ISBLANK (ch)) + || (is_cntrl && ISCNTRL (ch))) + SET_LIST_BIT (ch); + if ( (is_digit && ISDIGIT (ch)) + || (is_graph && ISGRAPH (ch)) + || (is_lower && ISLOWER (ch)) + || (is_print && ISPRINT (ch))) + SET_LIST_BIT (ch); + if ( (is_punct && ISPUNCT (ch)) + || (is_space && ISSPACE (ch)) + || (is_upper && ISUPPER (ch)) + || (is_xdigit && ISXDIGIT (ch))) + SET_LIST_BIT (ch); + if ( translate && (is_upper || is_lower) + && (ISUPPER (ch) || ISLOWER (ch))) + SET_LIST_BIT (ch); + } + had_char_class = true; +#endif /* libc || wctype.h */ + } + else + { + c1++; + while (c1--) + PATUNFETCH; + SET_LIST_BIT ('['); + SET_LIST_BIT (':'); + had_char_class = false; + } + } + else + { + had_char_class = false; + SET_LIST_BIT (c); + } + } + + /* Discard any (non)matching list bytes that are all 0 at the + end of the map. Decrease the map-length byte too. */ + while ((int) b[-1] > 0 && b[b[-1] - 1] == 0) + b[-1]--; + b += b[-1]; + } + break; + + + case '(': + if (syntax & RE_NO_BK_PARENS) + goto handle_open; + else + goto normal_char; + + + case ')': + if (syntax & RE_NO_BK_PARENS) + goto handle_close; + else + goto normal_char; + + + case '\n': + if (syntax & RE_NEWLINE_ALT) + goto handle_alt; + else + goto normal_char; + + + case '|': + if (syntax & RE_NO_BK_VBAR) + goto handle_alt; + else + goto normal_char; + + + case '{': + if (syntax & RE_INTERVALS && syntax & RE_NO_BK_BRACES) + goto handle_interval; + else + goto normal_char; + + + case '\\': + if (p == pend) FREE_STACK_RETURN (REG_EESCAPE); + + /* Do not translate the character after the \, so that we can + distinguish, e.g., \B from \b, even if we normally would + translate, e.g., B to b. */ + PATFETCH_RAW (c); + + switch (c) + { + case '(': + if (syntax & RE_NO_BK_PARENS) + goto normal_backslash; + + handle_open: + bufp->re_nsub++; + regnum++; + + if (COMPILE_STACK_FULL) + { + RETALLOC (compile_stack.stack, compile_stack.size << 1, + compile_stack_elt_t); + if (compile_stack.stack == NULL) return REG_ESPACE; + + compile_stack.size <<= 1; + } + + /* These are the values to restore when we hit end of this + group. They are all relative offsets, so that if the + whole pattern moves because of realloc, they will still + be valid. */ + COMPILE_STACK_TOP.begalt_offset = begalt - bufp->buffer; + COMPILE_STACK_TOP.fixup_alt_jump + = fixup_alt_jump ? fixup_alt_jump - bufp->buffer + 1 : 0; + COMPILE_STACK_TOP.laststart_offset = b - bufp->buffer; + COMPILE_STACK_TOP.regnum = regnum; + + /* We will eventually replace the 0 with the number of + groups inner to this one. But do not push a + start_memory for groups beyond the last one we can + represent in the compiled pattern. */ + if (regnum <= MAX_REGNUM) + { + COMPILE_STACK_TOP.inner_group_offset = b - bufp->buffer + 2; + BUF_PUSH_3 (start_memory, regnum, 0); + } + + compile_stack.avail++; + + fixup_alt_jump = 0; + laststart = 0; + begalt = b; + /* If we've reached MAX_REGNUM groups, then this open + won't actually generate any code, so we'll have to + clear pending_exact explicitly. */ + pending_exact = 0; + break; + + + case ')': + if (syntax & RE_NO_BK_PARENS) goto normal_backslash; + + if (COMPILE_STACK_EMPTY) + { + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) + goto normal_backslash; + else + FREE_STACK_RETURN (REG_ERPAREN); + } + + handle_close: + if (fixup_alt_jump) + { /* Push a dummy failure point at the end of the + alternative for a possible future + `pop_failure_jump' to pop. See comments at + `push_dummy_failure' in `re_match_2'. */ + BUF_PUSH (push_dummy_failure); + + /* We allocated space for this jump when we assigned + to `fixup_alt_jump', in the `handle_alt' case below. */ + STORE_JUMP (jump_past_alt, fixup_alt_jump, b - 1); + } + + /* See similar code for backslashed left paren above. */ + if (COMPILE_STACK_EMPTY) + { + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD) + goto normal_char; + else + FREE_STACK_RETURN (REG_ERPAREN); + } + + { + /* We don't just want to restore into `regnum', because + later groups should continue to be numbered higher, + as in `(ab)c(de)' -- the second group is #2. */ + regnum_t this_group_regnum; + + compile_stack.avail--; + begalt = bufp->buffer + COMPILE_STACK_TOP.begalt_offset; + fixup_alt_jump + = COMPILE_STACK_TOP.fixup_alt_jump + ? bufp->buffer + COMPILE_STACK_TOP.fixup_alt_jump - 1 + : 0; + laststart = bufp->buffer + COMPILE_STACK_TOP.laststart_offset; + this_group_regnum = COMPILE_STACK_TOP.regnum; + /* If we've reached MAX_REGNUM groups, then this open + won't actually generate any code, so we'll have to + clear pending_exact explicitly. */ + pending_exact = 0; + + /* We're at the end of the group, so now we know how many + groups were inside this one. */ + if (this_group_regnum <= MAX_REGNUM) + { + unsigned char *inner_group_loc + = bufp->buffer + COMPILE_STACK_TOP.inner_group_offset; + + *inner_group_loc = regnum - this_group_regnum; + BUF_PUSH_3 (stop_memory, this_group_regnum, + regnum - this_group_regnum); + } + } + break; + + + case '|': /* `\|'. */ + if (syntax & RE_LIMITED_OPS || syntax & RE_NO_BK_VBAR) + goto normal_backslash; + handle_alt: + if (syntax & RE_LIMITED_OPS) + goto normal_char; + + /* Insert before the previous alternative a jump which + jumps to this alternative if the former fails. */ + GET_BUFFER_SPACE (3); + INSERT_JUMP (on_failure_jump, begalt, b + 6); + pending_exact = 0; + b += 3; + + /* The alternative before this one has a jump after it + which gets executed if it gets matched. Adjust that + jump so it will jump to this alternative's analogous + jump (put in below, which in turn will jump to the next + (if any) alternative's such jump, etc.). The last such + jump jumps to the correct final destination. A picture: + _____ _____ + | | | | + | v | v + a | b | c + + If we are at `b', then fixup_alt_jump right now points to a + three-byte space after `a'. We'll put in the jump, set + fixup_alt_jump to right after `b', and leave behind three + bytes which we'll fill in when we get to after `c'. */ + + if (fixup_alt_jump) + STORE_JUMP (jump_past_alt, fixup_alt_jump, b); + + /* Mark and leave space for a jump after this alternative, + to be filled in later either by next alternative or + when know we're at the end of a series of alternatives. */ + fixup_alt_jump = b; + GET_BUFFER_SPACE (3); + b += 3; + + laststart = 0; + begalt = b; + break; + + + case '{': + /* If \{ is a literal. */ + if (!(syntax & RE_INTERVALS) + /* If we're at `\{' and it's not the open-interval + operator. */ + || ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES)) + || (p - 2 == pattern && p == pend)) + goto normal_backslash; + + handle_interval: + { + /* If got here, then the syntax allows intervals. */ + + /* At least (most) this many matches must be made. */ + int lower_bound = -1, upper_bound = -1; + + beg_interval = p - 1; + + if (p == pend) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + FREE_STACK_RETURN (REG_EBRACE); + } + + GET_UNSIGNED_NUMBER (lower_bound); + + if (c == ',') + { + GET_UNSIGNED_NUMBER (upper_bound); + if (upper_bound < 0) upper_bound = RE_DUP_MAX; + } + else + /* Interval such as `{1}' => match exactly once. */ + upper_bound = lower_bound; + + if (lower_bound < 0 || upper_bound > RE_DUP_MAX + || lower_bound > upper_bound) + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + FREE_STACK_RETURN (REG_BADBR); + } + + if (!(syntax & RE_NO_BK_BRACES)) + { + if (c != '\\') FREE_STACK_RETURN (REG_EBRACE); + + PATFETCH (c); + } + + if (c != '}') + { + if (syntax & RE_NO_BK_BRACES) + goto unfetch_interval; + else + FREE_STACK_RETURN (REG_BADBR); + } + + /* We just parsed a valid interval. */ + + /* If it's invalid to have no preceding re. */ + if (!laststart) + { + if (syntax & RE_CONTEXT_INVALID_OPS) + FREE_STACK_RETURN (REG_BADRPT); + else if (syntax & RE_CONTEXT_INDEP_OPS) + laststart = b; + else + goto unfetch_interval; + } + + /* If the upper bound is zero, don't want to succeed at + all; jump from `laststart' to `b + 3', which will be + the end of the buffer after we insert the jump. */ + if (upper_bound == 0) + { + GET_BUFFER_SPACE (3); + INSERT_JUMP (jump, laststart, b + 3); + b += 3; + } + + /* Otherwise, we have a nontrivial interval. When + we're all done, the pattern will look like: + set_number_at + set_number_at + succeed_n + + jump_n + (The upper bound and `jump_n' are omitted if + `upper_bound' is 1, though.) */ + else + { /* If the upper bound is > 1, we need to insert + more at the end of the loop. */ + unsigned nbytes = 10 + (upper_bound > 1) * 10; + + GET_BUFFER_SPACE (nbytes); + + /* Initialize lower bound of the `succeed_n', even + though it will be set during matching by its + attendant `set_number_at' (inserted next), + because `re_compile_fastmap' needs to know. + Jump to the `jump_n' we might insert below. */ + INSERT_JUMP2 (succeed_n, laststart, + b + 5 + (upper_bound > 1) * 5, + lower_bound); + b += 5; + + /* Code to initialize the lower bound. Insert + before the `succeed_n'. The `5' is the last two + bytes of this `set_number_at', plus 3 bytes of + the following `succeed_n'. */ + insert_op2 (set_number_at, laststart, 5, lower_bound, b); + b += 5; + + if (upper_bound > 1) + { /* More than one repetition is allowed, so + append a backward jump to the `succeed_n' + that starts this interval. + + When we've reached this during matching, + we'll have matched the interval once, so + jump back only `upper_bound - 1' times. */ + STORE_JUMP2 (jump_n, b, laststart + 5, + upper_bound - 1); + b += 5; + + /* The location we want to set is the second + parameter of the `jump_n'; that is `b-2' as + an absolute address. `laststart' will be + the `set_number_at' we're about to insert; + `laststart+3' the number to set, the source + for the relative address. But we are + inserting into the middle of the pattern -- + so everything is getting moved up by 5. + Conclusion: (b - 2) - (laststart + 3) + 5, + i.e., b - laststart. + + We insert this at the beginning of the loop + so that if we fail during matching, we'll + reinitialize the bounds. */ + insert_op2 (set_number_at, laststart, b - laststart, + upper_bound - 1, b); + b += 5; + } + } + pending_exact = 0; + beg_interval = NULL; + } + break; + + unfetch_interval: + /* If an invalid interval, match the characters as literals. */ + p = beg_interval; + beg_interval = NULL; + + /* normal_char and normal_backslash need `c'. */ + PATFETCH (c); + + if (!(syntax & RE_NO_BK_BRACES)) + { + if (p > pattern && p[-1] == '\\') + goto normal_backslash; + } + goto normal_char; + +#ifdef emacs + /* There is no way to specify the before_dot and after_dot + operators. rms says this is ok. --karl */ + case '=': + BUF_PUSH (at_dot); + break; + + case 's': + laststart = b; + PATFETCH (c); + BUF_PUSH_2 (syntaxspec, syntax_spec_code[c]); + break; + + case 'S': + laststart = b; + PATFETCH (c); + BUF_PUSH_2 (notsyntaxspec, syntax_spec_code[c]); + break; +#endif /* emacs */ + + + case 'w': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + laststart = b; + BUF_PUSH (wordchar); + break; + + + case 'W': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + laststart = b; + BUF_PUSH (notwordchar); + break; + + + case '<': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (wordbeg); + break; + + case '>': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (wordend); + break; + + case 'b': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (wordbound); + break; + + case 'B': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (notwordbound); + break; + + case '`': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (begbuf); + break; + + case '\'': + if (syntax & RE_NO_GNU_OPS) + goto normal_char; + BUF_PUSH (endbuf); + break; + + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': + if (syntax & RE_NO_BK_REFS) + goto normal_char; + + c1 = c - '0'; + + if (c1 > regnum) + FREE_STACK_RETURN (REG_ESUBREG); + + /* Can't back reference to a subexpression if inside of it. */ + if (group_in_compile_stack (compile_stack, (regnum_t) c1)) + goto normal_char; + + laststart = b; + BUF_PUSH_2 (duplicate, c1); + break; + + + case '+': + case '?': + if (syntax & RE_BK_PLUS_QM) + goto handle_plus; + else + goto normal_backslash; + + default: + normal_backslash: + /* You might think it would be useful for \ to mean + not to translate; but if we don't translate it + it will never match anything. */ + c = TRANSLATE (c); + goto normal_char; + } + break; + + + default: + /* Expects the character in `c'. */ + normal_char: + /* If no exactn currently being built. */ + if (!pending_exact + + /* If last exactn not at current position. */ + || pending_exact + *pending_exact + 1 != b + + /* We have only one byte following the exactn for the count. */ + || *pending_exact == (1 << BYTEWIDTH) - 1 + + /* If followed by a repetition operator. */ + || *p == '*' || *p == '^' + || ((syntax & RE_BK_PLUS_QM) + ? *p == '\\' && (p[1] == '+' || p[1] == '?') + : (*p == '+' || *p == '?')) + || ((syntax & RE_INTERVALS) + && ((syntax & RE_NO_BK_BRACES) + ? *p == '{' + : (p[0] == '\\' && p[1] == '{')))) + { + /* Start building a new exactn. */ + + laststart = b; + + BUF_PUSH_2 (exactn, 0); + pending_exact = b - 1; + } + + BUF_PUSH (c); + (*pending_exact)++; + break; + } /* switch (c) */ + } /* while p != pend */ + + + /* Through the pattern now. */ + + if (fixup_alt_jump) + STORE_JUMP (jump_past_alt, fixup_alt_jump, b); + + if (!COMPILE_STACK_EMPTY) + FREE_STACK_RETURN (REG_EPAREN); + + /* If we don't want backtracking, force success + the first time we reach the end of the compiled pattern. */ + if (syntax & RE_NO_POSIX_BACKTRACKING) + BUF_PUSH (succeed); + + free (compile_stack.stack); + + /* We have succeeded; set the length of the buffer. */ + bufp->used = b - bufp->buffer; + +#ifndef MATCH_MAY_ALLOCATE + /* Initialize the failure stack to the largest possible stack. This + isn't necessary unless we're trying to avoid calling alloca in + the search and match routines. */ + { + int num_regs = bufp->re_nsub + 1; + + /* Since DOUBLE_FAIL_STACK refuses to double only if the current size + is strictly greater than re_max_failures, the largest possible stack + is 2 * re_max_failures failure points. */ + if (fail_stack.size < (2 * re_max_failures * MAX_FAILURE_ITEMS)) + { + fail_stack.size = (2 * re_max_failures * MAX_FAILURE_ITEMS); + +# ifdef emacs + if (! fail_stack.stack) + fail_stack.stack + = (fail_stack_elt_t *) xmalloc (fail_stack.size + * sizeof (fail_stack_elt_t)); + else + fail_stack.stack + = (fail_stack_elt_t *) xrealloc (fail_stack.stack, + (fail_stack.size + * sizeof (fail_stack_elt_t))); +# else /* not emacs */ + if (! fail_stack.stack) + fail_stack.stack + = (fail_stack_elt_t *) malloc (fail_stack.size + * sizeof (fail_stack_elt_t)); + else + fail_stack.stack + = (fail_stack_elt_t *) realloc (fail_stack.stack, + (fail_stack.size + * sizeof (fail_stack_elt_t))); +# endif /* not emacs */ + } + + regex_grow_registers (num_regs); + } +#endif /* not MATCH_MAY_ALLOCATE */ + + return REG_NOERROR; +} /* regex_compile */ + +/* Subroutines for `regex_compile'. */ + +/* Store OP at LOC followed by two-byte integer parameter ARG. */ + +static void +store_op1 (re_opcode_t op, + unsigned char *loc, + int arg) +{ + *loc = (unsigned char) op; + STORE_NUMBER (loc + 1, arg); +} + + +/* Like `store_op1', but for two two-byte parameters ARG1 and ARG2. */ + +static void +store_op2 (re_opcode_t op, + unsigned char *loc, + int arg1, + int arg2) +{ + *loc = (unsigned char) op; + STORE_NUMBER (loc + 1, arg1); + STORE_NUMBER (loc + 3, arg2); +} + + +/* Copy the bytes from LOC to END to open up three bytes of space at LOC + for OP followed by two-byte integer parameter ARG. */ + +static void +insert_op1 (re_opcode_t op, + unsigned char *loc, + int arg, + unsigned char *end) +{ + register unsigned char *pfrom = end; + register unsigned char *pto = end + 3; + + while (pfrom != loc) + *--pto = *--pfrom; + + store_op1 (op, loc, arg); +} + + +/* Like `insert_op1', but for two two-byte parameters ARG1 and ARG2. */ + +static void +insert_op2 (re_opcode_t op, + unsigned char *loc, + int arg1, + int arg2, + unsigned char *end) +{ + register unsigned char *pfrom = end; + register unsigned char *pto = end + 5; + + while (pfrom != loc) + *--pto = *--pfrom; + + store_op2 (op, loc, arg1, arg2); +} + + +/* P points to just after a ^ in PATTERN. Return true if that ^ comes + after an alternative or a begin-subexpression. We assume there is at + least one character before the ^. */ + +static bool +at_begline_loc_p (const char *pattern, + const char *p, + reg_syntax_t syntax) +{ + const char *prev = p - 2; + bool prev_prev_backslash = prev > pattern && prev[-1] == '\\'; + + return + /* After a subexpression? */ + (*prev == '(' && (syntax & RE_NO_BK_PARENS || prev_prev_backslash)) + /* After an alternative? */ + || (*prev == '|' && (syntax & RE_NO_BK_VBAR || prev_prev_backslash)); +} + + +/* The dual of at_begline_loc_p. This one is for $. We assume there is + at least one character after the $, i.e., `P < PEND'. */ + +static bool +at_endline_loc_p (const char *p, + const char *pend, + reg_syntax_t syntax) +{ + const char *next = p; + bool next_backslash = *next == '\\'; + const char *next_next = p + 1 < pend ? p + 1 : 0; + + return + /* Before a subexpression? */ + (syntax & RE_NO_BK_PARENS ? *next == ')' + : next_backslash && next_next && *next_next == ')') + /* Before an alternative? */ + || (syntax & RE_NO_BK_VBAR ? *next == '|' + : next_backslash && next_next && *next_next == '|'); +} + + +/* Returns true if REGNUM is in one of COMPILE_STACK's elements and + false if it's not. */ + +static bool +group_in_compile_stack (compile_stack_type compile_stack, + regnum_t regnum) +{ + int this_element; + + for (this_element = compile_stack.avail - 1; + this_element >= 0; + this_element--) + if (compile_stack.stack[this_element].regnum == regnum) + return true; + + return false; +} + + +/* Read the ending character of a range (in a bracket expression) from the + uncompiled pattern *P_PTR (which ends at PEND). We assume the + starting character is in `P[-2]'. (`P[-1]' is the character `-'.) + Then we set the translation of all bits between the starting and + ending characters (inclusive) in the compiled pattern B. + + Return an error code. + + We use these short variable names so we can use the same macros as + `regex_compile' itself. */ + +static reg_errcode_t +compile_range (const char **p_ptr, + const char *pend, + RE_TRANSLATE_TYPE translate, + reg_syntax_t syntax, + unsigned char *b) +{ + unsigned this_char; + + const char *p = *p_ptr; + unsigned int range_start, range_end; + + if (p == pend) + return REG_ERANGE; + + /* Even though the pattern is a signed `char *', we need to fetch + with unsigned char *'s; if the high bit of the pattern character + is set, the range endpoints will be negative if we fetch using a + signed char *. + + We also want to fetch the endpoints without translating them; the + appropriate translation is done in the bit-setting loop below. */ + /* The SVR4 compiler on the 3B2 had trouble with unsigned const char *. */ + range_start = ((const unsigned char *) p)[-2]; + range_end = ((const unsigned char *) p)[0]; + + /* Have to increment the pointer into the pattern string, so the + caller isn't still at the ending character. */ + (*p_ptr)++; + + /* If the start is after the end, the range is empty. */ + if (range_start > range_end) + return syntax & RE_NO_EMPTY_RANGES ? REG_ERANGE : REG_NOERROR; + + /* Here we see why `this_char' has to be larger than an `unsigned + char' -- the range is inclusive, so if `range_end' == 0xff + (assuming 8-bit characters), we would otherwise go into an infinite + loop, since all characters <= 0xff. */ + for (this_char = range_start; this_char <= range_end; this_char++) + { + SET_LIST_BIT (TRANSLATE (this_char)); + } + + return REG_NOERROR; +} + +/* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in + BUFP. A fastmap records which of the (1 << BYTEWIDTH) possible + characters can start a string that matches the pattern. This fastmap + is used by re_search to skip quickly over impossible starting points. + + The caller must supply the address of a (1 << BYTEWIDTH)-byte data + area as BUFP->fastmap. + + We set the `fastmap', `fastmap_accurate', and `can_be_null' fields in + the pattern buffer. + + Returns 0 if we succeed, -2 if an internal error. */ + +int +re_compile_fastmap (struct re_pattern_buffer *bufp) +{ + int j, k; +#ifdef MATCH_MAY_ALLOCATE + fail_stack_type fail_stack; +#endif +#ifndef REGEX_MALLOC + char *destination; +#endif + + register char *fastmap = bufp->fastmap; + unsigned char *pattern = bufp->buffer; + unsigned char *p = pattern; + register unsigned char *pend = pattern + bufp->used; + +#ifdef REL_ALLOC + /* This holds the pointer to the failure stack, when + it is allocated relocatably. */ + fail_stack_elt_t *failure_stack_ptr; +#endif + + /* Assume that each path through the pattern can be null until + proven otherwise. We set this false at the bottom of switch + statement, to which we get only if a particular path doesn't + match the empty string. */ + bool path_can_be_null = true; + + /* We aren't doing a `succeed_n' to begin with. */ + bool succeed_n_p = false; + + INIT_FAIL_STACK (); + bzero (fastmap, 1 << BYTEWIDTH); /* Assume nothing's valid. */ + bufp->fastmap_accurate = 1; /* It will be when we're done. */ + bufp->can_be_null = 0; + + while (true) + { + if (p == pend || *p == succeed) + { + /* We have reached the (effective) end of pattern. */ + if (!FAIL_STACK_EMPTY ()) + { + bufp->can_be_null |= path_can_be_null; + + /* Reset for next path. */ + path_can_be_null = true; + + p = fail_stack.stack[--fail_stack.avail].pointer; + + continue; + } + else + break; + } + + switch (SWITCH_ENUM_CAST ((re_opcode_t) *p++)) + { + + /* I guess the idea here is to simply not bother with a fastmap + if a backreference is used, since it's too hard to figure out + the fastmap for the corresponding group. Setting + `can_be_null' stops `re_search_2' from using the fastmap, so + that is all we do. */ + case duplicate: + bufp->can_be_null = 1; + goto done; + + + /* Following are the cases which match a character. These end + with `break'. */ + + case exactn: + fastmap[p[1]] = 1; + break; + + + case charset: + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))) + fastmap[j] = 1; + break; + + + case charset_not: + /* Chars beyond end of map must be allowed. */ + for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++) + fastmap[j] = 1; + + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--) + if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))) + fastmap[j] = 1; + break; + + + case wordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == Sword) + fastmap[j] = 1; + break; + + + case notwordchar: + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != Sword) + fastmap[j] = 1; + break; + + + case anychar: + { + int fastmap_newline = fastmap['\n']; + + /* `.' matches anything ... */ + for (j = 0; j < (1 << BYTEWIDTH); j++) + fastmap[j] = 1; + + /* ... except perhaps newline. */ + if (!(bufp->syntax & RE_DOT_NEWLINE)) + fastmap['\n'] = fastmap_newline; + + /* Return if we have already set `can_be_null'; if we have, + then the fastmap is irrelevant. Something's wrong here. */ + else if (bufp->can_be_null) + goto done; + + /* Otherwise, have to check alternative paths. */ + break; + } + +#ifdef emacs + case syntaxspec: + k = *p++; + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) == (enum syntaxcode) k) + fastmap[j] = 1; + break; + + + case notsyntaxspec: + k = *p++; + for (j = 0; j < (1 << BYTEWIDTH); j++) + if (SYNTAX (j) != (enum syntaxcode) k) + fastmap[j] = 1; + break; + + + /* All cases after this match the empty string. These end with + `continue'. */ + + + case before_dot: + case at_dot: + case after_dot: + continue; +#endif /* emacs */ + + + case no_op: + case begline: + case endline: + case begbuf: + case endbuf: + case wordbound: + case notwordbound: + case wordbeg: + case wordend: + case push_dummy_failure: + continue; + + + case jump_n: + case pop_failure_jump: + case maybe_pop_jump: + case jump: + case jump_past_alt: + case dummy_failure_jump: + EXTRACT_NUMBER_AND_INCR (j, p); + p += j; + if (j > 0) + continue; + + /* Jump backward implies we just went through the body of a + loop and matched nothing. Opcode jumped to should be + `on_failure_jump' or `succeed_n'. Just treat it like an + ordinary jump. For a * loop, it has pushed its failure + point already; if so, discard that as redundant. */ + if ((re_opcode_t) *p != on_failure_jump + && (re_opcode_t) *p != succeed_n) + continue; + + p++; + EXTRACT_NUMBER_AND_INCR (j, p); + p += j; + + /* If what's on the stack is where we are now, pop it. */ + if (!FAIL_STACK_EMPTY () + && fail_stack.stack[fail_stack.avail - 1].pointer == p) + fail_stack.avail--; + + continue; + + + case on_failure_jump: + case on_failure_keep_string_jump: + handle_on_failure_jump: + EXTRACT_NUMBER_AND_INCR (j, p); + + /* For some patterns, e.g., `(a?)?', `p+j' here points to the + end of the pattern. We don't want to push such a point, + since when we restore it above, entering the switch will + increment `p' past the end of the pattern. We don't need + to push such a point since we obviously won't find any more + fastmap entries beyond `pend'. Such a pattern can match + the null string, though. */ + if (p + j < pend) + { + if (!PUSH_PATTERN_OP (p + j, fail_stack)) + { + RESET_FAIL_STACK (); + return -2; + } + } + else + bufp->can_be_null = 1; + + if (succeed_n_p) + { + EXTRACT_NUMBER_AND_INCR (k, p); /* Skip the n. */ + succeed_n_p = false; + } + + continue; + + + case succeed_n: + /* Get to the number of times to succeed. */ + p += 2; + + /* Increment p past the n for when k != 0. */ + EXTRACT_NUMBER_AND_INCR (k, p); + if (k == 0) + { + p -= 4; + succeed_n_p = true; /* Spaghetti code alert. */ + goto handle_on_failure_jump; + } + continue; + + + case set_number_at: + p += 4; + continue; + + + case start_memory: + case stop_memory: + p += 2; + continue; + + + default: + abort (); /* We have listed all the cases. */ + } /* switch *p++ */ + + /* Getting here means we have found the possible starting + characters for one path of the pattern -- and that the empty + string does not match. We need not follow this path further. + Instead, look at the next alternative (remembered on the + stack), or quit if no more. The test at the top of the loop + does these things. */ + path_can_be_null = false; + p = pend; + } /* while p */ + + /* Set `can_be_null' for the last path (also the first path, if the + pattern is empty). */ + bufp->can_be_null |= path_can_be_null; + + done: + RESET_FAIL_STACK (); + return 0; +} /* re_compile_fastmap */ +#ifdef _LIBC +weak_alias (__re_compile_fastmap, re_compile_fastmap) +#endif + +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and + ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use + this memory for recording register information. STARTS and ENDS + must be allocated using the malloc library routine, and must each + be at least NUM_REGS * sizeof (regoff_t) bytes long. + + If NUM_REGS == 0, then subsequent matches should allocate their own + register data. + + Unless this function is called, the first search or match using + PATTERN_BUFFER will allocate its own register data, without + freeing the old data. */ + +void +re_set_registers (struct re_pattern_buffer *bufp, + struct re_registers *regs, + unsigned num_regs, + regoff_t *starts, + regoff_t *ends) +{ + if (num_regs) + { + bufp->regs_allocated = REGS_REALLOCATE; + regs->num_regs = num_regs; + regs->start = starts; + regs->end = ends; + } + else + { + bufp->regs_allocated = REGS_UNALLOCATED; + regs->num_regs = 0; + regs->start = regs->end = (regoff_t *) 0; + } +} +#ifdef _LIBC +weak_alias (__re_set_registers, re_set_registers) +#endif + +/* Searching routines. */ + +/* Like re_search_2, below, but only one string is specified, and + doesn't let you say where to stop matching. */ + +int +re_search (struct re_pattern_buffer *bufp, + const char *string, + int size, + int startpos, + int range, + struct re_registers *regs) +{ + return re_search_2 (bufp, NULL, 0, string, size, startpos, range, + regs, size); +} +#ifdef _LIBC +weak_alias (__re_search, re_search) +#endif + + +/* Using the compiled pattern in BUFP->buffer, first tries to match the + virtual concatenation of STRING1 and STRING2, starting first at index + STARTPOS, then at STARTPOS + 1, and so on. + + STRING1 and STRING2 have length SIZE1 and SIZE2, respectively. + + RANGE is how far to scan while trying to match. RANGE = 0 means try + only at STARTPOS; in general, the last start tried is STARTPOS + + RANGE. + + In REGS, return the indices of the virtual concatenation of STRING1 + and STRING2 that matched the entire BUFP->buffer and its contained + subexpressions. + + Do not consider matching one past the index STOP in the virtual + concatenation of STRING1 and STRING2. + + We return either the position in the strings at which the match was + found, -1 if no match, or -2 if error (such as failure + stack overflow). */ + +int +re_search_2 (struct re_pattern_buffer *bufp, + const char *string1, + int size1, + const char *string2, + int size2, + int startpos, + int range, + struct re_registers *regs, + int stop) +{ + int val; + register char *fastmap = bufp->fastmap; + register RE_TRANSLATE_TYPE translate = bufp->translate; + int total_size = size1 + size2; + int endpos = startpos + range; + + /* Check for out-of-range STARTPOS. */ + if (startpos < 0 || startpos > total_size) + return -1; + + /* Fix up RANGE if it might eventually take us outside + the virtual concatenation of STRING1 and STRING2. + Make sure we won't move STARTPOS below 0 or above TOTAL_SIZE. */ + if (endpos < 0) + range = 0 - startpos; + else if (endpos > total_size) + range = total_size - startpos; + + /* If the search isn't to be a backwards one, don't waste time in a + search for a pattern that must be anchored. */ + if (bufp->used > 0 && range > 0 + && ((re_opcode_t) bufp->buffer[0] == begbuf + /* `begline' is like `begbuf' if it cannot match at newlines. */ + || ((re_opcode_t) bufp->buffer[0] == begline + && !bufp->newline_anchor))) + { + if (startpos > 0) + return -1; + else + range = 1; + } + +#ifdef emacs + /* In a forward search for something that starts with \=. + don't keep searching past point. */ + if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == at_dot && range > 0) + { + range = PT - startpos; + if (range <= 0) + return -1; + } +#endif /* emacs */ + + /* Update the fastmap now if not correct already. */ + if (fastmap && !bufp->fastmap_accurate) + if (re_compile_fastmap (bufp) == -2) + return -2; + + /* Loop through the string, looking for a place to start matching. */ + for (;;) + { + /* If a fastmap is supplied, skip quickly over characters that + cannot be the start of a match. If the pattern can match the + null string, however, we don't need to skip characters; we want + the first null string. */ + if (fastmap && startpos < total_size && !bufp->can_be_null) + { + if (range > 0) /* Searching forwards. */ + { + register const char *d; + register int lim = 0; + int irange = range; + + if (startpos < size1 && startpos + range >= size1) + lim = range - (size1 - startpos); + + d = (startpos >= size1 ? string2 - size1 : string1) + startpos; + + /* Written out as an if-else to avoid testing `translate' + inside the loop. */ + if (translate) + while (range > lim + && !fastmap[(unsigned char) + translate[(unsigned char) *d++]]) + range--; + else + while (range > lim && !fastmap[(unsigned char) *d++]) + range--; + + startpos += irange - range; + } + else /* Searching backwards. */ + { + register char c = (size1 == 0 || startpos >= size1 + ? string2[startpos - size1] + : string1[startpos]); + + if (!fastmap[(unsigned char) TRANSLATE (c)]) + goto advance; + } + } + + /* If can't match the null string, and that's all we have left, fail. */ + if (range >= 0 && startpos == total_size && fastmap + && !bufp->can_be_null) + return -1; + + val = re_match_2_internal (bufp, string1, size1, string2, size2, + startpos, regs, stop); +#ifndef REGEX_MALLOC +# ifdef C_ALLOCA + alloca (0); +# endif +#endif + + if (val >= 0) + return startpos; + + if (val == -2) + return -2; + + advance: + if (!range) + break; + else if (range > 0) + { + range--; + startpos++; + } + else + { + range++; + startpos--; + } + } + return -1; +} /* re_search_2 */ +#ifdef _LIBC +weak_alias (__re_search_2, re_search_2) +#endif + +/* This converts PTR, a pointer into one of the search strings `string1' + and `string2' into an offset from the beginning of that string. */ +#define POINTER_TO_OFFSET(ptr) \ + (FIRST_STRING_P (ptr) \ + ? ((regoff_t) ((ptr) - string1)) \ + : ((regoff_t) ((ptr) - string2 + size1))) + +/* Macros for dealing with the split strings in re_match_2. */ + +#define MATCHING_IN_FIRST_STRING (dend == end_match_1) + +/* Call before fetching a character with *d. This switches over to + string2 if necessary. */ +#define PREFETCH() \ + while (d == dend) \ + { \ + /* End of string2 => fail. */ \ + if (dend == end_match_2) \ + goto fail; \ + /* End of string1 => advance to string2. */ \ + d = string2; \ + dend = end_match_2; \ + } + + +/* Test if at very beginning or at very end of the virtual concatenation + of `string1' and `string2'. If only one string, it's `string2'. */ +#define AT_STRINGS_BEG(d) ((d) == (size1 ? string1 : string2) || !size2) +#define AT_STRINGS_END(d) ((d) == end2) + + +/* Test if D points to a character which is word-constituent. We have + two special cases to check for: if past the end of string1, look at + the first character in string2; and if before the beginning of + string2, look at the last character in string1. */ +#define WORDCHAR_P(d) \ + (SYNTAX ((d) == end1 ? *string2 \ + : (d) == string2 - 1 ? *(end1 - 1) : *(d)) \ + == Sword) + +/* Disabled due to a compiler bug -- see comment at case wordbound */ +#if 0 +/* Test if the character before D and the one at D differ with respect + to being word-constituent. */ +#define AT_WORD_BOUNDARY(d) \ + (AT_STRINGS_BEG (d) || AT_STRINGS_END (d) \ + || WORDCHAR_P (d - 1) != WORDCHAR_P (d)) +#endif + +/* Free everything we malloc. */ +#ifdef MATCH_MAY_ALLOCATE +# define FREE_VAR(var) if (var) REGEX_FREE (var); var = NULL +# define FREE_VARIABLES() \ + { \ + REGEX_FREE_STACK (fail_stack.stack); \ + FREE_VAR (regstart); \ + FREE_VAR (regend); \ + FREE_VAR (old_regstart); \ + FREE_VAR (old_regend); \ + FREE_VAR (best_regstart); \ + FREE_VAR (best_regend); \ + FREE_VAR (reg_info); \ + FREE_VAR (reg_dummy); \ + FREE_VAR (reg_info_dummy); \ + } +#else +# define FREE_VARIABLES() ((void)0) /* Do nothing! But inhibit gcc warning. */ +#endif /* not MATCH_MAY_ALLOCATE */ + +/* These values must meet several constraints. They must not be valid + register values; since we have a limit of 255 registers (because + we use only one byte in the pattern for the register number), we can + use numbers larger than 255. They must differ by 1, because of + NUM_FAILURE_ITEMS above. And the value for the lowest register must + be larger than the value for the highest register, so we do not try + to actually save any registers when none are active. */ +#define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH) +#define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1) + +/* Matching routines. */ + +#ifndef emacs /* Emacs never uses this. */ +/* re_match is like re_match_2 except it takes only a single string. */ + +int +re_match (struct re_pattern_buffer *bufp, + const char *string, + int size, + int pos, + struct re_registers *regs) +{ + int result = re_match_2_internal (bufp, NULL, 0, string, size, + pos, regs, size); +# ifndef REGEX_MALLOC +# ifdef C_ALLOCA + alloca (0); +# endif +# endif + return result; +} +# ifdef _LIBC +weak_alias (__re_match, re_match) +# endif +#endif /* not emacs */ + +static bool group_match_null_string_p(unsigned char **p, unsigned char *end, + register_info_type *reg_info); +static bool alt_match_null_string_p(unsigned char *p, unsigned char *end, + register_info_type *reg_info); +static bool common_op_match_null_string_p(unsigned char **p, + unsigned char *end, register_info_type *reg_info); +static int bcmp_translate(const char *s1, const char *s2, + int len, char *translate); + +/* re_match_2 matches the compiled pattern in BUFP against the + the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1 + and SIZE2, respectively). We start matching at POS, and stop + matching at STOP. + + If REGS is non-null and the `no_sub' field of BUFP is nonzero, we + store offsets for the substring each group matched in REGS. See the + documentation for exactly how many groups we fill. + + We return -1 if no match, -2 if an internal error (such as the + failure stack overflowing). Otherwise, we return the length of the + matched substring. */ + +int +re_match_2 (struct re_pattern_buffer *bufp, + const char *string1, + int size1, + const char *string2, + int size2, + int pos, + struct re_registers *regs, + int stop) +{ + int result = re_match_2_internal (bufp, string1, size1, string2, size2, + pos, regs, stop); +#ifndef REGEX_MALLOC +# ifdef C_ALLOCA + alloca (0); +# endif +#endif + return result; +} +#ifdef _LIBC +weak_alias (__re_match_2, re_match_2) +#endif + +/* This is a separate function so that we can force an alloca cleanup + afterwards. */ +static int +re_match_2_internal (struct re_pattern_buffer *bufp, + const char *string1, + int size1, + const char *string2, + int size2, + int pos, + struct re_registers *regs, + int stop) +{ + /* General temporaries. */ + int mcnt; + unsigned char *p1; + + /* Just past the end of the corresponding string. */ + const char *end1, *end2; + + /* Pointers into string1 and string2, just past the last characters in + each to consider matching. */ + const char *end_match_1, *end_match_2; + + /* Where we are in the data, and the end of the current string. */ + const char *d, *dend; + + /* Where we are in the pattern, and the end of the pattern. */ + unsigned char *p = bufp->buffer; + register unsigned char *pend = p + bufp->used; + + /* Mark the opcode just after a start_memory, so we can test for an + empty subpattern when we get to the stop_memory. */ + unsigned char *just_past_start_mem = 0; + + /* We use this to map every character in the string. */ + RE_TRANSLATE_TYPE translate = bufp->translate; + + /* Failure point stack. Each place that can handle a failure further + down the line pushes a failure point on this stack. It consists of + restart, regend, and reg_info for all registers corresponding to + the subexpressions we're currently inside, plus the number of such + registers, and, finally, two char *'s. The first char * is where + to resume scanning the pattern; the second one is where to resume + scanning the strings. If the latter is zero, the failure point is + a ``dummy''; if a failure happens and the failure point is a dummy, + it gets discarded and the next next one is tried. */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, this is global. */ + fail_stack_type fail_stack; +#endif + +#ifdef REL_ALLOC + /* This holds the pointer to the failure stack, when + it is allocated relocatably. */ + fail_stack_elt_t *failure_stack_ptr; +#endif + + /* We fill all the registers internally, independent of what we + return, for use in backreferences. The number here includes + an element for register zero. */ + size_t num_regs = bufp->re_nsub + 1; + + /* The currently active registers. */ + active_reg_t lowest_active_reg = NO_LOWEST_ACTIVE_REG; + active_reg_t highest_active_reg = NO_HIGHEST_ACTIVE_REG; + + /* Information on the contents of registers. These are pointers into + the input strings; they record just what was matched (on this + attempt) by a subexpression part of the pattern, that is, the + regnum-th regstart pointer points to where in the pattern we began + matching and the regnum-th regend points to right after where we + stopped matching the regnum-th subexpression. (The zeroth register + keeps track of what the whole pattern matches.) */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ + const char **regstart, **regend; +#endif + + /* If a group that's operated upon by a repetition operator fails to + match anything, then the register for its start will need to be + restored because it will have been set to wherever in the string we + are when we last see its open-group operator. Similarly for a + register's end. */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ + const char **old_regstart, **old_regend; +#endif + + /* The is_active field of reg_info helps us keep track of which (possibly + nested) subexpressions we are currently in. The matched_something + field of reg_info[reg_num] helps us tell whether or not we have + matched any of the pattern so far this time through the reg_num-th + subexpression. These two fields get reset each time through any + loop their register is in. */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, this is global. */ + register_info_type *reg_info; +#endif + + /* The following record the register info as found in the above + variables when we find a match better than any we've seen before. + This happens as we backtrack through the failure points, which in + turn happens only if we have not yet matched the entire string. */ + unsigned best_regs_set = false; +#ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ + const char **best_regstart, **best_regend; +#endif + + /* Logically, this is `best_regend[0]'. But we don't want to have to + allocate space for that if we're not allocating space for anything + else (see below). Also, we never need info about register 0 for + any of the other register vectors, and it seems rather a kludge to + treat `best_regend' differently than the rest. So we keep track of + the end of the best match so far in a separate variable. We + initialize this to NULL so that when we backtrack the first time + and need to test it, it's not garbage. */ + const char *match_end = NULL; + + /* This helps SET_REGS_MATCHED avoid doing redundant work. */ + int set_regs_matched_done = 0; + + /* Used when we pop values we don't care about. */ +#ifdef MATCH_MAY_ALLOCATE /* otherwise, these are global. */ + const char **reg_dummy; + register_info_type *reg_info_dummy; +#endif + + INIT_FAIL_STACK (); + +#ifdef MATCH_MAY_ALLOCATE + /* Do not bother to initialize all the register variables if there are + no groups in the pattern, as it takes a fair amount of time. If + there are groups, we include space for register 0 (the whole + pattern), even though we never use it, since it simplifies the + array indexing. We should fix this. */ + if (bufp->re_nsub) + { + regstart = REGEX_TALLOC (num_regs, const char *); + regend = REGEX_TALLOC (num_regs, const char *); + old_regstart = REGEX_TALLOC (num_regs, const char *); + old_regend = REGEX_TALLOC (num_regs, const char *); + best_regstart = REGEX_TALLOC (num_regs, const char *); + best_regend = REGEX_TALLOC (num_regs, const char *); + reg_info = REGEX_TALLOC (num_regs, register_info_type); + reg_dummy = REGEX_TALLOC (num_regs, const char *); + reg_info_dummy = REGEX_TALLOC (num_regs, register_info_type); + + if (!(regstart && regend && old_regstart && old_regend && reg_info + && best_regstart && best_regend && reg_dummy && reg_info_dummy)) + { + FREE_VARIABLES (); + return -2; + } + } + else + { + /* We must initialize all our variables to NULL, so that + `FREE_VARIABLES' doesn't try to free them. */ + regstart = regend = old_regstart = old_regend = best_regstart + = best_regend = reg_dummy = NULL; + reg_info = reg_info_dummy = (register_info_type *) NULL; + } +#endif /* MATCH_MAY_ALLOCATE */ + + /* The starting position is bogus. */ + if (pos < 0 || pos > size1 + size2) + { + FREE_VARIABLES (); + return -1; + } + + /* Initialize subexpression text positions to -1 to mark ones that no + start_memory/stop_memory has been seen for. Also initialize the + register information struct. */ + for (mcnt = 1; (unsigned) mcnt < num_regs; mcnt++) + { + regstart[mcnt] = regend[mcnt] + = old_regstart[mcnt] = old_regend[mcnt] = REG_UNSET_VALUE; + + REG_MATCH_NULL_STRING_P (reg_info[mcnt]) = MATCH_NULL_UNSET_VALUE; + IS_ACTIVE (reg_info[mcnt]) = 0; + MATCHED_SOMETHING (reg_info[mcnt]) = 0; + EVER_MATCHED_SOMETHING (reg_info[mcnt]) = 0; + } + + /* We move `string1' into `string2' if the latter's empty -- but not if + `string1' is null. */ + if (size2 == 0 && string1 != NULL) + { + string2 = string1; + size2 = size1; + string1 = 0; + size1 = 0; + } + end1 = string1 + size1; + end2 = string2 + size2; + + /* Compute where to stop matching, within the two strings. */ + if (stop <= size1) + { + end_match_1 = string1 + stop; + end_match_2 = string2; + } + else + { + end_match_1 = end1; + end_match_2 = string2 + stop - size1; + } + + /* `p' scans through the pattern as `d' scans through the data. + `dend' is the end of the input string that `d' points within. `d' + is advanced into the following input string whenever necessary, but + this happens before fetching; therefore, at the beginning of the + loop, `d' can be pointing at the end of a string, but it cannot + equal `string2'. */ + if (size1 > 0 && pos <= size1) + { + d = string1 + pos; + dend = end_match_1; + } + else + { + d = string2 + pos - size1; + dend = end_match_2; + } + + /* This loops over pattern commands. It exits by returning from the + function if the match is complete, or it drops through if the match + fails at this starting point in the input data. */ + for (;;) + { + if (p == pend) + { /* End of pattern means we might have succeeded. */ + + /* If we haven't matched the entire string, and we want the + longest match, try backtracking. */ + if (d != end_match_2) + { + /* 1 if this match ends in the same string (string1 or string2) + as the best previous match. */ + bool same_str_p = (FIRST_STRING_P (match_end) + == MATCHING_IN_FIRST_STRING); + /* 1 if this match is the best seen so far. */ + bool best_match_p; + + /* AIX compiler got confused when this was combined + with the previous declaration. */ + if (same_str_p) + best_match_p = d > match_end; + else + best_match_p = !MATCHING_IN_FIRST_STRING; + + if (!FAIL_STACK_EMPTY ()) + { /* More failure points to try. */ + + /* If exceeds best match so far, save it. */ + if (!best_regs_set || best_match_p) + { + best_regs_set = true; + match_end = d; + + for (mcnt = 1; (unsigned) mcnt < num_regs; mcnt++) + { + best_regstart[mcnt] = regstart[mcnt]; + best_regend[mcnt] = regend[mcnt]; + } + } + goto fail; + } + + /* If no failure points, don't restore garbage. And if + last match is real best match, don't restore second + best one. */ + else if (best_regs_set && !best_match_p) + { + restore_best_regs: + /* Restore best match. It may happen that `dend == + end_match_1' while the restored d is in string2. + For example, the pattern `x.*y.*z' against the + strings `x-' and `y-z-', if the two strings are + not consecutive in memory. */ + d = match_end; + dend = ((d >= string1 && d <= end1) + ? end_match_1 : end_match_2); + + for (mcnt = 1; (unsigned) mcnt < num_regs; mcnt++) + { + regstart[mcnt] = best_regstart[mcnt]; + regend[mcnt] = best_regend[mcnt]; + } + } + } /* d != end_match_2 */ + + succeed_label: + /* If caller wants register contents data back, do it. */ + if (regs && !bufp->no_sub) + { + /* Have the register data arrays been allocated? */ + if (bufp->regs_allocated == REGS_UNALLOCATED) + { /* No. So allocate them with malloc. We need one + extra element beyond `num_regs' for the `-1' marker + GNU code uses. */ + regs->num_regs = MAX (RE_NREGS, num_regs + 1); + regs->start = TALLOC (regs->num_regs, regoff_t); + regs->end = TALLOC (regs->num_regs, regoff_t); + if (regs->start == NULL || regs->end == NULL) + { + FREE_VARIABLES (); + return -2; + } + bufp->regs_allocated = REGS_REALLOCATE; + } + else if (bufp->regs_allocated == REGS_REALLOCATE) + { /* Yes. If we need more elements than were already + allocated, reallocate them. If we need fewer, just + leave it alone. */ + if (regs->num_regs < num_regs + 1) + { + regs->num_regs = num_regs + 1; + RETALLOC (regs->start, regs->num_regs, regoff_t); + RETALLOC (regs->end, regs->num_regs, regoff_t); + if (regs->start == NULL || regs->end == NULL) + { + FREE_VARIABLES (); + return -2; + } + } + } + + /* Convert the pointer data in `regstart' and `regend' to + indices. Register zero has to be set differently, + since we haven't kept track of any info for it. */ + if (regs->num_regs > 0) + { + regs->start[0] = pos; + regs->end[0] = (MATCHING_IN_FIRST_STRING + ? ((regoff_t) (d - string1)) + : ((regoff_t) (d - string2 + size1))); + } + + /* Go through the first `min (num_regs, regs->num_regs)' + registers, since that is all we initialized. */ + for (mcnt = 1; (unsigned) mcnt < MIN (num_regs, regs->num_regs); + mcnt++) + { + if (REG_UNSET (regstart[mcnt]) || REG_UNSET (regend[mcnt])) + regs->start[mcnt] = regs->end[mcnt] = -1; + else + { + regs->start[mcnt] + = (regoff_t) POINTER_TO_OFFSET (regstart[mcnt]); + regs->end[mcnt] + = (regoff_t) POINTER_TO_OFFSET (regend[mcnt]); + } + } + + /* If the regs structure we return has more elements than + were in the pattern, set the extra elements to -1. If + we (re)allocated the registers, this is the case, + because we always allocate enough to have at least one + -1 at the end. */ + for (mcnt = num_regs; (unsigned) mcnt < regs->num_regs; mcnt++) + regs->start[mcnt] = regs->end[mcnt] = -1; + } /* regs && !bufp->no_sub */ + + mcnt = d - pos - (MATCHING_IN_FIRST_STRING + ? string1 + : string2 - size1); + + FREE_VARIABLES (); + return mcnt; + } + + /* Otherwise match next pattern command. */ + switch (SWITCH_ENUM_CAST ((re_opcode_t) *p++)) + { + /* Ignore these. Used to ignore the n of succeed_n's which + currently have n == 0. */ + case no_op: + break; + + case succeed: + goto succeed_label; + + /* Match the next n pattern characters exactly. The following + byte in the pattern defines n, and the n bytes after that + are the characters to match. */ + case exactn: + mcnt = *p++; + /* This is written out as an if-else so we don't waste time + testing `translate' inside the loop. */ + if (translate) + { + do + { + PREFETCH (); + if ((unsigned char) translate[(unsigned char) *d++] + != (unsigned char) *p++) + goto fail; + } + while (--mcnt); + } + else + { + do + { + PREFETCH (); + if (*d++ != (char) *p++) goto fail; + } + while (--mcnt); + } + SET_REGS_MATCHED (); + break; + + + /* Match any character except possibly a newline or a null. */ + case anychar: + PREFETCH (); + + if ((!(bufp->syntax & RE_DOT_NEWLINE) && TRANSLATE (*d) == '\n') + || (bufp->syntax & RE_DOT_NOT_NULL && TRANSLATE (*d) == '\000')) + goto fail; + + SET_REGS_MATCHED (); + d++; + break; + + + case charset: + case charset_not: + { + register unsigned char c; + bool not_bool = (re_opcode_t) *(p - 1) == charset_not; + PREFETCH (); + c = TRANSLATE (*d); /* The character to match. */ + + /* Cast to `unsigned' instead of `unsigned char' in case the + bit list is a full 32 bytes long. */ + if (c < (unsigned) (*p * BYTEWIDTH) + && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not_bool = !not_bool; + + p += 1 + *p; + + if (!not_bool) goto fail; + + SET_REGS_MATCHED (); + d++; + break; + } + + + /* The beginning of a group is represented by start_memory. + The arguments are the register number in the next byte, and the + number of groups inner to this one in the next. The text + matched within the group is recorded (in the internal + registers data structure) under the register number. */ + case start_memory: + /* Find out if this group can match the empty string. */ + p1 = p; /* To send to group_match_null_string_p. */ + + if (REG_MATCH_NULL_STRING_P (reg_info[*p]) == MATCH_NULL_UNSET_VALUE) + REG_MATCH_NULL_STRING_P (reg_info[*p]) + = group_match_null_string_p (&p1, pend, reg_info); + + /* Save the position in the string where we were the last time + we were at this open-group operator in case the group is + operated upon by a repetition operator, e.g., with `(a*)*b' + against `ab'; then we want to ignore where we are now in + the string in case this attempt to match fails. */ + old_regstart[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) + ? REG_UNSET (regstart[*p]) ? d : regstart[*p] + : regstart[*p]; + regstart[*p] = d; + IS_ACTIVE (reg_info[*p]) = 1; + MATCHED_SOMETHING (reg_info[*p]) = 0; + + /* Clear this whenever we change the register activity status. */ + set_regs_matched_done = 0; + + /* This is the new highest active register. */ + highest_active_reg = *p; + + /* If nothing was active before, this is the new lowest active + register. */ + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) + lowest_active_reg = *p; + + /* Move past the register number and inner group count. */ + p += 2; + just_past_start_mem = p; + + break; + + + /* The stop_memory opcode represents the end of a group. Its + arguments are the same as start_memory's: the register + number, and the number of inner groups. */ + case stop_memory: + /* We need to save the string position the last time we were at + this close-group operator in case the group is operated + upon by a repetition operator, e.g., with `((a*)*(b*)*)*' + against `aba'; then we want to ignore where we are now in + the string in case this attempt to match fails. */ + old_regend[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p]) + ? REG_UNSET (regend[*p]) ? d : regend[*p] + : regend[*p]; + + regend[*p] = d; + + /* This register isn't active anymore. */ + IS_ACTIVE (reg_info[*p]) = 0; + + /* Clear this whenever we change the register activity status. */ + set_regs_matched_done = 0; + + /* If this was the only register active, nothing is active + anymore. */ + if (lowest_active_reg == highest_active_reg) + { + lowest_active_reg = NO_LOWEST_ACTIVE_REG; + highest_active_reg = NO_HIGHEST_ACTIVE_REG; + } + else + { /* We must scan for the new highest active register, since + it isn't necessarily one less than now: consider + (a(b)c(d(e)f)g). When group 3 ends, after the f), the + new highest active register is 1. */ + unsigned char r = *p - 1; + while (r > 0 && !IS_ACTIVE (reg_info[r])) + r--; + + /* If we end up at register zero, that means that we saved + the registers as the result of an `on_failure_jump', not + a `start_memory', and we jumped to past the innermost + `stop_memory'. For example, in ((.)*) we save + registers 1 and 2 as a result of the *, but when we pop + back to the second ), we are at the stop_memory 1. + Thus, nothing is active. */ + if (r == 0) + { + lowest_active_reg = NO_LOWEST_ACTIVE_REG; + highest_active_reg = NO_HIGHEST_ACTIVE_REG; + } + else + highest_active_reg = r; + } + + /* If just failed to match something this time around with a + group that's operated on by a repetition operator, try to + force exit from the ``loop'', and restore the register + information for this group that we had before trying this + last match. */ + if ((!MATCHED_SOMETHING (reg_info[*p]) + || just_past_start_mem == p - 1) + && (p + 2) < pend) + { + bool is_a_jump_n = false; + + p1 = p + 2; + mcnt = 0; + switch ((re_opcode_t) *p1++) + { + case jump_n: + is_a_jump_n = true; + case pop_failure_jump: + case maybe_pop_jump: + case jump: + case dummy_failure_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if (is_a_jump_n) + p1 += 2; + break; + + default: + /* do nothing */ ; + } + p1 += mcnt; + + /* If the next operation is a jump backwards in the pattern + to an on_failure_jump right before the start_memory + corresponding to this stop_memory, exit from the loop + by forcing a failure after pushing on the stack the + on_failure_jump's jump in the pattern, and d. */ + if (mcnt < 0 && (re_opcode_t) *p1 == on_failure_jump + && (re_opcode_t) p1[3] == start_memory && p1[4] == *p) + { + /* If this group ever matched anything, then restore + what its registers were before trying this last + failed match, e.g., with `(a*)*b' against `ab' for + regstart[1], and, e.g., with `((a*)*(b*)*)*' + against `aba' for regend[3]. + + Also restore the registers for inner groups for, + e.g., `((a*)(b*))*' against `aba' (register 3 would + otherwise get trashed). */ + + if (EVER_MATCHED_SOMETHING (reg_info[*p])) + { + unsigned r; + + EVER_MATCHED_SOMETHING (reg_info[*p]) = 0; + + /* Restore this and inner groups' (if any) registers. */ + for (r = *p; r < (unsigned) *p + (unsigned) *(p + 1); + r++) + { + regstart[r] = old_regstart[r]; + + /* xx why this test? */ + if (old_regend[r] >= regstart[r]) + regend[r] = old_regend[r]; + } + } + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + PUSH_FAILURE_POINT (p1 + mcnt, d, -2); + + goto fail; + } + } + + /* Move past the register number and the inner group count. */ + p += 2; + break; + + + /* \ has been turned into a `duplicate' command which is + followed by the numeric value of as the register number. */ + case duplicate: + { + register const char *d2, *dend2; + int regno = *p++; /* Get which register to match against. */ + + /* Can't back reference a group which we've never matched. */ + if (REG_UNSET (regstart[regno]) || REG_UNSET (regend[regno])) + goto fail; + + /* Where in input to try to start matching. */ + d2 = regstart[regno]; + + /* Where to stop matching; if both the place to start and + the place to stop matching are in the same string, then + set to the place to stop, otherwise, for now have to use + the end of the first string. */ + + dend2 = ((FIRST_STRING_P (regstart[regno]) + == FIRST_STRING_P (regend[regno])) + ? regend[regno] : end_match_1); + for (;;) + { + /* If necessary, advance to next segment in register + contents. */ + while (d2 == dend2) + { + if (dend2 == end_match_2) break; + if (dend2 == regend[regno]) break; + + /* End of string1 => advance to string2. */ + d2 = string2; + dend2 = regend[regno]; + } + /* At end of register contents => success */ + if (d2 == dend2) break; + + /* If necessary, advance to next segment in data. */ + PREFETCH (); + + /* How many characters left in this segment to match. */ + mcnt = dend - d; + + /* Want how many consecutive characters we can match in + one shot, so, if necessary, adjust the count. */ + if (mcnt > dend2 - d2) + mcnt = dend2 - d2; + + /* Compare that many; failure if mismatch, else move + past them. */ + if (translate + ? bcmp_translate (d, d2, mcnt, translate) + : memcmp (d, d2, mcnt)) + goto fail; + d += mcnt, d2 += mcnt; + + /* Do this because we've match some characters. */ + SET_REGS_MATCHED (); + } + } + break; + + + /* begline matches the empty string at the beginning of the string + (unless `not_bol' is set in `bufp'), and, if + `newline_anchor' is set, after newlines. */ + case begline: + if (AT_STRINGS_BEG (d)) + { + if (!bufp->not_bol) break; + } + else if (d[-1] == '\n' && bufp->newline_anchor) + { + break; + } + /* In all other cases, we fail. */ + goto fail; + + + /* endline is the dual of begline. */ + case endline: + if (AT_STRINGS_END (d)) + { + if (!bufp->not_eol) break; + } + + /* We have to ``prefetch'' the next character. */ + else if ((d == end1 ? *string2 : *d) == '\n' + && bufp->newline_anchor) + { + break; + } + goto fail; + + + /* Match at the very beginning of the data. */ + case begbuf: + if (AT_STRINGS_BEG (d)) + break; + goto fail; + + + /* Match at the very end of the data. */ + case endbuf: + if (AT_STRINGS_END (d)) + break; + goto fail; + + + /* on_failure_keep_string_jump is used to optimize `.*\n'. It + pushes NULL as the value for the string on the stack. Then + `pop_failure_point' will keep the current value for the + string, instead of restoring it. To see why, consider + matching `foo\nbar' against `.*\n'. The .* matches the foo; + then the . fails against the \n. But the next thing we want + to do is match the \n against the \n; if we restored the + string value, we would be back at the foo. + + Because this is used only in specific cases, we don't need to + check all the things that `on_failure_jump' does, to make + sure the right things get saved on the stack. Hence we don't + share its code. The only reason to push anything on the + stack at all is that otherwise we would have to change + `anychar's code to do something besides goto fail in this + case; that seems worse than this. */ + case on_failure_keep_string_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p); + PUSH_FAILURE_POINT (p + mcnt, NULL, -2); + break; + + + /* Uses of on_failure_jump: + + Each alternative starts with an on_failure_jump that points + to the beginning of the next alternative. Each alternative + except the last ends with a jump that in effect jumps past + the rest of the alternatives. (They really jump to the + ending jump of the following alternative, because tensioning + these jumps is a hassle.) + + Repeats start with an on_failure_jump that points past both + the repetition text and either the following jump or + pop_failure_jump back to this on_failure_jump. */ + case on_failure_jump: + on_failure: + EXTRACT_NUMBER_AND_INCR (mcnt, p); + + /* If this on_failure_jump comes right before a group (i.e., + the original * applied to a group), save the information + for that group and all inner ones, so that if we fail back + to this point, the group's information will be correct. + For example, in \(a*\)*\1, we need the preceding group, + and in \(zz\(a*\)b*\)\2, we need the inner group. */ + + /* We can't use `p' to check ahead because we push + a failure point to `p + mcnt' after we do this. */ + p1 = p; + + /* We need to skip no_op's before we look for the + start_memory in case this on_failure_jump is happening as + the result of a completed succeed_n, as in \(a\)\{1,3\}b\1 + against aba. */ + while (p1 < pend && (re_opcode_t) *p1 == no_op) + p1++; + + if (p1 < pend && (re_opcode_t) *p1 == start_memory) + { + /* We have a new highest active register now. This will + get reset at the start_memory we are about to get to, + but we will have saved all the registers relevant to + this repetition op, as described above. */ + highest_active_reg = *(p1 + 1) + *(p1 + 2); + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG) + lowest_active_reg = *(p1 + 1); + } + + PUSH_FAILURE_POINT (p + mcnt, d, -2); + break; + + + /* A smart repeat ends with `maybe_pop_jump'. + We change it to either `pop_failure_jump' or `jump'. */ + case maybe_pop_jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p); + { + register unsigned char *p2 = p; + + /* Compare the beginning of the repeat with what in the + pattern follows its end. If we can establish that there + is nothing that they would both match, i.e., that we + would have to backtrack because of (as in, e.g., `a*a') + then we can change to pop_failure_jump, because we'll + never have to backtrack. + + This is not true in the case of alternatives: in + `(a|ab)*' we do need to backtrack to the `ab' alternative + (e.g., if the string was `ab'). But instead of trying to + detect that here, the alternative has put on a dummy + failure point which is what we will end up popping. */ + + /* Skip over open/close-group commands. + If what follows this loop is a ...+ construct, + look at what begins its body, since we will have to + match at least one of that. */ + while (true) + { + if (p2 + 2 < pend + && ((re_opcode_t) *p2 == stop_memory + || (re_opcode_t) *p2 == start_memory)) + p2 += 3; + else if (p2 + 6 < pend + && (re_opcode_t) *p2 == dummy_failure_jump) + p2 += 6; + else + break; + } + + p1 = p + mcnt; + /* p1[0] ... p1[2] are the `on_failure_jump' corresponding + to the `maybe_finalize_jump' of this case. Examine what + follows. */ + + /* If we're at the end of the pattern, we can change. */ + if (p2 == pend) + { + /* Consider what happens when matching ":\(.*\)" + against ":/". I don't really understand this code + yet. */ + p[-3] = (unsigned char) pop_failure_jump; + } + + else if ((re_opcode_t) *p2 == exactn + || (bufp->newline_anchor && (re_opcode_t) *p2 == endline)) + { + register unsigned char c + = *p2 == (unsigned char) endline ? '\n' : p2[2]; + + if ((re_opcode_t) p1[3] == exactn && p1[5] != c) + { + p[-3] = (unsigned char) pop_failure_jump; + } + + else if ((re_opcode_t) p1[3] == charset + || (re_opcode_t) p1[3] == charset_not) + { + int not_int = (re_opcode_t) p1[3] == charset_not; + + if (c < (unsigned char) (p1[4] * BYTEWIDTH) + && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH))) + not_int = !not_int; + + /* `not' is equal to 1 if c would match, which means + that we can't change to pop_failure_jump. */ + if (!not_int) + { + p[-3] = (unsigned char) pop_failure_jump; + } + } + } + else if ((re_opcode_t) *p2 == charset) + { + if ((re_opcode_t) p1[3] == exactn + && ! ((int) p2[1] * BYTEWIDTH > (int) p1[4] + && (p2[2 + p1[4] / BYTEWIDTH] + & (1 << (p1[4] % BYTEWIDTH))))) + { + p[-3] = (unsigned char) pop_failure_jump; + } + + else if ((re_opcode_t) p1[3] == charset_not) + { + int idx; + /* We win if the charset_not inside the loop + lists every character listed in the charset after. */ + for (idx = 0; idx < (int) p2[1]; idx++) + if (! (p2[2 + idx] == 0 + || (idx < (int) p1[4] + && ((p2[2 + idx] & ~ p1[5 + idx]) == 0)))) + break; + + if (idx == p2[1]) + { + p[-3] = (unsigned char) pop_failure_jump; + } + } + else if ((re_opcode_t) p1[3] == charset) + { + int idx; + /* We win if the charset inside the loop + has no overlap with the one after the loop. */ + for (idx = 0; + idx < (int) p2[1] && idx < (int) p1[4]; + idx++) + if ((p2[2 + idx] & p1[5 + idx]) != 0) + break; + + if (idx == p2[1] || idx == p1[4]) + { + p[-3] = (unsigned char) pop_failure_jump; + } + } + } + } + p -= 2; /* Point at relative address again. */ + if ((re_opcode_t) p[-1] != pop_failure_jump) + { + p[-1] = (unsigned char) jump; + goto unconditional_jump; + } + /* Note fall through. */ + + + /* The end of a simple repeat has a pop_failure_jump back to + its matching on_failure_jump, where the latter will push a + failure point. The pop_failure_jump takes off failure + points put on by this pop_failure_jump's matching + on_failure_jump; we got through the pattern to here from the + matching on_failure_jump, so didn't fail. */ + case pop_failure_jump: + { + /* We need to pass separate storage for the lowest and + highest registers, even though we don't care about the + actual values. Otherwise, we will restore only one + register from the stack, since lowest will == highest in + `pop_failure_point'. */ + active_reg_t dummy_low_reg, dummy_high_reg; + unsigned char *pdummy; + const char *sdummy; + + POP_FAILURE_POINT (sdummy, pdummy, + dummy_low_reg, dummy_high_reg, + reg_dummy, reg_dummy, reg_info_dummy); + } + /* Note fall through. */ + + unconditional_jump: + /* Note fall through. */ + + /* Unconditionally jump (without popping any failure points). */ + case jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p); /* Get the amount to jump. */ + p += mcnt; /* Do the jump. */ + break; + + + /* We need this opcode so we can detect where alternatives end + in `group_match_null_string_p' et al. */ + case jump_past_alt: + goto unconditional_jump; + + + /* Normally, the on_failure_jump pushes a failure point, which + then gets popped at pop_failure_jump. We will end up at + pop_failure_jump, also, and with a pattern of, say, `a+', we + are skipping over the on_failure_jump, so we have to push + something meaningless for pop_failure_jump to pop. */ + case dummy_failure_jump: + /* It doesn't matter what we push for the string here. What + the code at `fail' tests is the value for the pattern. */ + PUSH_FAILURE_POINT (NULL, NULL, -2); + goto unconditional_jump; + + + /* At the end of an alternative, we need to push a dummy failure + point in case we are followed by a `pop_failure_jump', because + we don't want the failure point for the alternative to be + popped. For example, matching `(a|ab)*' against `aab' + requires that we match the `ab' alternative. */ + case push_dummy_failure: + /* See comments just above at `dummy_failure_jump' about the + two zeroes. */ + PUSH_FAILURE_POINT (NULL, NULL, -2); + break; + + /* Have to succeed matching what follows at least n times. + After that, handle like `on_failure_jump'. */ + case succeed_n: + EXTRACT_NUMBER (mcnt, p + 2); + + /* Originally, this is how many times we HAVE to succeed. */ + if (mcnt > 0) + { + mcnt--; + p += 2; + STORE_NUMBER_AND_INCR (p, mcnt); + } + else if (mcnt == 0) + { + p[2] = (unsigned char) no_op; + p[3] = (unsigned char) no_op; + goto on_failure; + } + break; + + case jump_n: + EXTRACT_NUMBER (mcnt, p + 2); + + /* Originally, this is how many times we CAN jump. */ + if (mcnt) + { + mcnt--; + STORE_NUMBER (p + 2, mcnt); + goto unconditional_jump; + } + /* If don't have to jump any more, skip over the rest of command. */ + else + p += 4; + break; + + case set_number_at: + { + EXTRACT_NUMBER_AND_INCR (mcnt, p); + p1 = p + mcnt; + EXTRACT_NUMBER_AND_INCR (mcnt, p); + STORE_NUMBER (p1, mcnt); + break; + } + + case wordbound: + { + bool prevchar, thischar; + + if (AT_STRINGS_BEG (d) || AT_STRINGS_END (d)) + break; + + prevchar = WORDCHAR_P (d - 1); + thischar = WORDCHAR_P (d); + if (prevchar != thischar) + break; + goto fail; + } + + case notwordbound: + { + bool prevchar, thischar; + + if (AT_STRINGS_BEG (d) || AT_STRINGS_END (d)) + goto fail; + + prevchar = WORDCHAR_P (d - 1); + thischar = WORDCHAR_P (d); + if (prevchar != thischar) + goto fail; + break; + } + + case wordbeg: + if (WORDCHAR_P (d) && (AT_STRINGS_BEG (d) || !WORDCHAR_P (d - 1))) + break; + goto fail; + + case wordend: + if (!AT_STRINGS_BEG (d) && WORDCHAR_P (d - 1) + && (!WORDCHAR_P (d) || AT_STRINGS_END (d))) + break; + goto fail; + +#ifdef emacs + case before_dot: + if (PTR_CHAR_POS ((unsigned char *) d) >= point) + goto fail; + break; + + case at_dot: + if (PTR_CHAR_POS ((unsigned char *) d) != point) + goto fail; + break; + + case after_dot: + if (PTR_CHAR_POS ((unsigned char *) d) <= point) + goto fail; + break; + + case syntaxspec: + mcnt = *p++; + goto matchsyntax; + + case wordchar: + mcnt = (int) Sword; + matchsyntax: + PREFETCH (); + /* Can't use *d++ here; SYNTAX may be an unsafe macro. */ + d++; + if (SYNTAX (d[-1]) != (enum syntaxcode) mcnt) + goto fail; + SET_REGS_MATCHED (); + break; + + case notsyntaxspec: + mcnt = *p++; + goto matchnotsyntax; + + case notwordchar: + mcnt = (int) Sword; + matchnotsyntax: + PREFETCH (); + /* Can't use *d++ here; SYNTAX may be an unsafe macro. */ + d++; + if (SYNTAX (d[-1]) == (enum syntaxcode) mcnt) + goto fail; + SET_REGS_MATCHED (); + break; + +#else /* not emacs */ + case wordchar: + PREFETCH (); + if (!WORDCHAR_P (d)) + goto fail; + SET_REGS_MATCHED (); + d++; + break; + + case notwordchar: + PREFETCH (); + if (WORDCHAR_P (d)) + goto fail; + SET_REGS_MATCHED (); + d++; + break; +#endif /* not emacs */ + + default: + abort (); + } + continue; /* Successfully executed one pattern command; keep going. */ + + + /* We goto here if a matching operation fails. */ + fail: + if (!FAIL_STACK_EMPTY ()) + { /* A restart point is known. Restore to that state. */ + POP_FAILURE_POINT (d, p, + lowest_active_reg, highest_active_reg, + regstart, regend, reg_info); + + /* If this failure point is a dummy, try the next one. */ + if (!p) + goto fail; + + /* If we failed to the end of the pattern, don't examine *p. */ + if (p < pend) + { + bool is_a_jump_n = false; + + /* If failed to a backwards jump that's part of a repetition + loop, need to pop this failure point and use the next one. */ + switch ((re_opcode_t) *p) + { + case jump_n: + is_a_jump_n = true; + case maybe_pop_jump: + case pop_failure_jump: + case jump: + p1 = p + 1; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + + if ((is_a_jump_n && (re_opcode_t) *p1 == succeed_n) + || (!is_a_jump_n + && (re_opcode_t) *p1 == on_failure_jump)) + goto fail; + break; + default: + /* do nothing */ ; + } + } + + if (d >= string1 && d <= end1) + dend = end_match_1; + } + else + break; /* Matching at this starting point really fails. */ + } /* for (;;) */ + + if (best_regs_set) + goto restore_best_regs; + + FREE_VARIABLES (); + + return -1; /* Failure to match. */ +} /* re_match_2 */ + +/* Subroutine definitions for re_match_2. */ + + +/* We are passed P pointing to a register number after a start_memory. + + Return true if the pattern up to the corresponding stop_memory can + match the empty string, and false otherwise. + + If we find the matching stop_memory, sets P to point to one past its number. + Otherwise, sets P to an undefined byte less than or equal to END. + + We don't handle duplicates properly (yet). */ + +static bool +group_match_null_string_p (unsigned char **p, + unsigned char *end, + register_info_type *reg_info) +{ + int mcnt; + /* Point to after the args to the start_memory. */ + unsigned char *p1 = *p + 2; + + while (p1 < end) + { + /* Skip over opcodes that can match nothing, and return true or + false, as appropriate, when we get to one that can't, or to the + matching stop_memory. */ + + switch ((re_opcode_t) *p1) + { + /* Could be either a loop or a series of alternatives. */ + case on_failure_jump: + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + + /* If the next operation is not a jump backwards in the + pattern. */ + + if (mcnt >= 0) + { + /* Go through the on_failure_jumps of the alternatives, + seeing if any of the alternatives cannot match nothing. + The last alternative starts with only a jump, + whereas the rest start with on_failure_jump and end + with a jump, e.g., here is the pattern for `a|b|c': + + /on_failure_jump/0/6/exactn/1/a/jump_past_alt/0/6 + /on_failure_jump/0/6/exactn/1/b/jump_past_alt/0/3 + /exactn/1/c + + So, we have to first go through the first (n-1) + alternatives and then deal with the last one separately. */ + + + /* Deal with the first (n-1) alternatives, which start + with an on_failure_jump (see above) that jumps to right + past a jump_past_alt. */ + + while ((re_opcode_t) p1[mcnt-3] == jump_past_alt) + { + /* `mcnt' holds how many bytes long the alternative + is, including the ending `jump_past_alt' and + its number. */ + + if (!alt_match_null_string_p (p1, p1 + mcnt - 3, + reg_info)) + return false; + + /* Move to right after this alternative, including the + jump_past_alt. */ + p1 += mcnt; + + /* Break if it's the beginning of an n-th alternative + that doesn't begin with an on_failure_jump. */ + if ((re_opcode_t) *p1 != on_failure_jump) + break; + + /* Still have to check that it's not an n-th + alternative that starts with an on_failure_jump. */ + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if ((re_opcode_t) p1[mcnt-3] != jump_past_alt) + { + /* Get to the beginning of the n-th alternative. */ + p1 -= 3; + break; + } + } + + /* Deal with the last alternative: go back and get number + of the `jump_past_alt' just before it. `mcnt' contains + the length of the alternative. */ + EXTRACT_NUMBER (mcnt, p1 - 2); + + if (!alt_match_null_string_p (p1, p1 + mcnt, reg_info)) + return false; + + p1 += mcnt; /* Get past the n-th alternative. */ + } /* if mcnt > 0 */ + break; + + + case stop_memory: + *p = p1 + 2; + return true; + + + default: + if (!common_op_match_null_string_p (&p1, end, reg_info)) + return false; + } + } /* while p1 < end */ + + return false; +} /* group_match_null_string_p */ + + +/* Similar to group_match_null_string_p, but doesn't deal with alternatives: + It expects P to be the first byte of a single alternative and END one + byte past the last. The alternative can contain groups. */ + +static bool +alt_match_null_string_p (unsigned char *p, + unsigned char *end, + register_info_type *reg_info) +{ + int mcnt; + unsigned char *p1 = p; + + while (p1 < end) + { + /* Skip over opcodes that can match nothing, and break when we get + to one that can't. */ + + switch ((re_opcode_t) *p1) + { + /* It's a loop. */ + case on_failure_jump: + p1++; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + break; + + default: + if (!common_op_match_null_string_p (&p1, end, reg_info)) + return false; + } + } /* while p1 < end */ + + return true; +} /* alt_match_null_string_p */ + + +/* Deals with the ops common to group_match_null_string_p and + alt_match_null_string_p. + + Sets P to one after the op and its arguments, if any. */ + +static bool +common_op_match_null_string_p (unsigned char **p, + unsigned char *end, + register_info_type *reg_info) +{ + int mcnt; + bool ret; + int reg_no; + unsigned char *p1 = *p; + + switch ((re_opcode_t) *p1++) + { + case no_op: + case begline: + case endline: + case begbuf: + case endbuf: + case wordbeg: + case wordend: + case wordbound: + case notwordbound: +#ifdef emacs + case before_dot: + case at_dot: + case after_dot: +#endif + break; + + case start_memory: + reg_no = *p1; + ret = group_match_null_string_p (&p1, end, reg_info); + + /* Have to set this here in case we're checking a group which + contains a group and a back reference to it. */ + + if (REG_MATCH_NULL_STRING_P (reg_info[reg_no]) == MATCH_NULL_UNSET_VALUE) + REG_MATCH_NULL_STRING_P (reg_info[reg_no]) = ret; + + if (!ret) + return false; + break; + + /* If this is an optimized succeed_n for zero times, make the jump. */ + case jump: + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + if (mcnt >= 0) + p1 += mcnt; + else + return false; + break; + + case succeed_n: + /* Get to the number of times to succeed. */ + p1 += 2; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + + if (mcnt == 0) + { + p1 -= 4; + EXTRACT_NUMBER_AND_INCR (mcnt, p1); + p1 += mcnt; + } + else + return false; + break; + + case duplicate: + if (!REG_MATCH_NULL_STRING_P (reg_info[*p1])) + return false; + break; + + case set_number_at: + p1 += 4; + + default: + /* All other opcodes mean we cannot match the empty string. */ + return false; + } + + *p = p1; + return true; +} /* common_op_match_null_string_p */ + + +/* Return zero if TRANSLATE[S1] and TRANSLATE[S2] are identical for LEN + bytes; nonzero otherwise. */ + +static int +bcmp_translate (const char *s1, + const char *s2, + register int len, + RE_TRANSLATE_TYPE translate) +{ + register const unsigned char *p1 = (const unsigned char *) s1; + register const unsigned char *p2 = (const unsigned char *) s2; + while (len) + { + if (translate[*p1++] != translate[*p2++]) return 1; + len--; + } + return 0; +} + +/* Entry points for GNU code. */ + +/* re_compile_pattern is the GNU regular expression compiler: it + compiles PATTERN (of length SIZE) and puts the result in BUFP. + Returns 0 if the pattern was valid, otherwise an error string. + + Assumes the `allocated' (and perhaps `buffer') and `translate' fields + are set in BUFP on entry. + + We call regex_compile to do the actual compilation. */ + +const char * +re_compile_pattern (const char *pattern, + size_t length, + struct re_pattern_buffer *bufp) +{ + reg_errcode_t ret; + + /* GNU code is written to assume at least RE_NREGS registers will be set + (and at least one extra will be -1). */ + bufp->regs_allocated = REGS_UNALLOCATED; + + /* And GNU code determines whether or not to get register information + by passing null for the REGS argument to re_match, etc., not by + setting no_sub. */ + bufp->no_sub = 0; + + /* Match anchors at newline. */ + bufp->newline_anchor = 1; + + ret = regex_compile (pattern, length, re_syntax_options, bufp); + + if (!ret) + return NULL; + return gettext (re_error_msgid[(int) ret]); +} +#ifdef _LIBC +weak_alias (__re_compile_pattern, re_compile_pattern) +#endif + +/* Entry points compatible with 4.2 BSD regex library. We don't define + them unless specifically requested. */ + +#if defined _REGEX_RE_COMP || defined _LIBC + +/* BSD has one and only one pattern buffer. */ +static struct re_pattern_buffer re_comp_buf; + +char * +#ifdef _LIBC +/* Make these definitions weak in libc, so POSIX programs can redefine + these names if they don't use our functions, and still use + regcomp/regexec below without link errors. */ +weak_function +#endif +re_comp (s) + const char *s; +{ + reg_errcode_t ret; + + if (!s) + { + if (!re_comp_buf.buffer) + return gettext ("No previous regular expression"); + return 0; + } + + if (!re_comp_buf.buffer) + { + re_comp_buf.buffer = (unsigned char *) malloc (200); + if (re_comp_buf.buffer == NULL) + return (char *) gettext (re_error_msgid[(int) REG_ESPACE]); + re_comp_buf.allocated = 200; + + re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH); + if (re_comp_buf.fastmap == NULL) + return (char *) gettext (re_error_msgid[(int) REG_ESPACE]); + } + + /* Since `re_exec' always passes NULL for the `regs' argument, we + don't need to initialize the pattern buffer fields which affect it. */ + + /* Match anchors at newlines. */ + re_comp_buf.newline_anchor = 1; + + ret = regex_compile (s, strlen (s), re_syntax_options, &re_comp_buf); + + if (!ret) + return NULL; + + /* Yes, we're discarding `const' here if !HAVE_LIBINTL. */ + return (char *) gettext (re_error_msgid[(int) ret]); +} + + +int +#ifdef _LIBC +weak_function +#endif +re_exec (s) + const char *s; +{ + const int len = strlen (s); + return + 0 <= re_search (&re_comp_buf, s, len, 0, len, (struct re_registers *) 0); +} + +#endif /* _REGEX_RE_COMP */ + +/* POSIX.2 functions. Don't define these for Emacs. */ + +#ifndef emacs + +/* regcomp takes a regular expression as a string and compiles it. + + PREG is a regex_t *. We do not expect any fields to be initialized, + since POSIX says we shouldn't. Thus, we set + + `buffer' to the compiled pattern; + `used' to the length of the compiled pattern; + `syntax' to RE_SYNTAX_POSIX_EXTENDED if the + REG_EXTENDED bit in CFLAGS is set; otherwise, to + RE_SYNTAX_POSIX_BASIC; + `newline_anchor' to REG_NEWLINE being set in CFLAGS; + `fastmap' to an allocated space for the fastmap; + `fastmap_accurate' to zero; + `re_nsub' to the number of subexpressions in PATTERN. + + PATTERN is the address of the pattern string. + + CFLAGS is a series of bits which affect compilation. + + If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we + use POSIX basic syntax. + + If REG_NEWLINE is set, then . and [^...] don't match newline. + Also, regexec will try a match beginning after every newline. + + If REG_ICASE is set, then we considers upper- and lowercase + versions of letters to be equivalent when matching. + + If REG_NOSUB is set, then when PREG is passed to regexec, that + routine will report only success or failure, and nothing about the + registers. + + It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for + the return codes and their meanings.) */ + +int +regcomp (regex_t *preg, + const char *pattern, + int cflags) +{ + reg_errcode_t ret; + reg_syntax_t syntax + = (cflags & REG_EXTENDED) ? + RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC; + + /* regex_compile will allocate the space for the compiled pattern. */ + preg->buffer = 0; + preg->allocated = 0; + preg->used = 0; + + /* Try to allocate space for the fastmap. */ + preg->fastmap = (char *) malloc (1 << BYTEWIDTH); + + if (cflags & REG_ICASE) + { + unsigned i; + + preg->translate + = (RE_TRANSLATE_TYPE) malloc (CHAR_SET_SIZE + * sizeof (*(RE_TRANSLATE_TYPE)0)); + if (preg->translate == NULL) + return (int) REG_ESPACE; + + /* Map uppercase characters to corresponding lowercase ones. */ + for (i = 0; i < CHAR_SET_SIZE; i++) + preg->translate[i] = ISUPPER (i) ? g_tolower (i) : i; + } + else + preg->translate = NULL; + + /* If REG_NEWLINE is set, newlines are treated differently. */ + if (cflags & REG_NEWLINE) + { /* REG_NEWLINE implies neither . nor [^...] match newline. */ + syntax &= ~RE_DOT_NEWLINE; + syntax |= RE_HAT_LISTS_NOT_NEWLINE; + /* It also changes the matching behavior. */ + preg->newline_anchor = 1; + } + else + preg->newline_anchor = 0; + + preg->no_sub = !!(cflags & REG_NOSUB); + + /* POSIX says a null character in the pattern terminates it, so we + can use strlen here in compiling the pattern. */ + ret = regex_compile (pattern, strlen (pattern), syntax, preg); + + /* POSIX doesn't distinguish between an unmatched open-group and an + unmatched close-group: both are REG_EPAREN. */ + if (ret == REG_ERPAREN) ret = REG_EPAREN; + + if (ret == REG_NOERROR && preg->fastmap) + { + /* Compute the fastmap now, since regexec cannot modify the pattern + buffer. */ + if (re_compile_fastmap (preg) == -2) + { + /* Some error occured while computing the fastmap, just forget + about it. */ + free (preg->fastmap); + preg->fastmap = NULL; + } + } + + return (int) ret; +} +#ifdef _LIBC +weak_alias (__regcomp, regcomp) +#endif + + +/* regexec searches for a given pattern, specified by PREG, in the + string STRING. + + If NMATCH is zero or REG_NOSUB was set in the cflags argument to + `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at + least NMATCH elements, and we set them to the offsets of the + corresponding matched substrings. + + EFLAGS specifies `execution flags' which affect matching: if + REG_NOTBOL is set, then ^ does not match at the beginning of the + string; if REG_NOTEOL is set, then $ does not match at the end. + + We return 0 if we find a match and REG_NOMATCH if not. */ + +int +regexec (const regex_t *preg, + const char *string, + size_t nmatch, + regmatch_t pmatch[], + int eflags) +{ + int ret; + struct re_registers regs; + regex_t private_preg; + int len = strlen (string); + bool want_reg_info = !preg->no_sub && nmatch > 0; + + private_preg = *preg; + + private_preg.not_bol = !!(eflags & REG_NOTBOL); + private_preg.not_eol = !!(eflags & REG_NOTEOL); + + /* The user has told us exactly how many registers to return + information about, via `nmatch'. We have to pass that on to the + matching routines. */ + private_preg.regs_allocated = REGS_FIXED; + + if (want_reg_info) + { + regs.num_regs = nmatch; + regs.start = TALLOC (nmatch * 2, regoff_t); + if (regs.start == NULL) + return (int) REG_NOMATCH; + regs.end = regs.start + nmatch; + } + + /* Perform the searching operation. */ + ret = re_search (&private_preg, string, len, + /* start: */ 0, /* range: */ len, + want_reg_info ? ®s : (struct re_registers *) 0); + + /* Copy the register information to the POSIX structure. */ + if (want_reg_info) + { + if (ret >= 0) + { + unsigned r; + + for (r = 0; r < nmatch; r++) + { + pmatch[r].rm_so = regs.start[r]; + pmatch[r].rm_eo = regs.end[r]; + } + } + + /* If we needed the temporary register info, free the space now. */ + free (regs.start); + } + + /* We want zero return to mean success, unlike `re_search'. */ + return ret >= 0 ? (int) REG_NOERROR : (int) REG_NOMATCH; +} +#ifdef _LIBC +weak_alias (__regexec, regexec) +#endif + + +/* Returns a message corresponding to an error code, ERRCODE, returned + from either regcomp or regexec. We don't use PREG here. */ + +size_t +regerror (int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size) +{ + const char *msg; + size_t msg_size; + + if (errcode < 0 + || errcode >= (int) (sizeof (re_error_msgid) + / sizeof (re_error_msgid[0]))) + /* Only error codes returned by the rest of the code should be passed + to this routine. If we are given anything else, or if other regex + code generates an invalid error code, then the program has a bug. + Dump core so we can fix it. */ + abort (); + + msg = gettext (re_error_msgid[errcode]); + + msg_size = strlen (msg) + 1; /* Includes the null. */ + + if (errbuf_size != 0) + { + if (msg_size > errbuf_size) + { +#if defined HAVE_MEMPCPY || defined _LIBC + *((char *) __mempcpy (errbuf, msg, errbuf_size - 1)) = '\0'; +#else + memcpy (errbuf, msg, errbuf_size - 1); + errbuf[errbuf_size - 1] = 0; +#endif + } + else + memcpy (errbuf, msg, msg_size); + } + + return msg_size; +} +#ifdef _LIBC +weak_alias (__regerror, regerror) +#endif + + +/* Free dynamically allocated space used by PREG. */ + +void +regfree (regex_t *preg) +{ + if (preg->buffer != NULL) + free (preg->buffer); + preg->buffer = NULL; + + preg->allocated = 0; + preg->used = 0; + + if (preg->fastmap != NULL) + free (preg->fastmap); + preg->fastmap = NULL; + preg->fastmap_accurate = 0; + + if (preg->translate != NULL) + free (preg->translate); + preg->translate = NULL; +} +#ifdef _LIBC +weak_alias (__regfree, regfree) +#endif + +#endif /* not emacs */ diff --git a/goldlib/smblib/lzh.cpp b/goldlib/smblib/lzh.cpp new file mode 100644 index 0000000..0484356 --- /dev/null +++ b/goldlib/smblib/lzh.cpp @@ -0,0 +1,813 @@ +/* lzh.c */ + +/* Synchronet LZH compression library */ + +/* $Id$ */ + +/**************************************************************************** + * @format.tab-size 4 (Plain Text/Source Code File Header) * + * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * + * * + * Rob Swindell's conversion of 1988 LZH (LHarc) encoding functions * + * Based on Japanese version 29-NOV-1988 * + * LZSS coded by Haruhiko Okumura * + * Adaptive Huffman Coding coded by Haruyasu Yoshizaki * + * * + * Anonymous FTP access to the most recent released source is available at * + * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * + * * + * Anonymous CVS access to the development source and modification history * + * is available at cvs.synchro.net:/cvsroot/sbbs, example: * + * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * + * (just hit return, no password is necessary) * + * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * + * * + * For Synchronet coding style and modification guidelines, see * + * http://www.synchro.net/source.html * + * * + * You are encouraged to submit any modifications (preferably in Unix diff * + * format) via e-mail to mods@synchro.net * + * * + * Note: If this box doesn't appear square, then you need to fix your tabs. * + ****************************************************************************/ + +#include +#include +#include + +/* FreeBSD's malloc.h is deprecated, it drops a warning and */ +/* #includes , which is already here. */ +#if !defined(__FreeBSD__) && !defined(__APPLE__) +#include +#endif + +#include "lzh.h" + +/****************************************************************************/ +/* Memory allocation macros for various compilers and environments */ +/* MALLOC is used for allocations of 64k or less */ +/* FREE is used to free buffers allocated with MALLOC */ +/* LMALLOC is used for allocations of possibly larger than 64k */ +/* LFREE is used to free buffers allocated with LMALLOC */ +/* REALLOC is used to re-size a previously MALLOCed or LMALLOCed buffer */ +/****************************************************************************/ +#if defined(__COMPACT__) || defined(__LARGE__) || defined(__HUGE__) + #if defined(__TURBOC__) + #define REALLOC(x,y) farrealloc(x,y) + #define LMALLOC(x) farmalloc(x) + #define MALLOC(x) farmalloc(x) + #define LFREE(x) farfree(x) + #define FREE(x) farfree(x) + #elif defined(__WATCOMC__) + #define REALLOC realloc + #define LMALLOC(x) halloc(x,1) /* far heap, but slow */ + #define MALLOC malloc /* far heap, but 64k max */ + #define LFREE hfree + #define FREE free + #else /* Other 16-bit Compiler */ + #define REALLOC realloc + #define LMALLOC malloc + #define MALLOC malloc + #define LFREE free + #define FREE free + #endif +#else /* 32-bit Compiler or Small Memory Model */ + #define REALLOC realloc + #define LMALLOC malloc + #define MALLOC malloc + #define LFREE free + #define FREE free +#endif + + + +/* LZSS Parameters */ + +#define LZH_N 4096 /* Size of string buffer */ +#define LZH_F 60 /* Size of look-ahead buffer */ +#define LZH_THRESHOLD 2 +#define LZH_NIL LZH_N /* End of tree's node */ + +#ifdef LZH_DYNAMIC_BUF + +uint8_t *lzh_text_buf; +int16_t lzh_match_position, lzh_match_length, + *lzh_lson, *lzh_rson, *lzh_dad; + +#else + +uint8_t lzh_text_buf[LZH_N + LZH_F - 1]; +int16_t lzh_match_position, lzh_match_length, + lzh_lson[LZH_N + 1], lzh_rson[LZH_N + 257], lzh_dad[LZH_N + 1]; + +#endif + + +void lzh_init_tree(void) /* Initializing tree */ +{ + int16_t i; + + for (i = LZH_N + 1; i <= LZH_N + 256; i++) + lzh_rson[i] = LZH_NIL; /* root */ + for (i = 0; i < LZH_N; i++) + lzh_dad[i] = LZH_NIL; /* node */ +} + +/******************************/ +/* Inserting node to the tree */ +/* Only used during encoding */ +/******************************/ +void lzh_insert_node(int16_t r) +{ + int16_t i, p, cmp; + uint8_t *key; + uint32_t c; + + cmp = 1; + key = lzh_text_buf+r; + p = LZH_N + 1 + key[0]; + lzh_rson[r] = lzh_lson[r] = LZH_NIL; + lzh_match_length = 0; + for ( ; ; ) { + if (cmp >= 0) { + if (lzh_rson[p] != LZH_NIL) + p = lzh_rson[p]; + else { + lzh_rson[p] = r; + lzh_dad[r] = p; + return; + } + } else { + if (lzh_lson[p] != LZH_NIL) + p = lzh_lson[p]; + else { + lzh_lson[p] = r; + lzh_dad[r] = p; + return; + } + } + for (i = 1; i < LZH_F; i++) + if ((cmp = key[i] - lzh_text_buf[p + i]) != 0) + break; + if (i > LZH_THRESHOLD) { + if (i > lzh_match_length) { + lzh_match_position = ((r - p) & (LZH_N - 1)) - 1; + if ((lzh_match_length = i) >= LZH_F) + break; + } + if (i == lzh_match_length) { + if ((c = ((r - p) & (LZH_N - 1)) - 1) + < (uint32_t)lzh_match_position) { + lzh_match_position = c; + } + } + } + } + lzh_dad[r] = lzh_dad[p]; + lzh_lson[r] = lzh_lson[p]; + lzh_rson[r] = lzh_rson[p]; + lzh_dad[lzh_lson[p]] = r; + lzh_dad[lzh_rson[p]] = r; + if (lzh_rson[lzh_dad[p]] == p) + lzh_rson[lzh_dad[p]] = r; + else + lzh_lson[lzh_dad[p]] = r; + lzh_dad[p] = LZH_NIL; /* remove p */ +} + +void lzh_delete_node(int16_t p) /* Deleting node from the tree */ +{ + int16_t q; + + if (lzh_dad[p] == LZH_NIL) + return; /* unregistered */ + if (lzh_rson[p] == LZH_NIL) + q = lzh_lson[p]; + else + if (lzh_lson[p] == LZH_NIL) + q = lzh_rson[p]; + else { + q = lzh_lson[p]; + if (lzh_rson[q] != LZH_NIL) { + do { + q = lzh_rson[q]; + } while (lzh_rson[q] != LZH_NIL); + lzh_rson[lzh_dad[q]] = lzh_lson[q]; + lzh_dad[lzh_lson[q]] = lzh_dad[q]; + lzh_lson[q] = lzh_lson[p]; + lzh_dad[lzh_lson[p]] = q; + } + lzh_rson[q] = lzh_rson[p]; + lzh_dad[lzh_rson[p]] = q; + } + lzh_dad[q] = lzh_dad[p]; + if (lzh_rson[lzh_dad[p]] == p) + lzh_rson[lzh_dad[p]] = q; + else + lzh_lson[lzh_dad[p]] = q; + lzh_dad[p] = LZH_NIL; +} + +/* Huffman coding parameters */ + +#define LZH_N_CHAR (256 - LZH_THRESHOLD + LZH_F) + /* character code (= 0..LZH_N_CHAR-1) */ +#define LZH_T (LZH_N_CHAR * 2 - 1) /* Size of table */ +#define LZH_R (LZH_T - 1) /* root position */ +#define MAX_FREQ 0x8000 + /* update when cumulative frequency */ + /* reaches to this value */ + +/* + * Tables for encoding/decoding upper 6 bits of + * sliding dictionary pointer + */ +/* encoder table */ +uint8_t lzh_p_len[64] = { + 0x03, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08 +}; + +uint8_t lzh_p_code[64] = { + 0x00, 0x20, 0x30, 0x40, 0x50, 0x58, 0x60, 0x68, + 0x70, 0x78, 0x80, 0x88, 0x90, 0x94, 0x98, 0x9C, + 0xA0, 0xA4, 0xA8, 0xAC, 0xB0, 0xB4, 0xB8, 0xBC, + 0xC0, 0xC2, 0xC4, 0xC6, 0xC8, 0xCA, 0xCC, 0xCE, + 0xD0, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA, 0xDC, 0xDE, + 0xE0, 0xE2, 0xE4, 0xE6, 0xE8, 0xEA, 0xEC, 0xEE, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, + 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF +}; + +/* decoder table */ +uint8_t lzh_d_code[256] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, + 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B, + 0x0C, 0x0C, 0x0C, 0x0C, 0x0D, 0x0D, 0x0D, 0x0D, + 0x0E, 0x0E, 0x0E, 0x0E, 0x0F, 0x0F, 0x0F, 0x0F, + 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, + 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, + 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, + 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, + 0x18, 0x18, 0x19, 0x19, 0x1A, 0x1A, 0x1B, 0x1B, + 0x1C, 0x1C, 0x1D, 0x1D, 0x1E, 0x1E, 0x1F, 0x1F, + 0x20, 0x20, 0x21, 0x21, 0x22, 0x22, 0x23, 0x23, + 0x24, 0x24, 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, + 0x28, 0x28, 0x29, 0x29, 0x2A, 0x2A, 0x2B, 0x2B, + 0x2C, 0x2C, 0x2D, 0x2D, 0x2E, 0x2E, 0x2F, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, +}; + +uint8_t lzh_d_len[256] = { + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, +}; + +#ifdef LZH_DYNAMIC_BUF + +uint16_t *lzh_freq = NULL; /* cumulative freq table */ + +/* + * pointing parent nodes. + * area [LZH_T..(LZH_T + LZH_N_CHAR - 1)] are pointers for leaves + */ +int16_t *lzh_prnt = NULL; + +/* pointing children nodes (son[], son[] + 1)*/ +int16_t *lzh_son = NULL; + +#else /* STATIC */ + +uint16_t lzh_freq[LZH_T + 1]; /* cumulative freq table */ +int16_t lzh_prnt[LZH_T + LZH_N_CHAR]; +int16_t lzh_son[LZH_T + 1]; /* bug fixed by Digital Dynamics */ + +#endif + + +uint16_t lzh_getbuf = 0; /* Was just "unsigned" fixed 04/12/95 */ +uint8_t lzh_getlen = 0; + +int lzh_getbit(uint8_t *inbuf, int32_t *incnt, int32_t inlen) /* get one bit */ +{ + int16_t i; + + while (lzh_getlen <= 8) { + if((*incnt)>=inlen) + i=0; + else + i=inbuf[(*incnt)++]; + lzh_getbuf |= i << (8 - lzh_getlen); + lzh_getlen += 8; + } + i = lzh_getbuf; + lzh_getbuf <<= 1; + lzh_getlen--; + return (i < 0); +} + +int16_t lzh_getbyte(uint8_t *inbuf, int32_t *incnt, int32_t inlen) /* get a byte */ +{ + uint16_t i; + + while (lzh_getlen <= 8) { + if((*incnt)>=inlen) + i=0; + else + i=inbuf[(*incnt)++]; + lzh_getbuf |= i << (8 - lzh_getlen); + lzh_getlen += 8; + } + i = lzh_getbuf; + lzh_getbuf <<= 8; + lzh_getlen -= 8; + return i >> 8; +} + +uint32_t lzh_putbuf = 0; +uint8_t lzh_putlen = 0; + +/* output c bits */ +void lzh_putcode(int16_t l, uint16_t c, uint8_t *outbuf, int32_t *outlen) +{ + lzh_putbuf |= c >> lzh_putlen; + if ((lzh_putlen += l) >= 8) { + outbuf[(*outlen)++]=(lzh_putbuf >> 8); + if ((lzh_putlen -= 8) >= 8) { + outbuf[(*outlen)++]=lzh_putbuf; + lzh_putlen -= 8; + lzh_putbuf = c << (l - lzh_putlen); + } else { + lzh_putbuf <<= 8; + } + } +} + + +/* initialize freq tree */ + +void lzh_start_huff(void) +{ + int16_t i, j; + + lzh_getbuf = 0; /* Added by Digital Dynamics for repeating operations */ + lzh_getlen = 0; + lzh_putbuf = 0; + lzh_putlen = 0; + + for (i = 0; i < LZH_N_CHAR; i++) { + lzh_freq[i] = 1; + lzh_son[i] = i + LZH_T; + lzh_prnt[i + LZH_T] = i; + } + i = 0; j = LZH_N_CHAR; + while (j <= LZH_R) { + lzh_freq[j] = lzh_freq[i] + lzh_freq[i + 1]; + lzh_son[j] = i; + lzh_prnt[i] = lzh_prnt[i + 1] = j; + i += 2; j++; + } + lzh_freq[LZH_T] = 0xffff; + lzh_prnt[LZH_R] = 0; +} + + +/* reconstruct freq tree */ + +void lzh_reconst(void) +{ + int16_t i, j, k; + uint16_t f, l; + + /* halven cumulative freq for leaf nodes */ + j = 0; + for (i = 0; i < LZH_T; i++) { + if (lzh_son[i] >= LZH_T) { + lzh_freq[j] = (lzh_freq[i] + 1) / 2; + lzh_son[j] = lzh_son[i]; + j++; + } + } + /* make a tree : first, connect children nodes */ + for (i = 0, j = LZH_N_CHAR; j < LZH_T; i += 2, j++) { + k = i + 1; + f = lzh_freq[j] = lzh_freq[i] + lzh_freq[k]; + for (k = j - 1; f < lzh_freq[k]; k--); + k++; + l = (j - k) * 2; + + /* movmem() is Turbo-C dependent + rewritten to memmove() by Kenji */ + + /* movmem(&lzh_freq[k], &lzh_freq[k + 1], l); */ + (void)memmove(lzh_freq+k+1,lzh_freq+k, l); + lzh_freq[k] = f; + /* movmem(&lzh_son[k], &lzh_son[k + 1], l); */ + (void)memmove(lzh_son+k+1,lzh_son+k, l); + lzh_son[k] = i; + } + /* connect parent nodes */ + for (i = 0; i < LZH_T; i++) { + if ((k = lzh_son[i]) >= LZH_T) { + lzh_prnt[k] = i; + } else { + lzh_prnt[k] = lzh_prnt[k + 1] = i; + } + } +} + +/* update freq tree */ + +void lzh_update(int16_t c) +{ + int16_t i, j, k, l; + + if (lzh_freq[LZH_R] == MAX_FREQ) { + lzh_reconst(); + } + c = lzh_prnt[c + LZH_T]; + do { + k = ++lzh_freq[c]; + + /* swap nodes to keep the tree freq-ordered */ + if (k > lzh_freq[l = c + 1]) { + while (k > lzh_freq[++l]); + l--; + lzh_freq[c] = lzh_freq[l]; + lzh_freq[l] = k; + + i = lzh_son[c]; + lzh_prnt[i] = l; + if (i < LZH_T) lzh_prnt[i + 1] = l; + + j = lzh_son[l]; + lzh_son[l] = i; + + lzh_prnt[j] = c; + if (j < LZH_T) lzh_prnt[j + 1] = c; + lzh_son[c] = j; + + c = l; + } + } while ((c = lzh_prnt[c]) != 0); /* do it until reaching the root */ +} + +uint16_t lzh_code, lzh_len; + +void lzh_encode_char(uint16_t c, uint8_t *outbuf, int32_t *outlen) +{ + uint16_t i; + int16_t j, k; + + i = 0; + j = 0; + k = lzh_prnt[c + LZH_T]; + + /* search connections from leaf node to the root */ + do { + i >>= 1; + + /* + if node's address is odd, output 1 + else output 0 + */ + if (k & 1) i += 0x8000; + + j++; + } while ((k = lzh_prnt[k]) != LZH_R); + lzh_putcode(j, i, outbuf, outlen); + lzh_code = i; + lzh_len = j; + lzh_update(c); +} + +void lzh_encode_position(uint16_t c, uint8_t *outbuf, int32_t *outlen) +{ + uint16_t i; + + /* output upper 6 bits with encoding */ + i = c >> 6; + lzh_putcode(lzh_p_len[i], (uint16_t)(lzh_p_code[i] << 8), outbuf, outlen); + + /* output lower 6 bits directly */ + lzh_putcode(6, (uint16_t)((c & 0x3f) << 10), outbuf, outlen); +} + +void lzh_encode_end(uint8_t *outbuf, int32_t *outlen) +{ + if (lzh_putlen) { + outbuf[(*outlen)++]=(lzh_putbuf >> 8); + } +} + +int16_t lzh_decode_char(uint8_t *inbuf, int32_t *incnt, int32_t inlen) +{ + uint16_t c; + + c = lzh_son[LZH_R]; + + /* + * start searching tree from the root to leaves. + * choose node #(lzh_son[]) if input bit == 0 + * else choose #(lzh_son[]+1) (input bit == 1) + */ + while (c < LZH_T) { + c += lzh_getbit(inbuf,incnt,inlen); + c = lzh_son[c]; + } + c -= LZH_T; + lzh_update(c); + return c; +} + +int16_t lzh_decode_position(uint8_t *inbuf, int32_t *incnt, int32_t inlen) +{ + uint16_t i, j, c; + + /* decode upper 6 bits from given table */ + i = lzh_getbyte(inbuf,incnt,inlen); + c = (uint32_t)lzh_d_code[i] << 6; + j = lzh_d_len[i]; + + /* input lower 6 bits directly */ + j -= 2; + while (j--) { + i = (i << 1) + lzh_getbit(inbuf,incnt,inlen); + } + return c | (i & 0x3f); +} + +/* Compression */ + +/* Encoding/Compressing */ +/* Returns length of outbuf */ +int32_t LZHCALL lzh_encode(uint8_t *inbuf, int32_t inlen, uint8_t *outbuf) +{ + int16_t i, c, len, r, s, last_match_length; + int32_t incnt, outlen; /* textsize=0; */ + +#ifdef LZH_DYNAMIC_BUF + + if((lzh_text_buf=(uint8_t *)MALLOC(LZH_N + LZH_F - 1))==NULL) + return(-1); + if((lzh_freq=(uint16_t *)MALLOC((LZH_T + 1)*sizeof(uint16_t)))==NULL) { + FREE(lzh_text_buf); + return(-1); } + if((lzh_prnt=(int16_t *)MALLOC((LZH_T + LZH_N_CHAR)*sizeof(int16_t)))==NULL) { + FREE(lzh_text_buf); + FREE(lzh_freq); + return(-1); } + if((lzh_son=(int16_t *)MALLOC((LZH_T + 1) * sizeof(int16_t)))==NULL) { + FREE(lzh_text_buf); + FREE(lzh_prnt); + FREE(lzh_freq); + return(-1); } + if((lzh_lson=(int16_t *)MALLOC((LZH_N + 1)*sizeof(int16_t)))==NULL) { + FREE(lzh_text_buf); + FREE(lzh_prnt); + FREE(lzh_freq); + FREE(lzh_son); + return(-1); } + if((lzh_rson=(int16_t *)MALLOC((LZH_N + 257)*sizeof(int16_t)))==NULL) { + FREE(lzh_text_buf); + FREE(lzh_prnt); + FREE(lzh_freq); + FREE(lzh_son); + FREE(lzh_lson); + return(-1); } + if((lzh_dad=(int16_t *)MALLOC((LZH_N + 1)*sizeof(int16_t)))==NULL) { + FREE(lzh_text_buf); + FREE(lzh_prnt); + FREE(lzh_freq); + FREE(lzh_son); + FREE(lzh_lson); + FREE(lzh_rson); + return(-1); } +#endif + + incnt=0; + memcpy(outbuf,&inlen,sizeof(inlen)); + outlen=sizeof(inlen); + if(!inlen) { +#ifdef LZH_DYNAMIC_BUF + FREE(lzh_text_buf); + FREE(lzh_prnt); + FREE(lzh_freq); + FREE(lzh_son); + FREE(lzh_lson); + FREE(lzh_rson); + FREE(lzh_dad); +#endif + return(outlen); } + lzh_start_huff(); + lzh_init_tree(); + s = 0; + r = LZH_N - LZH_F; + for (i = s; i < r; i++) + lzh_text_buf[i] = ' '; + for (len = 0; len < LZH_F && incnt len) + lzh_match_length = len; + if (lzh_match_length <= LZH_THRESHOLD) { + lzh_match_length = 1; + lzh_encode_char(lzh_text_buf[r],outbuf,&outlen); + } else { + lzh_encode_char((uint16_t)(255 - LZH_THRESHOLD + lzh_match_length) + ,outbuf,&outlen); + lzh_encode_position(lzh_match_position + ,outbuf,&outlen); + } + last_match_length = lzh_match_length; + for (i = 0; i < last_match_length && incnt printcount) { + printf("%12ld\r", textsize); + printcount += 1024; + } +***/ + while (i++ < last_match_length) { + lzh_delete_node(s); + s = (s + 1) & (LZH_N - 1); + r = (r + 1) & (LZH_N - 1); + if (--len) lzh_insert_node(r); + } + } while (len > 0); + lzh_encode_end(outbuf,&outlen); +/* + printf("input: %ld (%ld) bytes\n", inlen,textsize); + printf("output: %ld bytes\n", outlen); + printf("output/input: %.3f\n", (double)outlen / inlen); +*/ + +#ifdef LZH_DYNAMIC_BUF + FREE(lzh_text_buf); + FREE(lzh_prnt); + FREE(lzh_freq); + FREE(lzh_son); + FREE(lzh_lson); + FREE(lzh_rson); + FREE(lzh_dad); +#endif + + return(outlen); +} + +/* Decoding/Uncompressing */ +/* Returns length of outbuf */ +int32_t LZHCALL lzh_decode(uint8_t *inbuf, int32_t inlen, uint8_t *outbuf) +{ + int16_t i, j, k, r, c; + uint32_t count; + int32_t incnt, textsize; + +#ifdef LZH_DYNAMIC_BUF + + if((lzh_text_buf=(uint8_t *)MALLOC((LZH_N + LZH_F - 1)*2))==NULL) + return(-1); + if((lzh_freq=(uint16_t *)MALLOC((LZH_T + 1)*sizeof(uint16_t))) + ==NULL) { + FREE(lzh_text_buf); + return(-1); } + if((lzh_prnt=(int16_t *)MALLOC((LZH_T + LZH_N_CHAR)*sizeof(int16_t)))==NULL) { + FREE(lzh_text_buf); + FREE(lzh_freq); + return(-1); } + if((lzh_son=(int16_t *)MALLOC((LZH_T + 1) * sizeof(int16_t)))==NULL) { + FREE(lzh_text_buf); + FREE(lzh_prnt); + FREE(lzh_freq); + return(-1); } + +#endif + + incnt=0; + memcpy(&textsize,inbuf,sizeof(textsize)); + incnt+=sizeof(textsize); + if (textsize == 0) { +#ifdef LZH_DYNAMIC_BUF + FREE(lzh_text_buf); + FREE(lzh_prnt); + FREE(lzh_freq); + FREE(lzh_son); +#endif + return(textsize); } + lzh_start_huff(); + for (i = 0; i < LZH_N - LZH_F; i++) + *(lzh_text_buf+i) = ' '; + r = LZH_N - LZH_F; + for (count = 0; count < (uint32_t)textsize; ) { + c = lzh_decode_char(inbuf,&incnt,inlen); + if (c < 256) { + outbuf[count]=(uint8_t)c; +#if 0 + if(r>(LZH_N + LZH_F - 1) || r<0) { + printf("Overflow! (%d)\n",r); + getch(); + exit(-1); } +#endif + *(lzh_text_buf+r) = (uint8_t)c; + r++; + r &= (LZH_N - 1); + count++; + } else { + i = (r - lzh_decode_position(inbuf,&incnt,inlen) - 1) + & (LZH_N - 1); + j = c - 255 + LZH_THRESHOLD; + for (k = 0; k < j && count<(uint32_t)textsize; k++) { + c = lzh_text_buf[(i + k) & (LZH_N - 1)]; + outbuf[count]=(uint8_t)c; +#if 0 + if(r>(LZH_N + LZH_F - 1) || r<0) { + printf("Overflow! (%d)\n",r); + exit(-1); } +#endif + *(lzh_text_buf+r) = (uint8_t)c; + r++; + r &= (LZH_N - 1); + count++; + } + } + } +/*** + printf("%12ld\n", count); +***/ + +#ifdef LZH_DYNAMIC_BUF + FREE(lzh_text_buf); + FREE(lzh_prnt); + FREE(lzh_freq); + FREE(lzh_son); +#endif + +return(count); +} + + diff --git a/goldlib/smblib/smblib.cpp b/goldlib/smblib/smblib.cpp new file mode 100644 index 0000000..0f42587 --- /dev/null +++ b/goldlib/smblib/smblib.cpp @@ -0,0 +1,1729 @@ +/* smblib.c */ + +/* Synchronet message base (SMB) library routines */ + +/* $Id$ */ + +/**************************************************************************** + * @format.tab-size 4 (Plain Text/Source Code File Header) * + * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * + * * + * Copyright 2000 Rob Swindell - http://www.synchro.net/copyright.html * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * See the GNU Lesser General Public License for more details: lgpl.txt or * + * http://www.fsf.org/copyleft/lesser.html * + * * + * Anonymous FTP access to the most recent released source is available at * + * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net * + * * + * Anonymous CVS access to the development source and modification history * + * is available at cvs.synchro.net:/cvsroot/sbbs, example: * + * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login * + * (just hit return, no password is necessary) * + * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src * + * * + * For Synchronet coding style and modification guidelines, see * + * http://www.synchro.net/source.html * + * * + * You are encouraged to submit any modifications (preferably in Unix diff * + * format) via e-mail to mods@synchro.net * + * * + * Note: If this box doesn't appear square, then you need to fix your tabs. * + ****************************************************************************/ + +#if defined __WATCOMC__ || defined __TURBOC__ + #include +#else + #include +#endif + +#ifdef __WATCOMC__ + #include +#elif defined __TURBOC__ + #include +#endif + +/* ANSI C Library headers */ + +#if !defined(__FreeBSD__) && !defined(__APPLE__) + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +/* SMB-specific headers */ +#include "smblib.h" +#include +#include + +/* Use smb_ver() and smb_lib_ver() to obtain these values */ +#define SMBLIB_VERSION "2.16" /* SMB library version */ +#define SMB_VERSION 0x0121 /* SMB format version */ + /* High byte major, low byte minor */ + +#ifndef __gtimall_h +time32_t gtime(time32_t *timep) +{ + time32_t temp = (time32_t)time(NULL); + return timep ? *timep = temp : temp; +} +#endif //#ifndef __gtimall_h + +int SMBCALL smb_ver(void) +{ + return(SMB_VERSION); +} + +char* SMBCALL smb_lib_ver(void) +{ + return(SMBLIB_VERSION); +} + +/****************************************************************************/ +/* Open a message base of name 'smb->file' */ +/* Opens files for READing messages or updating message indices only */ +/****************************************************************************/ +int SMBCALL smb_open(smb_t* smb) +{ + int file; + char str[128]; + smbhdr_t hdr; + + /* Set default values, if uninitialized */ + if(!smb->retry_time) + smb->retry_time=10; /* seconds */ + if(!smb->retry_delay + || smb->retry_delay>(smb->retry_time*100)) /* at least ten retries */ + smb->retry_delay=250; /* milliseconds */ + smb->shd_fp=smb->sdt_fp=smb->sid_fp=NULL; + smb->last_error[0]=0; + sprintf(str,"%s.shd",smb->file); + if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO))==-1) { + sprintf(smb->last_error,"%d opening %s",errno,str); + return(2); + } + + if((smb->shd_fp=fdopen(file,"r+b"))==NULL) { + sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file); + close(file); + return(4); + } + + if(filelength(file)>=sizeof(smbhdr_t)) { + setvbuf(smb->shd_fp,smb->shd_buf,_IONBF,SHD_BLOCK_LEN); + if(smb_locksmbhdr(smb)!=0) { + smb_close(smb); + /* smb_lockmsghdr set last_error */ + return(-1); + } + memset(&hdr,0,sizeof(smbhdr_t)); + if(fread(&hdr,sizeof(smbhdr_t),1,smb->shd_fp)!=1) { + sprintf(smb->last_error,"reading header"); + smb_close(smb); + return(-10); + } + if(memcmp(hdr.id,SMB_HEADER_ID,LEN_HEADER_ID)) { + sprintf(smb->last_error,"corrupt SMB header ID: %.*s",LEN_HEADER_ID,hdr.id); + smb_close(smb); + return(-2); + } + if(hdr.version<0x110) { /* Compatibility check */ + sprintf(smb->last_error,"insufficient header version: %X",hdr.version); + smb_close(smb); + return(-3); + } + if(fread(&(smb->status),1,sizeof(smbstatus_t),smb->shd_fp) + !=sizeof(smbstatus_t)) { + sprintf(smb->last_error,"failed to read status"); + smb_close(smb); + return(-4); + } + smb_unlocksmbhdr(smb); + rewind(smb->shd_fp); + } + + setvbuf(smb->shd_fp,smb->shd_buf,_IOFBF,SHD_BLOCK_LEN); + + sprintf(str,"%s.sdt",smb->file); + if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO))==-1) { + sprintf(smb->last_error,"%d opening %s",errno,str); + smb_close(smb); + return(1); + } + + if((smb->sdt_fp=fdopen(file,"r+b"))==NULL) { + sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file); + close(file); + smb_close(smb); + return(5); + } + + setvbuf(smb->sdt_fp,NULL,_IOFBF,2*1024); + + sprintf(str,"%s.sid",smb->file); + if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO))==-1) { + sprintf(smb->last_error,"%d opening %s",errno,str); + smb_close(smb); + return(3); + } + + if((smb->sid_fp=fdopen(file,"r+b"))==NULL) { + sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file); + close(file); + smb_close(smb); + return(6); + } + + setvbuf(smb->sid_fp,NULL,_IOFBF,2*1024); + + return(0); +} + +/****************************************************************************/ +/* Closes the currently open message base */ +/****************************************************************************/ +void SMBCALL smb_close(smb_t* smb) +{ + if(smb->shd_fp!=NULL) { + smb_unlocksmbhdr(smb); /* In case it's been locked */ + fclose(smb->shd_fp); + } + if(smb->sid_fp!=NULL) + fclose(smb->sid_fp); + if(smb->sdt_fp!=NULL) + fclose(smb->sdt_fp); + smb->sid_fp=smb->shd_fp=smb->sdt_fp=NULL; +} + +/****************************************************************************/ +/* Opens the data block allocation table message base 'smb->file' */ +/* Retrys for retry_time number of seconds */ +/* Return 0 on success, non-zero otherwise */ +/****************************************************************************/ +int SMBCALL smb_open_da(smb_t* smb) +{ + int file; + char str[128]; + time32_t start = 0; + + sprintf(str,"%s.sda",smb->file); + while(1) { + if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYRW))!=-1) + break; + if(errno!=EACCES && errno!=EAGAIN) { + sprintf(smb->last_error,"%d opening %s",errno,str); + return(-1); + } + if(!start) + start = gtime(NULL); + else + if(gtime(NULL)-start >= smb->retry_time) { + sprintf(smb->last_error,"timeout opening %s (retry_time=%d)" + ,str,smb->retry_time); + return(-2); + } + usleep(smb->retry_delay); + } + if((smb->sda_fp=fdopen(file,"r+b"))==NULL) { + sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file); + close(file); + return(-3); + } + setvbuf(smb->sda_fp,NULL,_IOFBF,2*1024); + return(0); +} + +void SMBCALL smb_close_da(smb_t* smb) +{ + if(smb->sda_fp!=NULL) + fclose(smb->sda_fp); + smb->sda_fp=NULL; +} + +/****************************************************************************/ +/* Opens the header block allocation table for message base 'smb.file' */ +/* Retrys for smb.retry_time number of seconds */ +/* Return 0 on success, non-zero otherwise */ +/****************************************************************************/ +int SMBCALL smb_open_ha(smb_t* smb) +{ + int file; + char str[128]; + time32_t start = 0; + + sprintf(str,"%s.sha",smb->file); + while(1) { + if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYRW))!=-1) + break; + if(errno!=EACCES && errno!=EAGAIN) { + sprintf(smb->last_error,"%d opening %s",errno,str); + return(-1); + } + if(!start) + start = gtime(NULL); + else + if(gtime(NULL)-start >= smb->retry_time) { + sprintf(smb->last_error,"timeout opening %s (retry_time=%d)" + ,str,smb->retry_time); + return(-2); + } + usleep(smb->retry_delay); + } + if((smb->sha_fp=fdopen(file,"r+b"))==NULL) { + sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file); + close(file); + return(-3); + } + setvbuf(smb->sha_fp,NULL,_IOFBF,2*1024); + return(0); +} + +void SMBCALL smb_close_ha(smb_t* smb) +{ + if(smb->sha_fp!=NULL) + fclose(smb->sha_fp); + smb->sha_fp=NULL; +} + +/****************************************************************************/ +/* If the parameter 'push' is non-zero, this function stores the currently */ +/* open message base to the "virtual" smb stack. Up to SMB_STACK_LEN */ +/* message bases may be stored (defined in SMBDEFS.H). */ +/* The parameter 'op' is the operation to perform on the stack. Either */ +/* SMB_STACK_PUSH, SMB_STACK_POP, or SMB_STACK_XCHNG */ +/* If the operation is SMB_STACK_POP, this function restores a message base */ +/* previously saved with a SMB_STACK_PUSH call to this same function. */ +/* If the operation is SMB_STACK_XCHNG, then the current message base is */ +/* exchanged with the message base on the top of the stack (most recently */ +/* pushed. */ +/* If the current message base is not open, the SMB_STACK_PUSH and */ +/* SMB_STACK_XCHNG operations do nothing */ +/* Returns 0 on success, non-zero if stack full. */ +/* If operation is SMB_STACK_POP or SMB_STACK_XCHNG, it always returns 0. */ +/****************************************************************************/ +int SMBCALL smb_stack(smb_t* smb, int op) +{ + static smb_t stack[SMB_STACK_LEN]; + static int stack_idx; + smb_t tmp_smb; + + if(op==SMB_STACK_PUSH) { + if(stack_idx>=SMB_STACK_LEN) { + sprintf(smb->last_error,"SMB stack overflow"); + return(1); + } + if(smb->shd_fp==NULL || smb->sdt_fp==NULL || smb->sid_fp==NULL) + return(0); /* Msg base not open */ + memcpy(&stack[stack_idx],smb,sizeof(smb_t)); + stack_idx++; + return(0); + } + /* pop or xchng */ + if(!stack_idx) /* Nothing on the stack, so do nothing */ + return(0); + if(op==SMB_STACK_XCHNG) { + if(smb->shd_fp==NULL) + return(0); + memcpy(&tmp_smb,smb,sizeof(smb_t)); + } + + stack_idx--; + memcpy(smb,&stack[stack_idx],sizeof(smb_t)); + if(op==SMB_STACK_XCHNG) { + memcpy(&stack[stack_idx],&tmp_smb,sizeof(smb_t)); + stack_idx++; + } + return(0); +} + +/****************************************************************************/ +/* Truncates header file */ +/* Retrys for smb.retry_time number of seconds */ +/* Return 0 on success, non-zero otherwise */ +/****************************************************************************/ +int SMBCALL smb_trunchdr(smb_t* smb) +{ + time32_t start = 0; + + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + rewind(smb->shd_fp); + while(1) { + if(!chsize(fileno(smb->shd_fp),0L)) + break; + if(errno!=EACCES && errno!=EAGAIN) { + sprintf(smb->last_error,"%d changing header file size",errno); + return(-1); + } + if(!start) + start = gtime(NULL); + else + if(gtime(NULL)-start >= smb->retry_time) { /* Time-out */ + sprintf(smb->last_error,"timeout changing header file size (retry_time=%d)" + ,smb->retry_time); + return(-2); + } + usleep(smb->retry_delay); + } + return(0); +} + +/*********************************/ +/* Message Base Header Functions */ +/*********************************/ + +/****************************************************************************/ +/* Attempts for smb.retry_time number of seconds to lock the msg base hdr */ +/****************************************************************************/ +int SMBCALL smb_locksmbhdr(smb_t* smb) +{ + time32_t start = 0; + + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + while(1) { + if(lock(fileno(smb->shd_fp),0L,sizeof(smbhdr_t)+sizeof(smbstatus_t))==0) { + smb->locked=1; /* TRUE */ + return(0); + } + if(!start) + start = gtime(NULL); + else + if(gtime(NULL)-start >= smb->retry_time) + break; + /* In case we've already locked it */ + if(unlock(fileno(smb->shd_fp),0L,sizeof(smbhdr_t)+sizeof(smbstatus_t))==0) + smb->locked=0; /* FALSE */ + usleep(smb->retry_delay); + } + sprintf(smb->last_error,"timeout locking header"); + return(-1); +} + +/****************************************************************************/ +/* Read the SMB header from the header file and place into smb.status */ +/****************************************************************************/ +int SMBCALL smb_getstatus(smb_t* smb) +{ + int i; + + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + setvbuf(smb->shd_fp,smb->shd_buf,_IONBF,SHD_BLOCK_LEN); + clearerr(smb->shd_fp); + fseek(smb->shd_fp,sizeof(smbhdr_t),SEEK_SET); + i=fread(&(smb->status),1,sizeof(smbstatus_t),smb->shd_fp); + setvbuf(smb->shd_fp,smb->shd_buf,_IOFBF,SHD_BLOCK_LEN); + if(i==sizeof(smbstatus_t)) + return(0); + sprintf(smb->last_error,"read %d instead of %d",i,(int)sizeof(smbstatus_t)); + return(1); +} + +/****************************************************************************/ +/* Writes message base header */ +/****************************************************************************/ +int SMBCALL smb_putstatus(smb_t* smb) +{ + int i; + + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + clearerr(smb->shd_fp); + fseek(smb->shd_fp,sizeof(smbhdr_t),SEEK_SET); + i=fwrite(&(smb->status),1,sizeof(smbstatus_t),smb->shd_fp); + fflush(smb->shd_fp); + if(i==sizeof(smbstatus_t)) + return(0); + sprintf(smb->last_error,"wrote %d instead of %d",i,(int)sizeof(smbstatus_t)); + return(1); +} + +/****************************************************************************/ +/* Unlocks previously locks message base header */ +/****************************************************************************/ +int SMBCALL smb_unlocksmbhdr(smb_t* smb) +{ + int result; + + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + result = unlock(fileno(smb->shd_fp),0L,sizeof(smbhdr_t)+sizeof(smbstatus_t)); + if(result==0) + smb->locked=0; /* FALSE */ + return(result); +} + +/********************************/ +/* Individual Message Functions */ +/********************************/ + +/****************************************************************************/ +/* Attempts for smb.retry_time number of seconds to lock the hdr for 'msg' */ +/****************************************************************************/ +int SMBCALL smb_lockmsghdr(smb_t* smb, smbmsg_t* msg) +{ + time32_t start = 0; + + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + while(1) { + if(!lock(fileno(smb->shd_fp),msg->idx.offset,sizeof(msghdr_t))) + return(0); + if(!start) + start = gtime(NULL); + else + if(gtime(NULL)-start >= smb->retry_time) + break; + /* In case we've already locked it */ + unlock(fileno(smb->shd_fp),msg->idx.offset,sizeof(msghdr_t)); + usleep(smb->retry_delay); + } + sprintf(smb->last_error,"timeout locking header"); + return(-1); +} + +/****************************************************************************/ +/* Fills msg->idx with message index based on msg->hdr.number */ +/* OR if msg->hdr.number is 0, based on msg->offset (record offset). */ +/* if msg.hdr.number does not equal 0, then msg->offset is filled too. */ +/* Either msg->hdr.number or msg->offset must be initialized before */ +/* calling this function */ +/* Returns 1 if message number wasn't found, 0 if it was */ +/****************************************************************************/ +int SMBCALL smb_getmsgidx(smb_t* smb, smbmsg_t* msg) +{ + idxrec_t idx; + uint32_t l,length,total,bot,top; + + if(smb->sid_fp==NULL) { + sprintf(smb->last_error,"index not open"); + return(SMB_ERR_NOT_OPEN); + } + clearerr(smb->sid_fp); + if(!msg->hdr.number) { + fseek(smb->sid_fp,msg->offset*sizeof(idxrec_t),SEEK_SET); + if(!fread(&msg->idx,sizeof(idxrec_t),1,smb->sid_fp)) { + sprintf(smb->last_error,"reading index"); + return(1); + } + return(0); + } + + length=filelength(fileno(smb->sid_fp)); + if(!length) { + sprintf(smb->last_error,"invalid index file length: %d",length); + return(1); + } + total=length/sizeof(idxrec_t); + if(!total) { + sprintf(smb->last_error,"invalid index file length: %d",length); + return(1); + } + + bot=0; + top=total; + l=total/2; /* Start at middle index */ + while(1) { + fseek(smb->sid_fp,l*sizeof(idxrec_t),SEEK_SET); + if(!fread(&idx,sizeof(idxrec_t),1,smb->sid_fp)) { + sprintf(smb->last_error,"reading index"); + return(1); + } + if(bot==top-1 && idx.number!=msg->hdr.number) { + sprintf(smb->last_error,"msg %d not found",msg->hdr.number); + return(1); + } + if(idx.number>msg->hdr.number) { + top=l; + l=bot+((top-bot)/2); + continue; + } + if(idx.numberhdr.number) { + bot=l; + l=top-((top-bot)/2); + continue; + } + break; + } + msg->idx=idx; + msg->offset=l; + return(0); +} + +/****************************************************************************/ +/* Reads the first index record in the open message base */ +/****************************************************************************/ +int SMBCALL smb_getfirstidx(smb_t* smb, idxrec_t *idx) +{ + if(smb->sid_fp==NULL) { + sprintf(smb->last_error,"index not open"); + return(SMB_ERR_NOT_OPEN); + } + clearerr(smb->sid_fp); + fseek(smb->sid_fp,0,SEEK_SET); + if(!fread(idx,sizeof(idxrec_t),1,smb->sid_fp)) { + sprintf(smb->last_error,"reading index"); + return(-2); + } + return(0); +} + +/****************************************************************************/ +/* Reads the last index record in the open message base */ +/****************************************************************************/ +int SMBCALL smb_getlastidx(smb_t* smb, idxrec_t *idx) +{ + int32_t length; + + if(smb->sid_fp==NULL) { + sprintf(smb->last_error,"index not open"); + return(SMB_ERR_NOT_OPEN); + } + clearerr(smb->sid_fp); + length=filelength(fileno(smb->sid_fp)); + if(lengthlast_error,"invalid index file length: %d",length); + return(-1); + } + fseek(smb->sid_fp,length-sizeof(idxrec_t),SEEK_SET); + if(!fread(idx,sizeof(idxrec_t),1,smb->sid_fp)) { + sprintf(smb->last_error,"reading index"); + return(-2); + } + return(0); +} + +/****************************************************************************/ +/* Figures out the total length of the header record for 'msg' */ +/* Returns length */ +/****************************************************************************/ +uint SMBCALL smb_getmsghdrlen(smbmsg_t* msg) +{ + int i; + + /* fixed portion */ + msg->hdr.length=sizeof(msghdr_t); + /* data fields */ + msg->hdr.length+=msg->hdr.total_dfields*sizeof(dfield_t); + /* header fields */ + for(i=0;itotal_hfields;i++) { + msg->hdr.length+=sizeof(hfield_t); + msg->hdr.length+=msg->hfield[i].length; + } + return(msg->hdr.length); +} + +/****************************************************************************/ +/* Figures out the total length of the data buffer for 'msg' */ +/* Returns length */ +/****************************************************************************/ +uint32_t SMBCALL smb_getmsgdatlen(smbmsg_t* msg) +{ + int i; + uint32_t length=0L; + + for(i=0;ihdr.total_dfields;i++) + length+=msg->dfield[i].length; + return(length); +} + +/****************************************************************************/ +/* Read header information into 'msg' structure */ +/* msg->idx.offset must be set before calling this function */ +/* Must call smb_freemsgmem() to free memory allocated for var len strs */ +/* Returns 0 on success, non-zero if error */ +/****************************************************************************/ +int SMBCALL smb_getmsghdr(smb_t* smb, smbmsg_t* msg) +{ + hfield_t *vp; + void **vpp; + uint16_t i; + uint32_t l, offset; + idxrec_t idx; + + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + rewind(smb->shd_fp); + fseek(smb->shd_fp,msg->idx.offset,SEEK_SET); + idx=msg->idx; + offset=msg->offset; + memset(msg,0,sizeof(smbmsg_t)); + msg->idx=idx; + msg->offset=offset; + if(!fread(&msg->hdr,sizeof(msghdr_t),1,smb->shd_fp)) { + sprintf(smb->last_error,"reading msg header"); + return(-1); + } + if(memcmp(msg->hdr.id,SHD_HEADER_ID,LEN_HEADER_ID)) { + sprintf(smb->last_error,"corrupt message header ID: %.*s",LEN_HEADER_ID,msg->hdr.id); + return(-2); + } + if(msg->hdr.version<0x110) { + sprintf(smb->last_error,"insufficient header version: %X",msg->hdr.version); + return(-9); + } + l=sizeof(msghdr_t); + if(msg->hdr.total_dfields && (msg->dfield + =(dfield_t *)MALLOC(sizeof(dfield_t)*msg->hdr.total_dfields))==NULL) { + smb_freemsgmem(msg); + sprintf(smb->last_error,"malloc failure of %d bytes for %d data fields" + ,(int)sizeof(dfield_t)*msg->hdr.total_dfields, msg->hdr.total_dfields); + return(-3); + } + i=0; + while(ihdr.total_dfields && lhdr.length) { + if(!fread(&msg->dfield[i],sizeof(dfield_t),1,smb->shd_fp)) { + smb_freemsgmem(msg); + sprintf(smb->last_error,"reading data field %d",i); + return(-4); + } + i++; + l+=sizeof(dfield_t); + } + if(ihdr.total_dfields) { + smb_freemsgmem(msg); + sprintf(smb->last_error,"insufficient data fields read (%d instead of %d)" + ,i,msg->hdr.total_dfields); + return(-8); + } + while(lhdr.length) { + i=msg->total_hfields; + if((vpp=(void* *)REALLOC(msg->hfield_dat,sizeof(void* )*(i+1)))==NULL) { + smb_freemsgmem(msg); + sprintf(smb->last_error + ,"realloc failure of %d bytes for header field data" + ,(int)sizeof(void*)*(i+1)); + return(-3); + } + msg->hfield_dat=vpp; + if((vp=(hfield_t *)REALLOC(msg->hfield,sizeof(hfield_t)*(i+1)))==NULL) { + smb_freemsgmem(msg); + sprintf(smb->last_error + ,"realloc failure of %d bytes for header fields" + ,(int)sizeof(hfield_t)*(i+1)); + return(-3); + } + msg->hfield=vp; + msg->total_hfields++; + if(!fread(&msg->hfield[i],sizeof(hfield_t),1,smb->shd_fp)) { + smb_freemsgmem(msg); + sprintf(smb->last_error,"reading header field"); + return(-5); + } + l+=sizeof(hfield_t); + if((msg->hfield_dat[i]=(char*)MALLOC(msg->hfield[i].length+1)) + ==NULL) { /* Allocate 1 extra for NULL terminator */ + sprintf(smb->last_error + ,"malloc failure of %d bytes for header field %d" + ,msg->hfield[i].length+1, i); + smb_freemsgmem(msg); /* or 0 length field */ + return(-3); + } + memset(msg->hfield_dat[i],0,msg->hfield[i].length+1); /* init to NULL */ + if(msg->hfield[i].length + && !fread(msg->hfield_dat[i],msg->hfield[i].length,1,smb->shd_fp)) { + smb_freemsgmem(msg); + sprintf(smb->last_error,"reading header field data"); + return(-6); + } + switch(msg->hfield[i].type) { /* convenience variables */ + case SENDER: + if(!msg->from) { + msg->from=(char *)msg->hfield_dat[i]; + break; + } + case FORWARDED: /* fall through */ + msg->forwarded=1; + break; + case SENDERAGENT: + if(!msg->forwarded) + msg->from_agent=*(uint16_t *)msg->hfield_dat[i]; + break; + case SENDEREXT: + if(!msg->forwarded) + msg->from_ext=(char *)msg->hfield_dat[i]; + break; + case SENDERNETTYPE: + if(!msg->forwarded) + msg->from_net.type=*(uint16_t *)msg->hfield_dat[i]; + break; + case SENDERNETADDR: + if(!msg->forwarded) + msg->from_net.addr=(char *)msg->hfield_dat[i]; + break; + case REPLYTO: + msg->replyto=(char *)msg->hfield_dat[i]; + break; + case REPLYTOEXT: + msg->replyto_ext=(char *)msg->hfield_dat[i]; + break; + case REPLYTOAGENT: + msg->replyto_agent=*(uint16_t *)msg->hfield_dat[i]; + break; + case REPLYTONETTYPE: + msg->replyto_net.type=*(uint16_t *)msg->hfield_dat[i]; + break; + case REPLYTONETADDR: + msg->replyto_net.addr=(char *)msg->hfield_dat[i]; + break; + case RECIPIENT: + msg->to=(char *)msg->hfield_dat[i]; + break; + case RECIPIENTEXT: + msg->to_ext=(char *)msg->hfield_dat[i]; + break; + case RECIPIENTAGENT: + msg->to_agent=*(uint16_t *)msg->hfield_dat[i]; + break; + case RECIPIENTNETTYPE: + msg->to_net.type=*(uint16_t *)msg->hfield_dat[i]; + break; + case RECIPIENTNETADDR: + msg->to_net.addr=(char *)msg->hfield_dat[i]; + break; + case SUBJECT: + msg->subj=(char *)msg->hfield_dat[i]; + break; + case RFC822MSGID: + msg->id=(char *)msg->hfield_dat[i]; + break; + case RFC822REPLYID: + msg->reply_id=(char *)msg->hfield_dat[i]; + break; + case USENETPATH: + msg->path=(char *)msg->hfield_dat[i]; + break; + case USENETNEWSGROUPS: + msg->newsgroups=(char *)msg->hfield_dat[i]; + break; + case FIDOMSGID: + msg->ftn_msgid=(char *)msg->hfield_dat[i]; + break; + case FIDOREPLYID: + msg->ftn_reply=(char *)msg->hfield_dat[i]; + break; + case FIDOAREA: + msg->ftn_area=(char *)msg->hfield_dat[i]; + break; + case FIDOPID: + msg->ftn_pid=(char *)msg->hfield_dat[i]; + break; + case FIDOFLAGS: + msg->ftn_flags=(char *)msg->hfield_dat[i]; + break; + + } + l+=msg->hfield[i].length; + } + + if(!msg->from || !msg->to || !msg->subj) { + sprintf(smb->last_error,"missing required header field (from/to/subj)"); + smb_freemsgmem(msg); + return(-7); + } + return(0); +} + +/****************************************************************************/ +/* Frees memory allocated for 'msg' */ +/****************************************************************************/ +void SMBCALL smb_freemsgmem(smbmsg_t* msg) +{ + uint16_t i; + + if(msg->dfield) { + FREE(msg->dfield); + msg->dfield=NULL; + } + for(i=0;itotal_hfields;i++) + if(msg->hfield_dat[i]) { + FREE(msg->hfield_dat[i]); + msg->hfield_dat[i]=NULL; + } + msg->total_hfields=0; + if(msg->hfield) { + FREE(msg->hfield); + msg->hfield=NULL; + } + if(msg->hfield_dat) { + FREE(msg->hfield_dat); + msg->hfield_dat=NULL; + } +} + +/****************************************************************************/ +/* Copies memory allocated for 'srcmsg' to 'msg' */ +/****************************************************************************/ +int SMBCALL smb_copymsgmem(smbmsg_t* msg, smbmsg_t* srcmsg) +{ + int i; + + memcpy(msg,srcmsg,sizeof(smbmsg_t)); + + /* data field types/lengths */ + if((msg->dfield=(dfield_t *)MALLOC(msg->hdr.total_dfields*sizeof(dfield_t)))==NULL) + return(1); + memcpy(msg->dfield,srcmsg->dfield,msg->hdr.total_dfields*sizeof(dfield_t)); + + /* header field types/lengths */ + if((msg->hfield=(hfield_t *)MALLOC(msg->total_hfields*sizeof(hfield_t)))==NULL) + return(2); + memcpy(msg->hfield,srcmsg->hfield,msg->total_hfields*sizeof(hfield_t)); + + /* header field data */ + if((msg->hfield_dat=(void* *)MALLOC(msg->total_hfields*sizeof(void*)))==NULL) + return(3); + + for(i=0;itotal_hfields;i++) { + if((msg->hfield_dat[i]=(char*)MALLOC(msg->hfield[i].length))==NULL) + return(4); + memcpy(msg->hfield_dat[i],srcmsg->hfield_dat[i],msg->hfield[i].length); + } + + return(0); +} + +/****************************************************************************/ +/* Unlocks header for 'msg' */ +/****************************************************************************/ +int SMBCALL smb_unlockmsghdr(smb_t* smb, smbmsg_t* msg) +{ + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + return(unlock(fileno(smb->shd_fp),msg->idx.offset,sizeof(msghdr_t))); +} + + +/****************************************************************************/ +/* Adds a header field to the 'msg' structure (in memory only) */ +/****************************************************************************/ +int SMBCALL smb_hfield(smbmsg_t* msg, uint16_t type, size_t length, void* data) +{ + hfield_t* vp; + void* *vpp; + int i; + + i=msg->total_hfields; + if((vp=(hfield_t *)REALLOC(msg->hfield,sizeof(hfield_t)*(i+1)))==NULL) + return(1); + + msg->hfield=vp; + if((vpp=(void* *)REALLOC(msg->hfield_dat,sizeof(void* )*(i+1)))==NULL) + return(2); + + msg->hfield_dat=vpp; + msg->total_hfields++; + msg->hfield[i].type=type; + msg->hfield[i].length=length; + if(length) { + if((msg->hfield_dat[i]=(void* )MALLOC(length))==NULL) + return(4); + memcpy(msg->hfield_dat[i],data,length); + } + else + msg->hfield_dat[i]=NULL; + return(0); +} + +/****************************************************************************/ +/* Searches for a specific header field (by type) and returns it */ +/****************************************************************************/ +void* SMBCALL smb_get_hfield(smbmsg_t* msg, uint16_t type, hfield_t* hfield) +{ + int i; + + for(i=0;itotal_hfields;i++) + if(msg->hfield[i].type == type) { + if(hfield != NULL) + hfield = &msg->hfield[i]; + return(msg->hfield_dat[i]); + } + + return(NULL); +} + +/****************************************************************************/ +/* Adds a data field to the 'msg' structure (in memory only) */ +/* Automatically figures out the offset into the data buffer from existing */ +/* dfield lengths */ +/****************************************************************************/ +int SMBCALL smb_dfield(smbmsg_t* msg, uint16_t type, uint32_t length) +{ + dfield_t* vp; + int i,j; + + i=msg->hdr.total_dfields; + if((vp=(dfield_t *)REALLOC(msg->dfield,sizeof(dfield_t)*(i+1)))==NULL) + return(1); + + msg->dfield=vp; + msg->hdr.total_dfields++; + msg->dfield[i].type=type; + msg->dfield[i].length=length; + for(j=msg->dfield[i].offset=0;jdfield[i].offset+=msg->dfield[j].length; + return(0); +} + +/****************************************************************************/ +/* Checks CRC history file for duplicate crc. If found, returns 1. */ +/* If no dupe, adds to CRC history and returns 0, or negative if error. */ +/****************************************************************************/ +int SMBCALL smb_addcrc(smb_t* smb, uint32_t crc) +{ + char str[128]; + int file; + int32_t length; + uint32_t l, *buf; + time32_t start = 0; + + if(!smb->status.max_crcs) + return(0); + + sprintf(str,"%s.sch",smb->file); + while(1) { + if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYRW))!=-1) + break; + if(errno!=EACCES && errno!=EAGAIN) { + sprintf(smb->last_error,"%d opening %s", errno, str); + return(-1); + } + if(!start) + start = gtime(NULL); + else + if(gtime(NULL)-start >= smb->retry_time) { + sprintf(smb->last_error,"timeout opening %s (retry_time=%d)" + ,str,smb->retry_time); + return(-2); + } + usleep(smb->retry_delay); + } + + length=filelength(file); + if(length<0L) { + close(file); + sprintf(smb->last_error,"invalid file length: %d", length); + return(-4); + } + if((buf=(uint32_t*)MALLOC(smb->status.max_crcs*4))==NULL) { + close(file); + sprintf(smb->last_error + ,"malloc failure of %d bytes" + ,smb->status.max_crcs*4); + return(-3); + } + if((uint32_t)length>=smb->status.max_crcs*4L) { /* Reached or exceeds max crcs */ + read(file,buf,smb->status.max_crcs*4); + for(l=0;lstatus.max_crcs;l++) + if(crc==buf[l]) + break; + if(lstatus.max_crcs) { /* Dupe CRC found */ + close(file); + FREE(buf); + sprintf(smb->last_error + ,"duplicate message detected"); + return(1); + } + chsize(file,0L); /* truncate it */ + lseek(file,0L,SEEK_SET); + write(file,buf+4,(smb->status.max_crcs-1)*4); + } + + else if(length/4) { /* Less than max crcs */ + read(file,buf,length); + for(l=0;l<(uint32_t)(length/4);l++) + if(crc==buf[l]) + break; + if(l<(uint32_t)(length/4L)) { /* Dupe CRC found */ + close(file); + FREE(buf); + sprintf(smb->last_error + ,"duplicate message detected"); + return(1); + } + } + + lseek(file,0L,SEEK_END); + write(file,&crc,sizeof(crc)); /* Write to the end */ + FREE(buf); + close(file); + return(0); +} + +/****************************************************************************/ +/* Creates a new message header record in the header file. */ +/* If storage is SMB_SELFPACK, self-packing conservative allocation is used */ +/* If storage is SMB_FASTALLOC, fast allocation is used */ +/* If storage is SMB_HYPERALLOC, no allocation tables are used (fastest) */ +/* This function will UN-lock the SMB header */ +/****************************************************************************/ +int SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage) +{ + int i; + int32_t l; + + if(!smb->locked && smb_locksmbhdr(smb)) + return(1); + if(smb_getstatus(smb)) { + smb_unlocksmbhdr(smb); + return(2); + } + + if(storage!=SMB_HYPERALLOC && (i=smb_open_ha(smb))!=0) { + smb_unlocksmbhdr(smb); + return(i); + } + + msg->hdr.length=smb_getmsghdrlen(msg); + if(storage==SMB_HYPERALLOC) + l=smb_hallochdr(smb); + else if(storage==SMB_FASTALLOC) + l=smb_fallochdr(smb,msg->hdr.length); + else + l=smb_allochdr(smb,msg->hdr.length); + if(storage!=SMB_HYPERALLOC) + smb_close_ha(smb); + if(l==-1L) { + smb_unlocksmbhdr(smb); + return(-1); + } + + msg->idx.number=msg->hdr.number=smb->status.last_msg+1; + msg->idx.offset=smb->status.header_offset+l; + msg->idx.time=msg->hdr.when_imported.time; + msg->idx.attr=msg->hdr.attr; + msg->offset=smb->status.total_msgs; + i=smb_putmsg(smb,msg); + if(i==0) { /* success */ + smb->status.last_msg++; + smb->status.total_msgs++; + smb_putstatus(smb); + } + smb_unlocksmbhdr(smb); + return(i); +} + +/****************************************************************************/ +/* Writes both header and index information for msg 'msg' */ +/****************************************************************************/ +int SMBCALL smb_putmsg(smb_t* smb, smbmsg_t* msg) +{ + int i; + + i=smb_putmsghdr(smb,msg); + if(i) + return(i); + return(smb_putmsgidx(smb,msg)); +} + +/****************************************************************************/ +/* Writes index information for 'msg' */ +/* msg->idx */ +/* and msg->offset must be set prior to calling to this function */ +/* Returns 0 if everything ok */ +/****************************************************************************/ +int SMBCALL smb_putmsgidx(smb_t* smb, smbmsg_t* msg) +{ + if(smb->sid_fp==NULL) { + sprintf(smb->last_error,"index not open"); + return(SMB_ERR_NOT_OPEN); + } + clearerr(smb->sid_fp); + fseek(smb->sid_fp,msg->offset*sizeof(idxrec_t),SEEK_SET); + if(!fwrite(&msg->idx,sizeof(idxrec_t),1,smb->sid_fp)) { + sprintf(smb->last_error,"writing index"); + return(1); + } + fflush(smb->sid_fp); + return(0); +} + +/****************************************************************************/ +/* Writes header information for 'msg' */ +/* msg->hdr.length */ +/* msg->idx.offset */ +/* and msg->offset must be set prior to calling to this function */ +/* Returns 0 if everything ok */ +/****************************************************************************/ +int SMBCALL smb_putmsghdr(smb_t* smb, smbmsg_t* msg) +{ + uint16_t i; + uint32_t l; + + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + if(msg->idx.offsetidx.offsetstatus.header_offset) { + sprintf(smb->last_error,"invalid header offset: %d",msg->idx.offset); + return(-7); + } + clearerr(smb->shd_fp); + if(fseek(smb->shd_fp,msg->idx.offset,SEEK_SET)) { + sprintf(smb->last_error,"seeking to %d in index",msg->idx.offset); + return(-1); + } + + /**********************************/ + /* Set the message header ID here */ + /**********************************/ + memcpy(&msg->hdr.id,SHD_HEADER_ID,LEN_HEADER_ID); + + /************************************************/ + /* Write the fixed portion of the header record */ + /************************************************/ + if(!fwrite(&msg->hdr,sizeof(msghdr_t),1,smb->shd_fp)) { + sprintf(smb->last_error,"writing fixed portion of header record"); + return(-2); + } + + /************************************************/ + /* Write the data fields (each is fixed length) */ + /************************************************/ + for(i=0;ihdr.total_dfields;i++) + if(!fwrite(&msg->dfield[i],sizeof(dfield_t),1,smb->shd_fp)) { + sprintf(smb->last_error,"writing data field"); + return(-3); + } + + /*******************************************/ + /* Write the variable length header fields */ + /*******************************************/ + for(i=0;itotal_hfields;i++) { + if(!fwrite(&msg->hfield[i],sizeof(hfield_t),1,smb->shd_fp)) { + sprintf(smb->last_error,"writing header field"); + return(-4); + } + if(msg->hfield[i].length /* more then 0 bytes int32_t */ + && !fwrite(msg->hfield_dat[i],msg->hfield[i].length,1,smb->shd_fp)) { + sprintf(smb->last_error,"writing header field data"); + return(-5); + } + } + + l=smb_getmsghdrlen(msg); + while(l%SHD_BLOCK_LEN) { + if(fputc(0,smb->shd_fp)==EOF) { + sprintf(smb->last_error,"padding header block"); + return(-6); /* pad block with NULL */ + } + l++; + } + fflush(smb->shd_fp); + return(0); +} + +/****************************************************************************/ +/* Creates a sub-board's initial header file */ +/* Truncates and deletes other associated SMB files */ +/****************************************************************************/ +int SMBCALL smb_create(smb_t* smb) +{ + char str[128]; + smbhdr_t hdr; + + if(smb->shd_fp==NULL || smb->sdt_fp==NULL || smb->sid_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + if(filelength(fileno(smb->shd_fp))>=sizeof(smbhdr_t)+sizeof(smbstatus_t) + && smb_locksmbhdr(smb)) /* header exists, so lock it */ + return(1); + memset(&hdr,0,sizeof(smbhdr_t)); + memcpy(hdr.id,SMB_HEADER_ID,LEN_HEADER_ID); + hdr.version=SMB_VERSION; + hdr.length=sizeof(smbhdr_t)+sizeof(smbstatus_t); + smb->status.last_msg=smb->status.total_msgs=0; + smb->status.header_offset=sizeof(smbhdr_t)+sizeof(smbstatus_t); + rewind(smb->shd_fp); + fwrite(&hdr,1,sizeof(smbhdr_t),smb->shd_fp); + fwrite(&(smb->status),1,sizeof(smbstatus_t),smb->shd_fp); + rewind(smb->shd_fp); + chsize(fileno(smb->shd_fp),sizeof(smbhdr_t)+sizeof(smbstatus_t)); + fflush(smb->shd_fp); + + rewind(smb->sdt_fp); + chsize(fileno(smb->sdt_fp),0L); + rewind(smb->sid_fp); + chsize(fileno(smb->sid_fp),0L); + + sprintf(str,"%s.sda",smb->file); + remove(str); /* if it exists, delete it */ + sprintf(str,"%s.sha",smb->file); + remove(str); /* if it exists, delete it */ + sprintf(str,"%s.sch",smb->file); + remove(str); + smb_unlocksmbhdr(smb); + return(0); +} + +/****************************************************************************/ +/* Returns number of data blocks required to store "length" amount of data */ +/****************************************************************************/ +uint32_t SMBCALL smb_datblocks(uint32_t length) +{ + uint32_t blocks; + + blocks=length/SDT_BLOCK_LEN; + if(length%SDT_BLOCK_LEN) + blocks++; + return(blocks); +} + +/****************************************************************************/ +/* Returns number of header blocks required to store "length" size header */ +/****************************************************************************/ +uint32_t SMBCALL smb_hdrblocks(uint32_t length) +{ + uint32_t blocks; + + blocks=length/SHD_BLOCK_LEN; + if(length%SHD_BLOCK_LEN) + blocks++; + return(blocks); +} + +/****************************************************************************/ +/* Finds unused space in data file based on block allocation table and */ +/* marks space as used in allocation table. */ +/* File must be opened read/write DENY ALL */ +/* Returns offset to beginning of data (in bytes, not blocks) */ +/* Assumes smb_open_da() has been called */ +/* smb_close_da() should be called after */ +/* Returns negative on error */ +/****************************************************************************/ +int32_t SMBCALL smb_allocdat(smb_t* smb, uint32_t length, uint16_t headers) +{ + uint16_t i, j; + uint32_t l, blocks, offset = 0; + + if(smb->sda_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + blocks=smb_datblocks(length); + j=0; /* j is consecutive unused block counter */ + fflush(smb->sda_fp); + rewind(smb->sda_fp); + while(!feof(smb->sda_fp)) { + if(!fread(&i,2,1,smb->sda_fp)) + break; + offset+=SDT_BLOCK_LEN; + if(!i) j++; + else j=0; + if(j==blocks) { + offset-=(blocks*SDT_BLOCK_LEN); + break; + } + } + clearerr(smb->sda_fp); + fseek(smb->sda_fp,(offset/SDT_BLOCK_LEN)*2L,SEEK_SET); + for(l=0;lsda_fp)) { + sprintf(smb->last_error,"writing allocation bytes"); + return(-1); + } + fflush(smb->sda_fp); + return(offset); +} + +/****************************************************************************/ +/* Allocates space for data, but doesn't search for unused blocks */ +/* Returns negative on error */ +/****************************************************************************/ +int32_t SMBCALL smb_fallocdat(smb_t* smb, uint32_t length, uint16_t headers) +{ + uint32_t l,blocks,offset; + + if(smb->sda_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + fflush(smb->sda_fp); + clearerr(smb->sda_fp); + blocks=smb_datblocks(length); + fseek(smb->sda_fp,0L,SEEK_END); + offset=(ftell(smb->sda_fp)/2L)*SDT_BLOCK_LEN; + for(l=0;lsda_fp)) + break; + fflush(smb->sda_fp); + if(llast_error,"writing allocation bytes"); + return(-1L); + } + return(offset); +} + +/****************************************************************************/ +/* De-allocates space for data */ +/* Returns non-zero on error */ +/****************************************************************************/ +int SMBCALL smb_freemsgdat(smb_t* smb, uint32_t offset, uint32_t length, uint16_t headers) +{ + int da_opened = 0; + int retval = 0; + uint16_t i; + uint32_t l, blocks; + + if(smb->status.attr&SMB_HYPERALLOC) /* do nothing */ + return(0); + + blocks=smb_datblocks(length); + + if(smb->sda_fp==NULL) { + if((i=smb_open_da(smb))!=0) + return(i); + da_opened=1; + } + + clearerr(smb->sda_fp); + for(l=0;lsda_fp,((offset/SDT_BLOCK_LEN)+l)*2L,SEEK_SET)) { + sprintf(smb->last_error + ,"seeking to %ld of allocation file" + ,((offset/SDT_BLOCK_LEN)+l)*2L); + retval=1; + break; + } + if(!fread(&i,2,1,smb->sda_fp)) { + sprintf(smb->last_error,"reading allocation bytes"); + retval=2; + break; + } + if(!headers || headers>i) + i=0; /* don't want to go negative */ + else + i-=headers; + if(fseek(smb->sda_fp,-2L,SEEK_CUR)) { + sprintf(smb->last_error,"seeking backwards 2 bytes in allocation file"); + retval=3; + break; + } + if(!fwrite(&i,2,1,smb->sda_fp)) { + sprintf(smb->last_error,"writing allocation bytes"); + retval=4; + break; + } + } + fflush(smb->sda_fp); + if(da_opened) + smb_close_da(smb); + return(retval); +} + +/****************************************************************************/ +/* Adds to data allocation records for blocks starting at 'offset' */ +/* Returns non-zero on error */ +/****************************************************************************/ +int SMBCALL smb_incdat(smb_t* smb, uint32_t offset, uint32_t length, uint16_t headers) +{ + uint16_t i; + uint32_t l, blocks; + + if(smb->sda_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + clearerr(smb->sda_fp); + blocks=smb_datblocks(length); + for(l=0;lsda_fp,((offset/SDT_BLOCK_LEN)+l)*2L,SEEK_SET); + if(!fread(&i,2,1,smb->sda_fp)) { + sprintf(smb->last_error,"reading allocation record"); + return(1); + } + i+=headers; + fseek(smb->sda_fp,-2L,SEEK_CUR); + if(!fwrite(&i,2,1,smb->sda_fp)) { + sprintf(smb->last_error,"writing allocation record"); + return(2); + } + } + fflush(smb->sda_fp); + return(0); +} + +/****************************************************************************/ +/* De-allocates blocks for header record */ +/* Returns non-zero on error */ +/****************************************************************************/ +int SMBCALL smb_freemsghdr(smb_t* smb, uint32_t offset, uint32_t length) +{ + uint8_t c = 0; + uint32_t l,blocks; + + if(smb->sha_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + clearerr(smb->sha_fp); + blocks=smb_hdrblocks(length); + fseek(smb->sha_fp,offset/SHD_BLOCK_LEN,SEEK_SET); + for(l=0;lsha_fp)) { + sprintf(smb->last_error,"writing allocation record"); + return(1); + } + fflush(smb->sha_fp); + return(0); +} + +/****************************************************************************/ +/* Frees all allocated header and data blocks for 'msg' */ +/****************************************************************************/ +int SMBCALL smb_freemsg(smb_t* smb, smbmsg_t* msg) +{ + int i; + uint16_t x; + + if(smb->status.attr&SMB_HYPERALLOC) /* Nothing to do */ + return(0); + + for(x=0;xhdr.total_dfields;x++) { + if((i=smb_freemsgdat(smb,msg->hdr.offset+msg->dfield[x].offset + ,msg->dfield[x].length,1))!=0) + return(i); + } + return(smb_freemsghdr(smb,msg->idx.offset-smb->status.header_offset + ,msg->hdr.length)); +} + +/****************************************************************************/ +/* Finds unused space in header file based on block allocation table and */ +/* marks space as used in allocation table. */ +/* File must be opened read/write DENY ALL */ +/* Returns offset to beginning of header (in bytes, not blocks) */ +/* Assumes smb_open_ha() has been called */ +/* smb_close_ha() should be called after */ +/* Returns -1L on error */ +/****************************************************************************/ +int32_t SMBCALL smb_allochdr(smb_t* smb, uint32_t length) +{ + uint8_t c; + uint16_t i; + uint32_t l, blocks, offset = 0; + + if(smb->sha_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + blocks=smb_hdrblocks(length); + i=0; /* i is consecutive unused block counter */ + fflush(smb->sha_fp); + rewind(smb->sha_fp); + while(!feof(smb->sha_fp)) { + if(!fread(&c,1,1,smb->sha_fp)) + break; + offset+=SHD_BLOCK_LEN; + if(!c) i++; + else i=0; + if(i==blocks) { + offset-=(blocks*SHD_BLOCK_LEN); + break; + } + } + clearerr(smb->sha_fp); + fseek(smb->sha_fp,offset/SHD_BLOCK_LEN,SEEK_SET); + c=1; + for(l=0;lsha_fp)) { + sprintf(smb->last_error,"writing allocation record"); + return(-1L); + } + fflush(smb->sha_fp); + return(offset); +} + +/****************************************************************************/ +/* Allocates space for index, but doesn't search for unused blocks */ +/* Returns -1L on error */ +/****************************************************************************/ +int32_t SMBCALL smb_fallochdr(smb_t* smb, uint32_t length) +{ + uint8_t c = 1; + uint32_t l,blocks,offset; + + if(smb->sha_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + blocks=smb_hdrblocks(length); + fflush(smb->sha_fp); + clearerr(smb->sha_fp); + fseek(smb->sha_fp,0L,SEEK_END); + offset=ftell(smb->sha_fp)*SHD_BLOCK_LEN; + for(l=0;lsha_fp)) { + sprintf(smb->last_error,"writing allocation record"); + return(-1L); + } + fflush(smb->sha_fp); + return(offset); +} + +/************************************************************************/ +/* Allocate header blocks using Hyper Allocation */ +/* this function should be most likely not be called from anywhere but */ +/* smb_addmsghdr() */ +/************************************************************************/ +int32_t SMBCALL smb_hallochdr(smb_t* smb) +{ + uint32_t l; + + if(smb->shd_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + fflush(smb->shd_fp); + fseek(smb->shd_fp,0L,SEEK_END); + l=ftell(smb->shd_fp); + if(lstatus.header_offset) /* Header file truncated?!? */ + return(smb->status.header_offset); + while((l-smb->status.header_offset)%SHD_BLOCK_LEN) /* Even block boundry */ + l++; + return(l-smb->status.header_offset); +} + +/************************************************************************/ +/* Allocate data blocks using Hyper Allocation */ +/* smb_locksmbhdr() should be called before this function and not */ +/* unlocked until all data fields for this message have been written */ +/* to the SDT file */ +/************************************************************************/ +int32_t SMBCALL smb_hallocdat(smb_t* smb) +{ + int32_t l; + + if(smb->sdt_fp==NULL) { + sprintf(smb->last_error,"msgbase not open"); + return(SMB_ERR_NOT_OPEN); + } + fflush(smb->sdt_fp); + fseek(smb->sdt_fp,0L,SEEK_END); + l=ftell(smb->sdt_fp); + if(l<=0) + return(l); + while(l%SDT_BLOCK_LEN) /* Make sure even block boundry */ + l++; + return(l); +} + + +int SMBCALL smb_feof(FILE* fp) +{ + return(feof(fp)); +} + +int SMBCALL smb_ferror(FILE* fp) +{ + return(ferror(fp)); +} + +int SMBCALL smb_fflush(FILE* fp) +{ + return(fflush(fp)); +} + +int SMBCALL smb_fgetc(FILE* fp) +{ + return(fgetc(fp)); +} + +int SMBCALL smb_fputc(int ch, FILE* fp) +{ + return(fputc(ch,fp)); +} + +int SMBCALL smb_fseek(FILE* fp, int32_t offset, int whence) +{ + return(fseek(fp,offset,whence)); +} + +int32_t SMBCALL smb_ftell(FILE* fp) +{ + return(ftell(fp)); +} + +int32_t SMBCALL smb_fgetlength(FILE* fp) +{ + return(filelength(fileno(fp))); +} + +int SMBCALL smb_fsetlength(FILE* fp, int32_t length) +{ + return(chsize(fileno(fp),length)); +} + +void SMBCALL smb_rewind(FILE* fp) +{ + rewind(fp); +} + +void SMBCALL smb_clearerr(FILE* fp) +{ + clearerr(fp); +} + +int32_t SMBCALL smb_fread(void HUGE16* buf, int32_t bytes, FILE* fp) +{ +#ifdef __FLAT__ + return(fread(buf,1,bytes,fp)); +#else + int32_t count; + + for(count=bytes;count>0x7fff;count-=0x7fff,(char*)buf+=0x7fff) + if(fread((char*)buf,1,0x7fff,fp)!=0x7fff) + return(bytes-count); + if(fread((char*)buf,1,(size_t)count,fp)!=(size_t)count) + return(bytes-count); + return(bytes); +#endif +} + +int32_t SMBCALL smb_fwrite(void HUGE16* buf, int32_t bytes, FILE* fp) +{ +#ifdef __FLAT__ + return(fwrite(buf,1,bytes,fp)); +#else + int32_t count; + + for(count=bytes;count>0x7fff;count-=0x7fff,(char*)buf+=0x7fff) + if(fwrite((char*)buf,1,0x7fff,fp)!=0x7fff) + return(bytes-count); + if(fwrite((char*)buf,1,(size_t)count,fp)!=(size_t)count) + return(bytes-count); + return(bytes); +#endif +} + +/* End of SMBLIB.C */ diff --git a/goldlib/uulib/fptools.cpp b/goldlib/uulib/fptools.cpp new file mode 100644 index 0000000..2f21553 --- /dev/null +++ b/goldlib/uulib/fptools.cpp @@ -0,0 +1,514 @@ +/* + * fptools.c, some helper functions for getcgi.c and uu(en|de)view + * + * Distributed under the terms of the GNU General Public License. + * Use and be happy. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef SYSTEM_WINDLL +#include +#endif +#ifdef SYSTEM_OS2 +#include +#endif + +/* + * This file provides replacements for some handy functions that aren't + * available on all systems, like most of the functions. They + * should behave exactly as their counterparts. There are also extensions + * that aren't portable at all (like strirstr etc.). + * The proper behaviour in a configure script is as follows: + * AC_CHECK_FUNC(strrchr,AC_DEFINE(strrchr,_FP_strrchr)) + * This way, the (probably less efficient) replacements will only be used + * where it is not provided by the default libraries. Be aware that this + * does not work with replacements that just shadow wrong behaviour (like + * _FP_free) or provide extended functionality (_FP_gets). + * The above is not used in the uuenview/uudeview configuration script, + * since both only use the replacement functions in non-performance-cri- + * tical sections (except for _FP_tempnam and _FP_strerror, where some + * functionality of the original would be lost). + */ + +#include + +#ifdef STDC_HEADERS +#include +#include +#endif +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_MEMORY_H +#include +#endif + +#include +#include +#include + +#if 0 +#ifdef SYSTEM_WINDLL +BOOL _export WINAPI +DllEntryPoint (HINSTANCE hInstance, DWORD seginfo, + LPVOID lpCmdLine) +{ + /* Don't do anything, so just return true */ + return TRUE; +} +#endif +#endif + +char * fptools_id = "$Id$"; + +/* + * some versions of free can't handle a NULL pointer properly + * (ANSI says, free ignores a NULL pointer, but some machines + * prefer to SIGSEGV on it) + */ + +void TOOLEXPORT +_FP_free (void *ptr) +{ + if (ptr) free (ptr); +} + +/* + * This is non-standard, so I'm defining my own + */ + +char * TOOLEXPORT +_FP_strdup (char *string) +{ + char *result; + + if (string == NULL) + return NULL; + + if ((result = (char *) malloc (strlen (string) + 1)) == NULL) + return NULL; + + strcpy (result, string); + return result; +} + +/* + * limited-length string copy. this function behaves differently from + * the original in that the dest string is always terminated with a + * NULL character. + */ + +char * TOOLEXPORT +_FP_strncpy (char *dest, char *src, int length) +{ + char *odest=dest; + if (src == NULL || dest == NULL || length-- <= 0) + return dest; + + while (length-- && *src) + *dest++ = *src++; + + *dest++ = '\0'; + return odest; +} + +/* + * duplicate a memory area + */ + +void * TOOLEXPORT +_FP_memdup (void *ptr, int len) +{ + void *result; + + if (ptr == NULL) + return NULL; + + if ((result = malloc (len)) == NULL) + return NULL; + + memcpy (result, ptr, len); + return result; +} + +/* + * case-insensitive compare + */ + +int TOOLEXPORT +_FP_stricmp (char *str1, char *str2) +{ + if (str1==NULL || str2==NULL) + return -1; + + while (*str1) { + if (g_tolower(*str1) != g_tolower(*str2)) + break; + str1++; + str2++; + } + return (g_tolower (*str1) - g_tolower (*str2)); +} + +int TOOLEXPORT +_FP_strnicmp (char *str1, char *str2, int count) +{ + if (str1==NULL || str2==NULL) + return -1; + + while (*str1 && count) { + if (g_tolower(*str1) != g_tolower(*str2)) + break; + str1++; + str2++; + count--; + } + return count ? (g_tolower (*str1) - g_tolower (*str2)) : 0; +} + +/* + * autoconf says this function might be a compatibility problem + */ + +char * TOOLEXPORT +_FP_strstr (char *str1, char *str2) +{ + char *ptr1, *ptr2; + + if (str1==NULL) + return NULL; + if (str2==NULL) + return str1; + + while (*(ptr1=str1)) { + for (ptr2=str2; + *ptr1 && *ptr2 && *ptr1==*ptr2; + ptr1++, ptr2++) + /* empty loop */ ; + + if (*ptr2 == '\0') + return str1; + str1++; + } + return NULL; +} + +char * TOOLEXPORT +_FP_strpbrk (char *str, char *accept) +{ + char *ptr; + + if (str == NULL) + return NULL; + if (accept == NULL || *accept == '\0') + return str; + + for (; *str; str++) + for (ptr=accept; *ptr; ptr++) + if (*str == *ptr) + return str; + + return NULL; +} + +/* + * autoconf also complains about this one + */ + +char * TOOLEXPORT +_FP_strtok (char *str1, char *str2) +{ + static char *optr; + char *ptr; + + if (str2 == NULL) + return NULL; + + if (str1) { + optr = str1; + } + else { + if (*optr == '\0') + return NULL; + } + + while (*optr && strchr (str2, *optr)) /* look for beginning of token */ + optr++; + + if (*optr == '\0') /* no token found */ + return NULL; + + ptr = optr; + while (*optr && strchr (str2, *optr) == NULL) /* look for end of token */ + optr++; + + if (*optr) { + *optr++ = '\0'; + } + return ptr; +} + +/* + * case insensitive strstr. + */ + +char * TOOLEXPORT +_FP_stristr (char *str1, char *str2) +{ + char *ptr1, *ptr2; + + if (str1==NULL) + return NULL; + if (str2==NULL) + return str1; + + while (*(ptr1=str1)) { + for (ptr2=str2; + *ptr1 && *ptr2 && g_tolower(*ptr1)==g_tolower(*ptr2); + ptr1++, ptr2++) + /* empty loop */ ; + + if (*ptr2 == '\0') + return str1; + str1++; + } + return NULL; +} + +/* + * Nice fake of the real (non-standard) one + */ + +char * TOOLEXPORT +_FP_strrstr (char *ptr, char *str) +{ + char *found=NULL, *pstr, *iter=ptr; + + if (ptr==NULL || str==NULL) + return NULL; + + if (*str == '\0') + return ptr; + + while ((pstr = _FP_strstr (iter, str)) != NULL) { + found = pstr; + iter = pstr + 1; + } + return found; +} + +char * TOOLEXPORT +_FP_strirstr (char *ptr, char *str) +{ + char *found=NULL, *iter=ptr, *pstr; + + if (ptr==NULL || str==NULL) + return NULL; + if (*str == '\0') + return ptr; + + while ((pstr = _FP_stristr (iter, str)) != NULL) { + found = pstr; + iter = pstr + 1; + } + return found; +} + +/* + * convert whole string to case + */ + +char * TOOLEXPORT +_FP_stoupper (char *input) +{ + char *iter = input; + + if (input == NULL) + return NULL; + + while (*iter) { + *iter = g_toupper (*iter); + iter++; + } + return input; +} + +char * TOOLEXPORT +_FP_stolower (char *input) +{ + char *iter = input; + + if (input == NULL) + return NULL; + + while (*iter) { + *iter = g_tolower (*iter); + iter++; + } + return input; +} + +/* + * string matching with wildcards + */ + +int TOOLEXPORT +_FP_strmatch (char *string, char *pattern) +{ + char *p1 = string, *p2 = pattern; + + if (pattern==NULL || string==NULL) + return 0; + + while (*p1 && *p2) { + if (*p2 == '?') { + p1++; p2++; + } + else if (*p2 == '*') { + if (*++p2 == '\0') + return 1; + while (*p1 && *p1 != *p2) + p1++; + } + else if (*p1 == *p2) { + p1++; p2++; + } + else + return 0; + } + if (*p1 || *p2) + return 0; + + return 1; +} + +char * TOOLEXPORT +_FP_strrchr (char *string, int tc) +{ + char *ptr; + + if (string == NULL) + return NULL; + + ptr = string + strlen (string) - 1; + + while (ptr != string && *ptr != tc) + ptr--; + + if (*ptr == tc) + return ptr; + + return NULL; +} + +/* + * strip directory information from a filename. Works only on DOS and + * Unix systems so far ... + */ + +char * TOOLEXPORT +_FP_cutdir (char *filename) +{ + char *ptr; + + if (filename == NULL) + return NULL; + + if ((ptr = _FP_strrchr (filename, '/')) != NULL) + ptr++; + else if ((ptr = _FP_strrchr (filename, '\\')) != NULL) + ptr++; + else + ptr = filename; + + return ptr; +} + +/* + * My own fgets function. It handles all kinds of line terminators + * properly: LF (Unix), CRLF (DOS) and CR (Mac). In all cases, the + * terminator is replaced by a single LF + */ + +char * TOOLEXPORT +_FP_fgets (char *buf, int n, FILE *stream) +{ + char *obp = buf; + int c; + + if (feof (stream)) + return NULL; + + while (--n) { + if ((c = fgetc (stream)) == EOF) { + if (ferror (stream)) + return NULL; + else { + if (obp == buf) + return NULL; + *buf = '\0'; + return obp; + } + } + if (c == '\015') { /* CR */ + /* + * Peek next character. If it's no LF, push it back. + * ungetc(EOF, stream) is handled correctly according + * to the manual page + */ + if ((c = fgetc (stream)) != '\012') + if (!feof (stream)) + ungetc (c, stream); + *buf++ = '\012'; + *buf = '\0'; + return obp; + } + else if (c == '\012') { /* LF */ + *buf++ = '\012'; + *buf = '\0'; + return obp; + } + /* + * just another standard character + */ + *buf++ = c; + } + /* + * n-1 characters already transferred + */ + *buf = '\0'; + + return obp; +} + +#if 0 +/* + * A replacement strerror function that just returns the error code + */ + +char * TOOLEXPORT +_FP_strerror (int errcode) +{ + static char number[8]; + + sprintf (number, "%03d", errcode); + + return number; +} + +/* + * tempnam is not ANSI, but tmpnam is. Ignore the prefix here. + */ + +char * TOOLEXPORT +_FP_tempnam (char *dir, char *pfx) +{ + return _FP_strdup (tmpnam (NULL)); +} +#endif diff --git a/goldlib/uulib/uucheck.cpp b/goldlib/uulib/uucheck.cpp new file mode 100644 index 0000000..377ebbf --- /dev/null +++ b/goldlib/uulib/uucheck.cpp @@ -0,0 +1,1467 @@ +/* + * This file is part of uudeview, the simple and friendly multi-part multi- + * file uudecoder program (c) 1994-2001 by Frank Pilhofer. The author may + * be contacted at fp@fpx.de + * + * 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 2 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef SYSTEM_WINDLL +#include +#endif +#ifdef SYSTEM_OS2 +#include +#endif + +/* + * uucheck.c + * + * Various checking and processing of one input part + **/ + +#include + +#ifdef STDC_HEADERS +#include +#include +#endif +#ifdef HAVE_MALLOC_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_MEMORY_H +#include +#endif + +#include +#include +#include +#include +#include +#include + +char * uucheck_id = "$Id$"; + +/* + * Arbitrary number. This is the maximum number of part numbers we + * store for our have-parts and missing-parts lists + */ + +#define MAXPLIST 256 + + +/* + * forward declarations of local functions + */ + +static char * UUGetFileName(char *, char *, char *); +static int UUGetPartNo(char *, char **, char **); + +/* + * State of Scanner function and PreProcessPart + */ + +int lastvalid, lastenc, nofnum; +char *uucheck_lastname; +char *uucheck_tempname; +static int lastpart = 0; +static char *nofname = "UNKNOWN"; + +/* + * special characters we allow an unquoted filename to have + */ + +char *uufnchars = "._-~!$@"; + +/* + * Policy for extracting a part number from the subject line. + * usually, look for part numbers in () brackets first, then in [] + */ + +static char *brackchr[] = { + "()[]", "[]()" +}; + +/* + * Extract a filename from the subject line. We need anything to identify + * the name of the program for sorting. If a nice filename cannot be found, + * the subject line itself is used + * ptonum is, if not NULL, a pointer to the part number in the subject line, + * so that it won't be used as filename. + **/ + +static char * +UUGetFileName (char *subject, char *ptonum, char *ptonend) +{ + char *ptr = subject, *iter, *result, *part; + int count, length=0, alflag=0; + +/* + * If this file has no subject line, assume it is the next part of the + * previous file (this is done in UUPreProcessPart) + **/ + + if (subject == NULL) + return NULL; + +/* + * If the subject starts with 'Re', it is ignored + * REPosts or RETries are not ignored! + **/ + + if (uu_ignreply && + (subject[0] == 'R' || subject[0] == 'r') && + (subject[1] == 'E' || subject[1] == 'e') && + (subject[2] == ':' || subject[2] == ' ')) { + return NULL; + } + +/* + * Ignore a "Repost" prefix of the subject line. We don't want to get + * a file named "Repost" :-) + **/ + + if (_FP_strnicmp (subject, "repost", 6) == 0) + subject += 6; + if (_FP_strnicmp (subject, "re:", 3) == 0) + subject += 3; + + while (*subject == ' ' || *subject == ':') subject++; + + part = _FP_stristr (subject, "part"); + if (part == subject) { + subject += 4; + while (*subject == ' ') subject++; + } + + /* + * If the file was encoded by uuenview, then the filename is enclosed + * in [brackets]. But check what's inside these bracket's, try not to + * fall for something other than a filename + */ + + ptr = subject; + while ((iter = strchr (ptr, '[')) != NULL) { + if (strchr (iter, ']') == NULL) { + ptr = iter + 1; + continue; + } + iter++; + while (isspace (*iter)) + iter++; + count = length = alflag = 0; + while (iter[count] && + (isalnum (iter[count]) || strchr (uufnchars, iter[count])!=NULL)) { + if (g_isalpha(iter[count])) + alflag++; + count++; + } + if (count<4 || alflag==0) { + ptr = iter + 1; + continue; + } + length = count; + while (isspace (iter[count])) + count++; + if (iter[count] == ']') { + ptr = iter; + break; + } + length = 0; + ptr = iter + 1; + } + + /* + * new filename detection routine, fists mostly for files by ftp-by-email + * servers that create subject lines with ftp.host.address:/full/path/file + * on them. We look for slashes and take the filename from after the last + * one ... or at least we try to. + */ + + if (length == 0) { + ptr = subject; + while ((iter = strchr (ptr, '/')) != NULL) { + if (iter >= ptonum && iter <= ptonend) { + ptr = iter + 1; + continue; + } + count = length = 0; + iter++; + while (iter[count] && + (isalnum(iter[count])||strchr(uufnchars, iter[count])!=NULL)) + count++; + if (iter[count] == ' ' && length > 4) { + length = count; + break; + } + ptr = iter + ((count)?count:1); + } + } + + /* + * Look for two alphanumeric strings separated by a '.' + * (That's most likely a filename) + **/ + + if (length == 0) { + ptr = subject; + while (*ptr && *ptr != 0x0a && *ptr != 0x0d && ptr != part) { + iter = ptr; + count = length = alflag = 0; + + if (_FP_strnicmp (ptr, "ftp", 3) == 0) { + /* hey, that's an ftp address */ + while (g_isalpha(*ptr) || isdigit (*ptr) || *ptr == '.') + ptr++; + continue; + } + + while ((isalnum(*iter)||strchr(uufnchars, *iter)!=NULL|| + *iter=='/') && *iter && iter != ptonum && *iter != '.') { + if (g_isalpha(*iter)) + alflag = 1; + + count++; iter++; + } + if (*iter == '\0' || iter == ptonum) { + if (iter == ptonum) + ptr = ptonend; + else + ptr = iter; + + length = 0; + continue; + } + if (*iter++ != '.' || count > 32 || alflag == 0) { + ptr = iter; + length = 0; + continue; + } + if (_FP_strnicmp (iter, "edu", 3) == 0 || + _FP_strnicmp (iter, "gov", 3) == 0) { + /* hey, that's an ftp address */ + while (g_isalpha(*iter) || isdigit (*iter) || *iter == '.') + iter++; + ptr = iter; + length = 0; + continue; + } + + length += count + 1; + count = 0; + + while ((isalnum(iter[count])||strchr(uufnchars, iter[count])!=NULL|| + iter[count]=='/') && iter[count] && iter[count] != '.') + count++; + + if (iter[count]==':' && iter[count+1]=='/') { + /* looks like stuff from a mail server */ + ptr = iter + 1; + length = 0; + continue; + } + + if (count > 8 || iter == ptonum) { + ptr = iter; + length = 0; + continue; + } + + if (iter[count] != '.') { + length += count; + break; + } + + while (iter[count] && + (isalnum(iter[count])||strchr(uufnchars, iter[count])!=NULL|| + iter[count]=='/')) + count++; + + if (iter[count]==':' && iter[count+1]=='/') { + /* looks like stuff from a mail server */ + ptr = iter + 1; + length = 0; + continue; + } + + if (count < 12 && iter != ptonum) { + length += count; + break; + } + + ptr = iter; + length = 0; + } + } + + if (length == 0) { /* No filename found, use subject line for ident */ + ptr = subject; + + while (*ptr && !g_isalpha(*ptr)) + ptr++; + + while ((isalnum(ptr[length])||strchr(uufnchars,ptr[length])!=NULL|| + ptr[length] == '/') && + ptr[length] && ptr+length!=part && ptr+length!=ptonum) + length++; + + if (length) { + if (ptr[length] == '\0' || ptr[length] == 0x0a || ptr[length] == 0x0d) { + length--; + + /* + * I used to cut off digits from the end of the string, but + * let's try to live without. We want to distinguish + * DUTCH951 from DUTCH952 + * + * while ((ptr[length] == ' ' || isdigit (ptr[length])) && length > 0) + * length--; + */ + } + else { + length--; + + while (ptr[length] == ' ' && length > 0) + length--; + } + length++; + } + } + + if (length == 0) { /* Still found nothing? We need *something*! */ + ptr = nofname; + length = strlen (nofname); + } + + if ((result = (char *) malloc (length + 1)) == NULL) { + UUMessage (uucheck_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), length+1); + return NULL; + } + + memcpy (result, ptr, length); + result[length] = '\0'; + + return result; +} + +/* + * Extract the Part Number from the subject line. + * We look first for numbers in (#/#)'s, then for numbers in [#/#]'s + * and then for digits that are not part of a string. + * If we cannot find anything, assume it is the next part of the + * previous file. + * If we find a part number, we put a pointer to it in *where. This is + * done so that the UUGetFileName function doesn't accidentally use the + * part number as the file name. *whend points to the end of this part + * number. + **/ + +static int +UUGetPartNo (char *subject, char **where, char **whend) +{ + char *ptr = subject, *iter, *delim, bdel[2]=" "; + int count, length=0, bpc; + + *where = NULL; bdel[0] = ' '; + *whend = NULL; bdel[1] = '\0'; + + iter = NULL; + delim = ""; + + if (subject == NULL) + return -1; + + if (uu_ignreply && + (subject[0] == 'R' || subject[0] == 'r') && /* Ignore replies, but not */ + (subject[1] == 'E' || subject[1] == 'e') && /* reposts */ + (subject[2] == ':' || subject[2] == ' ')) + return -2; + + /* + * First try numbers in () or [] (or vice versa, according to bracket + * policy) + */ + + for (bpc=0, length=0; brackchr[uu_bracket_policy][bpc]; bpc+=2) { + ptr = subject; + while ((iter = strchr (ptr, brackchr[uu_bracket_policy][bpc])) != NULL) { + count = length = 0; iter++; + + while (*iter == ' ' || *iter == '#') + iter++; + + if (!isdigit (*iter)) { + ptr = iter; + continue; + } + while (isdigit (iter[count])) + count++; + length = count; + + if (iter[count] == '\0' || iter[count+1] == '\0') { + iter += count; + length = 0; + break; + } + if (iter[count] == brackchr[uu_bracket_policy][bpc+1]) { + *where = iter; + bdel[0] = brackchr[uu_bracket_policy][bpc+1]; + delim = bdel; + break; + } + + while (iter[count] == ' ' || iter[count] == '#' || + iter[count] == '/' || iter[count] == '\\') count++; + + if (_FP_strnicmp (iter + count, "of", 2) == 0) + count += 2; + + while (iter[count] == ' ') count++; + while (isdigit (iter[count])) count++; + while (iter[count] == ' ') count++; + + if (iter[count] == brackchr[uu_bracket_policy][bpc+1]) { + *where = iter; + bdel[0] = brackchr[uu_bracket_policy][bpc+1]; + delim = bdel; + break; + } + + length = 0; + ptr = iter; + } + if (length) + break; + } + +#if 0 /* asa */ + /* + * look for the string "part " followed by a number + */ + + if (length == 0) { + if ((iter = _FP_stristr (subject, "part ")) != NULL) { + iter += 5; + + while (isspace (*iter) || *iter == '.' || *iter == '-') + iter++; + + while (isdigit (iter[length])) + length++; + + if (length == 0) { + if (_FP_strnicmp (iter, "one", 3) == 0) length = 1; + else if (_FP_strnicmp (iter, "two", 3) == 0) length = 2; + else if (_FP_strnicmp (iter, "three", 5) == 0) length = 3; + else if (_FP_strnicmp (iter, "four", 4) == 0) length = 4; + else if (_FP_strnicmp (iter, "five", 4) == 0) length = 5; + else if (_FP_strnicmp (iter, "six", 3) == 0) length = 6; + else if (_FP_strnicmp (iter, "seven", 5) == 0) length = 7; + else if (_FP_strnicmp (iter, "eight", 5) == 0) length = 8; + else if (_FP_strnicmp (iter, "nine", 4) == 0) length = 9; + else if (_FP_strnicmp (iter, "ten", 3) == 0) length = 10; + + if (length && (*whend = strchr (iter, ' '))) { + *where = iter; + return length; + } + else + length = 0; + } + else { + *where = iter; + delim = "of"; + } + } + } +#endif /* asa */ + + /* + * look for the string "part" followed by a number + */ + + if (length == 0) { + if ((iter = _FP_stristr (subject, "part")) != NULL) { + iter += 4; + + while (isspace (*iter) || *iter == '.' || *iter == '-') + iter++; + + while (isdigit (iter[length])) + length++; + + if (length == 0) { + if (_FP_strnicmp (iter, "one", 3) == 0) length = 1; + else if (_FP_strnicmp (iter, "two", 3) == 0) length = 2; + else if (_FP_strnicmp (iter, "three", 5) == 0) length = 3; + else if (_FP_strnicmp (iter, "four", 4) == 0) length = 4; + else if (_FP_strnicmp (iter, "five", 4) == 0) length = 5; + else if (_FP_strnicmp (iter, "six", 3) == 0) length = 6; + else if (_FP_strnicmp (iter, "seven", 5) == 0) length = 7; + else if (_FP_strnicmp (iter, "eight", 5) == 0) length = 8; + else if (_FP_strnicmp (iter, "nine", 4) == 0) length = 9; + else if (_FP_strnicmp (iter, "ten", 3) == 0) length = 10; + + if (length && (*whend = strchr (iter, ' '))) { + *where = iter; + return length; + } + else + length = 0; + } + else { + *where = iter; + delim = "of"; + } + } + } + + /* + * look for [0-9]* "of" [0-9]* + */ + + if (length == 0) { + if ((iter = _FP_strirstr (subject, "of")) != NULL) { + while (iter>subject && isspace (*(iter-1))) + iter--; + if (isdigit(*(iter-1))) { + while (iter>subject && isdigit (*(iter-1))) + iter--; + if (!isdigit (*iter) && !g_isalpha(*iter) && *iter != '.') + iter++; + ptr = iter; + + while (isdigit (*ptr)) { + ptr++; length++; + } + *where = iter; + delim = "of"; + } + } + } + + /* + * look for whitespace-separated (or '/'-separated) digits + */ + + if (length == 0) { + ptr = subject; + + while (*ptr && length==0) { + while (*ptr && !isdigit (*ptr)) + ptr++; + if (isdigit (*ptr) && (ptr==subject || *ptr==' ' || *ptr=='/')) { + while (isdigit (ptr[length])) + length++; + if (ptr[length]!='\0' && ptr[length]!=' ' && ptr[length]!='/') { + ptr += length; + length = 0; + } + else { + iter = ptr; + bdel[0] = ptr[length]; + delim = bdel; + } + } + else { + while (isdigit (*ptr)) + ptr++; + } + } + } + + /* + * look for _any_ digits -- currently disabled, because it also fell + * for "part numbers" in file names + */ + +#if 0 + if (length == 0) { + count = strlen(subject) - 1; + ptr = subject; + + while (count > 0) { + if (!isdigit(ptr[count])||g_isalpha(ptr[count+1])||ptr[count+1] == '.') { + count--; + continue; + } + length = 0; + + while (count >= 0 && isdigit (ptr[count])) { + count--; length++; + } + if (count>=0 && ((g_isalpha(ptr[count]) && + (ptr[count] != 's' || ptr[count+1] != 't') && + (ptr[count] != 'n' || ptr[count+1] != 'd')) || + ptr[count] == '/' || ptr[count] == '.' || + ptr[count] == '-' || ptr[count] == '_')) { + length = 0; + continue; + } + count++; + iter = ptr + count; + + if (length > 4) { + length = 0; + continue; + } + *where = iter; + delim = "of"; + break; + } + } +#endif + + /* + * look for part numbering as string + */ + + if (length == 0) { + /* + * some people use the strangest things, including spelling mistakes :-) + */ + if ((iter = _FP_stristr (subject, "first")) != NULL) length = 1; + else if ((iter = _FP_stristr (subject, "second")) != NULL) length = 2; + else if ((iter = _FP_stristr (subject, "third")) != NULL) length = 3; + else if ((iter = _FP_stristr (subject, "forth")) != NULL) length = 4; + else if ((iter = _FP_stristr (subject, "fourth")) != NULL) length = 4; + else if ((iter = _FP_stristr (subject, "fifth")) != NULL) length = 5; + else if ((iter = _FP_stristr (subject, "sixth")) != NULL) length = 6; + else if ((iter = _FP_stristr (subject, "seventh")) != NULL) length = 7; + else if ((iter = _FP_stristr (subject, "eigth")) != NULL) length = 8; + else if ((iter = _FP_stristr (subject, "nineth")) != NULL) length = 9; + else if ((iter = _FP_stristr (subject, "ninth")) != NULL) length = 9; + else if ((iter = _FP_stristr (subject, "tenth")) != NULL) length = 10; + else iter = NULL; + + if (length && iter && (*whend = strchr (iter, ' '))) { + *where = iter; + return length; + } + else + length = 0; + } + + if (iter == NULL || length == 0) /* should be equivalent */ + return -1; + + *where = iter; + + if (delim && delim[0]) { + if ((*whend=_FP_stristr (iter, delim)) != NULL && (*whend - *where) < 12) { + ptr = (*whend += strlen (delim)); + + while (*ptr == ' ') + ptr++; + + if (isdigit (*ptr)) { + *whend = ptr; + while (isdigit (**whend)) + *whend += 1; + } + } + else { + *whend = iter + length; + } + } + else { + *whend = iter + length; + } + + return atoi (iter); +} + +/* + * Obtain and process some information about the data. + **/ + +uufile * +UUPreProcessPart (fileread *data, int *ret) +{ + char *where, *whend, temp[80], *ptr, *p2; + uufile *result; + + if ((result = (uufile *) malloc (sizeof (uufile))) == NULL) { + UUMessage (uucheck_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), sizeof (uufile)); + *ret = UURET_NOMEM; + return NULL; + } + memset (result, 0, sizeof (uufile)); + + if (data->partno) { + where = whend = NULL; + result->partno = data->partno; + } + else if (uu_dumbness) { + result->partno = -1; + where = whend = NULL; + } + else if ((result->partno=UUGetPartNo(data->subject,&where,&whend)) == -2) { + *ret = UURET_NODATA; + UUkillfile (result); + return NULL; + } + + if (data->filename != NULL) { + if ((result->filename = _FP_strdup (data->filename)) == NULL) { + UUMessage (uucheck_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), + strlen (data->filename)+1); + *ret = UURET_NOMEM; + UUkillfile (result); + return NULL; + } + } + else + result->filename = NULL; + + if (uu_dumbness <= 1) + result->subfname = UUGetFileName (data->subject, where, whend); + else + result->subfname = NULL; + + result->mimeid = _FP_strdup (data->mimeid); + result->mimetype = _FP_strdup (data->mimetype); + + if (result->partno == -1 && + (data->uudet == PT_ENCODED || data->uudet == QP_ENCODED)) + result->partno = 1; + + if (data->flags & FL_SINGLE) { + /* + * Don't touch this part. But it should really have a filename + */ + if (result->filename == NULL) { + sprintf (temp, "%s.%03d", nofname, ++nofnum); + result->filename = _FP_strdup (temp); + } + if (result->subfname == NULL) + result->subfname = _FP_strdup (result->filename); + + if (result->filename == NULL || + result->subfname == NULL) { + UUMessage (uucheck_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), + (result->filename==NULL)? + (strlen(temp)+1):(strlen(result->filename)+1)); + *ret = UURET_NOMEM; + UUkillfile(result); + return NULL; + } + if (result->partno == -1) + result->partno = 1; + } + else if (result->subfname == NULL && data->uudet && + (data->begin || result->partno == 1 || + (!uu_dumbness && result->partno == -1 && + (data->subject != NULL || result->filename != NULL)))) { + /* + * If it's the first part of something and has some valid data, but + * no subject or anything, initialize lastvalid + */ + /* + * in this case, it really _should_ have a filename somewhere + */ + if (result->filename != NULL) + result->subfname = _FP_strdup (result->filename); + else { /* if not, escape to UNKNOWN. We need to fill subfname */ + sprintf (temp, "%s.%03d", nofname, ++nofnum); + result->subfname = _FP_strdup (temp); + } + /* + * in case the strdup failed + */ + if (result->subfname == NULL) { + UUMessage (uucheck_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), + (result->filename)? + (strlen(result->filename)+1):(strlen(temp)+1)); + *ret = UURET_NOMEM; + UUkillfile (result); + return NULL; + } + /* + * if it's also got an 'end', or is the last part in a MIME-Mail, + * then don't set lastvalid + */ + if (!data->end && (!data->partno || data->partno != data->maxpno)) { + /* + * initialize lastvalid + */ + lastvalid = 1; + lastenc = data->uudet; + lastpart = result->partno = 1; + _FP_strncpy (uucheck_lastname, result->subfname, 256); + } + else + result->partno = 1; + } + else if (result->subfname == NULL && data->uudet && data->mimeid) { + /* + * if it's got a file name, use it. Else use the mime-id for identifying + * this part, and hope there's no other files encoded in the same message + * under the same id. + */ + if (result->filename) + result->subfname = _FP_strdup (result->filename); + else + result->subfname = _FP_strdup (result->mimeid); + } + else if (result->subfname == NULL && data->uudet) { + /* + * ff we have lastvalid, use it. Make an exception for + * Base64-encoded files. + */ + if (data->uudet == B64ENCODED) { + /* + * Assume it's the first part. I wonder why it's got no part number? + */ + if (result->filename != NULL) + result->subfname = _FP_strdup (result->filename); + else { /* if not, escape to UNKNOWN. We need to fill subfname */ + sprintf (temp, "%s.%03d", nofname, ++nofnum); + result->subfname = _FP_strdup (temp); + } + if (result->subfname == NULL) { + UUMessage (uucheck_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), + (result->filename)? + (strlen(result->filename)+1):(strlen(temp)+1)); + *ret = UURET_NOMEM; + UUkillfile (result); + return NULL; + } + lastvalid = 0; + } + else if (lastvalid && data->uudet == lastenc && result->partno == -1) { + result->subfname = _FP_strdup (uucheck_lastname); + result->partno = ++lastpart; + + /* + * if it's the last part, invalidate lastvalid + */ + if (data->end || (data->partno && data->partno == data->maxpno)) + lastvalid = 0; + } + else if (data->partno != -1 && result->filename) { + result->subfname = _FP_strdup (result->filename); + } + else { + /* + * it's got no info, it's got no begin, and we don't know anything + * about this part. Let's forget all about it. + */ + *ret = UURET_NODATA; + UUkillfile (result); + return NULL; + } + } + else if (result->subfname == NULL && result->partno == -1) { + /* + * This, too, is a part without any useful information that we + * should forget about. + */ + *ret = UURET_NODATA; + UUkillfile (result); + return NULL; + } + else if (result->subfname == NULL) { + /* + * This is a part without useful subject name, a valid part number + * but no encoded data. It *could* be the zeroeth part of something, + * but we don't care here. Just forget it. + */ + *ret = UURET_NODATA; + UUkillfile (result); + return NULL; + } + + /* + * now, handle some cases where we have a useful subject but no + * useful part number + */ + + if (result->partno == -1 && data->begin) { + /* + * hmm, this is reason enough to initialize lastvalid, at least + * if we have no end + */ + if (!data->end) { + _FP_strncpy (uucheck_lastname, result->subfname, 256); + result->partno = lastpart = 1; + lastenc = data->uudet; + lastvalid = 1; + } + else + result->partno = 1; + } + else if (result->partno == -1 && data->uudet) { + if (lastvalid && _FP_stricmp (uucheck_lastname, result->subfname) == 0) { + /* + * if the subject filename is the same as last time, use part no + * of lastvalid. If at end, invalidate lastvalid + */ + result->partno = ++lastpart; + + if (data->end) + lastvalid = 0; + } + else { + /* + * data but no part no. It's something UUInsertPartToList() should + * handle + */ + goto skipcheck; + } + } + else if (result->partno == -1) { + /* + * it's got no data, so why should we need this one anyway? + */ + *ret = UURET_NODATA; + UUkillfile (result); + return NULL; + } + + /* + * at this point, the part should have a valid subfname and a valid + * part number. If it doesn't, then fail. + */ + if (result->subfname == NULL || result->partno == -1) { + *ret = UURET_NODATA; + UUkillfile (result); + return NULL; + } + + skipcheck: + + if (result->filename) { + if (*(ptr = _FP_cutdir (result->filename))) { + p2 = _FP_strdup (ptr); + _FP_free (result->filename); + result->filename = p2; + } + } + + result->data = data; + result->NEXT = NULL; + + *ret = UURET_OK; + + return result; +} + +/* + * Insert one part of a file into the global list + **/ + +int +UUInsertPartToList (uufile *data) +{ + uulist *iter = UUGlobalFileList, *unew; + uufile *fiter, *last; + + /* + * Part belongs together, if + * (a) The file name received from the subject lines match _or_ + * the MIME-IDs match, + * (b) Not both parts have a begin line + * (c) Not both parts have an end line + * (d) Both parts don't have different MIME-IDs + * (e) Both parts don't encode different files + * (f) The other part wants to stay alone (FL_SINGLE) + */ + + /* + * check if this part wants to be left alone. If so, don't bother + * to do all the checks + */ + + while (iter) { + if (data->data->flags & FL_SINGLE) { + /* this space intentionally left blank */ + } + else if ((_FP_stricmp (data->subfname, iter->subfname) == 0 || + (data->mimeid && iter->mimeid && + strcmp (data->mimeid, iter->mimeid) == 0)) && + !(iter->begin && data->data->begin) && + !(iter->end && data->data->end) && + !(data->mimeid && iter->mimeid && + strcmp (data->mimeid, iter->mimeid) != 0) && + !(data->filename && iter->filename && + strcmp (data->filename, iter->filename) != 0) && + !(iter->flags & FL_SINGLE)) { + + /* + * if we already have this part, don't try to insert it + */ + + for (fiter=iter->thisfile; + fiter && (data->partno>fiter->partno) && !fiter->data->end; + fiter=fiter->NEXT) + /* empty loop */ ; + if (fiter && + (data->partno==fiter->partno || + (data->partno > fiter->partno && fiter->data->end))) + goto goahead; + + if (iter->filename == NULL && data->filename != NULL) { + if ((iter->filename = _FP_strdup (data->filename)) == NULL) + return UURET_NOMEM; + } + + /* + * special case when we might have tagged a part as Base64 when the + * file was really XX + */ + + if (data->data->uudet == B64ENCODED && + iter->uudet == XX_ENCODED && iter->begin) { + data->data->uudet = XX_ENCODED; + } + else if (data->data->uudet == XX_ENCODED && data->data->begin && + iter->uudet == B64ENCODED) { + iter->uudet = XX_ENCODED; + + fiter = iter->thisfile; + while (fiter) { + fiter->data->uudet = XX_ENCODED; + fiter = fiter->NEXT; + } + } + + /* + * If this is from a Message/Partial, we believe only the + * iter->uudet from the first part + */ + if (data->data->flags & FL_PARTIAL) { + if (data->partno == 1) { + iter->uudet = data->data->uudet; + iter->flags = data->data->flags; + } + } + else { + if (data->data->uudet) iter->uudet = data->data->uudet; + if (data->data->flags) iter->flags = data->data->flags; + } + + if (iter->mode == 0 && data->data->mode != 0) + iter->mode = data->data->mode; + if (data->data->begin) iter->begin = (data->partno)?data->partno:1; + if (data->data->end) iter->end = (data->partno)?data->partno:1; + + if (data->mimetype) { + _FP_free (iter->mimetype); + iter->mimetype = _FP_strdup (data->mimetype); + } + + /* + * insert part at the beginning + */ + + if (data->partno != -1 && data->partno < iter->thisfile->partno) { + iter->state = UUFILE_READ; + data->NEXT = iter->thisfile; + iter->thisfile = data; + return UURET_OK; + } + + /* + * insert part somewhere else + */ + + iter->state = UUFILE_READ; /* prepare for re-checking */ + fiter = iter->thisfile; + last = NULL; + + while (fiter) { + /* + * if we find the same part no again, check which one looks better + */ + if (data->partno == fiter->partno) { + if (fiter->data->subject == NULL) + return UURET_NODATA; + else if (_FP_stristr (fiter->data->subject, "repost") != NULL && + _FP_stristr (data->data->subject, "repost") == NULL) + return UURET_NODATA; + else if (fiter->data->uudet && !data->data->uudet) + return UURET_NODATA; + else { + /* + * replace + */ + data->NEXT = fiter->NEXT; + fiter->NEXT = NULL; + UUkillfile (fiter); + + if (last == NULL) + iter->thisfile = data; + else + last->NEXT = data; + + return UURET_OK; + } + } + + /* + * if at the end of the part list, add it + */ + + if (fiter->NEXT == NULL || + (data->partno != -1 && data->partno < fiter->NEXT->partno)) { + data->NEXT = fiter->NEXT; + fiter->NEXT = data; + + if (data->partno == -1) + data->partno = fiter->partno + 1; + + return UURET_OK; + } + last = fiter; + fiter = fiter->NEXT; + } + + return UURET_OK; /* Shouldn't get here */ + } + goahead: + /* + * we need iter below + */ + if (iter->NEXT == NULL) + break; + + iter = iter->NEXT; + } + /* + * handle new entry + */ + + if (data->partno == -1) { + /* + * if it's got no part no, and it's MIME mail, then assume this is + * part no. 1. If it's not MIME, then we can't handle it; if it + * had a 'begin', it'd have got a part number assigned by + * UUPreProcessPart(). + */ + if (data->data->uudet == B64ENCODED || data->data->uudet == BH_ENCODED) + data->partno = 1; + else + return UURET_NODATA; + } + + if ((unew = (uulist *) malloc (sizeof (uulist))) == NULL) { + return UURET_NOMEM; + } + + if ((unew->subfname = _FP_strdup (data->subfname)) == NULL) { + _FP_free (unew); + return UURET_NOMEM; + } + + if (data->filename != NULL) { + if ((unew->filename = _FP_strdup (data->filename)) == NULL) { + _FP_free (unew->subfname); + _FP_free (unew); + return UURET_NOMEM; + } + } + else + unew->filename = NULL; + + if (data->mimeid != NULL) { + if ((unew->mimeid = _FP_strdup (data->mimeid)) == NULL) { + _FP_free (unew->subfname); + _FP_free (unew->filename); + _FP_free (unew); + return UURET_NOMEM; + } + } + else + unew->mimeid = NULL; + + if (data->mimetype != NULL) { + if ((unew->mimetype = _FP_strdup (data->mimetype)) == NULL) { + _FP_free (unew->mimeid); + _FP_free (unew->subfname); + _FP_free (unew->filename); + _FP_free (unew); + return UURET_NOMEM; + } + } + else + unew->mimetype = NULL; + + unew->state = UUFILE_READ; + unew->binfile = NULL; + unew->thisfile = data; + unew->mode = data->data->mode; + unew->uudet = data->data->uudet; + unew->flags = data->data->flags; + unew->begin = (data->data->begin) ? ((data->partno)?data->partno:1) : 0; + unew->end = (data->data->end) ? ((data->partno)?data->partno:1) : 0; + unew->misparts = NULL; + unew->haveparts = NULL; + unew->NEXT = NULL; + + if (iter == NULL) + UUGlobalFileList = unew; + else + iter->NEXT = unew; + + return UURET_OK; +} + +/* + * At this point, all files are read in and stored in the + * "UUGlobalFileList". Do some checking. All parts there? + **/ + +uulist * +UUCheckGlobalList (void) +{ + int misparts[MAXPLIST], haveparts[MAXPLIST]; + int miscount, havecount, count, flag, part; + uulist *liter=UUGlobalFileList, *prev; + uufile *fiter; + long thesize; + + while (liter) { + miscount = 0; + thesize = 0; + + if (liter->state & UUFILE_OK) { + liter = liter->NEXT; + continue; + } + else if ((liter->uudet == QP_ENCODED || + liter->uudet == PT_ENCODED) && + (liter->flags & FL_SINGLE)) { + if ((liter->flags&FL_PROPER)==0) + liter->size = -1; + else + liter->size = liter->thisfile->data->length; + + liter->state = UUFILE_OK; + continue; + } + else if ((fiter = liter->thisfile) == NULL) { + liter->state = UUFILE_NODATA; + liter = liter->NEXT; + continue; + } + + /* + * Re-Check this file + */ + + flag = 0; + miscount = 0; + havecount = 0; + thesize = 0; + liter->state = UUFILE_READ; + + /* + * search encoded data + */ + + while (fiter && !fiter->data->uudet) { + if (havecountpartno; + } + fiter = fiter->NEXT; + } + + if (fiter == NULL) { + liter->state = UUFILE_NODATA; + liter = liter->NEXT; + continue; + } + + if (havecountpartno; + } + + if ((part = fiter->partno) > 1) { + if (!fiter->data->begin) { + for (count=1; count < part && miscount < MAXPLIST; count++) + misparts[miscount++] = count; + } + } + + /* + * don't care if so many parts are missing + */ + + if (miscount >= MAXPLIST) { + liter->state = UUFILE_MISPART; + liter = liter->NEXT; + continue; + } + + if (liter->uudet == B64ENCODED || + liter->uudet == QP_ENCODED || + liter->uudet == PT_ENCODED) + flag |= 3; /* Don't need begin or end with Base64 or plain text*/ + + if (fiter->data->begin) flag |= 1; + if (fiter->data->end) flag |= 2; + if (fiter->data->uudet) flag |= 4; + + /* + * guess size of part + */ + + switch (fiter->data->uudet) { + case UU_ENCODED: + case XX_ENCODED: + thesize += 3*fiter->data->length/4; + thesize -= 3*fiter->data->length/124; /* substract 2 of 62 chars */ + break; + case B64ENCODED: + thesize += 3*fiter->data->length/4; + thesize -= fiter->data->length/52; /* substract 2 of 78 chars */ + break; + case QP_ENCODED: + case PT_ENCODED: + thesize += fiter->data->length; + break; + } + + fiter = fiter->NEXT; + + while (fiter != NULL) { + for (count=part+1; countpartno && miscountpartno; + + if (havecountdata->begin) flag |= 1; + if (fiter->data->end) flag |= 2; + if (fiter->data->uudet) flag |= 4; + + switch (fiter->data->uudet) { + case UU_ENCODED: + case XX_ENCODED: + thesize += 3*fiter->data->length/4; + thesize -= 3*fiter->data->length/124; /* substract 2 of 62 chars */ + break; + case B64ENCODED: + thesize += 3*fiter->data->length/4; + thesize -= fiter->data->length/52; /* substract 2 of 78 chars */ + break; + case QP_ENCODED: + case PT_ENCODED: + thesize += fiter->data->length; + break; + } + + if (fiter->data->end) + break; + + fiter = fiter->NEXT; + } + + /* + * if in fast mode, we don't notice an 'end'. So if its uu or xx + * encoded, there's a begin line and encoded data, assume it's + * there. + */ + + if (uu_fast_scanning && (flag & 0x01) && (flag & 0x04) && + (liter->uudet == UU_ENCODED || liter->uudet == XX_ENCODED)) + flag |= 2; + + /* + * Set the parts we have and/or missing + */ + + _FP_free (liter->haveparts); + _FP_free (liter->misparts); + + liter->haveparts = NULL; + liter->misparts = NULL; + + if (havecount) { + if ((liter->haveparts=(int*)malloc((havecount+1)*sizeof(int)))!=NULL) { + memcpy (liter->haveparts, haveparts, havecount*sizeof(int)); + liter->haveparts[havecount] = 0; + } + } + + if (miscount) { + if ((liter->misparts=(int*)malloc((miscount+1)*sizeof(int)))!=NULL) { + memcpy (liter->misparts, misparts, miscount*sizeof(int)); + liter->misparts[miscount] = 0; + } + liter->state |= UUFILE_MISPART; + } + + /* + * Finalize checking + */ + + if ((flag & 4) == 0) liter->state |= UUFILE_NODATA; + if ((flag & 1) == 0) liter->state |= UUFILE_NOBEGIN; + if ((flag & 2) == 0) liter->state |= UUFILE_NOEND; + + if ((flag & 7) == 7 && miscount==0) { + liter->state = UUFILE_OK; + } + + if ((uu_fast_scanning && (liter->flags&FL_PROPER)==0) || thesize<=0) + liter->size = -1; + else + liter->size = thesize; + + if (liter->state==UUFILE_OK && + (liter->filename==NULL || liter->filename[0]=='\0')) { + /* + * Emergency backup if the file does not have a filename + */ + _FP_free (liter->filename); + if (liter->subfname && liter->subfname[0] && + _FP_strpbrk (liter->subfname, "()[];: ") == NULL) + liter->filename = _FP_strdup (liter->subfname); + else { + sprintf (uucheck_tempname, "%s.%03d", nofname, ++nofnum); + liter->filename = _FP_strdup (uucheck_tempname); + } + } + liter = liter->NEXT; + } + + /* + * Sets back (PREV) links + */ + + liter = UUGlobalFileList; + prev = NULL; + + while (liter) { + liter->PREV = prev; + prev = liter; + liter = liter->NEXT; + } + + return UUGlobalFileList; +} + diff --git a/goldlib/uulib/uuencode.cpp b/goldlib/uulib/uuencode.cpp new file mode 100644 index 0000000..09c4663 --- /dev/null +++ b/goldlib/uulib/uuencode.cpp @@ -0,0 +1,1793 @@ +/* + * This file is part of uudeview, the simple and friendly multi-part multi- + * file uudecoder program (c) 1994-2001 by Frank Pilhofer. The author may + * be contacted at fp@fpx.de + * + * 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 2 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef SYSTEM_WINDLL +#include +#endif +#ifdef SYSTEM_OS2 +#include +#endif + +#include +#include +#include +#include + +#ifdef STDC_HEADERS +#include +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_IO_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include +#include +#include +#include +#include + +/* for braindead systems */ +#ifndef SEEK_SET +#ifdef L_BEGIN +#define SEEK_SET L_BEGIN +#else +#define SEEK_SET 0 +#endif +#endif + +char * uuencode_id = "$Id$"; + +#if 0 +/* + * the End-Of-Line string. MIME enforces CRLF, so that's what we use. Some + * implementations of uudecode will complain about a missing end line, since + * they look for "end^J" but find "end^J^M". We don't care - especially be- + * cause they still decode the file properly despite this complaint. + */ + +#ifndef EOLSTRING +#define EOLSTRING "\015\012" +#endif + +#else + +/* + * Argh. Some delivery software (inews) has problems with the CRLF + * line termination. Let's try native EOL and see if we run into + * any problems. + * This involves opening output files in text mode instead of binary + */ + +#ifndef EOLSTRING +#define EOLSTRING "\n" +#endif + +#endif + + +/* + * ========================================================================= + * User-configurable settings end here. Don't spy below unless you know what + * you're doing. + * ========================================================================= + */ + +/* + * Define End-Of-Line sequence + */ + +#ifdef EOLSTRING +static unsigned char *eolstring = (unsigned char *) EOLSTRING; +#else +static unsigned char *eolstring = (unsigned char *) "\012"; +#endif + +/* + * Content-Transfer-Encoding types for non-MIME encodings + */ + +#define CTE_UUENC "x-uuencode" +#define CTE_XXENC "x-xxencode" +#define CTE_BINHEX "x-binhex" +#define CTE_YENC "x-yenc" + +#define CTE_TYPE(y) (((y)==B64ENCODED) ? "Base64" : \ + ((y)==UU_ENCODED) ? CTE_UUENC : \ + ((y)==XX_ENCODED) ? CTE_XXENC : \ + ((y)==PT_ENCODED) ? "8bit" : \ + ((y)==QP_ENCODED) ? "quoted-printable" : \ + ((y)==BH_ENCODED) ? CTE_BINHEX : \ + ((y)==YENC_ENCODED) ? CTE_YENC : "x-oops") + +/* + * encoding tables + */ + +unsigned char UUEncodeTable[64] = { + '`', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\',']', '^', '_' +}; + + +unsigned char B64EncodeTable[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +}; + +unsigned char XXEncodeTable[64] = { + '+', '-', '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'A', 'B', 'C', 'D', + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', + 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' +}; + +unsigned char BHEncodeTable[64] = { + '!', '"', '#', '$', '%', '&', '\'', '(', + ')', '*', '+', ',', '-', '0', '1', '2', + '3', '4', '5', '6', '8', '9', '@', 'A', + 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', + 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'X', 'Y', 'Z', '[', + '`', 'a', 'b', 'c', 'd', 'e', 'f', 'h', + 'i', 'j', 'k', 'l', 'm', 'p', 'q', 'r' +}; + +unsigned char HexEncodeTable[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' +}; + +typedef struct { + char *extension; + char *mimetype; +} mimemap; + +/* + * This table maps a file's extension into a Content-Type. The current + * official list can be downloaded as + * ftp://ftp.isi.edu/in-notes/iana/assignments/media-type + * I haven't listed any text types since it doesn't make sense to encode + * them. Everything not on the list gets mapped to application/octet-stream + */ + +static mimemap mimetable[] = { + { "gif", "image/gif" }, /* Grafics Interchange Format */ + { "jpg", "image/jpeg" }, /* JFIF encoded files */ + { "jpeg", "image/jpeg" }, + { "tif", "image/tiff" }, /* Tag Image File Format */ + { "tiff", "image/tiff" }, + { "cgm", "image/cgm" }, /* Computer Graphics Metafile */ + { "au", "audio/basic" }, /* 8kHz ulaw audio data */ + { "mov", "video/quicktime" }, /* Apple Quicktime */ + { "qt", "video/quicktime" }, /* Also infrequently used */ + { "mpeg", "video/mpeg" }, /* Motion Picture Expert Group */ + { "mpg", "video/mpeg" }, + { "mp2", "video/mpeg" }, /* dito, MPEG-2 encoded files */ + { "mp3", "audio/mpeg" }, /* dito, MPEG-3 encoded files */ + { "ps", "application/postscript" }, /* Postscript Language */ + { "zip", "application/zip" }, /* ZIP archive */ + { "doc", "application/msword"},/* assume Microsoft Word */ + { NULL, NULL } +}; + +/* + * the order of the following two tables must match the + * Encoding Types definition in uudeview.h + */ + +/* + * encoded bytes per line + */ + +static int bpl[8] = { 0, 45, 57, 45, 45, 0, 0, 128 }; + +/* + * tables + */ + +static unsigned char *etables[5] = { + NULL, + UUEncodeTable, + B64EncodeTable, + XXEncodeTable, + BHEncodeTable +}; + +/* + * variables to malloc upon initialization + */ + +char *uuestr_itemp; +char *uuestr_otemp; + +/* + * Encode one part of the data stream + */ + +static int +UUEncodeStream (FILE *outfile, FILE *infile, int encoding, long linperfile, dword *crc, dword *pcrc) +{ + unsigned char *itemp = (unsigned char *) uuestr_itemp; + unsigned char *otemp = (unsigned char *) uuestr_otemp; + unsigned char *optr, *table, *tptr; + int index, count; + long line=0; + size_t llen; + + if (outfile==NULL || infile==NULL || + (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&& + encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED)) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_PARM_CHECK), "UUEncodeStream()"); + return UURET_ILLVAL; + } + + /* + * Special handling for plain text and quoted printable. Text is + * read line oriented. + */ + + if (encoding == PT_ENCODED || encoding == QP_ENCODED) { + while (!feof (infile) && (linperfile <= 0 || line < linperfile)) { + if (_FP_fgets ((char *) itemp, 255, infile) == NULL) { + break; + } + + itemp[255] = '\0'; + count = strlen ((char *) itemp); + + llen = 0; + optr = otemp; + + /* + * Busy Callback + */ + + if (UUBUSYPOLL(ftell(infile)-progress.foffset,progress.fsize)) { + UUMessage (uuencode_id, __LINE__, UUMSG_NOTE, + uustring (S_ENCODE_CANCEL)); + return UURET_CANCEL; + } + + if (encoding == PT_ENCODED) { + /* + * If there is a line feed, replace by eolstring + */ + if (count > 0 && itemp[count-1] == '\n') { + itemp[--count] = '\0'; + if (fwrite (itemp, 1, count, outfile) != (unsigned)count || + fwrite ((char *) eolstring, 1, + strlen((char *) eolstring), outfile) != strlen ((char *) eolstring)) { + return UURET_IOERR; + } + } + else { + if (fwrite (itemp, 1, count, outfile) != llen) { + return UURET_IOERR; + } + } + } + else if (encoding == QP_ENCODED) { + for (index=0; index> 4]; + *optr++ = HexEncodeTable[itemp[index] & 0x0f]; + llen += 3; + } + else if ((itemp[index] >= 33 && itemp[index] <= 60) || + (itemp[index] >= 62 && itemp[index] <= 126) || + itemp[index] == 9 || itemp[index] == 32) { + *optr++ = itemp[index]; + llen++; + } + else if (itemp[index] == '\n') { + /* + * If the last character before EOL was a space or tab, + * we must encode it. If llen > 74, there's no space to do + * that, so generate a soft line break instead. + */ + + if (index>0 && (itemp[index-1] == 9 || itemp[index-1] == 32)) { + *(optr-1) = '='; + if (llen <= 74) { + *optr++ = HexEncodeTable[itemp[index-1] >> 4]; + *optr++ = HexEncodeTable[itemp[index-1] & 0x0f]; + llen += 2; + } + } + + if (fwrite (otemp, 1, llen, outfile) != llen || + fwrite ((char *) eolstring, 1, + strlen((char *) eolstring), outfile) != strlen ((char *) eolstring)) { + return UURET_IOERR; + } + + /* + * Fix the soft line break condition from above + */ + + if (index>0 && (itemp[index-1] == 9 || itemp[index-1] == 32) && + *(optr-1) == '=') { + otemp[0] = '='; + otemp[1] = HexEncodeTable[itemp[index-1] >> 4]; + otemp[2] = HexEncodeTable[itemp[index-1] & 0x0f]; + + if (fwrite (otemp, 1, 3, outfile) != 3 || + fwrite ((char *) eolstring, 1, + strlen((char *) eolstring), outfile) != strlen ((char *) eolstring)) { + return UURET_IOERR; + } + } + + optr = otemp; + llen = 0; + } + else { + *optr++ = '='; + *optr++ = HexEncodeTable[itemp[index] >> 4]; + *optr++ = HexEncodeTable[itemp[index] & 0x0f]; + llen += 3; + } + + /* + * Lines must be shorter than 76 characters (not counting CRLF). + * If the line grows longer than that, we must include a soft + * line break. + */ + + if (itemp[index+1] != 0 && itemp[index+1] != '\n' && + (llen >= 75 || + (!((itemp[index+1] >= 33 && itemp[index+1] <= 60) || + (itemp[index+1] >= 62 && itemp[index+1] <= 126)) && + llen >= 73))) { + + *optr++ = '='; + llen++; + + if (fwrite (otemp, 1, llen, outfile) != llen || + fwrite ((char *) eolstring, 1, + strlen((char *) eolstring), outfile) != strlen ((char *) eolstring)) { + return UURET_IOERR; + } + + optr = otemp; + llen = 0; + } + } + } + + line++; + } + + return UURET_OK; + } + + /* + * Special handling for yEnc + */ + + if (encoding == YENC_ENCODED) { + llen = 0; + optr = otemp; + + while (!feof (infile) && (linperfile <= 0 || line < linperfile)) { + if ((count = fread (itemp, 1, 128, infile)) != 128) { + if (count == 0) { + break; + } + else if (ferror (infile)) { + return UURET_IOERR; + } + } + + if (pcrc) *pcrc = memCrc32(*pcrc, itemp, count, false, CRC32_MASK_CCITT); + if (crc) *crc = memCrc32(*crc, itemp, count, false, CRC32_MASK_CCITT); + + line++; + + /* + * Busy Callback + */ + + if (UUBUSYPOLL(ftell(infile)-progress.foffset,progress.fsize)) { + UUMessage (uuencode_id, __LINE__, UUMSG_NOTE, + uustring (S_ENCODE_CANCEL)); + return UURET_CANCEL; + } + + for (index=0; index 127) { + if (fwrite (otemp, 1, llen, outfile) != llen || + fwrite ((char *) eolstring, 1, + strlen((char *) eolstring), outfile) != strlen ((char *) eolstring)) { + return UURET_IOERR; + } + llen = 0; + optr = otemp; + } + + switch ((char) ((int) itemp[index] + 42)) { + case '\0': + case '\t': + case '\n': + case '\r': + case '=': + case '\033': + *optr++ = '='; + *optr++ = (char) ((int) itemp[index] + 42 + 64); + llen += 2; + break; + + case '.': + if (llen == 0) { + *optr++ = '='; + *optr++ = (char) ((int) itemp[index] + 42 + 64); + llen += 2; + } + else { + *optr++ = (char) ((int) itemp[index] + 42); + llen++; + } + break; + + default: + *optr++ = (char) ((int) itemp[index] + 42); + llen++; + break; + } + } + } + + /* + * write last line + */ + + if (llen) { + if (fwrite (otemp, 1, llen, outfile) != llen || + fwrite ((char *) eolstring, 1, + strlen((char *) eolstring), outfile) != strlen ((char *) eolstring)) { + return UURET_IOERR; + } + } + + return UURET_OK; + } + + /* + * Handling for binary encodings + */ + + /* + * select charset + */ + + table = etables[encoding]; + + if (table==NULL || bpl[encoding]==0) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_PARM_CHECK), "UUEncodeStream()"); + return UURET_ILLVAL; + } + + while (!feof (infile) && (linperfile <= 0 || line < linperfile)) { + if ((count = fread (itemp, 1, bpl[encoding], infile)) != bpl[encoding]) { + if (count == 0) + break; + else if (ferror (infile)) + return UURET_IOERR; + } + + optr = otemp; + llen = 0; + + /* + * Busy Callback + */ + + if (UUBUSYPOLL(ftell(infile)-progress.foffset,progress.fsize)) { + UUMessage (uuencode_id, __LINE__, UUMSG_NOTE, + uustring (S_ENCODE_CANCEL)); + return UURET_CANCEL; + } + + /* + * for UU and XX, encode the number of bytes as first character + */ + + if (encoding == UU_ENCODED || encoding == XX_ENCODED) { + *optr++ = table[count]; + llen++; + } + + /* + * Main encoding + */ + + for (index=0; index<=count-3; index+=3, llen+=4) { + *optr++ = table[itemp[index] >> 2]; + *optr++ = table[((itemp[index ] & 0x03) << 4)|(itemp[index+1] >> 4)]; + *optr++ = table[((itemp[index+1] & 0x0f) << 2)|(itemp[index+2] >> 6)]; + *optr++ = table[ itemp[index+2] & 0x3f]; + } + + /* + * Special handling for incomplete lines + */ + + if (index != count) { + if (encoding == B64ENCODED) { + if (count - index == 2) { + *optr++ = table[itemp[index] >> 2]; + *optr++ = table[((itemp[index ] & 0x03) << 4) | + ((itemp[index+1] & 0xf0) >> 4)]; + *optr++ = table[((itemp[index+1] & 0x0f) << 2)]; + *optr++ = '='; + } + else if (count - index == 1) { + *optr++ = table[ itemp[index] >> 2]; + *optr++ = table[(itemp[index] & 0x03) << 4]; + *optr++ = '='; + *optr++ = '='; + } + llen += 4; + } + else if (encoding == UU_ENCODED || encoding == XX_ENCODED) { + if (count - index == 2) { + *optr++ = table[itemp[index] >> 2]; + *optr++ = table[((itemp[index ] & 0x03) << 4) | + ( itemp[index+1] >> 4)]; + *optr++ = table[((itemp[index+1] & 0x0f) << 2)]; + *optr++ = table[0]; + } + else if (count - index == 1) { + *optr++ = table[ itemp[index] >> 2]; + *optr++ = table[(itemp[index] & 0x03) << 4]; + *optr++ = table[0]; + *optr++ = table[0]; + } + llen += 4; + } + } + + /* + * end of line + */ + + tptr = eolstring; + + while (*tptr) + *optr++ = *tptr++; + + *optr++ = '\0'; + llen += strlen ((char *) eolstring); + + if (fwrite (otemp, 1, llen, outfile) != llen) + return UURET_IOERR; + + line++; + } + return UURET_OK; +} + +/* + * Encode as MIME multipart/mixed sub-message. + */ + +int UUEXPORT +UUEncodeMulti (FILE *outfile, FILE *infile, char *infname, int encoding, + char *outfname, char *mimetype, int filemode) +{ + mimemap *miter=mimetable; + struct stat finfo; + int res, themode; + FILE *theifile; + char *ptr; + dword crc; + dword *crcptr=NULL; + + if (outfile==NULL || + (infile == NULL && infname==NULL) || + (outfname==NULL && infname==NULL) || + (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&& + encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED)) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_PARM_CHECK), "UUEncodeMulti()"); + return UURET_ILLVAL; + } + + progress.action = 0; + + if (infile==NULL) { + if (stat (infname, &finfo) == -1) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_STAT_FILE), + infname, strerror (uu_errno=errno)); + return UURET_IOERR; + } + if ((theifile = fopen (infname, "rb")) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_FILE), + infname, strerror (uu_errno=errno)); + return UURET_IOERR; + } + themode = (filemode) ? filemode : ((int) finfo.st_mode & 0777); + progress.fsize = (long) finfo.st_size; + } + else { + if (fstat (fileno (infile), &finfo) != 0) { + themode = (filemode)?filemode:0644; + progress.fsize = -1; + } + else { + themode = (int) finfo.st_mode & 0777; + progress.fsize = (long) finfo.st_size; + } + theifile = infile; + } + + if (progress.fsize < 0) + progress.fsize = -1; + + _FP_strncpy (progress.curfile, (outfname)?outfname:infname, 256); + + progress.partno = 1; + progress.numparts = 1; + progress.percent = 0; + progress.foffset = 0; + progress.action = UUACT_ENCODING; + + /* + * If not given from outside, select an appropriate Content-Type by + * looking at the file's extension. If it is unknown, default to + * Application/Octet-Stream + */ + + if (mimetype == NULL) { + if ((ptr = _FP_strrchr ((outfname)?outfname:infname, '.'))) { + while (miter->extension && _FP_stricmp (ptr+1, miter->extension) != 0) + miter++; + mimetype = miter->mimetype; + } + } + + if (mimetype == NULL && (encoding == PT_ENCODED || encoding == QP_ENCODED)) { + mimetype = "text/plain"; + } + + /* + * print sub-header + */ + + if (encoding != YENC_ENCODED) { + fprintf (outfile, "Content-Type: %s%s", + (mimetype)?mimetype:"Application/Octet-Stream", + (char *) eolstring); + fprintf (outfile, "Content-Transfer-Encoding: %s%s", + CTE_TYPE(encoding), (char *) eolstring); + fprintf (outfile, "Content-Disposition: attachment; filename=\"%s\"%s", + UUFNameFilter ((outfname)?outfname:infname), (char *) eolstring); + fprintf (outfile, "%s", (char *) eolstring); + } + + if (encoding == UU_ENCODED || encoding == XX_ENCODED) { + fprintf (outfile, "begin %o %s%s", + (themode) ? themode : 0644, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + else if (encoding == YENC_ENCODED) { + crc = 0; + crcptr = &crc; + if (progress.fsize == -1) { + fprintf (outfile, "=ybegin line=128 name=%s%s", + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + else { + fprintf (outfile, "=ybegin line=128 size=%ld name=%s%s", + progress.fsize, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + } + + if ((res = UUEncodeStream (outfile, theifile, encoding, 0, crcptr, NULL)) != UURET_OK) { + if (res != UURET_CANCEL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_ERR_ENCODING), + UUFNameFilter ((infname)?infname:outfname), + (res==UURET_IOERR)?strerror(uu_errno):UUstrerror(res)); + } + progress.action = 0; + return res; + } + + if (encoding == UU_ENCODED || encoding == XX_ENCODED) { + fprintf (outfile, "%c%s", + (encoding==UU_ENCODED) ? UUEncodeTable[0] : XXEncodeTable[0], + (char *) eolstring); + fprintf (outfile, "end%s", (char *) eolstring); + } + else if (encoding == YENC_ENCODED) { + if (progress.fsize == -1) { + fprintf (outfile, "=yend crc32=%08lx%s", + crc, + (char *) eolstring); + } + else { + fprintf (outfile, "=yend size=%ld crc32=%08lx%s", + progress.fsize, + crc, + (char *) eolstring); + } + } + + /* + * empty line at end does no harm + */ + + fprintf (outfile, "%s", (char *) eolstring); + + if (infile==NULL) + fclose (theifile); + + progress.action = 0; + return UURET_OK; +} + +/* + * Encode as MIME message/partial + */ + +int UUEXPORT +UUEncodePartial (FILE *outfile, FILE *infile, + char *infname, int encoding, + char *outfname, char *mimetype, + int filemode, int partno, long linperfile, + dword *crcptr) +{ + mimemap *miter=mimetable; + static FILE *theifile; + int themode, numparts = 1; + struct stat finfo; + long thesize; + char *ptr; + int res; + dword pcrc; + dword *pcrcptr=NULL; + + if ((outfname==NULL&&infname==NULL) || partno<=0 || + (infile == NULL&&infname==NULL) || outfile==NULL || + (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&& + encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED)) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_PARM_CHECK), "UUEncodePartial()"); + return UURET_ILLVAL; + } + + /* + * The first part needs a set of headers + */ + + progress.action = 0; + + if (partno == 1) { + if (infile==NULL) { + if (stat (infname, &finfo) == -1) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_STAT_FILE), + infname, strerror (uu_errno=errno)); + return UURET_IOERR; + } + if ((theifile = fopen (infname, "rb")) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_FILE), + infname, strerror (uu_errno=errno)); + return UURET_IOERR; + } + if (linperfile <= 0) + numparts = 1; + else + numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/ + (linperfile*bpl[encoding])); + + themode = (filemode) ? filemode : ((int) finfo.st_mode & 0777); + thesize = (long) finfo.st_size; + } + else { + if (fstat (fileno (infile), &finfo) != 0) { + UUMessage (uuencode_id, __LINE__, UUMSG_WARNING, + uustring (S_STAT_ONE_PART)); + numparts = 1; + themode = (filemode)?filemode:0644; + thesize = -1; + } + else { + if (linperfile <= 0) + numparts = 1; + else + numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/ + (linperfile*bpl[encoding])); + + themode = (int) finfo.st_mode & 0777; + thesize = (long) finfo.st_size; + } + theifile = infile; + } + + _FP_strncpy (progress.curfile, (outfname)?outfname:infname, 256); + + progress.totsize = (thesize>=0) ? thesize : -1; + progress.partno = 1; + progress.numparts = numparts; + progress.percent = 0; + progress.foffset = 0; + + /* + * If not given from outside, select an appropriate Content-Type by + * looking at the file's extension. If it is unknown, default to + * Application/Octet-Stream + */ + + if (mimetype == NULL) { + if ((ptr = _FP_strrchr ((outfname)?outfname:infname, '.'))) { + while (miter->extension && _FP_stricmp (ptr+1, miter->extension) != 0) + miter++; + mimetype = miter->mimetype; + } + } + + if (mimetype == NULL && (encoding==PT_ENCODED || encoding==QP_ENCODED)) { + mimetype = "text/plain"; + } + + /* + * print sub-header + */ + + if (encoding != YENC_ENCODED) { + fprintf (outfile, "MIME-Version: 1.0%s", (char *) eolstring); + fprintf (outfile, "Content-Type: %s%s", + (mimetype)?mimetype:"Application/Octet-Stream", + (char *) eolstring); + fprintf (outfile, "Content-Transfer-Encoding: %s%s", + CTE_TYPE(encoding), (char *) eolstring); + fprintf (outfile, "Content-Disposition: attachment; filename=\"%s\"%s", + UUFNameFilter ((outfname)?outfname:infname), (char *) eolstring); + } + + fprintf (outfile, "%s", (char *) eolstring); + + /* + * for the first part of UU or XX messages, print a begin line + */ + + if (encoding == UU_ENCODED || encoding == XX_ENCODED) { + fprintf (outfile, "begin %o %s%s", + (themode) ? themode : ((filemode)?filemode:0644), + UUFNameFilter ((outfname)?outfname:infname), (char *) eolstring); + } + } + if (encoding == YENC_ENCODED) { + pcrc = 0; + pcrcptr = &pcrc; + if (numparts != 1) { + if (progress.totsize == -1) { + fprintf (outfile, "=ybegin part=%d line=128 name=%s%s", + partno, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + else { + fprintf (outfile, "=ybegin part=%d line=128 size=%ld name=%s%s", + partno, + progress.totsize, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + + fprintf (outfile, "=ypart begin=%ld end=%ld%s", + (partno-1)*linperfile*128+1, + (partno*linperfile*128) < progress.totsize ? + (partno*linperfile*128) : progress.totsize, + (char *) eolstring); + } + else { + if (progress.totsize == -1) { + fprintf (outfile, "=ybegin line=128 name=%s%s", + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + else { + fprintf (outfile, "=ybegin line=128 size=%ld name=%s%s", + progress.totsize, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + } + } + + /* + * update progress information + */ + + progress.partno = partno; + progress.percent = 0; + progress.foffset = ftell (theifile); + + if (progress.totsize <= 0) + progress.fsize = -1; + else if (linperfile <= 0) + progress.fsize = progress.totsize; + else if (progress.foffset+linperfile*bpl[encoding] > progress.totsize) + progress.fsize = progress.totsize - progress.foffset; + else + progress.fsize = linperfile*bpl[encoding]; + + progress.action = UUACT_ENCODING; + + if ((res = UUEncodeStream (outfile, theifile, encoding, linperfile, + crcptr, pcrcptr)) != UURET_OK) { + if (infile==NULL) fclose (theifile); + if (res != UURET_CANCEL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_ERR_ENCODING), + UUFNameFilter ((outfname)?outfname:infname), + (res==UURET_IOERR)?strerror(uu_errno):UUstrerror (res)); + } + progress.action = 0; + return res; + } + + /* + * print end line + */ + + if (feof (theifile) && + (encoding == UU_ENCODED || encoding == XX_ENCODED)) { + fprintf (outfile, "%c%s", + (encoding==UU_ENCODED) ? UUEncodeTable[0] : XXEncodeTable[0], + (char *) eolstring); + fprintf (outfile, "end%s", (char *) eolstring); + } + else if (encoding == YENC_ENCODED) { + if (numparts != 1) { + fprintf (outfile, "=yend size=%ld part=%d pcrc32=%08lx", + (partno*linperfile*128) < progress.totsize ? + linperfile*128 : (progress.totsize-(partno-1)*linperfile*128), + partno, + pcrc); + } + else { + fprintf (outfile, "=yend size=%ld", + progress.totsize); + } + if (feof (theifile)) + fprintf (outfile, " crc32=%08lx", *crcptr); + fprintf (outfile, "%s", (char *) eolstring); + } + + /* + * empty line at end does no harm + */ + + if (encoding != PT_ENCODED && encoding != QP_ENCODED) { + fprintf (outfile, "%s", (char *) eolstring); + } + + if (infile==NULL) { + if (res != UURET_OK) { + progress.action = 0; + fclose (theifile); + return res; + } + if (feof (theifile)) { + progress.action = 0; + fclose (theifile); + return UURET_OK; + } + return UURET_CONT; + } + + /* + * leave progress.action as-is + */ + + return UURET_OK; +} + +/* + * send output to a stream, don't do any headers at all + */ + +int UUEXPORT +UUEncodeToStream (FILE *outfile, FILE *infile, + char *infname, int encoding, + char *outfname, int filemode) +{ + struct stat finfo; + FILE *theifile; + int themode; + int res; + dword crc; + dword *crcptr=NULL; + + if (outfile==NULL || + (infile == NULL&&infname==NULL) || + (outfname==NULL&&infname==NULL) || + (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&& + encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED)) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_PARM_CHECK), "UUEncodeToStream()"); + return UURET_ILLVAL; + } + + progress.action = 0; + + if (infile==NULL) { + if (stat (infname, &finfo) == -1) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_STAT_FILE), + infname, strerror (uu_errno=errno)); + return UURET_IOERR; + } + if ((theifile = fopen (infname, "rb")) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_FILE), + infname, strerror (uu_errno=errno)); + return UURET_IOERR; + } + themode = (filemode) ? filemode : ((int) finfo.st_mode & 0777); + progress.fsize = (long) finfo.st_size; + } + else { + if (fstat (fileno (infile), &finfo) == -1) { + /* gotta live with it */ + themode = 0644; + progress.fsize = -1; + } + else { + themode = (filemode) ? filemode : ((int) finfo.st_mode & 0777); + progress.fsize = (long) finfo.st_size; + } + theifile = infile; + } + + if (progress.fsize < 0) + progress.fsize = -1; + + _FP_strncpy (progress.curfile, (outfname)?outfname:infname, 256); + + progress.partno = 1; + progress.numparts = 1; + progress.percent = 0; + progress.foffset = 0; + progress.action = UUACT_ENCODING; + + if (encoding == UU_ENCODED || encoding == XX_ENCODED) { + fprintf (outfile, "begin %o %s%s", + (themode) ? themode : 0644, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + else if (encoding == YENC_ENCODED) { + crc = 0; + crcptr = &crc; + if (progress.fsize == -1) { + fprintf (outfile, "=ybegin line=128 name=%s%s", + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + else { + fprintf (outfile, "=ybegin line=128 size=%ld name=%s%s", + progress.fsize, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + } + + if ((res = UUEncodeStream (outfile, theifile, encoding, 0, crcptr, NULL)) != UURET_OK) { + if (res != UURET_CANCEL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_ERR_ENCODING), + UUFNameFilter ((infname)?infname:outfname), + (res==UURET_IOERR)?strerror(uu_errno):UUstrerror (res)); + } + progress.action = 0; + return res; + } + + if (encoding == UU_ENCODED || encoding == XX_ENCODED) { + fprintf (outfile, "%c%s", + (encoding==UU_ENCODED) ? UUEncodeTable[0] : XXEncodeTable[0], + (char *) eolstring); + fprintf (outfile, "end%s", (char *) eolstring); + } + else if (encoding == YENC_ENCODED) { + if (progress.fsize == -1) { + fprintf (outfile, "=yend crc32=%08lx%s", + crc, + (char *) eolstring); + } + else { + fprintf (outfile, "=yend size=%ld crc32=%08lx%s", + progress.fsize, + crc, + (char *) eolstring); + } + } + + /* + * empty line at end does no harm + */ + + fprintf (outfile, "%s", (char *) eolstring); + + if (infile==NULL) fclose (theifile); + progress.action = 0; + + return UURET_OK; +} + +/* + * Encode to files on disk, don't generate any headers + */ + +int UUEXPORT +UUEncodeToFile (FILE *infile, char *infname, int encoding, + char *outfname, char *diskname, long linperfile) +{ + int part, numparts, len, filemode, res; + char *oname=NULL, *optr, *ptr; + FILE *theifile, *outfile; + struct stat finfo; + dword pcrc, crc; + dword *pcrcptr=NULL, *crcptr=NULL; + + if ((diskname==NULL&&infname==NULL) || + (outfname==NULL&&infname==NULL) || (infile==NULL&&infname==NULL) || + (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&& + encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED)) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_PARM_CHECK), "UUEncodeToFile()"); + return UURET_ILLVAL; + } + + if (diskname) { + if ((ptr = strchr (diskname, '/')) == NULL) + ptr = strchr (diskname, '\\'); + if (ptr) { + len = strlen (diskname) + ((uuencodeext)?strlen(uuencodeext):3) + 5; + + if ((oname = (char*)malloc (len)) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), len); + return UURET_NOMEM; + } + sprintf (oname, "%s", diskname); + } + else { + len = ((uusavepath)?strlen(uusavepath):0) + strlen (diskname) + + ((uuencodeext)?strlen(uuencodeext):0) + 5; + + if ((oname = (char*)malloc (len)) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), len); + return UURET_NOMEM; + } + sprintf (oname, "%s%s", (uusavepath)?uusavepath:"", diskname); + } + } + else { + len = ((uusavepath) ? strlen (uusavepath) : 0) + + strlen(UUFNameFilter(infname)) + + ((uuencodeext)?strlen(uuencodeext):0) + 5; + + if ((oname = (char*)malloc (len)) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), len); + return UURET_NOMEM; + } + optr = UUFNameFilter (infname); + sprintf (oname, "%s%s", + (uusavepath)?uusavepath:"", + (*optr=='.')?optr+1:optr); + } + + /* + * optr points after the last dot, so that we can print the part number + * there. + */ + + optr = _FP_strrchr (oname, '.'); + if (optr==NULL || strchr (optr, '/')!=NULL || strchr (optr, '\\')!=NULL) { + optr = oname + strlen (oname); + *optr++ = '.'; + } + else if (optr==oname || *(optr-1)=='/' || *(optr-1)=='\\') { + optr = oname + strlen (oname); + *optr++ = '.'; + } + else + optr++; + + progress.action = 0; + + if (infile==NULL) { + if (stat (infname, &finfo) == -1) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_STAT_FILE), + infname, strerror (uu_errno=errno)); + _FP_free (oname); + return UURET_IOERR; + } + if ((theifile = fopen (infname, "rb")) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_FILE), + infname, strerror (uu_errno=errno)); + _FP_free (oname); + return UURET_IOERR; + } + if (linperfile <= 0) + numparts = 1; + else + numparts = (int) (((long)finfo.st_size + (linperfile*bpl[encoding]-1)) / + (linperfile*bpl[encoding])); + + filemode = (int) finfo.st_mode & 0777; + progress.totsize = (long) finfo.st_size; + } + else { + if (fstat (fileno (infile), &finfo) == -1) { + /* gotta live with it */ + filemode = 0644; + numparts = -1; + progress.totsize = -1; + } + else { + if (linperfile <= 0) + numparts = 1; + else + numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/ + (linperfile*bpl[encoding])); + + filemode = (int) finfo.st_mode & 0777; + progress.totsize = -1; + } + theifile = infile; + } + + _FP_strncpy (progress.curfile, (outfname)?outfname:infname, 256); + + progress.totsize = (progress.totsize<0) ? -1 : progress.totsize; + progress.numparts = numparts; + + for (part=1; !feof (theifile); part++) { + /* + * Attach extension + */ + if (progress.numparts==1 && progress.totsize!=-1 && uuencodeext!=NULL) + strcpy (optr, uuencodeext); + else + sprintf (optr, "%03d", part); + + /* + * check if target file exists + */ + + if (!uu_overwrite) { + if (stat (oname, &finfo) == 0) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_TARGET_EXISTS), oname); + if (infile==NULL) fclose (theifile); + progress.action = 0; + free (oname); + return UURET_EXISTS; + } + } + + /* + * update progress information + */ + + progress.action = 0; + progress.partno = part; + progress.percent = 0; + progress.foffset = ftell (theifile); + + if (progress.totsize == -1) + progress.fsize = -1; + else if (linperfile <= 0) + progress.fsize = progress.totsize; + else if (progress.foffset+linperfile*bpl[encoding] > progress.totsize) + progress.fsize = progress.totsize - progress.foffset; + else + progress.fsize = linperfile*bpl[encoding]; + + progress.action = UUACT_ENCODING; + + if ((outfile = fopen (oname, "w")) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_TARGET), + oname, strerror (uu_errno = errno)); + if (infile==NULL) fclose (theifile); + progress.action = 0; + free (oname); + return UURET_IOERR; + } + + if (encoding != YENC_ENCODED) { + fprintf (outfile, "%s", (char *) eolstring); + fprintf (outfile, "_=_ %s", (char *) eolstring); + if (numparts == -1) + fprintf (outfile, "_=_ Part %03d of file %s%s", + part, UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + else + fprintf (outfile, "_=_ Part %03d of %03d of file %s%s", + part, numparts, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + fprintf (outfile, "_=_ %s", (char *) eolstring); + fprintf (outfile, "%s", (char *) eolstring); + } + + if (part==1 && (encoding == UU_ENCODED || encoding == XX_ENCODED)) { + fprintf (outfile, "begin %o %s%s", + (filemode)?filemode : 0644, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + else if (encoding == YENC_ENCODED) { + if (!crcptr) { + crc = 0; + crcptr = &crc; + } + pcrc = 0; + pcrcptr = &pcrc; + if (numparts != 1) { + if (progress.totsize == -1) { + fprintf (outfile, "=ybegin part=%d line=128 name=%s%s", + part, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + else { + fprintf (outfile, "=ybegin part=%d line=128 size=%ld name=%s%s", + part, + progress.totsize, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + + fprintf (outfile, "=ypart begin=%ld end=%ld%s", + (part-1)*linperfile*128+1, + (part*linperfile*128) < progress.totsize ? + (part*linperfile*128) : progress.totsize, + (char *) eolstring); + } + else { + if (progress.totsize == -1) { + fprintf (outfile, "=ybegin line=128 name=%s%s", + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + else { + fprintf (outfile, "=ybegin line=128 size=%ld name=%s%s", + progress.totsize, + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + } + } + } + + if ((res = UUEncodeStream (outfile, theifile, + encoding, linperfile, crcptr, pcrcptr)) != UURET_OK) { + if (res != UURET_CANCEL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_ERR_ENCODING), + UUFNameFilter ((infname)?infname:outfname), + (res==UURET_IOERR)?strerror(uu_errno):UUstrerror (res)); + } + if (infile==NULL) fclose (theifile); + progress.action = 0; + fclose (outfile); + unlink (oname); + _FP_free (oname); + return res; + } + + if (feof (theifile) && + (encoding == UU_ENCODED || encoding == XX_ENCODED)) { + fprintf (outfile, "%c%s", + (encoding==UU_ENCODED) ? UUEncodeTable[0] : XXEncodeTable[0], + (char *) eolstring); + fprintf (outfile, "end%s", (char *) eolstring); + } + else if (encoding == YENC_ENCODED) { + if (numparts != 1) { + fprintf (outfile, "=yend size=%ld part=%d pcrc32=%08lx", + (part*linperfile*128) < progress.totsize ? + linperfile*128 : (progress.totsize-(part-1)*linperfile*128), + part, + pcrc); + } + else { + fprintf (outfile, "=yend size=%ld", + progress.totsize); + } + if (feof (theifile)) + fprintf (outfile, " crc32=%08lx", crc); + fprintf (outfile, "%s", (char *) eolstring); + } + + /* + * empty line at end does no harm + */ + + fprintf (outfile, "%s", (char *) eolstring); + fclose (outfile); + } + + if (infile==NULL) fclose (theifile); + progress.action = 0; + _FP_free (oname); + return UURET_OK; +} + +/* + * Encode a MIME Mail message or Newsgroup posting and send to a + * stream. Still needs a somewhat smart MDA, since we only gene- + * rate a minimum set of headers. + */ + +int UUEXPORT +UUE_PrepSingle (FILE *outfile, FILE *infile, + char *infname, int encoding, + char *outfname, int filemode, + char *destination, char *from, + char *subject, int isemail) +{ + return UUE_PrepSingleExt (outfile, infile, + infname, encoding, + outfname, filemode, + destination, from, + subject, NULL, + isemail); +} + +int UUEXPORT +UUE_PrepSingleExt (FILE *outfile, FILE *infile, + char *infname, int encoding, + char *outfname, int filemode, + char *destination, char *from, + char *subject, char *replyto, + int isemail) +{ + mimemap *miter=mimetable; + char *subline, *oname; + char *mimetype, *ptr; + int res, len; + + if ((outfname==NULL&&infname==NULL) || (infile==NULL&&infname==NULL) || + (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&& + encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED)) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_PARM_CHECK), "UUE_PrepSingle()"); + return UURET_ILLVAL; + } + + oname = UUFNameFilter ((outfname)?outfname:infname); + len = ((subject)?strlen(subject):0) + strlen(oname) + 40; + + if ((ptr = _FP_strrchr (oname, '.'))) { + while (miter->extension && _FP_stricmp (ptr+1, miter->extension) != 0) + miter++; + mimetype = miter->mimetype; + } + else + mimetype = NULL; + + if (mimetype == NULL && (encoding == PT_ENCODED || encoding == QP_ENCODED)) { + mimetype = "text/plain"; + } + + if ((subline = (char *) malloc (len)) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), len); + return UURET_NOMEM; + } + + if (encoding == YENC_ENCODED) { + if (subject) + sprintf (subline, "- %s - %s (001/001)", oname, subject); + else + sprintf (subline, "- %s - (001/001)", oname); + } + else { + if (subject) + sprintf (subline, "%s (001/001) - [ %s ]", subject, oname); + else + sprintf (subline, "[ %s ] (001/001)", oname); + } + + if (from) { + fprintf (outfile, "From: %s%s", from, (char *) eolstring); + } + if (destination) { + fprintf (outfile, "%s: %s%s", + (isemail)?"To":"Newsgroups", + destination, (char *) eolstring); + } + + fprintf (outfile, "Subject: %s%s", subline, (char *) eolstring); + + if (replyto) { + fprintf (outfile, "Reply-To: %s%s", replyto, (char *) eolstring); + } + + if (encoding != YENC_ENCODED) { + fprintf (outfile, "MIME-Version: 1.0%s", (char *) eolstring); + fprintf (outfile, "Content-Type: %s; name=\"%s\"%s", + (mimetype)?mimetype:"Application/Octet-Stream", + UUFNameFilter ((outfname)?outfname:infname), + (char *) eolstring); + fprintf (outfile, "Content-Transfer-Encoding: %s%s", + CTE_TYPE(encoding), (char *) eolstring); + } + + fprintf (outfile, "%s", (char *) eolstring); + + res = UUEncodeToStream (outfile, infile, infname, encoding, + outfname, filemode); + + _FP_free (subline); + return res; +} + +int UUEXPORT +UUE_PrepPartial (FILE *outfile, FILE *infile, + char *infname, int encoding, + char *outfname, int filemode, + int partno, long linperfile, long filesize, + char *destination, char *from, char *subject, + int isemail) +{ + return UUE_PrepPartialExt (outfile, infile, + infname, encoding, + outfname, filemode, + partno, linperfile, filesize, + destination, + from, subject, NULL, + isemail); +} + +int UUEXPORT +UUE_PrepPartialExt (FILE *outfile, FILE *infile, + char *infname, int encoding, + char *outfname, int filemode, + int partno, long linperfile, long filesize, + char *destination, + char *from, char *subject, char *replyto, + int isemail) +{ + static int numparts, themode; + static char mimeid[64]; + static FILE *theifile; + struct stat finfo; + char *subline, *oname; + long thesize; + int res, len; + static dword crc; + dword *crcptr=NULL; + + if ((outfname==NULL&&infname==NULL) || (infile==NULL&&infname==NULL) || + (encoding!=UU_ENCODED&&encoding!=XX_ENCODED&&encoding!=B64ENCODED&& + encoding!=PT_ENCODED&&encoding!=QP_ENCODED&&encoding!=YENC_ENCODED)) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_PARM_CHECK), "UUE_PrepPartial()"); + return UURET_ILLVAL; + } + + oname = UUFNameFilter ((outfname)?outfname:infname); + len = ((subject)?strlen(subject):0) + strlen (oname) + 40; + + /* + * if first part, get information about the file + */ + + if (partno == 1) { + if (infile==NULL) { + if (stat (infname, &finfo) == -1) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_STAT_FILE), + infname, strerror (uu_errno=errno)); + return UURET_IOERR; + } + if ((theifile = fopen (infname, "rb")) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_FILE), + infname, strerror (uu_errno=errno)); + return UURET_IOERR; + } + if (linperfile <= 0) + numparts = 1; + else + numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/ + (linperfile*bpl[encoding])); + + themode = (filemode) ? filemode : ((int) finfo.st_mode & 0777); + thesize = (long) finfo.st_size; + } + else { + if (fstat (fileno (infile), &finfo) != 0) { + if (filesize <= 0) { + UUMessage (uuencode_id, __LINE__, UUMSG_WARNING, + uustring (S_STAT_ONE_PART)); + numparts = 1; + themode = (filemode)?filemode:0644; + thesize = -1; + } + else { + if (linperfile <= 0) + numparts = 1; + else + numparts = (int) ((filesize+(linperfile*bpl[encoding]-1))/ + (linperfile*bpl[encoding])); + + themode = (filemode)?filemode:0644; + thesize = filesize; + } + } + else { + if (linperfile <= 0) + numparts = 1; + else + numparts = (int) (((long)finfo.st_size+(linperfile*bpl[encoding]-1))/ + (linperfile*bpl[encoding])); + + filemode = (int) finfo.st_mode & 0777; + thesize = (long) finfo.st_size; + } + theifile = infile; + } + + /* + * if there's one part only, don't use Message/Partial + */ + + if (numparts == 1) { + if (infile==NULL) fclose (theifile); + return UUE_PrepSingleExt (outfile, infile, infname, encoding, + outfname, filemode, destination, + from, subject, replyto, isemail); + } + + /* + * we also need a unique ID + */ + + sprintf (mimeid, "UUDV-%ld.%ld.%s", + (long) time(NULL), thesize, + (strlen(oname)>16)?"oops":oname); + } + + if ((subline = (char *) malloc (len)) == NULL) { + UUMessage (uuencode_id, __LINE__, UUMSG_ERROR, + uustring (S_OUT_OF_MEMORY), len); + if (infile==NULL) fclose (theifile); + return UURET_NOMEM; + } + + + if (encoding == YENC_ENCODED) { + if (partno == 1) crc = 0; + crcptr = &crc; + if (subject) + sprintf (subline, "- %s - %s (%03d/%03d)", oname, subject, + partno, numparts); + else + sprintf (subline, "- %s - (%03d/%03d)", oname, + partno, numparts); + } + else { + if (subject) + sprintf (subline, "%s (%03d/%03d) - [ %s ]", + subject, partno, numparts, oname); + else + sprintf (subline, "[ %s ] (%03d/%03d)", + oname, partno, numparts); + } + + if (from) { + fprintf (outfile, "From: %s%s", from, (char *) eolstring); + } + + if (destination) { + fprintf (outfile, "%s: %s%s", + (isemail)?"To":"Newsgroups", + destination, (char *) eolstring); + } + + fprintf (outfile, "Subject: %s%s", subline, (char *) eolstring); + + if (replyto) { + fprintf (outfile, "Reply-To: %s%s", replyto, (char *) eolstring); + } + + if (encoding != YENC_ENCODED) { + fprintf (outfile, "MIME-Version: 1.0%s", (char *) eolstring); + fprintf (outfile, "Content-Type: Message/Partial; number=%d; total=%d;%s", + partno, numparts, (char *) eolstring); + fprintf (outfile, "\tid=\"%s\"%s", + mimeid, (char *) eolstring); + } + + fprintf (outfile, "%s", (char *) eolstring); + + res = UUEncodePartial (outfile, theifile, + infname, encoding, + (outfname)?outfname:infname, NULL, + themode, partno, linperfile, crcptr); + + _FP_free (subline); + + if (infile==NULL) { + if (res != UURET_OK) { + fclose (theifile); + return res; + } + if (feof (theifile)) { + fclose (theifile); + return UURET_OK; + } + return UURET_CONT; + } + + return res; +} diff --git a/goldlib/uulib/uulib.cpp b/goldlib/uulib/uulib.cpp new file mode 100644 index 0000000..f2e58b4 --- /dev/null +++ b/goldlib/uulib/uulib.cpp @@ -0,0 +1,1242 @@ +/* + * This file is part of uudeview, the simple and friendly multi-part multi- + * file uudecoder program (c) 1994-2001 by Frank Pilhofer. The author may + * be contacted at fp@fpx.de + * + * 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 2 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. + */ + +/* + * This file implements the externally visible functions, as declared + * in uudeview.h, and some internal interfacing functions + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef SYSTEM_WINDLL +#include +#endif +#ifdef SYSTEM_OS2 +#include +#endif + +#include +#include +#include + +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef STDC_HEADERS +#include +#include +#include +#else +#ifdef HAVE_STDARG_H +#include +#else +#ifdef HAVE_VARARGS_H +#include +#endif +#endif +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +#ifdef HAVE_ERRNO_H +#include +#endif + +/* to get open() in Windows */ +#ifdef HAVE_IO_H +#include +#endif + +#include +#include +#include +#include + +char * uulib_id = "$Id$"; + +#ifdef SYSTEM_WINDLL +BOOL _export WINAPI +DllEntryPoint (HINSTANCE hInstance, DWORD seginfo, + LPVOID lpCmdLine) +{ + /* Don't do anything, so just return true */ + return TRUE; +} +#endif + +/* + * In DOS, we must open the file binary, O_BINARY is defined there + */ + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +/* for braindead systems */ +#ifndef SEEK_SET +#ifdef L_BEGIN +#define SEEK_SET L_BEGIN +#else +#define SEEK_SET 0 +#endif +#endif + +/* + * Callback functions and their opaque arguments + */ + +void (*uu_MsgCallback) (void *, char *, int) = NULL; +int (*uu_BusyCallback) (void *, uuprogress *) = NULL; +int (*uu_FileCallback) (void *, char *, char *, int) = NULL; +char * (*uu_FNameFilter) (void *, char *) = NULL; + +void *uu_MsgCBArg = NULL; +void *uu_BusyCBArg = NULL; +void *uu_FileCBArg = NULL; +void *uu_FFCBArg = NULL; + +/* + * Global variables + */ + +int uu_fast_scanning = 0; /* assumes at most 1 part per file */ +int uu_bracket_policy = 0; /* gives part numbers in [] higher priority */ +int uu_verbose = 1; /* enables/disables messages¬es */ +int uu_desperate = 0; /* desperate mode */ +int uu_ignreply = 0; /* ignore replies */ +int uu_debug = 0; /* debugging mode (print __FILE__/__LINE__) */ +int uu_errno = 0; /* the errno that caused this UURET_IOERR */ +int uu_dumbness = 0; /* switch off the program's intelligence */ +int uu_overwrite = 1; /* whether it's ok to overwrite ex. files */ +int uu_ignmode = 0; /* ignore the original file mode */ +int uu_handletext = 0; /* do we want text/plain messages */ +int uu_usepreamble = 0; /* do we want Mime preambles/epilogues */ +int uu_tinyb64 = 0; /* detect short B64 outside of MIME */ +int uu_remove_input = 0; /* remove input files after decoding */ +int uu_more_mime = 0; /* strictly adhere to MIME headers */ + +headercount hlcount = { + 3, /* restarting after a MIME body */ + 2, /* after useful data in freestyle mode */ + 1 /* after useful data and an empty line */ +}; + +/* + * version string + */ + +char uulibversion[256] = VERSION "pl" PATCH; + +/* + * prefix to the files on disk, usually a path name to save files to + */ + +char *uusavepath; + +/* + * extension to use when encoding single-part files + */ + +char *uuencodeext; + +/* + * areas to malloc + */ + +char *uulib_msgstring; +char *uugen_inbuffer; +char *uugen_fnbuffer; + +/* + * The Global List of Files + */ + +uulist *UUGlobalFileList = NULL; + +/* + * time values for BusyCallback. msecs is MILLIsecs here + */ + +static long uu_busy_msecs = 0; /* call callback function each msecs */ +static long uu_last_secs = 0; /* secs of last call to callback */ +static long uu_last_usecs = 0; /* usecs of last call to callback */ + +/* + * progress information + */ + +uuprogress progress; + +/* + * Linked list of files we want to delete after decoding + */ + +typedef struct _itbd { + char *fname; + struct _itbd *NEXT; +} itbd; +static itbd * ftodel = NULL; + +/* + * for the busy poll + */ + +unsigned long uuyctr; + +/* + * Areas to allocate. Instead of using static memory areas, we malloc() + * the memory in UUInitialize() and release them in UUCleanUp to prevent + * blowing up of the binary size + * This is a table with the pointers to allocate and required sizes. + * They are guaranteed to be never NULL. + */ + +typedef struct { + char **ptr; + size_t size; +} allomap; + +static allomap toallocate[] = { + { &uugen_fnbuffer, 1024 }, /* generic filename buffer */ + { &uugen_inbuffer, 1024 }, /* generic input data buffer */ + { &uucheck_lastname, 256 }, /* from uucheck.c */ + { &uucheck_tempname, 256 }, + { &uuestr_itemp, 256 }, /* from uuencode.c:UUEncodeStream() */ + { &uuestr_otemp, 1024 }, + { &uulib_msgstring, 1024 }, /* from uulib.c:UUMessage() */ + { &uuncdl_fulline, 300 }, /* from uunconc.c:UUDecodeLine() */ + { &uuncdp_oline, 1200 }, /* from uunconc.c:UUDecodePart() */ + { &uunconc_UUxlat, 256 * sizeof (int) }, /* from uunconc.c:toplevel */ + { &uunconc_UUxlen, 64 * sizeof (int) }, + { &uunconc_B64xlat, 256 * sizeof (int) }, + { &uunconc_XXxlat, 256 * sizeof (int) }, + { &uunconc_BHxlat, 256 * sizeof (int) }, + { &uunconc_save, 3*300 }, /* from uunconc.c:decoding buffer */ + { &uuscan_shlline, 1024 }, /* from uuscan.c:ScanHeaderLine() */ + { &uuscan_pvvalue, 300 }, /* from uuscan.c:ParseValue() */ + { &uuscan_phtext, 300 }, /* from uuscan.c:ParseHeader() */ + { &uuscan_sdline, 300 }, /* from uuscan.c:ScanData() */ + { &uuscan_sdbhds1, 300 }, + { &uuscan_sdbhds2, 300 }, + { &uuscan_spline, 300 }, /* from uuscan.c:ScanPart() */ + { &uuutil_bhwtmp, 300 }, /* from uuutil.c:UUbhwrite() */ + { NULL, 0 } +}; + +/* + * Handle the printing of messages. Works like printf. + */ + +#if defined(STDC_HEADERS) || defined(HAVE_STDARG_H) +int +UUMessage (char *file, int line, int level, char *format, ...) +#else +int +UUMessage (va_alist) + va_dcl +#endif +{ + char *msgptr; +#if defined(STDC_HEADERS) || defined(HAVE_STDARG_H) + va_list ap; + + va_start (ap, format); +#else + char *file, *format; + int line, level; + va_list ap; + + va_start (ap); + file = va_arg (ap, char *); + line = va_arg (ap, int); + level = va_arg (ap, int); + format = va_arg (ap, char *); +#endif + + if (uu_debug) { + sprintf (uulib_msgstring, "%s(%d): %s", file, line, msgnames[level]); + msgptr = uulib_msgstring + strlen (uulib_msgstring); + } + else { + sprintf (uulib_msgstring, "%s", msgnames[level]); + msgptr = uulib_msgstring + strlen (uulib_msgstring); + } + + if (uu_MsgCallback && (level>UUMSG_NOTE || uu_verbose)) { + vsprintf (msgptr, format, ap); + + (*uu_MsgCallback) (uu_MsgCBArg, uulib_msgstring, level); + } + + va_end (ap); + + return UURET_OK; +} + +/* + * Call the Busy Callback from time to time. This function must be + * polled from the Busy loops. + */ + +int +UUBusyPoll (void) +{ +#ifdef HAVE_GETTIMEOFDAY + struct timeval tv; + long msecs; + + if (uu_BusyCallback) { + (void) gettimeofday (&tv, NULL); + + msecs = 1000*(tv.tv_sec-uu_last_secs)+(tv.tv_usec-uu_last_usecs)/1000; + + if (uu_last_secs==0 || msecs > uu_busy_msecs) { + uu_last_secs = tv.tv_sec; + uu_last_usecs = tv.tv_usec; + + return (*uu_BusyCallback) (uu_BusyCBArg, &progress); + } + } +#else + time_t now; + long msecs; + + if (uu_BusyCallback) { + now = time(NULL); + if (uu_busy_msecs <= 0) { + msecs = 1; + } + else { + msecs = 1000 * (now - uu_last_secs); + } + + if (uu_last_secs==0 || msecs > uu_busy_msecs) { + uu_last_secs = now; + uu_last_usecs = 0; + + return (*uu_BusyCallback) (uu_BusyCBArg, &progress); + } + } +#endif + + return 0; +} + +/* + * Initialization function + */ + +int UUEXPORT +UUInitialize (void) +{ + allomap *aiter; + + progress.action = 0; + progress.curfile[0] = '\0'; + + ftodel = NULL; + + uusavepath = NULL; + uuencodeext = NULL; + + mssdepth = 0; + memset (&localenv, 0, sizeof (headers)); + memset (&sstate, 0, sizeof (scanstate)); + + nofnum = 0; + mimseqno = 0; + lastvalid = 0; + lastenc = 0; + uuyctr = 0; + + /* + * Allocate areas + */ + + for (aiter=toallocate; aiter->ptr; aiter++) + *(aiter->ptr) = NULL; + + for (aiter=toallocate; aiter->ptr; aiter++) { + if ((*(aiter->ptr) = (char *) malloc (aiter->size)) == NULL) { + /* + * oops. we may not print a message here, because we need these + * areas (uulib_msgstring) in UUMessage() + */ + for (aiter=toallocate; aiter->ptr; aiter++) { + _FP_free (*(aiter->ptr)); + } + return UURET_NOMEM; + } + } + + /* + * Must be called after areas have been malloced + */ + + UUInitConc (); + + return UURET_OK; +} + +/* + * Set and get Options + */ + +int UUEXPORT +UUGetOption (int option, int *ivalue, char *cvalue, int clength) +{ + int result; + + switch (option) { + case UUOPT_VERSION: + _FP_strncpy (cvalue, uulibversion, clength); + result = 0; + break; + case UUOPT_FAST: + if (ivalue) *ivalue = uu_fast_scanning; + result = uu_fast_scanning; + break; + case UUOPT_DUMBNESS: + if (ivalue) *ivalue = uu_dumbness; + result = uu_dumbness; + break; + case UUOPT_BRACKPOL: + if (ivalue) *ivalue = uu_bracket_policy; + result = uu_bracket_policy; + break; + case UUOPT_VERBOSE: + if (ivalue) *ivalue = uu_verbose; + result = uu_verbose; + break; + case UUOPT_DESPERATE: + if (ivalue) *ivalue = uu_desperate; + result = uu_desperate; + break; + case UUOPT_IGNREPLY: + if (ivalue) *ivalue = uu_ignreply; + result = uu_ignreply; + break; + case UUOPT_DEBUG: + if (ivalue) *ivalue = uu_debug; + result = uu_debug; + break; + case UUOPT_ERRNO: + if (ivalue) *ivalue = uu_errno; + result = uu_errno; + break; + case UUOPT_OVERWRITE: + if (ivalue) *ivalue = uu_overwrite; + result = uu_overwrite; + break; + case UUOPT_SAVEPATH: + _FP_strncpy (cvalue, uusavepath, clength); + result = 0; + break; + case UUOPT_PROGRESS: + if (clength==sizeof(uuprogress)) { + memcpy (cvalue, &progress, sizeof (uuprogress)); + result = 0; + } + else + result = -1; + break; + case UUOPT_IGNMODE: + if (ivalue) *ivalue = uu_ignmode; + result = uu_ignmode; + break; + case UUOPT_USETEXT: + if (ivalue) *ivalue = uu_handletext; + result = uu_handletext; + break; + case UUOPT_PREAMB: + if (ivalue) *ivalue = uu_usepreamble; + result = uu_usepreamble; + break; + case UUOPT_TINYB64: + if (ivalue) *ivalue = uu_tinyb64; + result = uu_tinyb64; + break; + case UUOPT_ENCEXT: + _FP_strncpy (cvalue, uuencodeext, clength); + result = 0; + break; + case UUOPT_REMOVE: + if (ivalue) *ivalue = uu_remove_input; + result = uu_remove_input; + break; + case UUOPT_MOREMIME: + if (ivalue) *ivalue = uu_more_mime; + result = uu_more_mime; + break; + default: + return -1; + } + return result; +} + +int UUEXPORT +UUSetOption (int option, int ivalue, char *cvalue) +{ + switch (option) { + case UUOPT_FAST: + uu_fast_scanning = ivalue; + break; + case UUOPT_DUMBNESS: + uu_dumbness = ivalue; + break; + case UUOPT_BRACKPOL: + uu_bracket_policy = ivalue; + break; + case UUOPT_VERBOSE: + uu_verbose = ivalue; + break; + case UUOPT_DESPERATE: + uu_desperate = ivalue; + break; + case UUOPT_IGNREPLY: + uu_ignreply = ivalue; + break; + case UUOPT_DEBUG: + uu_debug = ivalue; + break; + case UUOPT_OVERWRITE: + uu_overwrite = ivalue; + break; + case UUOPT_SAVEPATH: + _FP_free (uusavepath); + uusavepath = _FP_strdup (cvalue); + break; + case UUOPT_IGNMODE: + uu_ignmode = ivalue; + break; + case UUOPT_USETEXT: + uu_handletext = ivalue; + break; + case UUOPT_PREAMB: + uu_usepreamble = ivalue; + break; + case UUOPT_TINYB64: + uu_tinyb64 = ivalue; + break; + case UUOPT_ENCEXT: + _FP_free (uuencodeext); + uuencodeext = _FP_strdup (cvalue); + break; + case UUOPT_REMOVE: + uu_remove_input = ivalue; + break; + case UUOPT_MOREMIME: + uu_more_mime = ivalue; + break; + default: + return UURET_ILLVAL; + } + return UURET_OK; +} + +char * UUEXPORT +UUstrerror (int code) +{ + return uuretcodes[code]; +} + +/* + * Set the various Callback functions + */ + +int UUEXPORT +UUSetMsgCallback (void *opaque, void (*func) (void *, char *, int)) +{ + uu_MsgCallback = func; + uu_MsgCBArg = opaque; + + return UURET_OK; +} + +int UUEXPORT +UUSetBusyCallback (void *opaque, int (*func)(void *, uuprogress *), long msecs) +{ + uu_BusyCallback = func; + uu_BusyCBArg = opaque; + uu_busy_msecs = msecs; + + return UURET_OK; +} + +int UUEXPORT +UUSetFileCallback (void *opaque, int (*func)(void *, char *, char *, int)) +{ + uu_FileCallback = func; + uu_FileCBArg = opaque; + + return UURET_OK; +} + +int UUEXPORT +UUSetFNameFilter (void *opaque, char * (*func)(void *, char *)) +{ + uu_FNameFilter = func; + uu_FFCBArg = opaque; + + return UURET_OK; +} + +/* + * Return a pointer to the nth element of the GlobalFileList + * zero-based, returns NULL if item is too large. + */ + +uulist * UUEXPORT +UUGetFileListItem (int item) +{ + uulist *iter=UUGlobalFileList; + + if (item < 0) + return NULL; + while (item && iter) { + iter = iter->NEXT; + item--; + } + return iter; +} + +/* + * call the current filter + */ + +char * UUEXPORT +UUFNameFilter (char *fname) +{ + if (uu_FNameFilter) + return (*uu_FNameFilter) (uu_FFCBArg, fname); + + return fname; +} + +/* + * Load a File. We call ScanPart repeatedly until at EOF and + * add the parts to UUGlobalFileList + */ + +int UUEXPORT +UULoadFile (char *filename, char *fileid, int delflag) +{ + int res, sr, count=0; + struct stat finfo; + fileread *loaded; + uufile *fload; + itbd *killem; + FILE *datei; + + if ((datei = fopen (filename, "rb")) == NULL) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_SOURCE), + filename, strerror (uu_errno = errno)); + return UURET_IOERR; + } + + if (fstat (fileno(datei), &finfo) == -1) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_STAT_FILE), + filename, strerror (uu_errno = errno)); + fclose (datei); + return UURET_IOERR; + } + + /* + * schedule for destruction + */ + + if (delflag && fileid==NULL) { + if ((killem = (itbd *) malloc (sizeof (itbd))) == NULL) { + UUMessage (uulib_id, __LINE__, UUMSG_WARNING, + uustring (S_OUT_OF_MEMORY), sizeof (itbd)); + } + else if ((killem->fname = _FP_strdup (filename)) == NULL) { + UUMessage (uulib_id, __LINE__, UUMSG_WARNING, + uustring (S_OUT_OF_MEMORY), strlen(filename)+1); + _FP_free (killem); + } + else { + killem->NEXT = ftodel; + ftodel = killem; + } + } + + progress.action = 0; + progress.partno = 0; + progress.numparts = 1; + progress.fsize = (long) ((finfo.st_size>0)?finfo.st_size:-1); + progress.percent = 0; + progress.foffset = 0; + _FP_strncpy (progress.curfile, + (strlen(filename)>255)? + (filename+strlen(filename)-255):filename, + 256); + progress.action = UUACT_SCANNING; + + if (fileid == NULL) + fileid = filename; + + while (!feof (datei) && !ferror (datei)) { + /* + * Peek file, or some systems won't detect EOF + */ + res = fgetc (datei); + if (feof (datei) || ferror (datei)) + break; + else + ungetc (res, datei); + + if ((loaded = ScanPart (datei, fileid, &sr)) == NULL) { + if (sr != UURET_NODATA && sr != UURET_OK && sr != UURET_CONT) { + UUkillfread (loaded); + if (sr != UURET_CANCEL) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_READ_ERROR), filename, + strerror (uu_errno)); + } + UUCheckGlobalList (); + progress.action = 0; + fclose (datei); + return sr; + } + continue; + } + + if (ferror (datei)) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_READ_ERROR), filename, + strerror (uu_errno = errno)); + UUCheckGlobalList (); + progress.action = 0; + fclose (datei); + return UURET_IOERR; + } + + if ((loaded->uudet == QP_ENCODED || loaded->uudet == PT_ENCODED) && + (loaded->filename == NULL || *(loaded->filename) == '\0') && + !uu_handletext && (loaded->flags&FL_PARTIAL)==0) { + /* + * Don't want text + */ + UUkillfread (loaded); + continue; + } + + if ((loaded->subject == NULL || *(loaded->subject) == '\0') && + (loaded->mimeid == NULL || *(loaded->mimeid) == '\0') && + (loaded->filename== NULL || *(loaded->filename)== '\0') && + (loaded->uudet == 0)) { + /* + * no useful data here + */ + UUkillfread (loaded); + if (uu_fast_scanning && sr != UURET_CONT) break; + continue; + } + + if ((fload = UUPreProcessPart (loaded, &res)) == NULL) { + /* + * no useful data found + */ + if (res != UURET_NODATA) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_READ_ERROR), filename, + (res==UURET_IOERR)?strerror(uu_errno):UUstrerror(res)); + } + UUkillfread (loaded); + if (uu_fast_scanning && sr != UURET_CONT) break; + continue; + } + + if ((loaded->subject && *(loaded->subject)) || + (loaded->mimeid && *(loaded->mimeid)) || + (loaded->filename&& *(loaded->filename))|| + (loaded->uudet)) { + UUMessage (uulib_id, __LINE__, UUMSG_MESSAGE, + uustring (S_LOADED_PART), + filename, + (loaded->subject) ? loaded->subject : "", + (fload->subfname) ? fload->subfname : "", + (loaded->filename) ? loaded->filename : "", + fload->partno, + (loaded->begin) ? "begin" : "", + (loaded->end) ? "end" : "", + codenames[loaded->uudet]); + } + + if ((res = UUInsertPartToList (fload))) { + /* + * couldn't use the data + */ + UUkillfile (fload); + + if (res != UURET_NODATA) { + UUCheckGlobalList (); + progress.action = 0; + fclose (datei); + return res; + } + if (uu_fast_scanning && sr != UURET_CONT) + break; + + continue; + } + + /* + * if in fast mode, we don't look any further, because we're told + * that each source file holds at most one encoded part + */ + + if (uu_fast_scanning && sr != UURET_CONT) + break; + + if (loaded->uudet) + count++; + } + fclose (datei); + + if (!uu_fast_scanning && count==0) { + UUMessage (uulib_id, __LINE__, UUMSG_NOTE, + uustring (S_NO_DATA_FOUND), filename); + } + + progress.action = 0; + UUCheckGlobalList (); + + return UURET_OK; +} + +/* + * decode to a temporary file. this is well handled by uudecode() + */ + +int UUEXPORT +UUDecodeToTemp (uulist *thefile) +{ + return UUDecode (thefile); +} + +/* + * decode file first to temp file, then copy it to a final location + */ + +int UUEXPORT +UUDecodeFile (uulist *thefile, char *destname) +{ + FILE *target, *source; + struct stat finfo; + int fildes, res; + size_t bytes; + + if (thefile == NULL) + return UURET_ILLVAL; + + if ((res = UUDecode (thefile)) != UURET_OK) + if (res != UURET_NOEND || !uu_desperate) + return res; + + if (thefile->binfile == NULL) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_NO_BIN_FILE)); + return UURET_IOERR; + } + + if ((source = fopen (thefile->binfile, "rb")) == NULL) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_FILE), + thefile->binfile, strerror (uu_errno = errno)); + return UURET_IOERR; + } + + /* + * for system security, strip setuid/setgid bits from mode + */ + + if ((thefile->mode & 0777) != thefile->mode) { + UUMessage (uulib_id, __LINE__, UUMSG_NOTE, + uustring (S_STRIPPED_SETUID), + destname, (int)thefile->mode); + thefile->mode &= 0777; + } + + /* + * Determine the name of the target file according to the rules: + * + * IF (destname!=NULL) THEN filename=destname; + * ELSE + * filename = thefile->filename + * IF (FilenameFilter!=NULL) THEN filename=FilenameFilter(filename); + * filename = SaveFilePath + filename + * END + */ + + if (destname) + strcpy (uugen_fnbuffer, destname); + else { + sprintf (uugen_fnbuffer, "%s%s", + (uusavepath)?uusavepath:"", + UUFNameFilter ((thefile->filename)? + thefile->filename:"unknown.xxx")); + } + + /* + * if we don't want to overwrite existing files, check if it's there + */ + + if (!uu_overwrite) { + if (stat (uugen_fnbuffer, &finfo) == 0) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_TARGET_EXISTS), uugen_fnbuffer); + fclose (source); + return UURET_EXISTS; + } + } + + if (fstat (fileno(source), &finfo) == -1) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_STAT_FILE), + thefile->binfile, strerror (uu_errno = errno)); + fclose (source); + return UURET_IOERR; + } + + progress.action = 0; + _FP_strncpy (progress.curfile, + (strlen(uugen_fnbuffer)>255)? + (uugen_fnbuffer+strlen(uugen_fnbuffer)-255):uugen_fnbuffer, + 256); + progress.partno = 0; + progress.numparts = 1; + progress.fsize = (long) ((finfo.st_size)?finfo.st_size:-1); + progress.foffset = 0; + progress.percent = 0; + progress.action = UUACT_COPYING; + + if ((fildes = open (uugen_fnbuffer, + O_WRONLY | O_CREAT | O_BINARY | O_TRUNC, + (uu_ignmode)?0666:thefile->mode)) == -1) { + progress.action = 0; + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_TARGET), + uugen_fnbuffer, strerror (uu_errno = errno)); + fclose (source); + return UURET_IOERR; + } + + if ((target = fdopen (fildes, "wb")) == NULL) { + progress.action = 0; + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_IO_ERR_TARGET), + uugen_fnbuffer, strerror (uu_errno = errno)); + fclose (source); + close (fildes); + return UURET_IOERR; + } + + while (!feof (source)) { + + if (UUBUSYPOLL(ftell(source),progress.fsize)) { + UUMessage (uulib_id, __LINE__, UUMSG_NOTE, + uustring (S_DECODE_CANCEL)); + fclose (source); + fclose (target); + unlink (uugen_fnbuffer); + return UURET_CANCEL; + } + + bytes = fread (uugen_inbuffer, 1, 1024, source); + + if (ferror (source) || (bytes == 0 && !feof (source))) { + progress.action = 0; + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_READ_ERROR), + thefile->binfile, strerror (uu_errno = errno)); + fclose (source); + fclose (target); + unlink (uugen_fnbuffer); + return UURET_IOERR; + } + if (fwrite (uugen_inbuffer, 1, bytes, target) != bytes) { + progress.action = 0; + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_WR_ERR_TARGET), + uugen_fnbuffer, strerror (uu_errno = errno)); + fclose (source); + fclose (target); + unlink (uugen_fnbuffer); + return UURET_IOERR; + } + } + + fclose (target); + fclose (source); + + /* + * after a successful decoding run, we delete the temporary file + */ + + if (unlink (thefile->binfile)) { + UUMessage (uulib_id, __LINE__, UUMSG_WARNING, + uustring (S_TMP_NOT_REMOVED), + thefile->binfile, + strerror (uu_errno = errno)); + } + _FP_free (thefile->binfile); + thefile->binfile = NULL; + thefile->state &= ~UUFILE_TMPFILE; + thefile->state |= UUFILE_DECODED; + progress.action = 0; + + return UURET_OK; +} + +/* + * Calls a function repeatedly with all the info we have for a file + * If the function returns non-zero, we break and don't send any more + */ + +int UUEXPORT +UUInfoFile (uulist *thefile, void *opaque, int (*func)(void *, char *)) +{ + int errflag=0, res, bhflag=0, dd; + long maxpos; + FILE *inpfile; + + /* + * We might need to ask our callback function to download the file + */ + + if (uu_FileCallback) { + if ((res = (*uu_FileCallback) (uu_FileCBArg, + thefile->thisfile->data->sfname, + uugen_fnbuffer, + 1)) != UURET_OK) + return res; + if ((inpfile = fopen (uugen_fnbuffer, "rb")) == NULL) { + (*uu_FileCallback) (uu_FileCBArg, thefile->thisfile->data->sfname, + uugen_fnbuffer, 0); + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_FILE), uugen_fnbuffer, + strerror (uu_errno = errno)); + return UURET_IOERR; + } + } + else { + if ((inpfile = fopen (thefile->thisfile->data->sfname, "rb")) == NULL) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_OPEN_FILE), + thefile->thisfile->data->sfname, + strerror (uu_errno=errno)); + return UURET_IOERR; + } + _FP_strncpy (uugen_fnbuffer, thefile->thisfile->data->sfname, 1024); + } + + /* + * seek to beginning of info + */ + + fseek (inpfile, thefile->thisfile->data->startpos, SEEK_SET); + maxpos = thefile->thisfile->data->startpos + thefile->thisfile->data->length; + + while (!feof (inpfile) && + (uu_fast_scanning || ftell(inpfile) < maxpos)) { + if (_FP_fgets (uugen_inbuffer, 511, inpfile) == NULL) + break; + uugen_inbuffer[511] = '\0'; + + if (ferror (inpfile)) + break; + + dd = UUValidData (uugen_inbuffer, 0, &bhflag); + + if (thefile->uudet == B64ENCODED && dd == B64ENCODED) + break; + else if (thefile->uudet == BH_ENCODED && bhflag) + break; + else if ((thefile->uudet == UU_ENCODED || thefile->uudet == XX_ENCODED) && + strncmp (uugen_inbuffer, "begin ", 6) == 0) + break; + else if (thefile->uudet == YENC_ENCODED && + strncmp (uugen_inbuffer, "=ybegin ", 8) == 0) + break; + + if ((*func) (opaque, uugen_inbuffer)) + break; + } + + if (ferror (inpfile)) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_READ_ERROR), + uugen_fnbuffer, strerror (uu_errno = errno)); + errflag = 1; + } + + fclose (inpfile); + + if (uu_FileCallback) + (*uu_FileCallback) (uu_FileCBArg, + thefile->thisfile->data->sfname, + uugen_fnbuffer, 0); + + if (errflag) + return UURET_IOERR; + + return UURET_OK; +} + +int UUEXPORT +UURenameFile (uulist *thefile, char *newname) +{ + char *oldname; + + if (thefile == NULL) + return UURET_ILLVAL; + + oldname = thefile->filename; + + if ((thefile->filename = _FP_strdup (newname)) == NULL) { + UUMessage (uulib_id, __LINE__, UUMSG_ERROR, + uustring (S_NOT_RENAME), + oldname, newname); + thefile->filename = oldname; + return UURET_NOMEM; + } + _FP_free (oldname); + return UURET_OK; +} + +int UUEXPORT +UURemoveTemp (uulist *thefile) +{ + if (thefile == NULL) + return UURET_ILLVAL; + + if (thefile->binfile) { + if (unlink (thefile->binfile)) { + UUMessage (uulib_id, __LINE__, UUMSG_WARNING, + uustring (S_TMP_NOT_REMOVED), + thefile->binfile, + strerror (uu_errno = errno)); + } + _FP_free (thefile->binfile); + thefile->binfile = NULL; + thefile->state &= ~UUFILE_TMPFILE; + } + return UURET_OK; +} + +int UUEXPORT +UUCleanUp (void) +{ + itbd *iter=ftodel, *ptr; + uulist *liter; + uufile *fiter; + allomap *aiter; + + /* + * delete temporary input files (such as the copy of stdin) + */ + + while (iter) { + if (unlink (iter->fname)) { + UUMessage (uulib_id, __LINE__, UUMSG_WARNING, + uustring (S_TMP_NOT_REMOVED), + iter->fname, strerror (uu_errno = errno)); + } + _FP_free (iter->fname); + ptr = iter; + iter = iter->NEXT; + _FP_free (ptr); + } + + ftodel = NULL; + + /* + * Delete input files after successful decoding + */ + + if (uu_remove_input) { + liter = UUGlobalFileList; + while (liter) { + if (liter->state & UUFILE_DECODED) { + fiter = liter->thisfile; + while (fiter) { + if (fiter->data && fiter->data->sfname) { + /* + * Error code ignored. We might want to delete a file multiple + * times + */ + unlink (fiter->data->sfname); + } + fiter = fiter->NEXT; + } + } + liter = liter->NEXT; + } + } + + UUkilllist (UUGlobalFileList); + UUGlobalFileList = NULL; + + _FP_free (uusavepath); + _FP_free (uuencodeext); + _FP_free (sstate.source); + + uusavepath = NULL; + uuencodeext = NULL; + + UUkillheaders (&localenv); + UUkillheaders (&sstate.envelope); + memset (&localenv, 0, sizeof (headers)); + memset (&sstate, 0, sizeof (scanstate)); + + while (mssdepth) { + mssdepth--; + UUkillheaders (&(multistack[mssdepth].envelope)); + _FP_free (multistack[mssdepth].source); + } + + /* + * clean up the malloc'ed stuff + */ + + for (aiter=toallocate; aiter->ptr; aiter++) { + _FP_free (*(aiter->ptr)); + *(aiter->ptr) = NULL; + } + + return UURET_OK; +} + diff --git a/goldlib/uulib/uunconc.cpp b/goldlib/uulib/uunconc.cpp new file mode 100644 index 0000000..16736a5 --- /dev/null +++ b/goldlib/uulib/uunconc.cpp @@ -0,0 +1,1625 @@ +/* + * This file is part of uudeview, the simple and friendly multi-part multi- + * file uudecoder program (c) 1994-2001 by Frank Pilhofer. The author may + * be contacted at fp@fpx.de + * + * 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 2 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. + */ + +/* + * These are the functions that are responsible for decoding. The + * original idea is from a freeware utility called "uunconc", and + * few lines of this code may still bear a remote resemblance to + * its code. If you are the author or know him, contact me. + * This program could only decode one multi-part, uuencoded file + * where the parts were in order. Base64, XX and BinHex decoding, + * support for multi-files and part-ordering covered by myself. + **/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef SYSTEM_WINDLL +#include +#endif +#ifdef SYSTEM_OS2 +#include +#endif + +#include + +#ifdef STDC_HEADERS +#include +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_IO_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +char * uunconc_id = "$Id$"; + +/* for braindead systems */ +#ifndef SEEK_SET +#ifdef L_BEGIN +#define SEEK_SET L_BEGIN +#else +#define SEEK_SET 0 +#endif +#endif + +/* + * decoder states + */ + +#define BEGIN (1) +#define DATA (2) +#define END (3) +#define END2 (4) +#define DONE (5) + +/* + * mallocable areas + */ + +char *uunconc_UUxlat; +char *uunconc_UUxlen; +char *uunconc_B64xlat; +char *uunconc_XXxlat; +char *uunconc_BHxlat; +char *uunconc_save; + +/* + * decoding translation tables and line length table + */ + +static int * UUxlen; /* initialized in UUInitConc() */ +static int * UUxlat; /* from the malloc'ed areas above */ +static int * B64xlat; +static int * XXxlat; +static int * BHxlat; + +/* + * buffer for decoding + */ + +static char *save[3]; + +/* + * mallocable areas + */ + +char *uuncdl_fulline; +char *uuncdp_oline; + +/* + * Return information for QuickDecode + */ + +static int uulboundary; + +/* + * To prevent warnings when using a char as index into an array + */ + +#define ACAST(s) ((int)(unsigned char)(s)) +#define isnotvalid(c) (ACAST(c) >= 0x80) + +/* + * Initialize decoding tables + */ + +void +UUInitConc (void) +{ + int i, j; + + /* + * Update pointers + */ + UUxlen = (int *) uunconc_UUxlen; + UUxlat = (int *) uunconc_UUxlat; + B64xlat = (int *) uunconc_B64xlat; + XXxlat = (int *) uunconc_XXxlat; + BHxlat = (int *) uunconc_BHxlat; + + save[0] = uunconc_save; + save[1] = uunconc_save + 256; + save[2] = uunconc_save + 512; + + /* prepare decoding translation table */ + for(i = 0; i < 256; i++) + UUxlat[i] = B64xlat[i] = XXxlat[i] = BHxlat[i] = -1; + + /* + * At some time I received a file which used lowercase characters for + * uuencoding. This shouldn't be, but let's accept it. Must take special + * care that this doesn't break xxdecoding. This is giving me quite a + * headache. If this one file hadn't been a Pocahontas picture, I might + * have ignored it for good. + */ + + for (i = ' ', j = 0; i < ' ' + 64; i++, j++) + UUxlat[i] /* = UUxlat[i+64] */ = j; + for (i = '`', j = 0; i < '`' + 32; i++, j++) + UUxlat[i] = j; + + /* add special cases */ + UUxlat['`'] = UUxlat[' ']; + UUxlat['~'] = UUxlat['^']; + + /* prepare line length table */ + UUxlen[0] = 1; + for(i = 1, j = 5; i <= 61; i += 3, j += 4) + UUxlen[i] = UUxlen[i+1] = UUxlen[i+2] = j; + + /* prepare other tables */ + for (i=0; i<64; i++) { + B64xlat[ACAST(B64EncodeTable[i])] = i; + XXxlat [ACAST(XXEncodeTable [i])] = i; + BHxlat [ACAST(BHEncodeTable [i])] = i; + } +} + +/* + * Workaround for Netscape + */ + +/* + * Determines whether Netscape may have broken up a data line (by + * inserting a newline). This only seems to happen after ") > ptr) + return 2; + } + + ptr = string + len; + + while (len && (*(ptr-1)=='\015' || *(ptr-1)=='\012')) { + ptr--; len--; + } + if (len<3) return 0; + if (*--ptr == ' ') ptr--; + ptr--; + + if (_FP_strnicmp (ptr, "",4)). If the first expression + * becomes true, the costly function isn't called :-) + * + * Since '<', '>', '&' might even be replaced by their html equivalents + * in href strings, I'm now using two passes, the first one for & + co, + * the second one for hrefs. + */ + +int +UUNetscapeCollapse (char *string) +{ + char *p1=string, *p2=string; + int res = 0; + + if (string==NULL) + return 0; + + /* + * First pass + */ + while (*p1) { + if (*p1 == '&') { + if (_FP_strnicmp (p1, "&", 5) == 0) { p1+=5; *p2++='&'; res=1; } + else if (_FP_strnicmp (p1, "<", 4) == 0) { p1+=4; *p2++='<'; res=1; } + else if (_FP_strnicmp (p1, ">", 4) == 0) { p1+=4; *p2++='>'; res=1; } + else *p2++ = *p1++; + } + else *p2++ = *p1++; + } + *p2 = '\0'; + /* + * Second pass + */ + p1 = p2 = string; + + while (*p1) { + if (*p1 == '<') { + if ((_FP_strnicmp (p1, "") != 0 || _FP_strstr (p1, "") != 0)) { + while (*p1 && *p1!='>') p1++; + if (*p1=='\0' || *(p1+1)!='<') return 0; + p1++; + while (*p1 && (*p1!='<' || _FP_strnicmp(p1,"",4)!=0)) { + *p2++ = *p1++; + } + if (_FP_strnicmp(p1,"",4) != 0) + return 0; + p1+=4; + res=1; + } + else + *p2++ = *p1++; + } + else + *p2++ = *p1++; + } + *p2 = '\0'; + + return res; +} + +/* + * The second parameter is 0 if we are still searching for encoded data, + * otherwise it indicates the encoding we're using right now. If we're + * still in the searching stage, we must be a little more strict in + * deciding for or against encoding; there's too much plain text looking + * like encoded data :-( + */ + +int +UUValidData (char *ptr, int encoding, int *bhflag) +{ + int i=0, j, len=0, suspicious=0, flag=0; + char *s = ptr; + + if ((s == NULL) || (*s == '\0')) { + return 0; /* bad string */ + } + + while (*s && *s!='\012' && *s!='\015') { + s++; + len++; + i++; + } + + if (i == 0) + return 0; + + switch (encoding) { + case UU_ENCODED: + goto _t_UU; + case XX_ENCODED: + goto _t_XX; + case B64ENCODED: + goto _t_B64; + case BH_ENCODED: + goto _t_Binhex; + case YENC_ENCODED: + return YENC_ENCODED; + } + + _t_Binhex: /* Binhex Test */ + len = i; s = ptr; + + /* + * bhflag notes the state we're in. Within the data, it's 1. If we're + * still looking for the initial :, it's 0 + */ + if (*bhflag == 0 && *s != ':') { + if (encoding==BH_ENCODED) return 0; + goto _t_B64; + } + else if (*bhflag == 0 /* *s == ':' */) { + s++; len--; + } + + while (len && BHxlat[ACAST(*s)] != -1) { + len--; s++; + } + + /* allow space characters at the end of the line if we are sure */ + /* that this is Binhex encoded data or the line was long enough */ + + flag = (*s == ':') ? 0 : 1; + + if (*s == ':' && len>0) { + s++; len--; + } + if (((i>=60 && len<=10) || encoding) && *s==' ') { + while (len && *s==' ') { + s++; len--; + } + } + + /* + * BinHex data shall have exactly 64 characters (except the last + * line). We ignore everything with less than 40 characters to + * be flexible + */ + + if (len != 0 || (flag && i < 40)) { + if (encoding==BH_ENCODED) return 0; + goto _t_B64; + } + + *bhflag = flag; + + return BH_ENCODED; + + _t_B64: /* Base64 Test */ + len = i; s = ptr; + + /* + * Face it: there _are_ Base64 lines that are not a multiple of four + * in length :-( + * + * if (len%4) + * goto _t_UU; + */ + + while (len--) { + if (/* *s < 0 */ isnotvalid(*s) || (B64xlat[ACAST(*s)] == -1 && *s != '=')) { + /* allow space characters at the end of the line if we are sure */ + /* that this is Base64 encoded data or the line was long enough */ + if (((i>=60 && len<=10) || encoding) && *s++==' ') { + while (*s==' ' && len) s++; + if (len==0) return B64ENCODED; + } + if (encoding==B64ENCODED) return 0; + goto _t_UU; + } + else if (*s == '=') { /* special case at end */ + /* if we know this is B64encoded, allow spaces at end of line */ + s++; + if (*s=='=' && len>=1) { + len--; s++; + } + if (encoding && len && *s==' ') { + while (len && *s==' ') { + s++; len--; + } + } + if (len != 0) { + if (encoding==B64ENCODED) return 0; + goto _t_UU; + } + return B64ENCODED; + } + s++; + } + return B64ENCODED; + + _t_UU: + len = i; s = ptr; + + if (UUxlat[ACAST(*s)] == -1) { /* uutest */ + if (encoding==UU_ENCODED) return 0; + goto _t_XX; + } + + j = UUxlen[UUxlat[ACAST(*s)]]; + + if (len-1 == j) /* remove trailing character */ + len--; + if (len != j) { + switch (UUxlat[ACAST(*s)]%3) { + case 1: + if (j-2 == len) j-=2; + break; + case 2: + if (j-1 == len) j-=1; + break; + } + } + + /* + * some encoders are broken with respect to encoding the last line of + * a file and produce extraoneous characters beyond the expected EOL + * So were not too picky here about the last line, as long as it's longer + * than necessary and shorter than the maximum + * this tolerance broke the xxdecoding, because xxencoded data was + * detected as being uuencoded :( so don't accept 'h' as first character + * also, if the first character is lowercase, don't accept the line to + * have space characters. the only encoder I've heard of which uses + * lowercase characters at least accepts the special case of encoding + * 0 as `. The strchr() shouldn't be too expensive here as it's only + * evaluated if the first character is lowercase, which really shouldn't + * be in uuencoded text. + */ + if (len != j && + ((ptr[0] == '-' && ptr[1] == '-' && strstr(ptr,"part")!=NULL) || + !(*ptr != 'M' && *ptr != 'h' && + len > j && len <= UUxlen[UUxlat['M']]))) { + if (encoding==UU_ENCODED) return 0; + goto _t_XX; /* bad length */ + } + + if (len != j || g_islower (*ptr)) { + /* + * if we are not in a 'uuencoded' state, don't allow the line to have + * space characters at all. if we know we _are_ decoding uuencoded + * data, the rest of the line, beyond the length of encoded data, may + * have spaces. + */ + if (encoding != UU_ENCODED) + if (strchr (ptr, ' ') != NULL) + goto _t_XX; + +/* suspicious = 1; we're careful here REMOVED 0.4.15 __FP__ */ + len = j; + } + + while (len--) { + if (/* *s < 0 */ isnotvalid(*s) || UUxlat[ACAST(*s++)] < 0) { + if (encoding==UU_ENCODED) return 0; + goto _t_XX; /* bad code character */ + } + if (*s == ' ' && suspicious) { + if (encoding==UU_ENCODED) return 0; + goto _t_XX; /* this line looks _too_ suspicious */ + } + } + return UU_ENCODED; /* data is valid */ + + _t_XX: /* XX Test */ + len = i; s = ptr; + + if (XXxlat[ACAST(*s)] == -1) + return 0; + + j = UUxlen[XXxlat[ACAST(*s)]]; /* Same line length table as UUencoding */ + + if (len-1 == j) /* remove trailing character */ + len--; + if (len != j) + switch (UUxlat[ACAST(*s)]%3) { + case 1: + if (j-2 == len) j-=2; + break; + case 2: + if (j-1 == len) j-=1; + break; + } + /* + * some encoders are broken with respect to encoding the last line of + * a file and produce extraoneous characters beyond the expected EOL + * So were not too picky here about the last line, as long as it's longer + * than necessary and shorter than the maximum + */ + if (len != j && !(*ptr != 'h' && len > j && len <= UUxlen[UUxlat['h']])) + return 0; /* bad length */ + + while(len--) { + if(/* *s < 0 */ isnotvalid(*s) || XXxlat[ACAST(*s++)] < 0) { + return 0; /* bad code character */ + } + } + return XX_ENCODED; /* data is valid */ +} + +/* + * This function may be called upon a line that does not look like + * valid encoding on first sight, but might be erroneously encoded + * data from Netscape, Lynx or MS Exchange. We might need to read + * a new line from the stream, which is why we need the FILE. + * Returns the type of encoded data if successful or 0 otherwise. + */ + +int +UURepairData (FILE *datei, char *line, int encoding, int *bhflag) +{ + int nflag, vflag=0, safety=42; + char *ptr; + + nflag = UUBrokenByNetscape (line); + + while (vflag == 0 && nflag && safety--) { + if (nflag == 1) { /* need next line to repair */ + ptr = line + strlen (line); + while (ptr>line && (*(ptr-1)=='\015' || *(ptr-1)=='\012')) + ptr--; + if (_FP_fgets (ptr, 255-(ptr-line), datei) == NULL) + break; + } + else { /* don't need next line to repair */ + } + if (UUNetscapeCollapse (line)) { + if ((vflag = UUValidData (line, encoding, bhflag)) == 0) + nflag = UUBrokenByNetscape (line); + } + else + nflag = 0; + } + /* + * Sometimes, a line is garbled even without it being split into + * the next line. Then we try this in our despair + */ + if (vflag == 0) { + if (UUNetscapeCollapse (line)) + vflag = UUValidData (line, encoding, bhflag); + } + + /* + * If this line looks uuencoded, but the line is one character short + * of a valid line, it was probably broken by MS Exchange. According + * to my test cases, there is at most one space character missing; + * there are never two spaces together. + * If adding a space character helps making this line uuencoded, do + * it! + */ + + if (vflag == 0) { + ptr = line + strlen(line); + while (ptr>line && (*(ptr-1)=='\012' || *(ptr-1)=='\015')) { + ptr--; + } + *ptr++ = ' '; + *ptr-- = '\0'; + if ((vflag = UUValidData (line, encoding, bhflag)) != UU_ENCODED) { + *ptr = '\0'; + vflag = 0; + } + } + return vflag; +} + +/* + * Decode a single encoded line using method + */ + +size_t +UUDecodeLine (char *s, char *d, int method) +{ + int i, j, c, cc, count=0, z1, z2, z3, z4; + static int leftover=0; + int *table; + + /* + * for re-initialization + */ + + if (s == NULL || d == NULL) { + leftover = 0; + return 0; + } + + /* + * To shut up gcc -Wall + */ + z1 = z2 = z3 = z4 = 0; + + if (method == UU_ENCODED || method == XX_ENCODED) { + if (method == UU_ENCODED) + table = UUxlat; + else + table = XXxlat; + + i = table [ACAST(*s++)]; + j = UUxlen[i] - 1; + + while(j > 0) { + c = table[ACAST(*s++)] << 2; + cc = table[ACAST(*s++)]; + c |= (cc >> 4); + + if(i-- > 0) + d[count++] = c; + + cc <<= 4; + c = table[ACAST(*s++)]; + cc |= (c >> 2); + + if(i-- > 0) + d[count++] = (char)(cc & 0xFF); + + c <<= 6; + c |= table[ACAST(*s++)]; + + if(i-- > 0) + d[count++] = (char)(c & 0xFF); + + j -= 4; + } + } + else if (method == B64ENCODED) { + if (leftover) { + strcpy (uuncdl_fulline+leftover, s); + leftover = 0; + s = uuncdl_fulline; + } + + while ((z1 = B64xlat[ACAST(*s)]) != -1) { + if ((z2 = B64xlat[ACAST(*(s+1))]) == -1) break; + if ((z3 = B64xlat[ACAST(*(s+2))]) == -1) break; + if ((z4 = B64xlat[ACAST(*(s+3))]) == -1) break; + + d[count++] = (char)(((z1 << 2) | (z2 >> 4)) & 0xFF); + d[count++] = (char)(((z2 << 4) | (z3 >> 2)) & 0xFF); + d[count++] = (char)(((z3 << 6) | (z4)) & 0xFF); + + s += 4; + } + if (z1 != -1 && z2 != -1 && *(s+2) == '=') { + d[count++] = (char)(((z1 << 2) | (z2 >> 4)) & 0xFF); + s+=2; + } + else if (z1 != -1 && z2 != -1 && z3 != -1 && *(s+3) == '=') { + d[count++] = (char)(((z1 << 2) | (z2 >> 4)) & 0xFF); + d[count++] = (char)(((z2 << 4) | (z3 >> 2)) & 0xFF); + s+=3; + } + while (B64xlat[ACAST(*s)] != -1) + uuncdl_fulline[leftover++] = *s++; + } + else if (method == BH_ENCODED) { + if (leftover) { + strcpy (uuncdl_fulline+leftover, s); + leftover = 0; + s = uuncdl_fulline; + } + else if (*s == ':') + s++; + + while ((z1 = BHxlat[ACAST(*s)]) != -1) { + if ((z2 = BHxlat[ACAST(*(s+1))]) == -1) break; + if ((z3 = BHxlat[ACAST(*(s+2))]) == -1) break; + if ((z4 = BHxlat[ACAST(*(s+3))]) == -1) break; + + d[count++] = (char)(((z1 << 2) | (z2 >> 4)) & 0xFF); + d[count++] = (char)(((z2 << 4) | (z3 >> 2)) & 0xFF); + d[count++] = (char)(((z3 << 6) | (z4)) & 0xFF); + + s += 4; + } + if (z1 != -1 && z2 != -1 && *(s+2) == ':') { + d[count++] = (char)(((z1 << 2) | (z2 >> 4)) & 0xFF); + s+=2; + } + else if (z1 != -1 && z2 != -1 && z3 != -1 && *(s+3) == ':') { + d[count++] = (char)(((z1 << 2) | (z2 >> 4)) & 0xFF); + d[count++] = (char)(((z2 << 4) | (z3 >> 2)) & 0xFF); + s+=3; + } + while (BHxlat[ACAST(*s)] != -1) + uuncdl_fulline[leftover++] = *s++; + } + else if (method == YENC_ENCODED) { + while (*s) { + if (*s == '=') { + if (*++s != '\0') { + d[count++] = (char) ((int) *s - 64 - 42); + s++; + } + } + else if (*s == '\n' || *s == '\r') { + s++; /* ignore */ + } + else { + d[count++] = (char) ((int) *s++ - 42); + } + } + } + + return count; +} + +/* + * ``Decode'' Quoted-Printable text + */ + +int +UUDecodeQP (FILE *datain, FILE *dataout, int *state, + long maxpos, int method, int flags, + char *boundary) +{ + char *line=uugen_inbuffer, *p1, *p2; + int val; + + uulboundary = -1; + + while (!feof (datain) && + (ftell(datain)line && (*(ptr-1) == '\012' || *(ptr-1) == '\015')) + ptr--; + + + /* + * If the part ends directly after this line, the data does not end + * with a linebreak. Or, as the docs put it, "the CRLF preceding the + * encapsulation line is conceptually attached to the boundary. + * So if the part ends here, don't print a line break" + */ + if ((*ptr == '\012' || *ptr == '\015') && + (ftell(datain) 5) + tf = tc = 0; + vlc = 0; + continue; + } + + /* + * Busy Polls + */ + + if (UUBUSYPOLL(ftell(datain)-progress.foffset,progress.fsize)) { + UUMessage (uunconc_id, __LINE__, UUMSG_NOTE, + uustring (S_DECODE_CANCEL)); + return UURET_CANCEL; + } + + /* + * try to make sense of data + */ + + line[299] = '\0'; /* For Safety of string functions */ + count = 0; + + if (boundary && line[0]=='-' && line[1]=='-' && + strncmp (line+2, boundary, strlen (boundary)) == 0) { + if (line[strlen(boundary)+2]=='-') + uulboundary = 1; + else + uulboundary = 0; + return UURET_OK; + } + + /* + * Use this pseudo-handling only if !FL_PROPER + */ + + if ((flags&FL_PROPER) == 0) { + if (strncmp (line, "BEGIN", 5) == 0 && + _FP_strstr (line, "CUT HERE") && !tf) { /* I hate these lines */ + tc = tf = vlc = 0; + continue; + } + /* MIME body boundary */ + if (line[0] == '-' && line[1] == '-' && method == B64ENCODED) { + if ((haddata || tc) && (haddh || hadct)) { + *state = DONE; + vlc = 0; + lc[0] = lc[1] = 0; + continue; + } + hadct = 0; + haddh = 1; + continue; + } + if (_FP_strnicmp (line, "Content-Type", 12) == 0) + hadct = 1; + } + + if (*state == BEGIN) { + if ((method == UU_ENCODED || method == XX_ENCODED) && + (strncmp (line, "begin ", 6) == 0 || + strncmp (line, "section ", 8) == 0 || + _FP_strnicmp (line, "
begin ", 11) == 0)) { /* for LYNX */
+    *state = DATA;
+    continue;
+      }
+      else if (method == BH_ENCODED && line[0] == ':') {
+    if (UUValidData (line, BH_ENCODED, &bhflag) == BH_ENCODED) {
+      bhflag = 0;
+      *state = DATA;
+    }
+    else
+      continue;
+      }
+      else if (method == YENC_ENCODED &&
+           strncmp (line, "=ybegin ", 8) == 0 &&
+           _FP_strstr (line, " name=") != NULL) {
+    *state = DATA;
+
+    if ((ptr = _FP_strstr (line, " size=")) != NULL) {
+      ptr += 6;
+      yefilesize = atoi (ptr);
+    }
+    else {
+      yefilesize = -1;
+    }
+
+    if (_FP_strstr (line, " part=") != NULL) {
+      if (_FP_fgets (line, 299, datain) == NULL) {
+        break;
+      }
+
+      if ((ptr = _FP_strstr (line, " end=")) == NULL) {
+        break;
+      }
+
+      yepartends = atoi (ptr + 5);
+    }
+    tf = 1;
+    continue;
+      }
+      else {
+    continue;
+      }
+
+      tc = tf = vlc = 0;
+      lc[0] = lc[1] = 0;
+    }
+    else if ((*state == END) && (method == UU_ENCODED)) {
+      if (strncmp (line, "`", 1) == 0)
+    *state = END2;
+    }
+    else if ((*state == END) && (method == XX_ENCODED)) {
+      if (strncmp (line, "+", 1) == 0)
+    *state = END2;
+    }
+    else if ((*state == END2) &&
+         (method == UU_ENCODED || method == XX_ENCODED)) {
+      if (strncmp (line, "end", 3) == 0) {
+    *state = DONE;
+    break;
+      }
+    }
+
+    if (*state == DATA && method == YENC_ENCODED &&
+    strncmp (line, "=yend ", 6) == 0) {
+      if ((ptr = _FP_strstr (line, " pcrc32=")) != NULL) {
+    dword pcrc32 = strtoul (ptr + 8, NULL, 16);
+    if (pcrc32 != yepartcrc) {
+      UUMessage (uunconc_id, __LINE__, UUMSG_WARNING,
+             uustring (S_PCRC_MISMATCH), progress.curfile, progress.partno);
+    }
+      }
+      if ((ptr = _FP_strstr (line, " crc32=")) != NULL)
+      {
+    dword fcrc32 = strtoul (ptr + 7, NULL, 16);
+    if (fcrc32 != yefilecrc) {
+      UUMessage (uunconc_id, __LINE__, UUMSG_WARNING,
+             uustring (S_CRC_MISMATCH), progress.curfile);
+    }
+      }
+      if ((ptr = _FP_strstr (line, " size=")) != NULL)
+      {
+    size_t size = atol(ptr + 6);
+    if (size != yepartsize && yefilesize != -1) {
+      if (size != (size_t)yefilesize)
+        UUMessage (uunconc_id, __LINE__, UUMSG_WARNING,
+               uustring (S_PSIZE_MISMATCH), progress.curfile,
+               progress.partno, yepartsize, size);
+      else
+        UUMessage (uunconc_id, __LINE__, UUMSG_WARNING,
+               uustring (S_SIZE_MISMATCH), progress.curfile,
+               yepartsize, size);
+    }
+      }
+      if (yepartends == 0 || yepartends >= yefilesize) {
+    *state = DONE;
+      }
+      break;
+    }
+
+    if (*state == DATA || *state == END || *state == END2) {
+      if (method==B64ENCODED && line[0]=='-' && line[1]=='-' && tc) {
+    break;
+      }
+
+      if ((vflag = UUValidData (line, (tf)?method:0, &bhflag)) == 0)
+    vflag = UURepairData (datain, line, (tf)?method:0, &bhflag);
+
+      /*
+       * correct XX/UUencoded lines that were declared Base64
+       */
+
+      if ((method == XX_ENCODED || method == UU_ENCODED) &&
+      vflag == B64ENCODED) {
+    if (UUValidData (line, method, &bhflag) == method)
+      vflag = method;
+      }
+
+      if (vflag == method) {
+/*  if (tf) { */
+    if (tf || (method == UU_ENCODED || method == XX_ENCODED)) {
+      count  = UUDecodeLine (line, oline, method);
+      if (method == YENC_ENCODED) {
+        if (yepartends)
+          yepartcrc = memCrc32(yepartcrc, (unsigned char *) oline, count, false, CRC32_MASK_CCITT);
+        yefilecrc = memCrc32(yefilecrc, (unsigned char *) oline, count, false, CRC32_MASK_CCITT);
+        yepartsize += count;
+      }
+      tf = 1;
+      vlc++; lc[1]++;
+    }
+    else if (tc == 3) {
+      count  = UUDecodeLine (save[0], oline,         method);
+      count += UUDecodeLine (save[1], oline + count, method);
+      count += UUDecodeLine (save[2], oline + count, method);
+      count += UUDecodeLine (line,    oline + count, method);
+      tf     = 1;
+      tc     = 0;
+
+      /*
+       * complain if we had one or two invalid lines amidst of
+       * correctly encoded data. This usually means that the
+       * file is in error
+       */
+
+      if (lc[1] > 10 && (lc[0] >= 1 && lc[0] <= 2) && !warning) {
+        UUMessage (uunconc_id, __LINE__, UUMSG_WARNING,
+               uustring (S_DATA_SUSPICIOUS));
+        warning=1;
+      }
+      lc[0] = 0;
+      lc[1] = 3;
+    }
+    else {
+      _FP_strncpy (save[tc++], line, 256);
+    }
+
+    if (method == UU_ENCODED)
+      *state = (line[0] == 'M') ? DATA : (line[0] == '`') ? END2 : END;
+    else if (method == XX_ENCODED)
+      *state = (line[0] == 'h') ? DATA : (line[0] == '+') ? END2 : END;
+    else if (method == B64ENCODED)
+      *state = (strchr (line, '=') == NULL) ? DATA : DONE;
+    else if (method == BH_ENCODED)
+      *state = (!line[0] || strchr(line+1,':')==NULL)?DATA:DONE;
+      }
+      else {
+    vlc = tf = tc = 0;
+    haddh = 0;
+    lc[0]++;
+      }
+    }
+    else if (*state != DONE) {
+      return UURET_NOEND;
+    }
+
+    if (count) {
+      if (method == BH_ENCODED) {
+    if (UUbhwrite (oline, 1, count, dataout) != count) {
+      UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+             uustring (S_WR_ERR_TEMP),
+             strerror (uu_errno = errno));
+      return UURET_IOERR;
+    }
+      }
+      else if (fwrite (oline, 1, count, dataout) != count) {
+    UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+           uustring (S_WR_ERR_TEMP),
+           strerror (uu_errno = errno));
+    return UURET_IOERR;
+      }
+      haddata++;
+      count = 0;
+    }
+  }
+
+  if (*state  == DONE ||
+      (*state == DATA && method == B64ENCODED &&
+       vflag == B64ENCODED && (flags&FL_PROPER || haddh))) {
+    for (tf=0; tfthisfile == NULL)
+    return UURET_ILLVAL;
+
+  if (data->state & UUFILE_TMPFILE)
+    return UURET_OK;
+
+  if (data->state & UUFILE_NODATA)
+    return UURET_NODATA;
+
+  if (data->state & UUFILE_NOBEGIN && !uu_desperate)
+    return UURET_NODATA;
+
+  if (data->uudet == PT_ENCODED)
+    mode = "wt";    /* open text files in text mode */
+  else
+    mode = "wb";    /* otherwise in binary          */
+
+  if ((data->binfile = tempnam (NULL, "uu")) == NULL) {
+    UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+           uustring (S_NO_TEMP_NAME));
+    return UURET_NOMEM;
+  }
+
+  if ((dataout = fopen (data->binfile, mode)) == NULL) {
+    /*
+     * we couldn't create a temporary file. Usually this means that TMP
+     * and TEMP aren't set
+     */
+    UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+           uustring (S_WR_ERR_TARGET),
+           data->binfile, strerror (uu_errno = errno));
+    _FP_free (data->binfile);
+    data->binfile = NULL;
+    uu_errno = errno;
+    return UURET_IOERR;
+  }
+  /*
+   * we don't have begin lines in Base64 or plain text files.
+   */
+  if (data->uudet == B64ENCODED || data->uudet == QP_ENCODED ||
+      data->uudet == PT_ENCODED)
+    state = DATA;
+
+  /*
+   * If we know that the file does not have a begin, we simulate
+   * it in desperate mode
+   */
+
+  if ((data->state & UUFILE_NOBEGIN) && uu_desperate)
+    state = DATA;
+
+  (void) UUDecodeLine (NULL, NULL, 0);                   /* init */
+  (void) UUbhwrite    (NULL, 0, 0, NULL);                /* dito */
+  (void) UUDecodePart (NULL, NULL, NULL, 0, 0, 0, NULL); /* yep  */
+
+  /*
+   * initialize progress information
+   */
+  progress.action = 0;
+  if (data->filename != NULL) {
+    _FP_strncpy (progress.curfile,
+         (strlen(data->filename)>255)?
+         (data->filename+strlen(data->filename)-255):data->filename,
+         256);
+  }
+  else {
+    _FP_strncpy (progress.curfile,
+         (strlen(data->binfile)>255)?
+         (data->binfile+strlen(data->binfile)-255):data->binfile,
+         256);
+  }
+  progress.partno   =  0;
+  progress.numparts =  0;
+  progress.fsize    = -1;
+  progress.percent  =  0;
+  progress.action   =  UUACT_DECODING;
+
+  iter = data->thisfile;
+  while (iter) {
+    progress.numparts = (iter->partno)?iter->partno:1;
+    iter = iter->NEXT;
+  }
+
+  /*
+   * let's rock!
+   */
+
+  iter = data->thisfile;
+  while (iter) {
+    if (part != -1 && iter->partno != part+1)
+      break;
+    else
+      part = iter->partno;
+
+    if (iter->data->sfname == NULL) {
+      iter = iter->NEXT;
+      continue;
+    }
+
+    /*
+     * call our FileCallback to retrieve the file
+     */
+
+    if (uu_FileCallback) {
+      if ((res = (*uu_FileCallback) (uu_FileCBArg, iter->data->sfname,
+                     uugen_fnbuffer, 1)) != UURET_OK)
+    break;
+      if ((datain = fopen (uugen_fnbuffer, "rb")) == NULL) {
+    (*uu_FileCallback) (uu_FileCBArg, iter->data->sfname,
+                uugen_fnbuffer, 0);
+    UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+           uustring (S_NOT_OPEN_FILE),
+           uugen_fnbuffer, strerror (uu_errno = errno));
+    res = UURET_IOERR;
+    break;
+      }
+    }
+    else {
+      if ((datain = fopen (iter->data->sfname, "rb")) == NULL) {
+    UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+           uustring (S_NOT_OPEN_FILE),
+           iter->data->sfname, strerror (uu_errno = errno));
+    res = UURET_IOERR;
+    break;
+      }
+      _FP_strncpy (uugen_fnbuffer, iter->data->sfname, 1024);
+    }
+
+    progress.partno  = part;
+    progress.fsize   = (iter->data->length)?iter->data->length:-1;
+    progress.percent = 0;
+    progress.foffset = iter->data->startpos;
+
+    fseek              (datain, iter->data->startpos, SEEK_SET);
+    res = UUDecodePart (datain, dataout, &state,
+            iter->data->startpos+iter->data->length,
+            data->uudet, iter->data->flags, NULL);
+    fclose             (datain);
+
+    if (uu_FileCallback)
+      (*uu_FileCallback) (uu_FileCBArg, iter->data->sfname, uugen_fnbuffer, 0);
+
+    if (state == DONE || res != UURET_OK)
+      break;
+
+    iter = iter->NEXT;
+  }
+
+  if (state == DATA &&
+      (data->uudet == B64ENCODED || data->uudet == QP_ENCODED ||
+       data->uudet == PT_ENCODED))
+    state = DONE; /* assume we're done */
+
+  fclose (dataout);
+
+  if (res != UURET_OK || (state != DONE && !uu_desperate)) {
+    unlink (data->binfile);
+    _FP_free (data->binfile);
+    data->binfile = NULL;
+    data->state  &= ~UUFILE_TMPFILE;
+    data->state  |=  UUFILE_ERROR;
+
+    if (res == UURET_OK && state != DONE)
+      res = UURET_NOEND;
+  }
+  else if (res != UURET_OK) {
+    data->state &= ~UUFILE_DECODED;
+    data->state |=  UUFILE_ERROR | UUFILE_TMPFILE;
+  }
+  else {
+    data->state &= ~UUFILE_ERROR;
+    data->state |=  UUFILE_TMPFILE;
+  }
+
+  /*
+   * If this was a BinHex file, we must extract its data or resource fork
+   */
+
+  if (data->uudet == BH_ENCODED && data->binfile) {
+    if ((ntmp = tempnam (NULL, "uu")) == NULL) {
+      UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+         uustring (S_NO_TEMP_NAME));
+      progress.action = 0;
+      return UURET_NOMEM;
+    }
+    if ((datain = fopen (data->binfile, "rb")) == NULL) {
+      UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+         uustring (S_NOT_OPEN_FILE),
+         data->binfile, strerror (uu_errno = errno));
+      progress.action = 0;
+      free (ntmp);
+      return UURET_IOERR;
+    }
+    if ((dataout = fopen (ntmp, "wb")) == NULL) {
+      UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+         uustring (S_NOT_OPEN_TARGET),
+         ntmp, strerror (uu_errno = errno));
+      progress.action = 0;
+      fclose (datain);
+      free   (ntmp);
+      return UURET_IOERR;
+    }
+    /*
+     * read fork lengths. remember they're in Motorola format
+     */
+    r[0] = fgetc (datain);
+    hb   = (int) r[0] + 22;
+    fseek (datain, (int) r[0] + 12, SEEK_SET);
+    fread (r, 1, 8, datain);
+
+    dsize = (((long) 1 << 24) * (long) r[0]) +
+            (((long) 1 << 16) * (long) r[1]) +
+            (((long) 1 <<  8) * (long) r[2]) +
+            (                   (long) r[3]);
+    rsize = (((long) 1 << 24) * (long) r[4]) +
+        (((long) 1 << 16) * (long) r[5]) +
+        (((long) 1 <<  8) * (long) r[6]) +
+        (                   (long) r[7]);
+
+    UUMessage (uunconc_id, __LINE__, UUMSG_MESSAGE,
+           uustring (S_BINHEX_SIZES),
+           dsize, rsize);
+
+    if (dsize == 0) {
+      fseek  (datain, dsize + hb + 2, SEEK_SET);
+      numbytes = rsize;
+    }
+    else if (rsize == 0) {
+      fseek  (datain, hb, SEEK_SET);
+      numbytes = dsize;
+    }
+    else {
+      /* we should let the user have the choice here */
+      UUMessage (uunconc_id, __LINE__, UUMSG_NOTE,
+         uustring (S_BINHEX_BOTH));
+      fseek  (datain, hb, SEEK_SET);
+      numbytes = dsize;
+    }
+
+    progress.action   = 0;
+    progress.partno   = 0;
+    progress.numparts = 1;
+    progress.fsize    = (numbytes)?numbytes:-1;
+    progress.foffset  = hb;
+    progress.percent  = 0;
+    progress.action   = UUACT_COPYING;
+
+    /*
+     * copy the chosen fork
+     */
+
+    while (!feof (datain) && numbytes) {
+      if (UUBUSYPOLL(ftell(datain)-progress.foffset,progress.fsize)) {
+    UUMessage (uunconc_id, __LINE__, UUMSG_NOTE,
+           uustring (S_DECODE_CANCEL));
+    fclose (datain);
+    fclose (dataout);
+    unlink (ntmp);
+    free   (ntmp);
+    return UURET_CANCEL;
+      }
+
+      bytes = fread (uugen_inbuffer, 1,
+             (size_t) ((numbytes>1024)?1024:numbytes), datain);
+
+      if (ferror (datain) || (bytes == 0 && !feof (datain))) {
+    progress.action = 0;
+    UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+           uustring (S_SOURCE_READ_ERR),
+           data->binfile, strerror (uu_errno = errno));
+    fclose (datain);
+    fclose (dataout);
+    unlink (ntmp);
+    free   (ntmp);
+    return UURET_IOERR;
+      }
+      if (fwrite (uugen_inbuffer, 1, bytes, dataout) != bytes) {
+    progress.action = 0;
+    UUMessage (uunconc_id, __LINE__, UUMSG_ERROR,
+           uustring (S_WR_ERR_TARGET),
+           ntmp, strerror (uu_errno = errno));
+    fclose (datain);
+    fclose (dataout);
+    unlink (ntmp);
+    free   (ntmp);
+    return UURET_IOERR;
+      }
+      numbytes -= bytes;
+    }
+
+    if (numbytes) {
+      UUMessage (uunconc_id, __LINE__, UUMSG_WARNING,
+         uustring (S_SHORT_BINHEX),
+         (data->filename)?data->filename:
+         (data->subfname)?data->subfname:"???",
+         numbytes);
+    }
+
+    /*
+     * replace temp file
+     */
+
+    fclose (datain);
+    fclose (dataout);
+
+    if (unlink (data->binfile)) {
+      UUMessage (uunconc_id, __LINE__, UUMSG_WARNING,
+         uustring (S_TMP_NOT_REMOVED),
+         data->binfile, strerror (uu_errno = errno));
+    }
+
+    free (data->binfile);
+    data->binfile = ntmp;
+  }
+
+  progress.action = 0;
+  return res;
+}
+
+/*
+ * QuickDecode for proper MIME attachments. We expect the pointer to
+ * be on the first header line.
+ */
+
+int
+UUQuickDecode (FILE *datain, FILE *dataout, char *boundary, long maxpos)
+{
+  int state=BEGIN, encoding=-1;
+  headers myenv;
+
+  /*
+   * Read header and find out about encoding.
+   */
+
+  memset (&myenv, 0, sizeof (headers));
+  UUScanHeader (datain, &myenv);
+
+  if (_FP_stristr (myenv.ctenc, "uu") != NULL)
+    encoding = UU_ENCODED;
+  else if (_FP_stristr (myenv.ctenc, "xx") != NULL)
+    encoding = XX_ENCODED;
+  else if (_FP_stricmp (myenv.ctenc, "base64") == 0)
+    encoding = B64ENCODED;
+  else if (_FP_stricmp (myenv.ctenc, "quoted-printable") == 0)
+    encoding = QP_ENCODED;
+  else
+    encoding = PT_ENCODED;
+
+  UUkillheaders (&myenv);
+
+  /*
+   * okay, so decode this one
+   */
+
+  (void) UUDecodePart (NULL, NULL, NULL, 0, 0, 0, NULL); /* init  */
+  return UUDecodePart (datain, dataout, &state, maxpos,
+               encoding, FL_PROPER|FL_TOEND,
+               boundary);
+}
diff --git a/goldlib/uulib/uuscan.cpp b/goldlib/uulib/uuscan.cpp
new file mode 100644
index 0000000..7e9bbc3
--- /dev/null
+++ b/goldlib/uulib/uuscan.cpp
@@ -0,0 +1,3170 @@
+/*
+ * This file is part of uudeview, the simple and friendly multi-part multi-
+ * file uudecoder  program  (c) 1994-2001 by Frank Pilhofer. The author may
+ * be contacted at fp@fpx.de
+ *
+ * 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 2 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.
+ */
+
+/*
+ * These are very central functions of UUDeview. Here, we scan a file
+ * and decide whether it contains encoded data or not. ScanPart() must
+ * be called repeatedly on the same file until feof(file). Each time,
+ * it returns information about the next part found within.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef SYSTEM_WINDLL
+#include 
+#endif
+#ifdef SYSTEM_OS2
+#include 
+#endif
+
+#include 
+
+#ifdef STDC_HEADERS
+#include 
+#include 
+#endif
+#ifdef HAVE_MALLOC_H
+#include 
+#endif
+#ifdef HAVE_UNISTD_H
+#include 
+#endif
+#ifdef HAVE_MEMORY_H
+#include 
+#endif
+#ifdef HAVE_ERRNO_H
+#include 
+#endif
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+char * uuscan_id = "$Id$";
+
+/*
+ * Header fields we recognize as such. See RFC822. We add "From ",
+ * the usual marker for a beginning of a new message, and a couple
+ * of usual MDA, News and MIME headers.
+ * We make a distinction of MIME headers as we need the difference
+ * to scan the bodies from partial multipart messages.
+ */
+
+static char *knownmsgheaders[] = {
+  "From ", "Return-Path:", "Received:", "Reply-To:",
+  "From:", "Sender:", "Resent-Reply-To:", "Resent-From:",
+  "Resent-Sender:", "Date:", "Resent-Date:", "To:",
+  "Resent-To:", "Cc:", "Bcc:", "Resent-bcc:",
+  "Message-ID:", "Resent-Message-Id:", "In-Reply-To:",
+  "References:", "Keywords:", "Subject:", "Comments:",
+  
+  "Delivery-Date:", "Posted-Date:", "Received-Date:",
+  "Precedence:", 
+
+  "Path:", "Newsgroups:", "Organization:", "Lines:",
+  "NNTP-Posting-Host:",
+  NULL
+};
+
+static char *knownmimeheaders[] = {
+  "Mime-Version:",  "Content-Transfer-Encoding:",
+  "Content-Type:", "Content-Disposition:", 
+  "Content-Description:", "Content-Length:",
+  NULL
+};
+
+/*
+ * for MIME (plaintext) parts without filename
+ */
+int mimseqno;
+
+/*
+ * how many lines do we read when checking for headers
+ */
+#define WAITHEADER  10
+
+/*
+ * The stack for encapsulated Multipart messages
+ */
+#define MSMAXDEPTH  3
+
+int       mssdepth = 0;
+scanstate multistack[MSMAXDEPTH+1];
+
+/*
+ * The state and the local envelope
+ */
+headers   localenv;
+scanstate sstate;
+
+/*
+ * mallocable areas
+ */
+
+char *uuscan_shlline;
+char *uuscan_pvvalue;
+char *uuscan_phtext;
+char *uuscan_sdline;
+char *uuscan_sdbhds1;
+char *uuscan_sdbhds2;
+char *uuscan_spline;
+
+/*
+ * Macro: print cancellation message in UUScanPart
+ */
+
+#define SPCANCEL()  {UUMessage(uuscan_id,__LINE__,UUMSG_NOTE,uustring(S_SCAN_CANCEL));*errcode=UURET_CANCEL;goto ScanPartEmergency;}
+
+/*
+ * Is line empty? A line is empty if it is composed of whitespace.
+ */
+
+static int
+IsLineEmpty (char *data)
+{
+  if (data == NULL) return 0;
+  while (*data && isspace (*data)) data++;
+  return ((*data)?0:1);
+}
+
+/*
+ * Scans a potentially folded header line from the input file. If
+ * initial is non-NULL, it is the first line of the header, useful
+ * if the calling function just coincidentally noticed that this is
+ * a header.
+ * RFC0822 does not specify a maximum length for headers, but we
+ * truncate everything beyond an insane value of 1024 characters.
+ */
+
+static char *
+ScanHeaderLine (FILE *datei, char *initial)
+{
+  char *ptr=uuscan_shlline;
+  int llength, c;
+  long curpos;
+  int hadcr;
+
+  if (initial) {
+    _FP_strncpy (uuscan_shlline, initial, 1024);
+  }
+  else {
+    /* read first line */
+    if (feof (datei) || ferror (datei))
+      return NULL;
+    if (_FP_fgets (uuscan_shlline, 1023, datei) == NULL)
+      return NULL;
+    uuscan_shlline[1023] = '\0';
+  }
+
+  llength = strlen (uuscan_shlline);
+  hadcr   = 0;
+
+  /* strip whitespace at end */
+  ptr = uuscan_shlline + llength;
+  while (llength && isspace(*(ptr-1))) {
+    if (*(ptr-1) == '\012' || *(ptr-1) == '\015')
+      hadcr = 1;
+    ptr--; llength--;
+  }
+  if (llength == 0) {
+    uuscan_shlline[0] = '\0';
+    return uuscan_shlline;
+  }
+
+  while (!feof (datei)) {
+    c = fgetc (datei);
+    if (feof (datei))
+      break;
+
+    /*
+     * If the line didn't have a CR, it was longer than 256 characters
+     * and is continued anyway.
+     */
+
+    if (hadcr==1 && c != ' ' && c != '\t') {
+      /* no LWSP-char, header line does not continue */
+      ungetc (c, datei);
+      break;
+    }
+    while (!feof (datei) && (c == ' ' || c == '\t'))
+      c = fgetc (datei);
+
+    if (!feof (datei))
+      ungetc (c, datei);    /* push back for fgets() */
+
+    /* insert a single LWSP */
+    if (hadcr==1 && llength < 1023) {
+      *ptr++ = ' ';
+      llength++;
+    }
+    *ptr = '\0'; /* make lint happier */
+
+    if (feof (datei))
+      break;
+
+    /* read next line */
+    curpos = ftell (datei);
+    if (_FP_fgets (uugen_inbuffer, 255, datei) == NULL)
+      break;
+    uugen_inbuffer[255] = '\0';
+
+    if (IsLineEmpty (uugen_inbuffer)) { /* oops */
+      fseek (datei, curpos, SEEK_SET);
+      break;
+    }
+
+    _FP_strncpy (ptr, uugen_inbuffer, 1024-llength);
+
+    /*
+     * see if line was terminated with CR. Otherwise, it continues ...
+     */
+    c = strlen (ptr);
+    if (c>0 && (ptr[c-1] == '\012' || ptr[c-1] == '\015'))
+      hadcr = 1;
+    else
+      hadcr = 0;
+
+    /*
+     * strip whitespace
+     */
+
+    ptr     += c;
+    llength += c;
+    while (llength && isspace(*(ptr-1))) {
+      ptr--; llength--;
+    }
+  }
+  *ptr = '\0';
+
+  if (llength == 0)
+    return NULL;
+
+  return uuscan_shlline;
+}
+
+/*
+ * Extract the value from a MIME attribute=value pair. This function
+ * receives a pointer to the attribute.
+ */
+static char *
+ParseValue (char *attribute)
+{
+  char *ptr=uuscan_pvvalue;
+  int length=0;
+
+  if (attribute == NULL)
+    return NULL;
+
+  while ((isalnum(*attribute) || *attribute=='_') && *attribute != '=')
+    attribute++;
+
+  while (isspace(*attribute))
+    attribute++;
+
+  if (*attribute == '=') {
+    attribute++;
+    while (isspace (*attribute))
+      attribute++;
+  }
+  else
+    return NULL;
+
+  if (*attribute == '"') {
+    /* quoted-string */
+    attribute++;
+    while (*attribute && *attribute != '"' && length < 255) {
+      if (*attribute == '\\')
+    *ptr++ = *++attribute;
+      else
+    *ptr++ = *attribute;
+      attribute++;
+      length++;
+    }
+    *ptr = '\0';
+  }
+  else {
+    /* tspecials from RFC1521 */
+
+    while (*attribute && !isspace (*attribute) &&
+       *attribute != '(' && *attribute != ')' &&
+       *attribute != '<' && *attribute != '>' &&
+       *attribute != '@' && *attribute != ',' &&
+       *attribute != ';' && *attribute != ':' &&
+       *attribute != '\\' &&*attribute != '"' &&
+       *attribute != '/' && *attribute != '[' &&
+       *attribute != ']' && *attribute != '?' &&
+       *attribute != '=' && length < 255)
+      *ptr++ = *attribute++;
+
+    *ptr = '\0';
+  }
+  return uuscan_pvvalue;
+}
+
+/*
+ * Extract the information we need from header fields
+ */
+
+static headers *
+ParseHeader (headers *theheaders, char *line)
+{
+  char **variable=NULL;
+  char *value = NULL, *ptr, *thenew;
+  int delimit = 0, length;
+
+  if (line == NULL)
+    return theheaders;
+
+  if (_FP_strnicmp (line, "From:", 5) == 0) {
+    if (theheaders->from) return theheaders;
+    variable = &theheaders->from;
+    value    = line+5;
+    delimit  = 0;
+  }
+  else if (_FP_strnicmp (line, "Subject:", 8) == 0) {
+    if (theheaders->subject) return theheaders;
+    variable = &theheaders->subject;
+    value    = line+8;
+    delimit  = 0;
+  }
+  else if (_FP_strnicmp (line, "To:", 3) == 0) {
+    if (theheaders->rcpt) return theheaders;
+    variable = &theheaders->rcpt;
+    value    = line+3;
+    delimit  = 0;
+  }
+  else if (_FP_strnicmp (line, "Date:", 5) == 0) {
+    if (theheaders->date) return theheaders;
+    variable = &theheaders->date;
+    value    = line+5;
+    delimit  = 0;
+  }
+  else if (_FP_strnicmp (line, "Mime-Version:", 13) == 0) {
+    if (theheaders->mimevers) return theheaders;
+    variable = &theheaders->mimevers;
+    value    = line+13;
+    delimit  = 0;
+  }
+  else if (_FP_strnicmp (line, "Content-Type:", 13) == 0) {
+    if (theheaders->ctype) return theheaders;
+    variable = &theheaders->ctype;
+    value    = line+13;
+    delimit  = ';';
+
+    /* we can probably extract more information */
+    if ((ptr = _FP_stristr (line, "boundary")) != NULL) {
+      if ((thenew = ParseValue (ptr))) {
+    if (theheaders->boundary) free (theheaders->boundary);
+    theheaders->boundary = _FP_strdup (thenew);
+      }
+    }
+    if ((ptr = _FP_stristr (line, "name")) != NULL) {
+      if ((thenew = ParseValue (ptr))) {
+    if (theheaders->fname) free (theheaders->fname);
+    theheaders->fname = _FP_strdup (thenew);
+      }
+    }
+    if ((ptr = _FP_stristr (line, "id")) != NULL) {
+      if ((thenew = ParseValue (ptr))) {
+    if (theheaders->mimeid) free (theheaders->mimeid);
+    theheaders->mimeid = _FP_strdup (thenew);
+      }
+    }
+    if ((ptr = _FP_stristr (line, "number")) != NULL) {
+      if ((thenew = ParseValue (ptr))) {
+    theheaders->partno = atoi (thenew);
+      }
+    }
+    if ((ptr = _FP_stristr (line, "total")) != NULL) {
+      if ((thenew = ParseValue (ptr))) {
+    theheaders->numparts = atoi (thenew);
+      }
+    }
+  }
+  else if (_FP_strnicmp (line, "Content-Transfer-Encoding:", 26) == 0) {
+    if (theheaders->ctenc) return theheaders;
+    variable = &theheaders->ctenc;
+    value    = line+26;
+    delimit  = ';';
+  }
+  else if (_FP_strnicmp (line, "Content-Disposition:", 20) == 0) {
+    /*
+     * Some encoders mention the original filename as parameter to
+     * Content-Type, others as parameter to Content-Disposition. We
+     * do prefer the first solution, but accept this situation, too.
+     * TODO: Read RFC1806
+     */
+    if ((ptr = _FP_stristr (line, "name")) != NULL) {
+      if (theheaders->fname == NULL && (thenew=ParseValue(ptr)) != NULL) {
+    theheaders->fname = _FP_strdup (thenew);
+      }
+    }
+    variable = NULL;
+  }
+  else {
+    /*
+     * nothing interesting
+     */
+    return theheaders;
+  }
+
+  /*
+   * okay, so extract the actual data
+   */
+  if (variable) {
+    length = 0;
+    ptr = uuscan_phtext;
+
+    while (isspace (*value))
+      value++;
+    while (*value && (delimit==0 || *value!=delimit) &&
+       *value != '\012' && *value != '\015' && length < 255) {
+      *ptr++ = *value++;
+      length++;
+    }
+    while (length && isspace(*(ptr-1))) {
+      ptr--; length--;
+    }
+    *ptr = '\0';
+
+    if ((*variable = _FP_strdup (uuscan_phtext)) == NULL)
+      return NULL;
+  }
+
+  return theheaders;
+}
+
+/*
+ * is this a header line we know about?
+ */
+
+static int
+IsKnownHeader (char *line)
+{
+  char **iter = knownmsgheaders;
+
+  while (iter && *iter) {
+    if (_FP_strnicmp (line, *iter, strlen (*iter)) == 0)
+      return 1;
+    iter++;
+  }
+
+  iter = knownmimeheaders;
+
+  while (iter && *iter) {
+    if (_FP_strnicmp (line, *iter, strlen (*iter)) == 0)
+      return 2;
+    iter++;
+  }
+
+  return 0;
+}
+
+/*
+ * Scan a header
+ */
+
+int
+UUScanHeader (FILE *datei, headers *envelope)
+{
+  char *ptr;
+
+  while (!feof (datei)) {
+    if ((ptr = ScanHeaderLine (datei, NULL)) == NULL)
+      break;
+    if (*ptr == '\0' || *ptr == '\012' || *ptr == '\015')
+      break;
+    ParseHeader (envelope, ptr);
+  }
+  return 0;
+}
+
+/*
+ * Scan something for encoded data and fill the fileread* structure.
+ * If boundary is non-NULL, we stop after having read it. If Check-
+ * Headers != 0, we also stop after we've found uu_headercount recog-
+ * nized header lines.
+ * If we have a boundary, then we also don't accept Base64; MIME mails
+ * really should handle this properly.
+ * We return -1 if something went wrong, 0 if everything is normal,
+ * 1 if we found a new header and 2 if we found a boundary.
+ * In MIME message bodies (not multiparts), we also disable our reduced
+ * MIME handling.
+ */
+
+static int
+ScanData (FILE *datei, char *fname, int *errcode,
+      char *boundary, int ismime, int checkheaders,
+      fileread *result)
+{
+  char *line=uuscan_sdline, *bhds1=uuscan_sdbhds1, *bhds2=uuscan_sdbhds2;
+  static char *ptr, *p2, *p3=NULL, *bhdsp, bhl;
+  int isb64[10], isuue[10], isxxe[10], isbhx[10], iscnt;
+  int cbb64, cbuue, cbxxe, cbbhx;
+  int bhflag=0, vflag, haddh=0, hadct=0;
+  int bhrpc=0, bhnf=0, c, hcount, lcount, blen=0;
+  int encoding=0, dflag=0, ctline=42;
+  int dontcare=0, hadnl=0;
+  long preheaders=0, oldposition;
+  long yefilesize=0, yepartends=0;
+  size_t dcc, bhopc;
+  char prevlinefirstchar;
+
+  *errcode = UURET_OK;
+  (void) UUDecodeLine (NULL, NULL, 0);          /* init */
+  bhdsp = bhds2;
+
+  if (datei == NULL || feof (datei))
+    return -1;
+
+  result->startpos = ftell (datei);
+  hcount = lcount  = 0;
+
+  for (iscnt=0; iscnt<10; iscnt++) {
+    isb64[iscnt] = isuue[iscnt] = isxxe[iscnt] = isbhx[iscnt] = 0;
+  }
+
+  iscnt = 0;
+
+  if (boundary)
+    blen = strlen (boundary);
+
+  line[0] = '\0';
+  while (!feof (datei)) {
+    prevlinefirstchar = line[0];
+    oldposition = ftell (datei);
+    if (_FP_fgets (line, 299, datei) == NULL)
+      break;
+    if (ferror (datei))
+      break;
+
+    line[299] = '\0'; /* For Safety of string functions */
+
+    /*
+     * Make Busy Polls
+     */
+
+    if (UUBUSYPOLL(ftell(datei),progress.fsize)) {
+      UUMessage (uuscan_id, __LINE__, UUMSG_NOTE,
+         uustring (S_SCAN_CANCEL));
+      *errcode = UURET_CANCEL;
+      break;
+    }
+
+    if (IsLineEmpty (line)) { /* line empty? */
+      hcount = 0;
+      hadnl  = 1;
+      continue;               /* then ignore */
+    }
+
+    if (checkheaders) {
+      if (IsKnownHeader (line)) {
+    (void) ScanHeaderLine (datei, line);
+
+    if (hcount == 0) {
+      preheaders = oldposition;
+      lcount     = 0;
+    }
+    hcount++;
+    lcount++;
+
+    /*
+     * check for the various restart counters
+     */
+
+    if ((hcount >= hlcount.restart) ||
+        (hcount >= hlcount.afterdata && ismime == 0) ||
+        (hcount >= hlcount.afterdata && result->uudet) ||
+        (hcount >= hlcount.afternl   && result->uudet && hadnl)) {
+      /*
+       * Hey, a new header starts here
+       */
+      fseek (datei, preheaders, SEEK_SET);
+      break;
+    }
+    /* continue; */
+      }
+      else if (lcount > WAITHEADER) {
+    hcount = 0;
+    lcount = 0;
+    dontcare=0;
+      }
+      else if (hcount) {
+    lcount++;
+    dontcare=1;
+      }
+      else {
+    dontcare=0;
+      }
+    }
+    else {
+      dontcare=0;
+    }
+
+    if (boundary != NULL && 
+    line[0] == '-' && line[1] == '-' &&
+    strncmp (line+2, boundary, blen) == 0) {
+      fseek (datei, oldposition, SEEK_SET);
+      break;
+    }
+    if (boundary != NULL && line[0] == 'C' && line[1] == 'o' &&
+    _FP_strnicmp (line, "Content-Type:", 13) == 0) {
+      ptr = ScanHeaderLine (datei, line);
+      p2  = (ptr)?_FP_stristr(ptr,"boundary"):NULL;
+      p3  = (p2)?ParseValue(p2):NULL;
+
+      if (p3 && strcmp (p3, boundary) == 0) {
+    fseek (datei, oldposition, SEEK_SET);
+    break;
+      }
+      else {
+    p3 = NULL;
+      }
+    }
+
+    if (strncmp      (line, "begin ",       6) == 0 ||
+    _FP_strnicmp (line, "
begin ", 11) == 0) {
+      if ((result->begin || result->end ||
+       result->uudet == B64ENCODED ||
+       result->uudet == BH_ENCODED) && !uu_more_mime) {
+    fseek (datei, oldposition, SEEK_SET);
+    break;
+      }
+      
+      if (*line == '<')
+    ptr = line + 10;
+      else
+    ptr = line + 5;
+
+      while (*ptr == ' ') ptr++;
+      while (isdigit (*ptr)) 
+    result->mode = result->mode * 8 + *ptr++ - '0';
+      while (*ptr == ' ') ptr++;
+
+      /*
+       * We may have picked up a filename from a uuenview-style header
+       */
+      _FP_free (result->filename);
+      result->filename = _FP_strdup (ptr);
+      result->begin    = 1;
+
+      while (isspace (result->filename[strlen(result->filename)-1]))
+    result->filename[strlen(result->filename)-1] = '\0';
+
+      continue;
+    }
+
+    if ((strncmp (line, "end", 3) == 0) &&
+    result->uudet != BH_ENCODED &&
+    result->uudet != YENC_ENCODED) {
+      if (result->uudet == B64ENCODED && result->begin)
+    result->uudet = XX_ENCODED;
+
+      if (result->uudet != B64ENCODED) {
+    if (result->uudet == XX_ENCODED) {
+      if (prevlinefirstchar == '+')
+        result->end = 1;
+    }
+    if (result->uudet == UU_ENCODED) {
+      if (prevlinefirstchar == '`')
+        result->end = 1;
+    }
+    else {
+      result->end = 1;
+    }
+    if (dflag && encoding)
+      result->uudet = encoding;
+    continue;
+      }
+    }
+
+    hadnl = 0;
+
+    /*
+     * Detect a UUDeview-Style header
+     */
+
+    if ((_FP_strnicmp (line, "_=_ Part ", 9) == 0 ||
+    _FP_strnicmp (line, "section ", 8) == 0) &&
+    result->uudet != YENC_ENCODED) {
+      if (result->uudet) {
+    fseek (datei, oldposition, SEEK_SET);
+    break;
+      }
+      result->partno = atoi (line + 8);
+      if ((ptr = _FP_stristr (line, " of ")) != NULL) {
+        int maxpno = atoi (ptr + 4);
+        if(maxpno != 0) result->maxpno = maxpno;
+      }
+      if ((ptr = _FP_stristr (line, "of file ")) != NULL) {
+    ptr += 8;
+    while (isspace (*ptr)) ptr++;
+    p2 = ptr;
+    while (isalnum(*p2) || (strchr(uufnchars, *p2)!=NULL))
+      p2++;
+    c = *p2; *p2 = '\0';
+    if (p2 != ptr && result->filename == NULL)
+      result->filename = _FP_strdup (ptr);
+    else if (p2 - ptr > 5 && strchr (ptr, '.') != NULL) {
+      /*
+       * This file name looks good, too. Let's use it
+       */
+      _FP_free (result->filename);
+      result->filename = _FP_strdup (ptr);
+    }
+    *p2 = c;
+      }
+    }
+
+    /*
+     * Some reduced MIME handling. Only use if boundary == NULL. Also
+     * accept the "X-Orcl-Content-Type" used by some braindead program.
+     */
+    if (boundary == NULL && !ismime && !uu_more_mime &&
+    result->uudet != YENC_ENCODED) {
+      if (_FP_strnicmp (line, "Content-Type", 12) == 0 ||
+      _FP_strnicmp (line, "X-Orcl-Content-Type", 19) == 0) {
+    /*
+     * We use Content-Type to mark a new attachment and split the file.
+     * However, we do not split if we haven't found anything encoded yet.
+     */
+    if (result->uudet) {
+      fseek (datei, oldposition, SEEK_SET);
+      break;
+    }
+    if ((ptr = strchr (line, ':')) != NULL) {
+      ptr++;
+      while (isspace (*ptr)) ptr++; p2 = ptr;
+      while (!isspace (*p2) && *p2 != ';') p2++;
+      c = *p2; *p2 = '\0';
+      if (p2 != ptr) {
+        _FP_free (result->mimetype);
+        result->mimetype = _FP_strdup (ptr);
+      }
+      *p2 = c;
+    }
+    ctline=0;
+    hadct=1;
+      }
+      if ((ptr = _FP_stristr (line, "number=")) && ctline<4) {
+    ptr += 7; if (*ptr == '"') ptr++;
+    result->partno = atoi (ptr);
+      }
+      if ((ptr = _FP_stristr (line, "total=")) && ctline<4) {
+    ptr += 6; if (*ptr == '"') ptr++;
+    result->maxpno = atoi (ptr);
+      }
+      if ((ptr = _FP_stristr (line, "name=")) && ctline<4) {
+    ptr += 5;
+    while (isspace (*ptr)) ptr++;
+    if (*ptr == '"' && *(ptr+1) && (p2 = strchr (ptr+2, '"')) != NULL) {
+      c = *p2; *p2 = '\0';
+      _FP_free (result->filename);
+      result->filename = _FP_strdup (ptr+1);
+      *p2 = c;
+    }
+    else if (*ptr=='\''&&*(ptr+1)&&(p2 = strchr(ptr+2, '\'')) != NULL) {
+      c = *p2; *p2 = '\0';
+      _FP_free (result->filename);
+      result->filename = _FP_strdup (ptr+1);
+      *p2 = c;
+    }
+    else {
+      p2 = ptr;
+      while (isalnum(*p2) || 
+         *p2 == '.' || *p2=='_' || *p2 == '-' ||
+         *p2 == '!' || *p2=='@' || *p2 == '$')
+        p2++;
+      c = *p2; *p2 = '\0';
+      if (p2 != ptr && result->filename == NULL)
+        result->filename = _FP_strdup (ptr);
+      else if (p2 - ptr > 5 && strchr (ptr, '.') != NULL) {
+        /*
+         * This file name looks good, too. Let's use it
+         */
+        _FP_free (result->filename);
+        result->filename = _FP_strdup (ptr);
+      }
+      *p2 = c;
+    }
+      }
+      if ((ptr = _FP_stristr (line, "id=")) && ctline<4) {
+    p2 = ptr += 3;
+    if (*p2 == '"') {
+      p2 = strchr (++ptr, '"');
+    }
+    else {
+      while (*p2 && isprint(*p2) && !isspace(*p2) && *p2 != ';')
+        p2++;
+    }
+    if (p2 && *p2 && p2!=ptr) {
+      c = *p2; *p2 = '\0';
+      if (result->mimeid)
+        _FP_free (result->mimeid);
+      result->mimeid = _FP_strdup (ptr);
+      *p2 = c;
+    }
+      }
+      
+      /* 
+       * Handling for very short Base64 files.
+       */
+      if (uu_tinyb64 && !ismime && !uu_more_mime) {
+    if (line[0] == '-' && line[1] == '-') {
+      if (dflag && (encoding==B64ENCODED || result->uudet==B64ENCODED)) {
+        if (encoding==B64ENCODED && result->uudet==0 && (haddh||hadct)) {
+          result->uudet = encoding;
+          encoding = dflag = 0;
+        }
+        haddh = 1;
+        continue;
+      }
+      hadct = 0;
+    }
+      }
+    } /* end of reduced MIME handling */
+
+    /*
+     * If we're in "freestyle" mode, have not encountered anything
+     * interesting yet, and stumble upon something that looks like
+     * a boundary, followed by a Content-* line, try to use it.
+     */
+
+    if (boundary == NULL && !ismime && !uu_more_mime && dflag <= 1 &&
+    line[0] == '-' && line[1] == '-' && strlen(line+2)>10 &&
+    (((ptr = _FP_strrstr (line+2, "--")) == NULL) ||
+     (*(ptr+2) != '\012' && *(ptr+2) != '\015')) &&
+    _FP_strstr (line+2, "_=_") != NULL) {
+      prevlinefirstchar = line[0];
+      if (_FP_fgets (line, 255, datei) == NULL) {
+    break;
+      }
+      if (_FP_strnicmp (line, "Content-", 8) == 0) {
+    /*
+     * Okay, let's do it. This breaks out of ScanData. ScanPart will
+     * recognize the boundary on the next call and use it.
+     */
+    fseek (datei, oldposition, SEEK_SET);
+    break;
+      }
+    }
+
+    /*
+     * Detection for yEnc encoding
+     */
+
+    if (strncmp (line, "=ybegin ", 8) == 0 &&
+    _FP_strstr (line, " name=") != NULL) {
+      if ((result->begin || result->end) && !uu_more_mime) {
+    fseek (datei, oldposition, SEEK_SET);
+    break;
+      }
+
+      /*
+       * name continues to the end of the line
+       */
+      
+      _FP_free (result->filename);
+      ptr = _FP_strstr (line, " name=") + 6;
+      result->filename = _FP_strdup (ptr);
+
+      while (isspace (result->filename[strlen(result->filename)-1]))
+    result->filename[strlen(result->filename)-1] = '\0';
+
+      /*
+       * Determine size
+       */
+
+      if ((ptr = _FP_strstr (line, " size=")) != NULL) {
+    ptr += 6;
+    yefilesize = atoi (ptr);
+      }
+      else {
+    yefilesize = -1;
+      }
+
+      /*
+       * check for multipart file and read =ypart line
+       */
+
+      if ((ptr = _FP_strstr (line, " part=")) != NULL) {
+    result->partno = atoi (ptr + 6);
+
+    if (result->partno == 1) {
+      result->begin = 1;
+    }
+
+    if (_FP_fgets (line, 255, datei) == NULL) {
+      break;
+    }
+
+    line[255] = '\0';
+
+    if (strncmp (line, "=ypart ", 7) != 0) {
+      break;
+    }
+
+    if ((ptr = _FP_strstr (line, " end=")) == NULL) {
+      break;
+    }
+       
+    yepartends = atoi (ptr + 5);
+      }
+      else {
+    result->partno = 1;
+    result->begin = 1;
+      }
+
+      /*
+       * Don't want auto-detection
+       */
+
+      result->uudet = YENC_ENCODED;
+      continue;
+    }
+
+    if (strncmp (line, "=yend ", 6) == 0 &&
+    result->uudet == YENC_ENCODED) {
+      if (yepartends == 0 || yepartends >= yefilesize) {
+    result->end = 1;
+      }
+      if (!uu_more_mime)
+    break;
+    }
+
+    /*
+     * if we haven't yet found anything encoded, try to find something
+     */
+
+    if (!(result->uudet)) {
+      /*
+       * Netscape-Repair code is the same as in uunconc.c
+       */
+
+      if ((vflag = UUValidData (line, 0, &bhflag)) == 0 && !ismime)
+    vflag = UURepairData (datei, line, 0, &bhflag);
+
+      /*
+       * Check data against all possible encodings
+       */
+
+      isb64[iscnt%10] = (UUValidData (line, B64ENCODED, &bhflag)==B64ENCODED);
+      isuue[iscnt%10] = (UUValidData (line, UU_ENCODED, &bhflag)==UU_ENCODED);
+      isxxe[iscnt%10] = (UUValidData (line, XX_ENCODED, &bhflag)==XX_ENCODED);
+      isbhx[iscnt%10] = (UUValidData (line, BH_ENCODED, &bhflag)==BH_ENCODED);
+
+      /*
+       * If we've got a first valid encoded line, we get suspicious if
+       * it's shorter than, say, 40 characters.
+       */
+
+      if (vflag == B64ENCODED &&
+      (dflag == 0 || encoding != B64ENCODED) &&
+      strlen (line) < 40 && !result->begin && !uu_tinyb64) {
+    isb64[iscnt%10] = 0;
+    vflag = 0;
+      }
+
+      if ((vflag == UU_ENCODED || vflag == XX_ENCODED) &&
+          (dflag == 0 || encoding != vflag) &&
+          strlen (line) < 40 && !result->begin) {
+    isuue[iscnt%10] = isxxe[iscnt%10] = 0;
+    vflag = 0;
+      }
+
+      iscnt++;
+
+      /*
+       * Ah, so we got an encoded line? How interesting!
+       */
+
+      if (vflag) {
+    /*
+     * For BinHex data, we can use the initial colon ':' as begin
+     * and the terminating colon as ':'.
+     * If (vflag && !bhflag), this is the last line,
+     */
+    if (vflag == BH_ENCODED) {
+      if (line[0] == ':' && result->end) {
+        fseek (datei, oldposition, SEEK_SET);
+        break;
+      }
+      if (line[0] == ':')
+        result->begin = 1;
+      if (bhflag == 0) {
+        result->uudet = BH_ENCODED;
+        result->end   = 1;
+      }
+    }
+    /*
+     * For BinHex files, the file name is encoded in the first encoded
+     * data bytes. We try to extract it here
+     */
+    if (vflag == BH_ENCODED && bhnf == 0 && result->filename == NULL) {
+      if (bhdsp == bhds2 ||
+          ((bhdsp-bhds2) <= (int) bhds2[0] &&
+           (bhdsp-bhds2) <  256)) { 
+        dcc = UUDecodeLine (line, bhds1, BH_ENCODED);
+        UUbhdecomp (bhds1, bhdsp, &bhl, &bhrpc,
+            dcc, 256-(bhdsp-bhds2), &bhopc);
+        bhdsp += bhopc;
+      }
+      if ((bhdsp-bhds2) > (int) bhds2[0] && bhds2[0]>0 &&
+          result->filename==NULL) {
+        memcpy (bhds1, bhds2+1, (int) bhds2[0]);
+        bhds1[(int)bhds2[0]]='\0';
+        result->filename = _FP_strdup (bhds1);
+        bhnf             = 1;
+      }
+      else if (bhdsp-bhds2 >= 256 && bhds2[0]>0) {
+        memcpy (bhds1, bhds2+1, 255);
+        bhds1[255]       = '\0';
+        result->filename = _FP_strdup (bhds1);
+        bhnf             = 1;
+      }
+      else if (bhds2[0] <= 0)
+        bhnf = 1;
+    }
+
+    /*
+     * We accept an encoding if it has been true for four consecutive
+     * lines. Check the is arrays to avoid mistaking one encoding
+     * for the other. Uuencoded data is rather easily mistaken for
+     * Base 64. If the data matches more than one encoding, we need to
+     * scan further.
+     */
+
+    if (iscnt > 3) {
+      cbb64 = (isb64[(iscnt-1)%10] && isb64[(iscnt-2)%10] &&
+           isb64[(iscnt-3)%10] && isb64[(iscnt-4)%10]);
+      cbuue = (isuue[(iscnt-1)%10] && isuue[(iscnt-2)%10] &&
+           isuue[(iscnt-3)%10] && isuue[(iscnt-4)%10]);
+      cbxxe = (isxxe[(iscnt-1)%10] && isxxe[(iscnt-2)%10] &&
+           isxxe[(iscnt-3)%10] && isxxe[(iscnt-4)%10]);
+      cbbhx = (isbhx[(iscnt-1)%10] && isbhx[(iscnt-2)%10] &&
+           isbhx[(iscnt-3)%10] && isbhx[(iscnt-4)%10]);
+    }
+    else {
+      cbb64 = cbuue = cbxxe = cbbhx = 0;
+    }
+
+    if (cbb64 && !cbuue && !cbxxe && !cbbhx) {
+      result->uudet = B64ENCODED;
+    }
+    else if (!cbb64 && cbuue && !cbxxe && !cbbhx) {
+      result->uudet = UU_ENCODED;
+    }
+    else if (!cbb64 && !cbuue && cbxxe && !cbbhx) {
+      result->uudet = XX_ENCODED;
+    }
+    else if (!cbb64 && !cbuue && !cbxxe && cbbhx) {
+      result->uudet = BH_ENCODED;
+    }
+
+    if (result->uudet) {
+          encoding = dflag = 0;
+
+      /*
+       * If we're in fast mode, we're told we don't have to look
+       * for anything below, so we can as well break out of every-
+       * thing
+       * We cannot fast-scan if we have a boundary to look after.
+       */
+
+      if (uu_fast_scanning && boundary == NULL)
+        break;
+
+      /*
+       * Skip the encoded data. We simply wait for a boundary, for
+       * a header or for an empty line. But we also try to pick up
+       * an "end"
+       */
+
+      hcount = lcount = 0;
+
+      while (!feof (datei)) {
+        /*
+         * Make Busy Polls
+         */
+        if (UUBUSYPOLL(ftell(datei),progress.fsize)) {
+          UUMessage (uuscan_id, __LINE__, UUMSG_NOTE,
+             uustring (S_SCAN_CANCEL));
+          *errcode = UURET_CANCEL;
+          break;
+        }
+
+        prevlinefirstchar = line[0];
+        oldposition = ftell (datei);
+        if (_FP_fgets (line, 255, datei) == NULL)
+          break;
+        if (ferror (datei))
+          break;
+
+        line[255] = '\0';
+
+        /*
+         * Stop scanning at an empty line or a MIME-boundary.
+         */
+        if (IsLineEmpty (line))
+          break;
+        if (boundary && line[0] == '-' && line[1] == '-' &&
+        strncmp (line+2, boundary, blen) == 0) {
+          fseek (datei, oldposition, SEEK_SET);
+          break;
+        }
+        else if (line[0] == 'e' && (result->uudet == UU_ENCODED ||
+                    result->uudet == XX_ENCODED)) {
+          if (strncmp (line, "end", 3) == 0) {
+        if (result->uudet == XX_ENCODED) {
+          if (prevlinefirstchar == '+') {
+            result->end = 1;
+            break;
+          }
+        }
+        else {
+          if (prevlinefirstchar == '`') {
+            result->end = 1;
+            break;
+          }
+        }
+          }
+        }
+        else if (line[0] == 'b') {
+          if (strncmp (line, "begin ", 6) == 0) {
+        fseek (datei, oldposition, SEEK_SET);
+        break;
+          }
+        }
+
+        if (checkheaders) {
+          if (IsKnownHeader (line)) {
+        (void) ScanHeaderLine (datei, line);
+        if (hcount == 0)
+          preheaders = oldposition;
+        hcount++;
+        lcount++;
+        if ((hcount >= hlcount.restart) ||
+            (hcount >= hlcount.afterdata && result->uudet)) {
+          /*
+           * Hey, a new header starts here
+           */
+          fseek (datei, preheaders, SEEK_SET);
+          break;
+        }
+          }
+          else if (lcount > WAITHEADER) {
+        hcount = 0;
+        lcount = 0;
+          }
+          else if (hcount) {
+        lcount++;
+          }
+        }
+        if (result->uudet == BH_ENCODED) {
+          /* pick up ``EOF'' for BinHex files. Slow :-< */
+          if (line[0] && strchr (line+1, ':') != NULL) {
+        result->end = 1;
+        bhflag      = 0;
+        break;
+          }
+        }
+      }
+
+      if (ferror (datei) || *errcode == UURET_CANCEL)
+        break;
+
+      if (line[0] == '-' && line[1] == '-')
+        haddh = 1;
+
+      /*
+       * Okay, got everything we need. If we had headers or a
+       * boundary, we break out of the outer loop immediately.
+       */
+
+      if (IsKnownHeader (line) ||
+          (boundary && line[0] == '-' && line[1] == '-' &&
+           strncmp (line+2, boundary, blen) == 0)) {
+        break;
+      }
+
+      /*
+       * Otherwise, we wait until finding something more interesting
+       * in the outer loop
+       */
+
+      continue;
+    }
+    
+    /*
+     * Select the encoding with the best "history"
+     */
+
+    cbb64 = isb64[(iscnt-1)%10];
+    cbuue = isuue[(iscnt-1)%10];
+    cbxxe = isxxe[(iscnt-1)%10];
+    cbbhx = isbhx[(iscnt-1)%10];
+    dflag = 0;
+
+    if (cbb64 || cbuue || cbxxe || cbbhx) {
+      for (dflag=2; dflagbegin) {
+      encoding = UU_ENCODED;
+    }
+    else if (!encoding && cbxxe && result->begin) {
+      encoding = XX_ENCODED;
+    }
+    else if (!encoding && cbb64) {
+      encoding = B64ENCODED;
+    }
+    else if (!encoding && cbuue) {
+      encoding = UU_ENCODED;
+    }
+    else if (!encoding && cbxxe) {
+      encoding = XX_ENCODED;
+    }
+    else if (!encoding && cbbhx) {
+      encoding = BH_ENCODED;
+    }
+      }
+      else if (!dontcare) {
+    encoding = 0;
+        dflag = 0;
+    haddh = 0;
+      }
+    } /* if (!uudet) */
+    /*
+     * End of scanning loop
+     */
+  } /* while (!feof (datei)) */
+
+  if (feof (datei))
+    oldposition = ftell (datei);
+
+  if (dflag && encoding == B64ENCODED && haddh)
+    result->uudet = B64ENCODED;
+  else if (dflag && encoding == BH_ENCODED)
+    result->uudet = BH_ENCODED;
+
+  /* Base64 doesn't have begin or end, so it was probably XX */
+  if (result->uudet == B64ENCODED && result->begin && result->end)
+    result->uudet = XX_ENCODED;
+
+  /* Base64 doesn't have begin or end */
+  if (result->uudet == B64ENCODED)
+    result->begin = result->end = 0;
+
+  /* Base64 and BinHex don't have a file mode */
+  if (result->uudet == B64ENCODED || result->uudet == BH_ENCODED ||
+      result->uudet == YENC_ENCODED)
+    result->mode  = 6*64+4*8+4;
+
+  /*
+   * When strict MIME adherance is set, throw out suspicious attachments
+   */
+
+  if (uu_more_mime) {
+    /*
+     * In a MIME message, Base64 should be appropriately tagged
+     */
+
+    if (result->uudet == B64ENCODED) {
+      result->uudet = 0;
+    }
+
+    /*
+     * Do not accept incomplete UU or XX encoded messages
+     */
+
+    if ((result->uudet != 0 && result->uudet != B64ENCODED) &&
+    (!result->begin || !result->end)) {
+      result->uudet = 0;
+    }
+  }
+
+  /*
+   * In fast mode, this length will yield a false value. We don't care.
+   * This must be checked for in uunconc(), where we usually stop decoding
+   * after reaching startpos+length
+   */
+
+  if (uu_fast_scanning)
+    result->length = progress.fsize-result->startpos;
+  else
+    result->length = ftell(datei)-result->startpos;
+
+  if (ferror (datei)) {
+    *errcode = UURET_IOERR;
+    uu_errno = errno;
+    return -1;
+  }
+  if (*errcode != UURET_OK) {
+    return -1;
+  }
+
+  if (boundary && line[0] == '-' && line[1] == '-' &&
+      strncmp (line+2, boundary, blen) == 0)
+    return 2;
+  else if (boundary && p3 &&
+       line[0] == 'C' && line[1] == 'o' &&
+       _FP_strnicmp (line, "Content-Type:", 13) == 0 &&
+       strcmp (p3, boundary) == 0)
+    return 2;
+  else if (IsKnownHeader (line))
+    return 1;
+
+  return 0;
+}
+
+/*
+ * This is the main scanning function.
+ */
+
+fileread *
+ScanPart (FILE *datei, char *fname, int *errcode)
+{
+  int ecount, hcount, lcount;
+  int bhflag, begflag, vflag, blen=0, res;
+  long preheaders, prevpos=0, preenc, before;
+  char *line=uuscan_spline;
+  fileread *result;
+  char *ptr1, *ptr2;
+
+  (void) UUDecodeLine (NULL, NULL, 0);          /* init */
+  if (datei == NULL || feof (datei)) {
+    *errcode = UURET_OK;
+    return NULL;
+  }
+
+  *errcode = UURET_OK;
+
+  if ((result = (fileread *) malloc (sizeof (fileread))) == NULL) {
+    *errcode = UURET_NOMEM;
+    return NULL;
+  }
+  memset (result, 0, sizeof (fileread));
+  result->startpos = ftell (datei);
+  preheaders       = result->startpos;
+  before           = result->startpos;
+
+  /* if this is a new file, reset our scanning state */
+  if (sstate.source == NULL || strcmp (fname, sstate.source) != 0) {
+    sstate.isfolder  = 1;       /* assume yes            */
+    sstate.ismime    = 0;       /* wait for MIME-Version */
+    sstate.mimestate = MS_HEADERS;  /* assume headers follow */
+    /* mimseqno      = 1; */
+
+    while (mssdepth) {
+      mssdepth--;
+      UUkillheaders (&(multistack[mssdepth].envelope));
+      _FP_free (multistack[mssdepth].source);
+    }
+
+    UUkillheaders (&sstate.envelope);
+    memset (&sstate.envelope, 0, sizeof (headers));
+
+    _FP_free (sstate.source);
+    if ((sstate.source = _FP_strdup (fname)) == NULL) {
+      *errcode = UURET_NOMEM;
+      _FP_free (result);
+      return NULL;
+    }
+
+    /* ignore empty lines at the beginning of a file */
+    preheaders = ftell (datei);
+    while (!feof (datei)) {
+      if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+      if (_FP_fgets (line, 255, datei) == NULL)
+    break;
+      if (!IsLineEmpty (line)) {
+    fseek (datei, preheaders, SEEK_SET);
+    line[255] = '\0';
+    break;
+      }
+      preheaders = ftell (datei);
+    }
+  }
+
+  if (ferror(datei) || feof(datei)) {
+    _FP_free (result);
+    return NULL;
+  }
+
+  /*
+   * If we are confident that this is a mail folder and are at the
+   * beginning of the file, expecting to read some headers, scan
+   * the envelope.
+   */
+
+  if (sstate.isfolder && sstate.mimestate == MS_HEADERS) {
+    hcount = 0;
+    lcount = 0;
+    UUkillheaders (&sstate.envelope);
+
+    /*
+     * clean up leftovers from invalid messages
+     */
+
+    while (mssdepth) {
+      mssdepth--;
+      UUkillheaders (&(multistack[mssdepth].envelope));
+      _FP_free (multistack[mssdepth].source);
+    }
+
+    if (_FP_fgets (line, 255, datei) == NULL) {
+      _FP_free (result);
+      return NULL;
+    }
+    line[255] = '\0';
+
+    /*
+     * Special handling for AOL folder files, which start off with a boundary.
+     * We recognize them by a valid boundary line as the first line of a file.
+     * Note that the rest of the scanning code becomes suspicious if a boun-
+     * dary does never appear in a file -- this should save us from grave
+     * false detection errors
+     */
+
+    if (!feof (datei) && line[0] == '-' && line[1] == '-' && line[2]) {
+      while (line[strlen(line)-1] == '\012' ||
+         line[strlen(line)-1] == '\015') {
+    line[strlen(line)-1] = '\0';
+      }
+
+      sstate.ismime            = 1;
+      sstate.envelope.mimevers = _FP_strdup ("1.0");
+      sstate.envelope.boundary = _FP_strdup (line+2);
+      sstate.envelope.ctype    = _FP_strdup ("multipart/mixed");
+      sstate.mimestate         = MS_SUBPART;
+
+      *errcode = UURET_CONT;
+      _FP_free (result);
+      return NULL;
+    }
+
+    /*
+     * Normal behavior: look for a RFC 822 header
+     */
+
+    while (!feof (datei) && !IsLineEmpty (line)) {
+      if (IsKnownHeader (line))
+    hcount++;
+      if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+      ptr1 = ScanHeaderLine (datei, line);
+      if (ParseHeader (&sstate.envelope, ptr1) == NULL) {
+    *errcode = UURET_NOMEM;
+    _FP_free (result);
+    return NULL;
+      }
+      /*
+       * if we've read too many lines without finding headers, then
+       * this probably isn't a mail folder after all
+       */
+      lcount++;
+      if (lcount > WAITHEADER && hcount < hlcount.afternl)
+    break;
+
+      if (_FP_fgets (line, 255, datei) == NULL)
+    break;
+      line[255] = '\0';
+    }
+    /* skip empty lines */
+    prevpos = ftell (datei);
+    while (!feof (datei)) {
+      if (_FP_fgets (line, 255, datei) == NULL)
+    break;
+      if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+      if (!IsLineEmpty (line)) {
+    fseek (datei, prevpos, SEEK_SET);
+    line[255] = '\0';
+    break;
+      }
+      prevpos = ftell (datei);
+    }
+    /*
+     * If we don't have all valid MIME headers yet, but the following
+     * line is a MIME header, accept it anyway.
+     */
+
+    if (!uu_more_mime &&
+    sstate.envelope.mimevers == NULL &&
+    sstate.envelope.ctype    == NULL &&
+    sstate.envelope.ctenc    == NULL &&
+    IsKnownHeader (line)) {
+      /*
+       * see above
+       */
+      if (_FP_fgets (line, 255, datei) == NULL) {
+    line[0] = '\012';
+    line[1] = '\0';
+      }
+      line[255] = '\0';
+
+      while (!feof (datei) && !IsLineEmpty (line)) {
+    if (IsKnownHeader (line))
+      hcount++;
+    if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+    ptr1 = ScanHeaderLine (datei, line);
+    if (ParseHeader (&sstate.envelope, ptr1) == NULL) {
+      *errcode = UURET_NOMEM;
+      _FP_free (result);
+      return NULL;
+    }
+    if (_FP_fgets (line, 255, datei) == NULL)
+      break;
+    line[255] = '\0';
+      }
+      /* skip empty lines */
+      prevpos = ftell (datei);
+      while (!feof (datei)) {
+    if (_FP_fgets (line, 255, datei) == NULL)
+      break;
+    if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+    if (!IsLineEmpty (line)) {
+      fseek (datei, prevpos, SEEK_SET);
+      line[255] = '\0';
+      break;
+    }
+    prevpos = ftell (datei);
+      }
+    }
+
+    /*
+     * A partial multipart message probably has only a Content-Type
+     * header but nothing else. In this case, at least simulate a
+     * MIME message
+     * if mimevers is not set but there are other well-known MIME
+     * headers, don't be too picky about it.
+     */
+    if (sstate.envelope.ctype && sstate.envelope.mimevers==NULL  &&
+    _FP_stristr (sstate.envelope.ctype, "multipart") != NULL &&
+    sstate.envelope.boundary != NULL) {
+      sstate.envelope.mimevers = _FP_strdup ("1.0");
+      hcount = hlcount.afternl;
+    }
+    else if (sstate.envelope.mimevers==NULL && sstate.envelope.ctype &&
+         sstate.envelope.fname && sstate.envelope.ctenc) {
+      sstate.envelope.mimevers = _FP_strdup ("1.0");
+      hcount = hlcount.afternl;
+    }
+
+    if (hcount < hlcount.afternl) {
+      /* not a folder after all */
+      fseek (datei, preheaders, SEEK_SET);
+      sstate.isfolder = 0;
+      sstate.ismime   = 0;
+    }
+    else if (sstate.envelope.mimevers != NULL) {
+      /* this is a MIME file. check the Content-Type */
+      sstate.ismime = 1;
+      if (_FP_stristr (sstate.envelope.ctype, "multipart") != NULL) {
+    if (sstate.envelope.boundary == NULL) {
+      UUMessage (uuscan_id, __LINE__, UUMSG_WARNING,
+             uustring (S_MIME_NO_BOUNDARY));
+      sstate.mimestate = MS_BODY;
+      _FP_free (sstate.envelope.ctype);
+      sstate.envelope.ctype = _FP_strdup ("text/plain");
+    }
+    else {
+      sstate.mimestate = MS_PREAMBLE;
+    }
+      }
+      else {
+    sstate.mimestate = MS_BODY; /* just a `simple' message */
+      }
+    }
+  }
+
+  if (feof (datei) || ferror (datei)) { /* oops */
+    _FP_free (result);
+    return NULL;
+  }
+
+  /*
+   * Handle MIME stuff
+   */
+
+  /*
+   * Read Preamble. This must be ended by a sstate.envelope.boundary line.
+   * If uu_usepreamble is set, we produce a result from this one
+   */
+
+  if (sstate.ismime && sstate.mimestate == MS_PREAMBLE) {
+    result->startpos = ftell (datei);   /* remember start of preamble */
+    prevpos          = ftell (datei);
+    preheaders       = ftell (datei);
+
+    blen   = strlen (sstate.envelope.boundary);
+    lcount = 0;
+    
+    while (!feof (datei)) {
+      if (_FP_fgets (line, 255, datei) == NULL) {
+    line[0] = '\0';
+    break;
+      }
+      if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+      if (line[0] == '-' && line[1] == '-' &&
+      strncmp (line+2, sstate.envelope.boundary, blen) == 0)
+    break;
+      if (!IsLineEmpty (line))
+    lcount++;
+
+      prevpos = ftell (datei);
+    }
+    if (feof (datei) || ferror (datei)) {
+      UUMessage (uuscan_id, __LINE__, UUMSG_WARNING,
+         uustring (S_MIME_B_NOT_FOUND));
+      /*
+       * restart and try again; don't restart if uu_fast_scanning
+       */
+      sstate.isfolder  = 0;
+      sstate.ismime    = 0;
+      sstate.mimestate = MS_BODY;
+
+      if (!uu_fast_scanning) {
+    *errcode = UURET_CONT;
+    fseek (datei, preheaders, SEEK_SET);
+      }
+      _FP_free (result);
+      return NULL;
+    }
+    if (line[0] == '-' && line[1] == '-' &&
+    strncmp (line+2, sstate.envelope.boundary, blen) == 0) {
+      ptr1 = line + 2 + blen;
+      if (*ptr1 == '-' && *(ptr1+1) == '-') {
+    /* Empty Multipart Message. Duh. */
+    sstate.mimestate = MS_EPILOGUE;
+      }
+      else {
+    sstate.mimestate = MS_SUBPART;
+      }
+    }
+    else { /* shouldn't happen */
+      UUMessage (uuscan_id, __LINE__, UUMSG_WARNING,
+         uustring (S_MIME_B_NOT_FOUND));
+      /*
+       * restart and try again; don't restart if uu_fast_scanning
+       */
+      sstate.isfolder  = 0;
+      sstate.ismime    = 0;
+      sstate.mimestate = MS_BODY;
+
+      if (!uu_fast_scanning) {
+    *errcode = UURET_CONT;
+    fseek (datei, preheaders, SEEK_SET);
+      }
+      _FP_free (result);
+      return NULL;
+    }
+    /* produce result if uu_usepreamble is set */
+    if (uu_usepreamble && lcount) {
+      sprintf (line, "%04d.txt", ++mimseqno);
+      result->subject  = _FP_strdup (sstate.envelope.subject);
+      result->filename = _FP_strdup (line);
+      result->origin   = _FP_strdup (sstate.envelope.from);
+      result->mimeid   = _FP_strdup (sstate.envelope.mimeid);
+      result->mimetype = _FP_strdup ("text/plain");
+      result->mode     = 0644;
+      result->uudet    = PT_ENCODED;    /* plain text */
+      result->sfname   = _FP_strdup (fname);
+      result->flags    = FL_SINGLE | FL_PROPER;
+      /* result->startpos set from above */
+      result->length   = prevpos - result->startpos;
+      result->partno   = 1;
+
+      /* MIME message, let's continue */
+      *errcode = UURET_CONT;
+
+      if ((sstate.envelope.subject != NULL && result->subject == NULL) ||
+      result->filename == NULL || result->sfname == NULL) {
+    *errcode = UURET_NOMEM;
+      }
+
+      return result;
+    }
+    /* MIME message, let's continue */
+    if (*errcode == UURET_OK)
+      *errcode = UURET_CONT;
+
+    /* otherwise, just return NULL */
+    _FP_free (result);
+    return NULL;
+  }
+
+  /*
+   * Read Epilogue, the plain text after the last boundary.
+   * This can either end with new headers from the next message of a
+   * mail folder or with a `parent' boundary if we are inside an
+   * encapsulated Multipart message. Oh yes, and of course the file
+   * may also simply end :-)
+   * Another possibility is that we might find plain encoded data
+   * without encapsulating message. We're not _too_ flexible here,
+   * we won't detect Base64, and require a proper `begin' line for
+   * uuencoding and xxencoding
+   * If uu_usepreamble is set, we produce a result from this one
+   */
+
+  if (sstate.ismime && sstate.mimestate == MS_EPILOGUE) {
+    result->startpos = ftell (datei);   /* remember start of epilogue */
+    prevpos          = ftell (datei);
+    preheaders       = ftell (datei);
+    preenc           = ftell (datei);
+    hcount = lcount  = 0;
+    ecount = bhflag  = 0;
+    begflag = vflag  = 0;
+    res = 0;
+
+    /*
+     * If we are in the outermost message and uu_fast_scanning, we
+     * know (or assume) that no more messages will follow, so there's
+     * no need to scan the rest.
+     */
+    if (uu_fast_scanning && mssdepth == 0) {
+      /*
+       * check if the epilogue is empty
+       */
+      while (!feof (datei) && !ferror (datei) && lcount<10 && res==0) {
+    if (_FP_fgets (line, 255, datei) == NULL)
+      break;
+    if (!IsLineEmpty (line))
+      res++;
+    lcount++;
+      }
+      if (uu_usepreamble && res) {
+    sprintf (line, "%04d.txt", ++mimseqno);
+    result->subject  = _FP_strdup (sstate.envelope.subject);
+    result->filename = _FP_strdup (line);
+    result->origin   = _FP_strdup (sstate.envelope.from);
+    result->mimeid   = _FP_strdup (sstate.envelope.mimeid);
+    result->mimetype = _FP_strdup ("text/plain");
+    result->mode     = 0644;
+    result->uudet    = PT_ENCODED;  /* plain text */
+    result->sfname   = _FP_strdup (fname);
+    result->flags    = FL_SINGLE | FL_PROPER | FL_TOEND;
+    result->partno   = 1;
+    /* result->startpos set from above */
+    result->length   = progress.fsize - result->startpos;
+
+    if ((sstate.envelope.subject != NULL && result->subject == NULL) ||
+        result->filename == NULL || result->sfname == NULL) {
+      *errcode = UURET_NOMEM;
+    }
+
+    return result;
+      }
+      _FP_free (result);
+      return NULL;
+    }
+
+    if (mssdepth > 0)
+      blen = strlen (multistack[mssdepth-1].envelope.boundary);
+
+    while (!feof (datei)) {
+      if (_FP_fgets (line, 255, datei) == NULL) {
+    line[0] = '\0';
+    break;
+      }
+      if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+      line[255] = '\0';
+      /* check for parent boundary */
+      if (mssdepth > 0 && line[0] == '-' && line[1] == '-' &&
+      strncmp (line+2,
+           multistack[mssdepth-1].envelope.boundary, blen) == 0)
+    break;
+
+      /* check for next headers only at outermost level */
+      if (mssdepth == 0 && IsKnownHeader (line)) {
+    (void) ScanHeaderLine (datei, line);
+    if (hcount == 0) {
+      preheaders = prevpos;
+      lcount     = 0;
+    }
+    hcount++; 
+    lcount++;
+
+    if (hcount >= hlcount.restart) {
+      /* okay, new headers */
+      break;
+    }
+      }
+      else if (lcount > WAITHEADER) {
+    hcount = 0;
+    lcount = 0;
+      }
+      else if (hcount) {
+    lcount++;
+      }
+      else {
+    hcount = lcount = 0;
+      }
+
+      /* check for begin and encoded data only at outermost level */
+      if (mssdepth == 0 && !uu_more_mime) {
+    if (strncmp      (line, "begin ",       6) == 0 ||
+        _FP_strnicmp (line, "
begin ", 11) == 0) {
+      preenc  = prevpos;
+      begflag = 1;
+    }
+    else if (strncmp (line, "end", 3) == 0 && begflag) {
+      ecount = ELC_COUNT;
+      break;
+    }
+    else if ((vflag = UUValidData (line, 0, &bhflag)) != 0) {
+      if (vflag == BH_ENCODED && bhflag == 0) {
+        /* very short BinHex file follows */
+        preenc = prevpos;
+        break;
+      }
+      /* remember that XX can easily be mistaken as Base64 */
+      if ((vflag == UU_ENCODED || vflag == XX_ENCODED ||
+           vflag == B64ENCODED) && begflag) {
+        if (++ecount >= ELC_COUNT)
+          break;
+      }
+      else {
+        begflag = 0;
+        ecount  = 0;
+      }
+    }
+    else {
+      begflag = 0;
+      ecount  = 0;
+    }
+      }
+
+      if (!IsLineEmpty (line))
+    res++;
+
+      prevpos = ftell (datei);
+    }
+
+    if (mssdepth > 0 && line[0] == '-' && line[1] == '-' &&
+    strncmp (line+2,
+         multistack[mssdepth-1].envelope.boundary, blen) == 0) {
+      /* restore previous state */
+      mssdepth--;
+      UUkillheaders (&sstate.envelope);
+      _FP_free  (sstate.source);
+      memcpy (&sstate, &(multistack[mssdepth]), sizeof (scanstate));
+
+      ptr1 = line + 2 + strlen (sstate.envelope.boundary);
+
+      if (*ptr1 == '-' && *(ptr1+1) == '-') {
+    sstate.mimestate = MS_EPILOGUE;
+      }
+      else {
+    sstate.mimestate = MS_SUBPART;
+      }
+      result->length = prevpos - result->startpos;
+      *errcode = UURET_CONT;
+    }
+    else if (mssdepth > 0) {
+      UUMessage (uuscan_id, __LINE__, UUMSG_WARNING,
+         uustring (S_MIME_B_NOT_FOUND));
+      /*
+       * restart and try again; don't restart if uu_fast_scanning
+       */
+      sstate.isfolder  = 0;
+      sstate.ismime    = 0;
+      sstate.mimestate = MS_BODY;
+
+      while (mssdepth) {
+    mssdepth--;
+    UUkillheaders (&(multistack[mssdepth].envelope));
+    _FP_free (multistack[mssdepth].source);
+      }
+
+      if (!uu_fast_scanning) {
+    *errcode = UURET_CONT;
+    fseek (datei, preheaders, SEEK_SET);
+      }
+      _FP_free (result);
+      return NULL;
+    }
+    else if (IsKnownHeader (line)) {
+      /* new message follows */
+      sstate.isfolder  = 1;
+      sstate.ismime    = 0;
+      sstate.mimestate = MS_HEADERS;
+      result->length   = preheaders - result->startpos;
+      fseek (datei, preheaders, SEEK_SET);
+    }
+    else if (ecount >= ELC_COUNT) {
+      /* new plain encoding */
+      sstate.isfolder  = 0;
+      sstate.ismime    = 0;
+      sstate.mimestate = MS_BODY;
+      result->length   = preenc - result->startpos;
+      fseek (datei, preenc, SEEK_SET);
+    }
+
+    /* produce result if uu_usepreamble is set */
+    if (uu_usepreamble && res) {
+      sprintf (line, "%04d.txt", ++mimseqno);
+      result->subject  = _FP_strdup (sstate.envelope.subject);
+      result->filename = _FP_strdup (line);
+      result->origin   = _FP_strdup (sstate.envelope.from);
+      result->mimeid   = _FP_strdup (sstate.envelope.mimeid);
+      result->mimetype = _FP_strdup ("text/plain");
+      result->mode     = 0644;
+      result->uudet    = PT_ENCODED;    /* plain text */
+      result->sfname   = _FP_strdup (fname);
+      result->flags    = FL_SINGLE | FL_PROPER;
+      result->partno   = 1;
+      /* result->startpos set from above */
+      /* result->length set from above */
+
+      if ((sstate.envelope.subject != NULL && result->subject == NULL) ||
+      result->filename == NULL || result->sfname == NULL) {
+    *errcode = UURET_NOMEM;
+      }
+
+      return result;
+    }
+    /* otherwise, just return NULL */
+    _FP_free (result);
+    return NULL;
+  }
+
+  /*
+   * Scan a new part from a Multipart message. Check for a new local
+   * envelope (which defaults to `Content-Type: text/plain') and
+   * evaluate its Content-Type and Content-Transfer-Encoding. If this
+   * is another Multipart/something, push the current state onto our
+   * stack and dive into the new environment, starting with another
+   * preamble.
+   */
+               
+  if (sstate.ismime && sstate.mimestate == MS_SUBPART) {
+    memset (&localenv, 0, sizeof (headers));
+    result->startpos = ftell (datei);
+    prevpos = ftell (datei);
+    hcount  = 0;
+    lcount  = 0;
+
+    /*
+     * Duplicate some data from outer envelope
+     */
+
+    localenv.mimevers = _FP_strdup (sstate.envelope.mimevers);
+    localenv.from     = _FP_strdup (sstate.envelope.from);
+    localenv.subject  = _FP_strdup (sstate.envelope.subject);
+    localenv.rcpt     = _FP_strdup (sstate.envelope.rcpt);
+    localenv.date     = _FP_strdup (sstate.envelope.date);
+
+    if ((sstate.envelope.mimevers != NULL && localenv.mimevers == NULL) ||
+    (sstate.envelope.from     != NULL && localenv.from     == NULL) ||
+    (sstate.envelope.subject  != NULL && localenv.subject  == NULL) ||
+    (sstate.envelope.rcpt     != NULL && localenv.rcpt     == NULL) ||
+    (sstate.envelope.date     != NULL && localenv.date     == NULL)) {
+
+      while (mssdepth) {
+    mssdepth--;
+    UUkillheaders (&(multistack[mssdepth].envelope));
+    _FP_free (multistack[mssdepth].source);
+      }
+      sstate.isfolder = 0;
+      sstate.ismime   = 0;
+      
+      UUkillheaders (&localenv);
+      *errcode = UURET_NOMEM;
+      _FP_free (result);
+      return NULL;
+    }
+    
+    /* Scan subheader. But what if there is no subheader? */
+    hcount = 0;
+    lcount = 0;
+    preheaders = prevpos;
+    
+    if (_FP_fgets (line, 255, datei) == NULL) {
+      sstate.isfolder = 0;
+      sstate.ismime   = 0;
+      while (mssdepth) {
+    mssdepth--;
+    UUkillheaders (&(multistack[mssdepth].envelope));
+    _FP_free (multistack[mssdepth].source);
+      }
+      UUkillheaders (&localenv);
+      _FP_free (result);
+      return NULL;
+    }
+    line[255] = '\0';
+
+    while (!feof (datei) && !IsLineEmpty (line)) {
+      if (IsKnownHeader (line))
+    hcount++;
+      if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+      if (lcount > WAITHEADER && hcount == 0) {
+    fseek (datei, preheaders, SEEK_SET);
+    prevpos = preheaders;
+    break;
+      }
+      ptr1 = ScanHeaderLine (datei, line);
+      if (ParseHeader (&localenv, ptr1) == NULL)
+    *errcode = UURET_NOMEM;
+
+      if (line[0] == '-' && line[1] == '-')
+    break;
+
+      prevpos = ftell (datei);
+
+      if (_FP_fgets (line, 255, datei) == NULL)
+    break;
+      line[255] = '\0';
+      lcount++;
+    }
+    if (line[0] == '-' && line[1] == '-') {
+      /*
+       * this shouldn't happen, there must always be an empty line,
+       * but let's accept it anyway. Just skip back to before the
+       * boundary, so that it gets handled below
+       */
+      fseek (datei, prevpos, SEEK_SET);
+    }
+
+    if (_FP_stristr (localenv.ctype, "multipart") != NULL) {
+      /* oh no, not again */
+      if (mssdepth >= MSMAXDEPTH) {
+    /* Argh, what an isane message. Treat as plain text */
+    UUMessage (uuscan_id, __LINE__, UUMSG_WARNING,
+           uustring (S_MIME_MULTI_DEPTH));
+      }
+      else if (localenv.boundary == NULL) {
+    UUMessage (uuscan_id, __LINE__, UUMSG_WARNING,
+           uustring (S_MIME_NO_BOUNDARY));
+      }
+      else {
+    memcpy (&multistack[mssdepth], &sstate, sizeof (scanstate));
+    memcpy (&sstate.envelope,    &localenv, sizeof (headers));
+    memset (&localenv, 0, sizeof (headers));
+    sstate.mimestate = MS_PREAMBLE;
+    if ((sstate.source = _FP_strdup (sstate.source)) == NULL)
+      *errcode = UURET_NOMEM;
+
+    if (*errcode == UURET_OK)
+      *errcode = UURET_CONT;
+
+    mssdepth++;
+    /* need a restart */
+    _FP_free (result);
+    return NULL;
+      }
+    }
+
+    /*
+     * So this subpart is either plain text or something else. Check
+     * the Content-Type and Content-Transfer-Encoding. If the latter
+     * is a defined value, we know what to do and just copy everything
+     * up to the boundary.
+     * If Content-Transfer-Encoding is unknown or missing, look at the
+     * Content-Type. If it's "text/plain" or undefined, we subject the
+     * message to our encoding detection. Otherwise, treat as plain
+     * text.
+     * This is done because users might `attach' a uuencoded file, which
+     * would then be correctly typed as `text/plain'.
+     */
+
+    if (_FP_stristr (localenv.ctenc, "base64") != NULL)
+      result->uudet = B64ENCODED;
+    else if (_FP_stristr (localenv.ctenc, "x-uue") != NULL) {
+      result->uudet = UU_ENCODED;
+      result->begin = result->end = 1;
+    }
+    else if (_FP_stristr (localenv.ctenc, "x-yenc") != NULL) {
+      result->uudet = YENC_ENCODED;
+      result->begin = result->end = 1;
+    }
+    else if (_FP_stristr (localenv.ctenc, "quoted-printable") != NULL)
+      result->uudet = QP_ENCODED;
+    else if (_FP_stristr (localenv.ctenc, "7bit") != NULL ||
+         _FP_stristr (localenv.ctenc, "8bit") != NULL)
+      result->uudet = PT_ENCODED;
+    else if (_FP_stristr (localenv.ctype, "multipart") != NULL ||
+         _FP_stristr (localenv.ctype, "message")   != NULL)
+      result->uudet = PT_ENCODED;
+
+    /*
+     * If we're switched to MIME-only mode, handle as text
+     */
+
+    if (uu_more_mime >= 2 && !result->uudet) {
+      result->uudet = PT_ENCODED;
+    }
+
+    if (result->uudet) {
+      /*
+       * Oh-kay, go ahead. Just read and wait for the boundary
+       */
+      result->startpos = ftell (datei);
+      prevpos          = ftell (datei);
+      blen = strlen (sstate.envelope.boundary);
+      lcount = 0;
+      
+      while (!feof (datei)) {
+    if (_FP_fgets (line, 255, datei) == NULL) {
+      line[0] = '\0';
+      break;
+    }
+    if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+    line[255] = '\0';
+    if (line[0] == '-' && line[1] == '-' &&
+        strncmp (line+2, sstate.envelope.boundary, blen) == 0)
+      break;
+    /*
+     * I've had a report of someone who tried to decode a huge file
+     * that had an early truncated multipart message and later another
+     * multipart message with the *same* boundary. Consequently, all
+     * some hundred messages inbetween were ignored ...
+     * This check here doesn't cover folded header lines, but we don't
+     * want to slow down scanning too much. We just check for
+     * Content-Type: multipart/... boundary="same-boundary"
+     */
+    if (line[0] == 'C' && line[1] == 'o' &&
+        _FP_strnicmp (line, "Content-Type:", 13) == 0) {
+      ptr1 = ScanHeaderLine (datei, line);
+      ptr2 = (ptr1)?_FP_stristr(ptr1,"boundary"):NULL;
+      ptr1 = (ptr2)?ParseValue(ptr2):NULL;
+      if (ptr1 && strcmp (ptr1, sstate.envelope.boundary) == 0)
+        break;
+      for (res=0; ptr1 && resstartpos, SEEK_SET);
+
+    UUkillfread (result);
+    if ((result = (fileread *) malloc (sizeof (fileread))) == NULL) {
+      *errcode = UURET_NOMEM;
+      sstate.isfolder = 0;
+      sstate.ismime   = 0;
+      UUkillheaders (&localenv);
+      return NULL;
+    }
+    memset (result, 0, sizeof (fileread));
+
+    if ((res = ScanData (datei, fname, errcode, NULL, 1, 1, result))==-1) {
+      /* oops, something went wrong */
+      sstate.isfolder = 0;
+      sstate.ismime   = 0;
+      UUkillfread   (result);
+      UUkillheaders (&localenv);
+      return NULL;
+    }
+    if (res == 1) {
+      /*
+       * new headers found
+       */
+      sstate.isfolder  = 1;
+      sstate.ismime    = 0;
+      sstate.mimestate = MS_HEADERS;
+    }
+    else {
+      sstate.isfolder  = 0;
+      sstate.ismime    = 0;
+    }
+      }
+      /* produce result if uu_handletext is set */
+      /* or if the file is explicitely named */
+      if (result->uudet == B64ENCODED || lcount) {
+    if (localenv.fname) {
+      _FP_free (result->filename);
+      if ((result->filename = _FP_strdup (localenv.fname)) == NULL)
+        *errcode = UURET_NOMEM;
+    }
+    else if ((result->uudet==QP_ENCODED||result->uudet==PT_ENCODED) &&
+         result->filename == NULL && uu_handletext) {
+      sprintf (line, "%04d.txt", ++mimseqno);
+      if ((result->filename = _FP_strdup (line)) == NULL)
+        *errcode = UURET_NOMEM;
+    }
+    result->subject  = _FP_strdup (localenv.subject);
+    result->origin   = _FP_strdup (localenv.from);
+    result->mimeid   = _FP_strdup (localenv.mimeid);
+    result->mimetype = _FP_strdup (localenv.ctype);
+    result->mode     = 0644;
+    result->sfname   = _FP_strdup (fname);
+    result->flags    = FL_SINGLE | FL_PROPER;
+    result->partno   = 1;
+    /* result->uudet determined above */
+    /* result->startpos set from above */
+    result->length   = prevpos - result->startpos;
+
+    if ((localenv.subject != NULL && result->subject == NULL) ||
+        result->filename == NULL  || result->sfname == NULL) {
+      *errcode = UURET_NOMEM;
+    }
+      }
+      else {
+    /* don't produce a result */
+    _FP_free (result);
+    result = NULL;
+      }
+      if (*errcode == UURET_OK)
+    *errcode = UURET_CONT;
+      /*
+       * destroy local envelope
+       */
+      UUkillheaders (&localenv);
+      return result;
+    }
+
+    /*
+     * we're in a subpart, but the local headers don't give us any
+     * clue about what's to find here. So look for encoded data by
+     * ourselves.
+     */
+
+    if ((res = ScanData (datei, fname, errcode,
+             sstate.envelope.boundary,
+             1, 0, result)) == -1) {
+      /* oops, something went wrong */
+      sstate.isfolder = 0;
+      sstate.ismime   = 0;
+      UUkillfread   (result);
+      UUkillheaders (&localenv);
+      return NULL;
+    }
+    /*
+     * we should really be at a boundary here, but check again
+     */
+    blen    = strlen (sstate.envelope.boundary);
+    prevpos = ftell  (datei);
+
+    while (!feof (datei)) {
+      if (_FP_fgets (line, 255, datei) == NULL) {
+    line[0] = '\0';
+    break;
+      }
+      if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+      line[255] = '\0';
+      if (line[0] == '-' && line[1] == '-' &&
+      strncmp (line+2, sstate.envelope.boundary, blen) == 0)
+    break;
+      if (line[0] == 'C' && line[1] == 'o' &&
+      _FP_strnicmp (line, "Content-Type:", 13) == 0) {
+    ptr1 = ScanHeaderLine (datei, line);
+    ptr2 = (ptr1)?_FP_stristr(ptr1,"boundary"):NULL;
+    ptr1 = (ptr2)?ParseValue(ptr2):NULL;
+    if (ptr1 && strcmp (ptr1, sstate.envelope.boundary) == 0)
+      break;
+      }
+      prevpos = ftell (datei);
+    }
+    /*
+     * check if this was the last subpart
+     */
+    if (line[0] == '-' && line[1] == '-' &&
+    strncmp (line+2, sstate.envelope.boundary, blen) == 0) {
+      ptr1 = line + 2 + blen;
+      if (*ptr1 == '-' && *(ptr1+1) == '-')
+    sstate.mimestate = MS_EPILOGUE;
+      else
+    sstate.mimestate = MS_SUBPART;
+    }
+    else {
+      UUMessage (uuscan_id, __LINE__, UUMSG_WARNING,
+         uustring (S_MIME_B_NOT_FOUND));
+      
+      while (mssdepth) {
+    mssdepth--;
+    UUkillheaders (&(multistack[mssdepth].envelope));
+    _FP_free (multistack[mssdepth].source);
+      }
+
+      if (uu_fast_scanning) {
+    UUkillheaders (&localenv);
+    sstate.isfolder  = 0;
+    sstate.ismime    = 0;
+    sstate.mimestate = MS_BODY;
+    _FP_free (result);
+    return NULL;
+      }
+
+      /*
+       * Retry, listening to headers this time
+       */
+      fseek (datei, result->startpos, SEEK_SET);
+      
+      UUkillfread (result);
+      if ((result = (fileread *) malloc (sizeof (fileread))) == NULL) {
+    *errcode = UURET_NOMEM;
+    sstate.isfolder = 0;
+    sstate.ismime   = 0;
+    UUkillheaders (&localenv);
+    return NULL;
+      }
+      memset (result, 0, sizeof (fileread));
+
+      if ((res = ScanData (datei, fname, errcode, NULL, 1, 1, result))==-1) {
+    /* oops, something went wrong */
+    sstate.isfolder = 0;
+    sstate.ismime   = 0;
+    UUkillfread   (result);
+    UUkillheaders (&localenv);
+    return NULL;
+      }
+      if (res == 1) {
+    /*
+     * new headers found
+     */
+    sstate.isfolder  = 1;
+    sstate.ismime    = 0;
+    sstate.mimestate = MS_HEADERS;
+      }
+      else {
+    sstate.isfolder  = 0;
+    sstate.ismime    = 0;
+      }
+    }
+
+    /*
+     * If this file has been nicely MIME so far, then be very suspicious
+     * if ScanData reports anything else. So do a double check, and if
+     * it doesn't hold up, handle as plain text instead.
+     */
+
+    if (strcmp (localenv.mimevers, "1.0") == 0 &&
+    _FP_stristr (localenv.ctype, "text") != NULL &&
+    sstate.ismime && sstate.mimestate == MS_SUBPART &&
+    !uu_desperate) {
+      if (result->uudet == UU_ENCODED && !(result->begin || result->end)) {
+    result->uudet = 0;
+      }
+    }
+
+    /*
+     * produce result
+     */
+
+    if (result->uudet == 0) {
+      result->uudet = PT_ENCODED; /* plain text */
+    }
+
+    if (localenv.fname) {
+      _FP_free (result->filename);
+      if ((result->filename = _FP_strdup (localenv.fname)) == NULL)
+    *errcode = UURET_NOMEM;
+    }
+    else if ((result->uudet==QP_ENCODED || result->uudet==PT_ENCODED) &&
+         result->filename==NULL && uu_handletext) {
+      sprintf (line, "%04d.txt", ++mimseqno);
+      if ((result->filename = _FP_strdup (line)) == NULL)
+    *errcode = UURET_NOMEM;
+    }
+    else {
+      /* assign a filename lateron */
+    }
+    if (result->mimetype) _FP_free (result->mimetype);
+    if (result->uudet) {
+      if (_FP_stristr (localenv.ctype, "text") != NULL &&
+      result->uudet != QP_ENCODED && result->uudet != PT_ENCODED)
+    result->mimetype = NULL; /* better don't set it */
+      else
+    result->mimetype = _FP_strdup (localenv.ctype);
+    }
+    if (result->origin) _FP_free  (result->origin);
+    result->origin  = _FP_strdup  (localenv.from);
+
+    if (result->subject) _FP_free (result->subject);
+    result->subject = _FP_strdup  (localenv.subject);
+
+    if (result->sfname == NULL)
+      if ((result->sfname = _FP_strdup (fname)) == NULL)
+    *errcode = UURET_NOMEM;
+
+    result->length = prevpos - result->startpos;
+    result->flags  = FL_SINGLE | FL_PROPER;
+    result->partno = 1;
+
+    if (result->mode == 0)
+      result->mode = 0644;
+
+    /*
+     * the other fields should already be set appropriately
+     */
+
+    if (*errcode == UURET_OK)
+      *errcode = UURET_CONT;
+
+    /*
+     * kill local envelope
+     */
+    UUkillheaders (&localenv);
+    
+    return result;
+  }
+
+  /*
+   * All right, so we're not in a Multipart message. Phew, took quite
+   * long to figure this out. But this might still be a MIME message
+   * body. And if it's a message/partial, we need more special handling
+   */
+
+  if (sstate.isfolder && sstate.ismime && sstate.mimestate == MS_BODY &&
+      _FP_stristr (sstate.envelope.ctype, "message") != NULL &&
+      _FP_stristr (sstate.envelope.ctype, "partial") != NULL) {
+
+    result->startpos = ftell (datei);
+
+    if (sstate.envelope.partno == 1) {
+      /* read local envelope */
+      UUkillheaders (&localenv);
+      memset (&localenv, 0, sizeof (headers));
+
+      /* skip over blank lines first */
+      prevpos = ftell (datei);
+      while (!feof (datei)) {
+    if (_FP_fgets (line, 255, datei) == NULL)
+      break;
+    line[255] = '\0';
+    if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+    if (!IsLineEmpty (line))
+      break;
+    prevpos = ftell (datei);
+      }
+      /* Next, read header. But what if there is no subheader? */
+      hcount = 0;
+      lcount = 0;
+      preheaders = prevpos;
+
+      while (!feof (datei) && !IsLineEmpty (line)) {
+    if (IsKnownHeader (line))
+      hcount++;
+    if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+    if (lcount > WAITHEADER && hcount == 0) {
+      fseek (datei, preheaders, SEEK_SET);
+      break;
+    }
+    ptr1 = ScanHeaderLine (datei, line);
+    if (ParseHeader (&localenv, ptr1) == NULL)
+      *errcode = UURET_NOMEM;
+
+    if (_FP_fgets (line, 255, datei) == NULL)
+      break;
+    line[255] = '\0';
+    lcount++;
+      }
+      prevpos = ftell (datei);
+      /*
+       * Examine local header. We're mostly interested in the Content-Type
+       * and the Content-Transfer-Encoding.
+       */
+      if (_FP_stristr (localenv.ctype, "multipart") != NULL) {
+    UUMessage (uuscan_id, __LINE__, UUMSG_WARNING,
+           uustring (S_MIME_PART_MULTI));
+      }
+      if (localenv.subject)
+    result->subject  = _FP_strdup (localenv.subject);
+      else
+    result->subject  = _FP_strdup (sstate.envelope.subject);
+
+      if (localenv.from)
+    result->origin   = _FP_strdup (localenv.from);
+      else
+    result->origin   = _FP_strdup (sstate.envelope.from);
+
+      if (localenv.ctype)
+    result->mimetype = _FP_strdup (localenv.ctype);
+      else
+    result->mimetype = _FP_strdup ("text/plain");
+
+      if (_FP_stristr (localenv.ctenc, "quoted-printable") != NULL)
+    result->uudet = QP_ENCODED;
+      else if (_FP_stristr (localenv.ctenc, "base64") != NULL)
+    result->uudet = B64ENCODED;
+      else if (_FP_stristr (localenv.ctenc, "x-uue") != NULL) {
+    result->uudet = UU_ENCODED;
+    result->begin = result->end = 1;
+      }
+      else if (_FP_stristr (localenv.ctenc, "x-yenc") != NULL) {
+    result->uudet = YENC_ENCODED;
+    result->begin = result->end = 1;
+      }
+      else if (_FP_stristr (localenv.ctenc, "7bit") != NULL ||
+           _FP_stristr (localenv.ctenc, "8bit") != NULL)
+    result->uudet = PT_ENCODED;
+      else if (_FP_stristr (localenv.ctype, "multipart") != NULL ||
+           _FP_stristr (localenv.ctype, "message")   != NULL)
+    result->uudet = PT_ENCODED;
+
+      /*
+       * If we're switched to MIME-only mode, handle as text
+       */
+
+      if (uu_more_mime >= 2 && !result->uudet) {
+    result->uudet = PT_ENCODED;
+      }
+    }
+    else {
+      memset (&localenv, 0, sizeof (headers));
+    }
+
+    /*
+     * If this is Quoted-Printable or Plain Text, just try looking
+     * for the next message header. If uu_fast_scanning, and the
+     * encoding is known, there's no need to look below. Otherwise,
+     * we check the type of encoding first.
+     * The encoding type is determined on the first part; in all
+     * others, we also don't read on.
+     * If we have a partial multipart message, scan for headers, but
+     * do not react on standard MIME headers, as they are probably
+     * from the subparts. However, we're stuck if there's an embedded
+     * message/rfc822 :-(
+     * If it is a "trivial" (non-embedded) message/rfc822, skip over
+     * the message header and then start looking for the next header.
+     */
+    if (uu_fast_scanning && (result->uudet!=0||sstate.envelope.partno!=1)) {
+      /* do nothing */
+      res = 0;
+    }
+    else if (result->uudet != 0) {
+      hcount = lcount = 0;
+      prevpos = ftell (datei);
+
+      if (_FP_stristr (localenv.ctype, "message") != NULL &&
+      _FP_stristr (localenv.ctype, "rfc822")  != NULL) {
+    /*
+     * skip over empty lines and local header
+     */
+    preheaders = ftell (datei);
+    while (!feof (datei)) {
+      if (_FP_fgets (line, 255, datei) == NULL)
+        break;
+      line[255] = '\0';
+      if (!IsLineEmpty (line)) {
+        break;
+      }
+    }
+
+    while (!feof (datei) && !IsLineEmpty (line)) { 
+      if (IsKnownHeader (line))
+        hcount++;
+      lcount++;
+      if (lcount > WAITHEADER && hcount < hlcount.afternl)
+        break;
+
+      if (_FP_fgets (line, 255, datei) == NULL)
+        break;
+      line[255] = '\0';
+    }
+    if (hcount < hlcount.afternl)
+      fseek (datei, preheaders, SEEK_SET);
+    hcount = lcount = 0;
+      }
+
+      /*
+       * look for next header
+       */
+
+      while (!feof (datei)) {
+    if (_FP_fgets (line, 255, datei) == NULL)
+      break;
+    if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+    if (ferror (datei))
+      break;
+    line[255] = '\0';
+
+    if ((vflag = IsKnownHeader (line))) {
+      (void) ScanHeaderLine (datei, line);
+
+      if (result->uudet != PT_ENCODED || vflag == 1) {
+        if (hcount == 0)
+          preheaders = prevpos;
+        hcount++;
+        lcount++;
+        if (hcount >= hlcount.restart) {
+          /*
+           * Hey, a new header starts here
+           */
+          fseek (datei, preheaders, SEEK_SET);
+          prevpos = preheaders;
+          break;
+        }
+      }
+    }
+    else if (lcount > WAITHEADER) {
+      hcount = 0;
+      lcount = 0;
+    }
+    else if (hcount) {
+      lcount++;
+    }
+    prevpos = ftell (datei);
+      }
+      res = 1;
+    }
+    else {
+      /*
+       * Otherwise, let's see what we can find ourself. No
+       * boundary (NULL) but MIME, and respect new headers.
+       */
+      if ((res = ScanData (datei, fname, errcode, NULL, 1, 1, result)) == -1) {
+    /* oops, something went wrong */
+    sstate.isfolder = 0;
+    sstate.ismime   = 0;
+    UUkillfread   (result);
+    UUkillheaders (&localenv);
+    return NULL;
+      }
+      if (result->uudet == 0 && uu_handletext)
+    result->uudet = PT_ENCODED;
+
+      prevpos = ftell (datei);
+    }
+    /*
+     * produce result
+     */
+    if (localenv.fname) {
+      _FP_free (result->filename);
+      if ((result->filename = _FP_strdup (localenv.fname)) == NULL)
+    *errcode = UURET_NOMEM;
+    }
+    else if (sstate.envelope.fname) {
+      _FP_free (result->filename);
+      if ((result->filename = _FP_strdup (sstate.envelope.fname)) == NULL)
+    *errcode = UURET_NOMEM;
+    }
+    else if ((result->uudet==QP_ENCODED || result->uudet==PT_ENCODED) &&
+         result->filename == NULL) {
+      sprintf (line, "%04d.txt", ++mimseqno);
+      if ((result->filename = _FP_strdup (line)) == NULL)
+    *errcode = UURET_NOMEM;
+    }
+    else {
+      /* assign a filename lateron */
+    }
+    if (result->subject == NULL) {
+      if (sstate.envelope.subject)
+    result->subject = _FP_strdup (sstate.envelope.subject);
+    }
+    result->partno = sstate.envelope.partno;
+    result->maxpno = sstate.envelope.numparts;
+    result->flags  = FL_PARTIAL | 
+      ((res==1 || uu_fast_scanning) ? FL_PROPER : 0) |
+    ((uu_fast_scanning) ? FL_TOEND : 0);
+    result->mimeid = _FP_strdup (sstate.envelope.mimeid);
+    if (result->partno == 1)
+      result->begin = 1;
+
+    if (uu_fast_scanning)
+      result->length = progress.fsize - result->startpos;
+    else
+      result->length = prevpos - result->startpos;
+
+    if (result->sfname == NULL)
+      result->sfname = _FP_strdup (fname);
+
+    if (result->mode == 0)
+      result->mode = 0644;
+
+    /*
+     * the other fields should already be set appropriately
+     */
+
+    if (res == 1) {
+      /*
+       * new headers found
+       */
+      sstate.isfolder  = 1;
+      sstate.ismime    = 0;
+      sstate.mimestate = MS_HEADERS;
+      
+      UUkillheaders (&sstate.envelope);
+      memset (&sstate.envelope, 0, sizeof (headers));
+    }
+    else {
+      /*
+       * otherwise, this can't be a mail folder
+       */
+      sstate.isfolder  = 0;
+      sstate.ismime    = 0;
+    }
+    /*
+     * kill local envelope
+     */
+    UUkillheaders (&localenv);
+    return result;
+  }
+
+  /*
+   * If this is a MIME body, honor a Content-Type different than
+   * text/plain or a proper Content-Transfer-Encoding.
+   * We also go in here if we have an assigned filename - this means
+   * that we've had a Content-Disposition field, and we should probably
+   * decode a plain-text segment with a filename.
+   */
+
+  if (sstate.isfolder && sstate.ismime &&
+      sstate.mimestate == MS_BODY &&
+      (_FP_stristr (sstate.envelope.ctenc, "quoted-printable") != NULL ||
+       _FP_stristr (sstate.envelope.ctenc, "base64")           != NULL ||
+       _FP_stristr (sstate.envelope.ctenc, "x-uue")            != NULL ||
+       _FP_stristr (sstate.envelope.ctenc, "x-yenc")           != NULL ||
+       _FP_stristr (sstate.envelope.ctype, "message")          != NULL ||
+       sstate.envelope.fname != NULL)) {
+
+    if (sstate.envelope.subject)
+      result->subject = _FP_strdup (sstate.envelope.subject);
+    if (sstate.envelope.from)
+      result->origin  = _FP_strdup (sstate.envelope.from);
+
+    if (sstate.envelope.ctype)
+      result->mimetype = _FP_strdup (sstate.envelope.ctype);
+    else
+      result->mimetype = _FP_strdup ("text/plain");
+
+    if (_FP_stristr (sstate.envelope.ctenc, "quoted-printable") != NULL)
+      result->uudet = QP_ENCODED;
+    else if (_FP_stristr (sstate.envelope.ctenc, "base64") != NULL)
+      result->uudet = B64ENCODED;
+    else if (_FP_stristr (sstate.envelope.ctenc, "x-uue") != NULL) {
+      result->uudet = UU_ENCODED;
+      result->begin = result->end = 1;
+    }
+    else if (_FP_stristr (sstate.envelope.ctenc, "x-yenc") != NULL) {
+      result->uudet = YENC_ENCODED;
+    }
+    else if (_FP_stristr (sstate.envelope.ctenc, "7bit") != NULL ||
+         _FP_stristr (sstate.envelope.ctenc, "8bit") != NULL)
+      result->uudet = PT_ENCODED;
+    else if (_FP_stristr (sstate.envelope.ctype, "multipart") != NULL ||
+         _FP_stristr (sstate.envelope.ctype, "message")   != NULL ||
+         sstate.envelope.fname != NULL)
+      result->uudet = PT_ENCODED;
+
+    /*
+     * If we're switched to MIME-only mode, handle as text
+     */
+
+    if (uu_more_mime >= 2 && !result->uudet) {
+      result->uudet = PT_ENCODED;
+    }
+
+    result->startpos = prevpos = ftell (datei);
+    
+    /*
+     * If this is Quoted-Printable or Plain Text, just try looking
+     * for the next message header. If uu_fast_scanning, we know
+     * there won't be more headers.
+     * If it is a "trivial" (non-embedded) message/rfc822, skip over
+     * the message header and then start looking for the next header.
+     */
+    if (result->uudet != 0 && uu_fast_scanning) {
+      /* do nothing */
+      res = 0;
+    }
+    else if (result->uudet != 0) {
+      hcount = lcount = 0;
+      prevpos = ftell (datei);
+
+      if (_FP_stristr (sstate.envelope.ctype, "message") != NULL &&
+      _FP_stristr (sstate.envelope.ctype, "rfc822")  != NULL) {
+    /*
+     * skip over empty lines and local header
+     */
+    preheaders = ftell (datei);
+    while (!feof (datei)) {
+      if (_FP_fgets (line, 255, datei) == NULL)
+        break;
+      line[255] = '\0';
+      if (!IsLineEmpty (line)) {
+        break;
+      }
+    }
+
+    while (!feof (datei) && !IsLineEmpty (line)) { 
+      if (IsKnownHeader (line))
+        hcount++;
+      lcount++;
+      if (lcount > WAITHEADER && hcount < hlcount.afternl)
+        break;
+
+      if (_FP_fgets (line, 255, datei) == NULL)
+        break;
+      line[255] = '\0';
+    }
+    if (hcount < hlcount.afternl)
+      fseek (datei, preheaders, SEEK_SET);
+    hcount = lcount = 0;
+      }
+
+      /*
+       * look for next header
+       */
+
+      while (!feof (datei)) {
+    if (_FP_fgets (line, 255, datei) == NULL)
+      break;
+    if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+    if (ferror (datei))
+      break;
+    line[255] = '\0';
+
+    if (IsKnownHeader (line)) {
+      (void) ScanHeaderLine (datei, line);
+      if (hcount == 0)
+        preheaders = prevpos;
+      hcount++;
+      lcount++;
+      if (hcount >= hlcount.restart) {
+        /*
+         * Hey, a new header starts here
+         */
+        fseek (datei, preheaders, SEEK_SET);
+        prevpos = preheaders;
+        break;
+      }
+    }
+    else if (lcount > WAITHEADER) {
+      hcount = 0;
+      lcount = 0;
+    }
+    else if (hcount) {
+      lcount++;
+    }
+    prevpos = ftell (datei);
+      }
+      res = 1;
+    }
+    else {
+      /*
+       * Otherwise, let's see what we can find ourself. No
+       * boundary (NULL) but MIME, and respect new headers.
+       */
+      if ((res = ScanData (datei, fname, errcode, NULL, 1, 1, result)) == -1) {
+    /* oops, something went wrong */
+    sstate.isfolder = 0;
+    sstate.ismime   = 0;
+    UUkillfread   (result);
+    return NULL;
+      }
+      if (result->uudet == 0 && uu_handletext) {
+    result->startpos = before;  /* display headers */
+    result->uudet = PT_ENCODED;
+      }
+
+      prevpos = ftell (datei);
+    }
+    /*
+     * produce result
+     */
+    if (sstate.envelope.fname) {
+      _FP_free (result->filename);
+      if ((result->filename = _FP_strdup (sstate.envelope.fname)) == NULL)
+    *errcode = UURET_NOMEM;
+    }
+    else if ((result->uudet==QP_ENCODED||result->uudet==PT_ENCODED) &&
+         result->filename == NULL) {
+      sprintf (line, "%04d.txt", ++mimseqno);
+      if ((result->filename = _FP_strdup (line)) == NULL)
+    *errcode = UURET_NOMEM;
+    }
+    else {
+      /* assign a filename lateron */
+    }
+    if (result->subject == NULL) {
+      if (sstate.envelope.subject)
+    result->subject = _FP_strdup (sstate.envelope.subject);
+    }
+    result->flags  = ((res==1||uu_fast_scanning)?FL_PROPER:0) |
+      ((uu_fast_scanning) ? FL_TOEND : 0);
+    result->mimeid = _FP_strdup (sstate.envelope.mimeid);
+
+    if (uu_fast_scanning)
+      result->length = progress.fsize - result->startpos;
+    else
+      result->length = prevpos - result->startpos;
+
+    if (result->sfname == NULL)
+      result->sfname = _FP_strdup (fname);
+
+    if (result->mode == 0)
+      result->mode = 0644;
+
+    /*
+     * the other fields should already be set appropriately
+     */
+
+    if (res == 1) {
+      /*
+       * new headers found
+       */
+      sstate.isfolder  = 1;
+      sstate.ismime    = 0;
+      sstate.mimestate = MS_HEADERS;
+
+      UUkillheaders (&sstate.envelope);
+      memset (&sstate.envelope, 0, sizeof (headers));
+    }
+    else {
+      /*
+       * otherwise, this can't be a mail folder
+       */
+      sstate.isfolder  = 0;
+      sstate.ismime    = 0;
+    }
+
+    return result;
+  }
+
+  /*
+   * Some files have reduced headers, and what should be a multipart
+   * message is missing the proper Content-Type. If the first thing
+   * we find after a couple of empty lines is a boundary, try it!
+   * But make sure that this is indeed intended as being a boundary.
+   *
+   * Only accept it if there was indeed no Content-Type header line
+   * and if the following line is a proper Content-Type header. BTW,
+   * we know that sstate.envelope.boundary is NULL, or we wouldn't
+   * be here!
+   */
+
+  if ((sstate.envelope.ctype == NULL ||
+       _FP_stristr (sstate.envelope.ctype, "multipart") != NULL) &&
+      !uu_more_mime) {
+    prevpos = ftell (datei);
+    while (!feof (datei)) {
+      if (_FP_fgets (line, 255, datei) == NULL) {
+    line[0] = '\0';
+    break;
+      }
+      if (UUBUSYPOLL(ftell(datei),progress.fsize)) SPCANCEL();
+      if (!IsLineEmpty (line))
+    break;
+    }
+    if (line[0] == '-' && line[1] == '-' &&
+    !IsLineEmpty (line+2) && !feof (datei)) {
+      ptr1 = _FP_strrstr (line+2, "--");
+      ptr2 = ScanHeaderLine (datei, NULL);
+      if ((ptr1 == NULL || (*(ptr1+2) != '\012' && *(ptr1+2) != '\015')) &&
+      ptr2 && _FP_strnicmp (ptr2, "Content-", 8) == 0) {
+    /*
+     * hmm, okay, let's do it!
+     */
+    sstate.isfolder  = 1;
+    sstate.ismime    = 1;
+    sstate.mimestate = MS_PREAMBLE;
+    /*
+     * get boundary
+     */
+    ptr1 = line+2;
+    while (*ptr1 && !isspace(*ptr1))
+      ptr1++;
+    *ptr1 = '\0';
+
+    sstate.envelope.mimevers = _FP_strdup ("1.0");
+    sstate.envelope.boundary = _FP_strdup (line+2);
+    
+    /*
+     * need restart
+     */
+    
+    fseek (datei, prevpos, SEEK_SET);
+    
+    _FP_free (result);
+    return NULL;
+      }
+    }
+    fseek (datei, prevpos, SEEK_SET);
+  }
+
+  /*
+   * Hmm, we're not in a ''special'' state, so it's more or less
+   * Freestyle time. Anyway, if this seems to be a Mime message,
+   * don't allow the minimal Base64 handling.
+   */
+
+  if (sstate.envelope.subject)
+    result->subject = _FP_strdup (sstate.envelope.subject);
+  if (sstate.envelope.from)
+    result->origin  = _FP_strdup (sstate.envelope.from);
+
+  if (sstate.envelope.ctype)
+    result->mimetype = _FP_strdup (sstate.envelope.ctype);
+  
+  if ((res=ScanData (datei, fname, errcode, NULL, 
+             sstate.ismime, 1, result))==-1) {
+    /* oops, something went wrong */
+    sstate.isfolder = 0;
+    sstate.ismime   = 0;
+    UUkillfread   (result);
+    return NULL;
+  }
+
+  /*
+   * produce result
+   */
+
+  if (result->uudet == 0 && uu_handletext) {
+    result->startpos = before;  /* display headers */
+    result->uudet  = PT_ENCODED;
+    result->partno = 1;
+  }
+
+  if (result->uudet == YENC_ENCODED && result->filename != NULL) {
+    /*
+     * prevent replacing the filename found on the =ybegin line
+     */
+  }
+  else if (sstate.envelope.fname) {
+    _FP_free (result->filename);
+    if ((result->filename = _FP_strdup (sstate.envelope.fname)) == NULL)
+      *errcode = UURET_NOMEM;
+  }
+  else if ((result->uudet==QP_ENCODED||result->uudet==PT_ENCODED) &&
+       result->filename == NULL) {
+    sprintf (line, "%04d.txt", ++mimseqno);
+    if ((result->filename = _FP_strdup (line)) == NULL)
+      *errcode = UURET_NOMEM;
+  }
+  else {
+    /* assign a filename lateron */
+  }
+
+  if (result->subject == NULL) {
+    if (sstate.envelope.subject)
+      result->subject = _FP_strdup (sstate.envelope.subject);
+  }
+
+  result->flags  = (result->uudet==PT_ENCODED)?FL_SINGLE:0;
+  result->mimeid = _FP_strdup (sstate.envelope.mimeid);
+  result->length = ftell (datei) - result->startpos;
+
+  if (result->mode == 0)
+    result->mode = 0644;
+
+  if (result->sfname == NULL)
+    result->sfname = _FP_strdup (fname);
+
+  if (res == 1) {
+    /*
+     * new headers found
+     */
+    sstate.isfolder  = 1;
+    sstate.ismime    = 0;
+    sstate.mimestate = MS_HEADERS;
+
+    UUkillheaders (&sstate.envelope);
+    memset (&sstate.envelope, 0, sizeof (headers));
+  }
+  else {
+    /*
+     * otherwise, this can't be a mail folder
+     */
+    sstate.isfolder  = 0;
+    sstate.ismime    = 0;
+  }
+
+  return result;
+
+  /*
+   * Emergency handling. Set errcode before jumping here.
+   */
+ ScanPartEmergency:
+  UUkillfread   (result);
+  UUkillheaders (&localenv);
+
+  while (mssdepth) {
+    mssdepth--;
+    UUkillheaders (&(multistack[mssdepth].envelope));
+    _FP_free (multistack[mssdepth].source);
+  }
+
+  return NULL;
+}
+
diff --git a/goldlib/uulib/uustring.cpp b/goldlib/uulib/uustring.cpp
new file mode 100644
index 0000000..c9f8e44
--- /dev/null
+++ b/goldlib/uulib/uustring.cpp
@@ -0,0 +1,168 @@
+/*
+ * This file is part of uudeview, the simple and friendly multi-part multi-
+ * file uudecoder  program  (c) 1994-2001 by Frank Pilhofer. The author may
+ * be contacted at fp@fpx.de
+ *
+ * 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 2 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.
+ */
+
+/*
+ * Strings used in the library for easier translation etc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef SYSTEM_WINDLL
+#include 
+#endif
+#ifdef SYSTEM_OS2
+#include 
+#endif
+
+#include 
+
+#ifdef STDC_HEADERS
+#include 
+#include 
+#endif
+#ifdef HAVE_MALLOC_H
+#include 
+#endif
+#ifdef HAVE_UNISTD_H
+#include 
+#endif
+#ifdef HAVE_MEMORY_H
+#include 
+#endif
+
+#include 
+#include 
+#include 
+
+char * uustring_id = "$Id$";
+
+typedef struct {
+  int code;
+  char * msg;
+} stringmap;
+
+/*
+ * Map of messages. This table is not exported, the messages must
+ * be retrieved via the below uustring() function.
+ */
+
+static stringmap messages[] = {
+  /* I/O related errors/messages. Last parameter is strerror() */
+  { S_NOT_OPEN_SOURCE,  "Could not open source file %s: %s" },
+  { S_NOT_OPEN_TARGET,  "Could not open target file %s for writing: %s" },
+  { S_NOT_OPEN_FILE,    "Could not open file %s: %s" },
+  { S_NOT_STAT_FILE,    "Could not stat file %s: %s" },
+  { S_SOURCE_READ_ERR,  "Read error on source file: %s" },
+  { S_READ_ERROR,       "Error reading from %s: %s" },
+  { S_IO_ERR_TARGET,    "I/O error on target file %s: %s" },
+  { S_WR_ERR_TARGET,    "Write error on target file %s: %s" },
+  { S_WR_ERR_TEMP,      "Write error on temp file: %s" },
+  { S_TMP_NOT_REMOVED,  "Could not remove temp file %s: %s (ignored)" },
+
+  /* some other problems */
+  { S_OUT_OF_MEMORY,    "Out of memory allocating %d bytes" },
+  { S_TARGET_EXISTS,    "Target file %s exists and overwriting is not allowed" },
+  { S_NOT_RENAME,       "Could not change name of %s to %s" },
+  { S_ERR_ENCODING,     "Error while encoding %s: %s" },
+  { S_STAT_ONE_PART,    "Could not stat input, encoding to one part only" },
+  { S_PARM_CHECK,       "Parameter check failed in %s" },
+  { S_SHORT_BINHEX,     "BinHex encoded file %s ended prematurely (%ld bytes left)" },
+  { S_DECODE_CANCEL,    "Decode operation canceled" },
+  { S_ENCODE_CANCEL,    "Encode operation canceled" },
+  { S_SCAN_CANCEL,      "Scanning canceled" },
+  { S_SIZE_MISMATCH,    "%s: Decoded size (%ld) does not match expected size (%ld)" },
+  { S_PSIZE_MISMATCH,   "%s part %d: Decoded size (%ld) does not match expected size (%ld)" },
+  { S_CRC_MISMATCH,     "CRC32 mismatch in %s. Decoded file probably corrupt." },
+  { S_PCRC_MISMATCH,    "PCRC32 mismatch in %s part %d. Decoded file probably corrupt." },
+
+  /* informational messages */
+  { S_LOADED_PART,      "Loaded from %s: '%s' (%s): %s part %d %s %s %s" },
+  { S_NO_DATA_FOUND,    "No encoded data found in %s" },
+  { S_NO_BIN_FILE,      "Oops, could not find decoded file?" },
+  { S_STRIPPED_SETUID,  "Stripped setuid/setgid bits from target file %s mode %d" },
+  { S_DATA_SUSPICIOUS,  "Data looks suspicious. Decoded file might be corrupt." },
+  { S_NO_TEMP_NAME,     "Could not get name for temporary file" },
+  { S_BINHEX_SIZES,     "BinHex file: data/resource fork sizes %ld/%ld" },
+  { S_BINHEX_BOTH,      "BinHex file: both forks non-empty, decoding data fork" },
+  { S_SMERGE_MERGED,    "Parts of '%s' merged with parts of '%s' (%d)" },
+  
+  /* MIME-related messages */
+  { S_MIME_NO_BOUNDARY, "Multipart message without boundary ignored" },
+  { S_MIME_B_NOT_FOUND, "Boundary expected on Multipart message but found EOF" },
+  { S_MIME_MULTI_DEPTH, "Multipart message nested too deep" },
+  { S_MIME_PART_MULTI,  "Handling partial multipart message as plain text" },
+
+  { 0, "" }
+};
+
+/*
+ * description of the return values UURET_*
+ */
+
+char *uuretcodes[] = {
+  "OK",
+  "File I/O Error",
+  "Not Enough Memory",
+  "Illegal Value",
+  "No Data found",
+  "Unexpected End of File",
+  "Unsupported function",
+  "File exists",
+  "Continue -- no error",   /* only to be seen internally */
+  "Operation Canceled"
+};
+
+/*
+ * Names of encoding types
+ */
+
+char *codenames[8] = {
+  "", "UUdata", "Base64", "XXdata", "Binhex", "Text", "Text", "yEnc"
+};
+
+/*
+ * Message types
+ */
+
+char *msgnames[6] = {
+  "", "Note: ", "Warning: ", "ERROR: ", "FATAL ERROR: ", "PANIC: "
+};
+
+/*
+ * Retrieve one of the messages. We never return NULL
+ * but instead escape to "oops".
+ */
+
+char *
+uustring (int codeno)
+{
+  static char * faileddef = "oops";
+  stringmap *ptr = messages;
+
+  while (ptr->code) {
+    if (ptr->code == codeno)
+      return ptr->msg;
+    ptr++;
+  }
+
+  UUMessage (uustring_id, __LINE__, UUMSG_ERROR,
+         "Could not retrieve string no %d",
+         codeno);
+
+  return faileddef;
+}
diff --git a/goldlib/uulib/uuutil.cpp b/goldlib/uulib/uuutil.cpp
new file mode 100644
index 0000000..0de157f
--- /dev/null
+++ b/goldlib/uulib/uuutil.cpp
@@ -0,0 +1,481 @@
+/*
+ * This file is part of uudeview, the simple and friendly multi-part multi-
+ * file uudecoder  program  (c) 1994-2001 by Frank Pilhofer. The author may
+ * be contacted at fp@fpx.de
+ *
+ * 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 2 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.
+ */
+
+/*
+ * certain utilitarian functions that didn't fit anywhere else
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef SYSTEM_WINDLL
+#include 
+#endif
+#ifdef SYSTEM_OS2
+#include 
+#endif
+
+#include 
+
+#ifdef STDC_HEADERS
+#include 
+#include 
+#endif
+#ifdef HAVE_MALLOC_H
+#include 
+#endif
+#ifdef HAVE_UNISTD_H
+#include 
+#endif
+#ifdef HAVE_IO_H
+#include 
+#endif
+#ifdef HAVE_MEMORY_H
+#include 
+#endif
+#ifdef HAVE_ERRNO_H
+#include 
+#endif
+
+#include 
+#include 
+#include 
+#include 
+
+char * uuutil_id = "$Id$";
+
+/*
+ * Parts with different known extensions will not be merged by SPMS.
+ * if first character is '@', it is synonymous to the previous one.
+ */
+
+static char *knownexts[] = {
+  "mpg", "@mpeg", "avi", "mov",
+  "gif", "jpg", "@jpeg", "tif",
+  "voc", "wav", "@wave", "au",
+  "zip", "arj", "tar",
+  NULL
+};
+
+/*
+ * forward declarations of local functions
+ */
+
+static int  UUSMPKnownExt     (char *filename);
+static uulist * UU_smparts_r  (uulist *, int);
+
+/*
+ * mallocable areas
+ */
+
+char *uuutil_bhwtmp;
+
+/*
+ * free some memory
+ **/
+
+void
+UUkillfread (fileread *data)
+{
+  if (data != NULL) {
+    _FP_free (data->subject);
+    _FP_free (data->filename);
+    _FP_free (data->origin);
+    _FP_free (data->mimeid);
+    _FP_free (data->mimetype);
+    _FP_free (data->sfname);
+    _FP_free (data);
+  }
+}
+
+void
+UUkillfile (uufile *data)
+{
+  uufile *next;
+
+  while (data) {
+    _FP_free    (data->filename);
+    _FP_free    (data->subfname);
+    _FP_free    (data->mimeid);
+    _FP_free    (data->mimetype);
+    UUkillfread (data->data);
+
+    next = data->NEXT;
+    _FP_free  (data);
+    data = next;
+  }
+}
+
+void
+UUkilllist (uulist *data)
+{
+  uulist *next;
+
+  while (data) {
+    if (data->binfile != NULL)
+      if (unlink (data->binfile))
+    UUMessage (uuutil_id, __LINE__, UUMSG_WARNING,
+           uustring (S_TMP_NOT_REMOVED),
+           data->binfile, strerror (errno));
+
+    _FP_free   (data->filename);
+    _FP_free   (data->subfname);
+    _FP_free   (data->mimeid);
+    _FP_free   (data->mimetype);
+    _FP_free   (data->binfile);
+    UUkillfile (data->thisfile);
+    _FP_free   (data->haveparts);
+    _FP_free   (data->misparts);
+
+    next = data->NEXT;
+    _FP_free (data);
+    data = next;
+  }
+}
+
+/*
+ * this kill function is an exception in that it doesn't kill data itself
+ */
+
+void
+UUkillheaders (headers *data)
+{
+  if (data != NULL) {
+    _FP_free (data->from);
+    _FP_free (data->subject);
+    _FP_free (data->rcpt);
+    _FP_free (data->date);
+    _FP_free (data->mimevers);
+    _FP_free (data->ctype);
+    _FP_free (data->ctenc);
+    _FP_free (data->fname);
+    _FP_free (data->boundary);
+    _FP_free (data->mimeid);
+    memset   (data, 0, sizeof (headers));
+  }
+}
+
+/*
+ * checks for various well-known extensions. if two parts have different
+ * known extensions, we won't merge them.
+ */
+
+static int
+UUSMPKnownExt (char *filename)
+{
+  char **eiter = knownexts, *ptr=_FP_strrchr(filename, '.');
+  int count=0, where=0;
+
+  if (ptr == NULL)
+    return -1;
+  ptr++;
+
+  while (*eiter) {
+    if (_FP_stricmp (ptr, (**eiter=='@')?*eiter+1:*eiter) == 0)
+      return where;
+    else
+      eiter++;
+
+    if (*eiter == NULL)
+      break;
+
+    if (**eiter=='@')
+      count++;
+    else
+      where = ++count;
+  }
+  return -1;
+}
+
+/*
+ * de-compress a binhex RLE stream
+ * the data read from in is uncompressed, and at most maxcount bytes
+ * (or octets, as they say) are copied to out. Because an uncompression
+ * might not be completed because of this maximum number of bytes. There-
+ * for, the leftover character and repetition count is saved. If a marker
+ * has been read but not the repetition count, *rpc is set to -256.
+ *
+ * the function returns the number of bytes eaten from in. If opc is not
+ * NULL, the total number of characters stored in out is saved there
+ *
+ * with repetition counts, remember that we've already transferred *one*
+ * occurence
+ */
+
+int
+UUbhdecomp (char *in, char *out, char *last, int *rpc, 
+        size_t inc, size_t max, size_t *opc)
+{
+  size_t count, used=0, dummy;
+  char marker = '\220' /* '\x90' */;
+
+  if (opc == NULL)
+    opc = &dummy;
+  else
+    *opc = 0;
+
+  if (*rpc == -256) {
+    if (inc == 0)
+      return 0;
+    *rpc = (int) (unsigned char) *in++; used++;
+
+    if (*rpc == 0) {
+      *last = *out++ = marker;
+      max--; *opc+=1;
+    }
+    else
+      *rpc-=1;
+  }
+
+  if (*rpc) {
+    count = (max > (size_t) *rpc) ? (size_t) *rpc : max;
+
+    memset (out, *last, count);
+
+    out  += count;
+    *opc += count;
+    max  -= count;
+    *rpc -= count;
+  }
+
+  while (used < inc && max) {
+    if (*in == marker) {
+      used++; in++;
+      if (used == inc) {
+    *rpc = -256;
+    return used;
+      }
+      *rpc = (int) (unsigned char) *in++; used++;
+
+      if (*rpc == 0) {
+    *last = *out++ = marker;
+    max--; *opc+=1;
+    continue;
+      }
+      else
+    *rpc -= 1;
+
+      count = (max > (size_t) *rpc) ? (size_t) *rpc : max;
+      memset (out, *last, count);
+
+      out  += count;
+      *opc += count;
+      max  -= count;
+      *rpc -= count;
+    }
+    else {
+      *last = *out++ = *in++;
+      used++; *opc+=1; max--;
+    }
+  }
+
+  return used;
+}
+
+/*
+ * write to binhex file
+ */
+
+size_t
+UUbhwrite (char *ptr, size_t sel, size_t nel, FILE *file)
+{
+  char *tmpstring=uuutil_bhwtmp;
+  static int rpc = 0;
+  static char lc;
+  int count, tc=0;
+  size_t opc;
+
+  if (ptr == NULL) { /* init */
+    rpc = 0;
+    return 0;
+  }
+
+  while (nel || (rpc != 0 && rpc != -256)) {
+    count = UUbhdecomp (ptr, tmpstring, &lc, &rpc,
+            nel, 256, &opc);
+    if (fwrite (tmpstring, 1, opc, file) != opc)
+      return 0;
+    if (ferror (file))
+      return 0;
+    nel -= count;
+    ptr += count;
+    tc  += count;
+  }
+
+  return tc;
+}
+
+static uulist *
+UU_smparts_r (uulist *addit, int pass)
+{
+  uulist *iter = UUGlobalFileList;
+  uufile *fiter, *dest, *temp;
+  int count, flag, a, b;
+
+  while (iter) {
+    if ((iter->state & UUFILE_OK) || iter->uudet == 0) {
+      iter = iter->NEXT;
+      continue;
+    }
+    if (iter == addit) {
+      iter = iter->NEXT;
+      continue;
+    }
+    if ((iter->begin && addit->begin) || (iter->end && addit->end) ||
+    (iter->uudet != addit->uudet)) {
+      iter = iter->NEXT;
+      continue;
+    }
+    if ((a = UUSMPKnownExt (addit->subfname)) != -1 &&
+        (b = UUSMPKnownExt (iter->subfname))  != -1)
+      if (a != b) {
+        iter = iter->NEXT;
+        continue;
+      }
+
+    flag  = count = 0;
+    fiter = iter->thisfile;
+    temp  = addit->thisfile;
+    dest  = NULL;
+
+    while (temp) {
+      if (!(temp->data->uudet)) {
+    temp = temp->NEXT;
+    continue;
+      }
+
+      while (fiter && fiter->partno < temp->partno) {
+        dest  = fiter;
+        fiter = fiter->NEXT;
+      }
+      if (fiter && fiter->partno == temp->partno) {
+        flag = 0;
+        break;
+      }
+      else {
+    flag   = 1;
+        count += ((dest)  ? temp->partno - dest->partno - 1 : 0) +
+                 ((fiter) ? fiter->partno - temp->partno - 1 : 0);
+      }
+
+      temp = temp->NEXT;
+    }
+    if (flag == 0 ||
+        (pass == 0 && count > 0) ||
+        (pass == 1 && count > 5)) {
+      iter = iter->NEXT;
+      continue;
+    }
+
+    dest  = iter->thisfile;
+    fiter = addit->thisfile;
+
+    if (iter->filename == NULL && addit->filename != NULL)
+      iter->filename = _FP_strdup (addit->filename);
+
+    if (addit->begin) iter->begin = 1;
+    if (addit->end)   iter->end   = 1;
+
+    if (addit->mode != 0 && iter->mode == 0)
+      iter->mode = addit->mode;
+
+    while (fiter) {
+      flag = 0;
+
+      if (fiter->partno == iter->thisfile->partno ||
+      (dest->NEXT != NULL && fiter->partno == dest->NEXT->partno)) {
+    temp           = fiter->NEXT;
+    fiter->NEXT    = NULL;
+
+    UUkillfile (fiter);
+
+    addit->thisfile= temp;
+    fiter          = temp;
+    continue;
+      }
+      if (fiter->partno < iter->thisfile->partno) {
+    temp           = fiter->NEXT;
+    fiter->NEXT    = iter->thisfile;
+    iter->thisfile = fiter;
+    dest           = fiter;
+    addit->thisfile= temp;
+    fiter          = temp;
+      }
+      else if (dest->NEXT == NULL || fiter->partno < dest->NEXT->partno) {
+    temp           = fiter->NEXT;
+    fiter->NEXT    = dest->NEXT;
+    dest->NEXT     = fiter;
+    addit->thisfile= temp;
+    fiter          = temp;
+      }
+      else {
+    dest = dest->NEXT;
+      }
+    }
+    break;
+  }
+  return iter;
+}
+
+int UUEXPORT
+UUSmerge (int pass)
+{
+  uulist *iter = UUGlobalFileList, *last=NULL, *res, *temp;
+  int flag = 0;
+
+  while (iter) {
+    if ((iter->state & UUFILE_OK) || iter->uudet == 0) {
+      last = iter;
+      iter = iter->NEXT;
+      continue;
+    }
+    if ((res = UU_smparts_r (iter, pass)) != NULL) {
+      UUMessage (uuutil_id, __LINE__, UUMSG_MESSAGE,
+         uustring (S_SMERGE_MERGED),
+         (iter->subfname) ? iter->subfname : "",
+         (res->subfname)  ? res->subfname  : "", pass);
+ 
+      temp       = iter->NEXT;
+      iter->NEXT = NULL;
+      UUkilllist (iter);
+
+      flag++;
+
+      if (last == NULL) {
+    UUGlobalFileList = temp;
+    iter             = temp;
+      }
+      else {
+    last->NEXT       = temp;
+    iter             = temp;
+      }
+
+      continue;
+    }
+    last = iter;
+    iter = iter->NEXT;
+  }
+
+  /*
+   * check again
+   */
+
+  UUCheckGlobalList ();
+
+  return flag;
+}
+