Logo Search packages:      
Sourcecode: yydecode version File versions  Download package

yydecode.c

/* yydecode utility -- http://nerv.cx/liyang/

   Copyright (C) 1994, 1995 Free Software Foundation, Inc.

   This product 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, or (at your option)
   any later version.

   This product is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this product; see the file COPYING.  If not, write to
   the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.  */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "system.h"
#include "getopt.h"
#include "crc32.h"
#include "yydecode.h"
#include "evilchar.h"
#include "base64tab.h"

/* entry point */
int main(int argc, char * const *argv);

/* static prototypes */
static u_int32_t atocrc32(char *str);
static int decoded_file_open(struct decoded_file *f);
/*static struct decoded_file *decoded_file_lookup(struct decoded_file **list, const char *filename, const char *forced_outname);
static int read_yenc(const char *inname, const char *forced_outname, struct decoded_file **decoded_list, int whinge);
static int read_stduu(const char *inname, struct decoded_file *f);
static int read_base64(const char *inname, struct decoded_file *f);*/
static int decode(const char *inname, const char *forced_outname, struct decoded_file **decoded_list);
static void usage(int status);

/* extern variables */
const char *program_name = NULL;

/* command-line flags */
static const char *opt_directory = NULL;
static int opt_evil_filename = 0;
static int opt_clobber_filename = 0;
static int opt_force_overwrite = 0;
static int opt_write_broken = 0;
static int opt_broken_encoder = 0;
static int opt_remove_broken = 0;
static int opt_large_parts = 0;
static int opt_verbose = 0;

/* code starts here */

static u_int32_t atocrc32(char *str)
{
      return (u_int32_t)strtoul((char *)str, NULL, 16);
}

static char *fgets_(char *s, int size, FILE *stream)
{
      int i;
      int c = '\0', d;
      char *p = s;

      if(size <= 1)
            return NULL;

      c = fgetc(stream);
      if(c == EOF)
            return NULL;
      ungetc(c, stream);

      for(i = size; i > 2; i--)
      {
            c = fgetc(stream);
            if((c == '\x0a') || (c == '\x0d'))
            {
                  /* ick. Still, don't leave any surprises. */
                  *p++ = '\n';
                  break;
            }
            if(c == EOF)
                  break;
            *p++ = (char)c;
      }
      *p++ = '\0';

      /* We could do with all the empty lines being eaten up, but, as above... */
      do
      {
            d = fgetc(stream);
            if(d == EOF)
                  return s;
      }
      while(((d == '\x0a') || (d == '\x0d')) && (d != c));
      ungetc(d, stream);

      return s;
}

static int decoded_file_open(struct decoded_file *f)
{
      int fd;

      if(f->handle)
            return EXIT_SUCCESS;

      if(!strcmp(f->outname, "-"))
      {
            f->handle = stdout;
            return EXIT_SUCCESS;
      }

      if(f->previously_existed)
            return EXIT_FAILURE;

      /* is_seekable (despite the misleading name) is always zero on the
       * first run. Make sure here that we're not trying to re-open a
       * seekable file. */
      if(!f->is_seekable)
      {
            /* blame wget for the name */
            int clobber_num;
            char *clobber_part;

            /* If it's writable, we'll say it exists. (because that's the
             * only case when we can overwrite it, and that's all we care
             * about.) Otherwise, we pretend it doesn't, and let open()
             * below whinge instead. */
            f->previously_existed = access(f->outname, W_OK) ? 0 : 1;

            for(clobber_num = 1, clobber_part = f->outname + strlen(f->outname);
                  f->previously_existed && !opt_force_overwrite && opt_clobber_filename;
                  clobber_num++)
            {
                  if(opt_verbose > 1)
                        error(0, 0, _("%s: Output exists; trying .%i"), f->outname, clobber_num);

                  sprintf(clobber_part, ".%i", clobber_num);
                  f->previously_existed = access(f->outname, W_OK) ? 0 : 1;
            }

            fd = open(f->outname, O_WRONLY | O_CREAT
            /* Ask for exclusiveness if the file existed before we
             * started, and we didn't force overwriting. In a way, we
             * already know that this is going to fail, but we're lazy,
             * so we let open() generate the error message for us. */
                        | (f->previously_existed && !opt_force_overwrite
                              ? O_EXCL : 0)
            /* Note that we truncate (overwriting in the process) a file
             * only if we're not writing broken parts. */
                        | (f->previously_existed && opt_force_overwrite && !opt_write_broken
                              ? O_TRUNC : 0),
                  f->mode);
            if(fd >= 0)
                  close(fd);
            else
            {
                  f->previously_existed = 1;
                  return EXIT_FAILURE;
            }
      }

      /* w+ truncates the file; if that's what we had wanted, then open()
       * above would have already done it for us. */
      if(!(f->handle = fopen(f->outname, "r+b")))
      {
            f->previously_existed = 1;
            return EXIT_FAILURE;
      }

      /* If it didn't fail above, then we 0wnz this file now <g> */
      f->previously_existed = 0;

      f->is_seekable = ftell(f->handle) >= 0;

      return EXIT_SUCCESS;
}

static struct decoded_file * decoded_file_lookup(struct decoded_file **list,
      const char *filename, int mode, const char *forced_outname)
{
      struct decoded_file **last = list;
      struct decoded_file *p = *list;
      char *pf;

      for(; p; p = p->next)
      {
            if(!strcmp(p->filename, filename))
                  return p;
            last = &p->next;
      }

      /* Requested file doesn't exist in list -- set one up. */
      p = malloc(sizeof(struct decoded_file));
      memset(p, 0, sizeof(struct decoded_file));

      /* Not sure why we bother checking /dev/stdout -- uudecode does. */
      if(forced_outname && !strcmp(forced_outname, "/dev/stdout"))
            forced_outname = "-";

      p->filename = strdup(filename);
      p->mode = mode;
      {
            const char *fn;
            fn = forced_outname ? forced_outname : filename;
            p->outname = malloc(strlen(fn) + 32);     /* leave some space for clobbering the file name */
            strcpy(p->outname, fn);
      }

      /* don't apply evil char checking for forced output filenames */
      if(!forced_outname)
      {
            for(pf = p->outname; *pf; pf++)
            {
                  if((int)char_evilness[*pf & 0xff] > opt_evil_filename)
                        *pf = '_';
            }
      }

      crc32_init_ctx(&p->crc32_context);

      return *last = p;
}

/* Various macros, although only mostly used by read_yenc() */
#define ASSERT(expr)                                        \
      if(!(expr))                                     \
      {                                               \
            error(0, 0, _("%s: Assertion `%s' failed -- output may be corrupt"),    \
                  c->inname, #expr);                        \
            return EXIT_FAILURE;                            \
      }

#define B64DECODE(c) base64_tab[(size_t)(c)]

#define UUDECODE(c)     (((c) - ' ') & 077)

#define SEARCH_MARKER(marker)                   \
      if(!fgets_(buf, BUFSIZ, stdin))                 \
      {                                               \
            error(0, 0, _("%s: %s:%i: No `%s' marker found"),     \
                  c->inname, f->outname, c->part, (marker));      \
            return EXIT_FAILURE;                            \
      }                                               \
      if(!strncmp(buf, marker, lenof(marker)))              \
            break;

#define PARSE_TAG_OPTIONAL(tag, convertfn, assignto)              \
      if((p = strstr(buf, tag)))          \
            assignto = convertfn(p + lenof(tag))

#define PARSE_TAG(tag, convertfn, assignto)                       \
      PARSE_TAG_OPTIONAL(tag, convertfn, assignto);               \
      else                                            \
      {                                               \
            error(0, 0, _("%s: %s:%i: No `%s' tag found"),        \
                  c->inname, f->outname, c->part, tag);           \
            return EXIT_FAILURE;                            \
      }

static int read_base64(struct decode_context *c, struct decoded_file *f, char *buf)
{
      int exit_status = EXIT_EOF;
      int last_data = 0;

      /* We don't use c->out, so the CRC32 is never calculated. */
      c->total_parts = 1;

      if(opt_verbose > 0)
            error(0, 0, _("%s: %s: base64 encoding"), c->inname, f->outname);

      for(;;)
      {
            int i;
            char *pi, *po;

            if(!fgets_(buf, BUFSIZ, stdin))
            {
                  error(0, 0, _("%s: %s: Short file"), c->inname, f->outname);
                  return EXIT_FAILURE;
            }

            if(!memcmp(buf, B64MARKER_END, lenof(B64MARKER_END)))
                  break;

            if(last_data)
            {
                  error(0, 0, _("%s: %s: warning: Data following `=' padding character"), c->inname, f->outname);
                  exit_status = EXIT_WARNING;
                  break;
            }
            
            /* Rather than taking the convoluted (and frankly, nasty) algo-
             * rithm used by uudecode to ignore characters outside of that
             * defined by RFC2045 (which obsoletes RFC1521, referred to by
             * uuencode), we make two passes over the input: once to remove
             * said characters, while a second pass decodes the input. */

            /* Note this also removes the EOL markers fgets_ leaves behind. */
            for(po = pi = buf; *pi; pi++)
            {
                  if((*pi & 0x80) || (base64_tab[(size_t)*pi] == 0177))
                        continue;
                  *po++ = *pi;
                  if(*pi == '=')
                  {
                        last_data = 1;
                        exit_status = EXIT_SUCCESS;
                        if((po - buf) % 4)
                              continue;
                        break;
                  }
                  if(last_data)
                  {
                        error(0, 0, _("%s: %s: warning: Data following `=' padding character"), c->inname, f->outname);
                        exit_status = EXIT_WARNING;
                        po--; /* undo the last character */
                        break;
                  }
            }
            *po = '\0';

            /* the resulting input line length should be a multiple of 4 */
            i = po - buf;
            if(i % 4)
            {
                  error(0, 0, _("%s: %s: Illegal line -- ignored"), c->inname, f->outname);
                  exit_status = EXIT_FAILURE;
                  continue;
            }
            else if(i == 0)
                  continue;
            i /= 4;

            for(po = pi = buf; i; pi += 4, i--)
            {
                  *po++ = B64DECODE(pi[0]) << 2 | B64DECODE(pi[1]) >> 4;
                  *po++ = B64DECODE(pi[1]) << 4 | B64DECODE(pi[2]) >> 2;
                  *po++ = B64DECODE(pi[2]) << 6 | B64DECODE(pi[3]);

                  if(pi[3] == '=')
                  {
                        po--;
                        if(pi[2] == '=')
                        {
                              po--;
                              if(pi[1] == '=')
                                    po--;
                        }
                        break;
                  }
            }

#undef B64DECODE

            if(fwrite(buf, po - buf, 1, f->handle) != 1)
            {
                  error(0, errno, _("%s: %s"), c->inname, f->outname);
                  return EXIT_FAILURE;
            }
      }

      c->status = part_intact;
      return exit_status;
}

static int read_stduu(struct decode_context *c, struct decoded_file *f, char *buf)
{
      int exit_status = EXIT_EOF;
      int line;

      /* We don't use c->out, so the CRC32 is never calculated. */
      c->total_parts = 1;

      if(opt_verbose > 0)
            error(0, 0, _("%s: %s: uu encoding"), c->inname, f->outname);

      for(line = 1; ; line++)
      {
            int i, n, l;
            char *pi, *po;
            int pad = 0;

            if(!fgets_(buf, BUFSIZ, stdin))
            {
                  error(0, 0, _("%s: %s: unexpected end of file at line #%i"), c->inname, f->outname, line);
                  return EXIT_FAILURE;
            }

            po = pi = buf;
            n = *pi++ - ' ';
            if(n < 0)   /* ignores empty lines */
                  continue;
            n &= 077;
            if(n == 0)
            {
                  exit_status = EXIT_SUCCESS;
                  break;
            }

            for(l = 0; pi[l] >= ' '; )
                  l++;

            /* count should be a multiple of 4 */
            if(l != ((n + 2) / 3) * 4)
            {
                  i = (l * 3) / 4;
                  if(opt_write_broken)
                  {
                        /* write however many bytes we can see */
                        n = i;
                        error(0, 0, _("%s: %s: warning: malformed line #%i"), c->inname, f->outname, line);
                  }
                  else
                  {
                        if(n > i)
                        {
                              pad = n - i;
                              n = i;
                              error(0, 0, _("%s: %s: short line #%i (output padded)"), c->inname, f->outname, line);
                        }
                        else
                              error(0, 0, _("%s: %s: overlong line #%i (truncated)"), c->inname, f->outname, line);
                        exit_status = EXIT_FAILURE;
                  }
            }

            /* At this point, we're guaranteed (ha!) to have enough
             * input to work with, whatever the value of n, so no need
             * to check for malformed lines. */
            for(i = n / 3; i; pi += 4, i--)
            {
                  *po++ = UUDECODE(pi[0]) << 2 | UUDECODE(pi[1]) >> 4;
                  *po++ = UUDECODE(pi[1]) << 4 | UUDECODE(pi[2]) >> 2;
                  *po++ = UUDECODE(pi[2]) << 6 | UUDECODE(pi[3]);
            }

            switch(n % 3)
            {
            case 2:
                  *po++ = UUDECODE(pi[0]) << 2 | UUDECODE(pi[1]) >> 4;
                  *po++ = UUDECODE(pi[1]) << 4 | UUDECODE(pi[2]) >> 2;
                  break;
            case 1:
                  *po++ = UUDECODE(pi[0]) << 2 | UUDECODE(pi[1]) >> 4;
                  break;
            }

#undef UUDECODE

            if(pad)
            {
                  memset(po, 0, pad);
                  po += pad;
            }

            if(fwrite(buf, po - buf, 1, f->handle) != 1)
            {
                  error(0, errno, _("%s: %s:1"), c->inname, f->outname);
                  return EXIT_FAILURE;
            }
      }

      if((fgets_(buf, BUFSIZ, stdin) == NULL) || strcmp(buf, UUMARKER_END))
      {
            error(0, 0, _("%s: %s:1: No `end' line"), c->inname, f->outname);
            return EXIT_FAILURE;
      }

      c->status = part_intact;
      return exit_status;
}

static int read_yenc(struct decode_context *c, struct decoded_file *f, char *buf)
{
      int is_multi_part = 0;
      int has_more_parts = 0;
      char *p;
      char *pout;

      int unrecognised_escapes[0x100];
      off_t part_begin = 0;
      off_t part_end = 0;

      memset(unrecognised_escapes, 0, sizeof(unrecognised_escapes));

      /* ick. Non-zero on entry. Needed to detect multi-part files */
      c->part = -1;
      c->total_parts = 0;
      PARSE_TAG_OPTIONAL(YTAG_PART, (int)atol, c->part);
      PARSE_TAG_OPTIONAL(YTAG_TOTAL, (int)atol, c->total_parts);

      /* Pretend single-part articles have a `part=1' tag */
      is_multi_part = c->part != -1;
      if(!is_multi_part)
            c->part = 1;

      ASSERT(c->part > 0);

      if(c->total_parts)
      {
            ASSERT(c->part <= c->total_parts);
      }
      else
      {
            /* we've at least this many parts; can't tell for now */
            has_more_parts = 1;
            if(c->total_parts < c->part)
                  c->total_parts = c->part;
      }

      {     /* Assert that we can handle the line width.
             * sizeof(buf) should be 8kb, hope that's sufficient. */
            int line_width;

            PARSE_TAG(YTAG_LINE, (int)atol, line_width);
            ASSERT(line_width >= 0);
            ASSERT(line_width < BUFSIZ);
      }

      /* opt_verbose == 1: report the first part only.
       * opt_verbose == 2: report all parts. */
      if((!f->total_parts && (opt_verbose > 0))
                  || (opt_verbose > 1))
            error(0, 0, _("%s: %s:%i: yEnc file (%s%i parts)"), c->inname,
                  f->outname, c->part, has_more_parts ? ">=" : "", c->total_parts);

      {
            off_t file_size = 0;

            PARSE_TAG(YTAG_SIZE, (off_t)atol, file_size);
            ASSERT(file_size >= 0);

            if(f->total_parts && (f->total_size != file_size))
                  error(0, 0, _("%s: %s:%i: warning: File size mismatch -- previous parts says %li, this part %li; ignoring this part"),
                        c->inname, f->outname, c->part, f->total_size, file_size);
            else
                  f->total_size = file_size;
      }

      if(!is_multi_part)
            part_end = f->total_size;

      /* make sure we've got a valid handle in f->handle */
      if(decoded_file_open(f) != EXIT_SUCCESS)
      {
            /* Only show error for the first part. */
            if(!f->total_parts)
                  error(0, errno, _("%s: %s:%i"), c->inname, f->outname, c->part);
            return EXIT_FAILURE;
      }

      /* Is this a multi-part article? */
      if(is_multi_part)
      {
            for(;;)
            {
                  SEARCH_MARKER(YMARKER_PART);
                  error(0, 0, _("%s: %s:%i: No `=ypart' line found after `=ybegin'"),
                        c->inname, f->outname, c->part);
                  return EXIT_FAILURE;
            }

            PARSE_TAG(YTAG_BEGIN, (off_t)atol, part_begin);
            part_begin--;
            ASSERT(part_begin >= 0);

            PARSE_TAG(YTAG_END, (off_t)atol, part_end);
            ASSERT(part_end >= 0);
            ASSERT(part_end >= part_begin);
            ASSERT(part_end <= f->total_size);

            /* This fails on stdout, but we ignore that. */
            (void)fseek(f->handle, part_begin, SEEK_SET);
      }

      /* Prepare the output buffer. */
      if((part_end - part_begin > PART_SIZE_SOFT_LIMIT) && !opt_large_parts)
      {
            error(0, 0, _("%s: %s:%i: Not going to malloc() %lu bytes (broken header?); use --large-parts"),
                  c->inname, f->outname, c->part, part_end - part_begin);
            return EXIT_FAILURE;
      }

      if(!(pout = c->out = malloc((size_t)(part_end - part_begin))))
      {
            error(0, 0, _("%s: %s:%i: Unable to malloc() %lu bytes"),
                  c->inname, f->outname, c->part, part_end - part_begin);
            return EXIT_FAILURE;
      }

      /* Begin decoding! */
      for(;/* each line */;)
      {
            /* Breaks out if =yend found. */
            SEARCH_MARKER(YMARKER_END);

            p = buf + strlen(buf) - 1;
            while((p >= buf) && ((*p == '\r') || (*p == '\n')))
                  --p;
            *++p = '\0';

            for(p = buf; *p; p++)
            {
                  if(pout - c->out >= part_end - part_begin)
                  {
                        error(0, 0, _("%s: %s:%i: Part longer than expected"),
                              c->inname, f->outname, c->part);
                        c->out_size = pout - c->out;
                        return EXIT_FAILURE;
                  }
                  if(*p != '=')
                  {
                        *pout++ = *p - 42;
                        continue;
                  }

                  switch(*++p)
                  {
                  default:    /* Be liberal in what we accept. */
                        if(!unrecognised_escapes[*p & 0xff])
                        {     /* Just once is enough. */
                              error(0, 0, _("%s: %s:%i: warning: Unrecognised escape code `\\%o' (allowing it anyway)"),
                                    c->inname, f->outname, c->part, *p);
                        }
                        unrecognised_escapes[*p & 0xff]++;
                  /*   NUL       TAB        LF        CR */
                  case '@': case 'I': case 'J': case 'M':
                  /*    =         .        ??? */
                  case '}': case 'n': case '`':
                        *pout++ = *p - '@' - 42;
                        break;
                  }
            }
      }
      c->out_size = pout - c->out;

      {     /* verify the CRC32 sum */
            u_int32_t part_crc32;
            struct crc32_ctx crc32_context;

            crc32_init_ctx(&crc32_context);
            crc32_process_bytes(c->out, c->out_size, &crc32_context);
            crc32_finish_ctx(&crc32_context);

            if(is_multi_part)
            {
                  u_int32_t file_crc32 = 0;

                  PARSE_TAG_OPTIONAL(YTAG_CRC32, atocrc32, file_crc32);
                  if(file_crc32 && f->total_parts && f->crc32 && (f->crc32 != file_crc32))
                  {
                        error(0, 0, _("%s: %s:%i: warning: File CRC mismatch in trailer -- previous parts says %08x, this part %08x -- ignoring this part"),
                              c->inname, f->outname, c->part, f->crc32, file_crc32);
                  }
                  else if(file_crc32)
                        f->crc32 = file_crc32;

                  PARSE_TAG(YTAG_PCRC32, atocrc32, part_crc32);
            }
            else
            {
                  PARSE_TAG(YTAG_CRC32, atocrc32, part_crc32);
            }

            if(part_crc32 != crc32_read_ctx(&crc32_context))
            {
                  error(0, 0, _("%s: %s:%i: Part CRC32 error -- got 0x%08x, should be 0x%08x"),
                        c->inname, f->outname, c->part, crc32_read_ctx(&crc32_context), part_crc32);
                  return EXIT_FAILURE;
            }
      }

      /* Check the trailer part number. */
      {
            int trailer_part = 0;
            PARSE_TAG_OPTIONAL(YTAG_PART, (int)atol, trailer_part);
            if(!is_multi_part)
                  trailer_part++;
            ASSERT(trailer_part > 0);

            if(trailer_part != c->part)
            {
                  error(0, 0, _("%s: %s:%i: warning: Part number mismatch in trailer (part %i); ignoring trailer"),
                        c->inname, f->outname, c->part, trailer_part);
            }
      }

      /* All of the following are warnings, since the CRC matched. And
       * frankly, I trust the CRC more than I trust the ability of some
       * random sample of the human population to be able to code. */
      {
            off_t part_size = 0;

            PARSE_TAG(YTAG_SIZE, (off_t)atol, part_size);
            ASSERT(part_size >= 0);

            if((size_t)part_size != c->out_size)
            {
                  error(0, 0, _("%s: %s:%i: warning: Wrong part size -- decoded %lu bytes, expected %lu"),
                        c->inname, f->outname, c->part, c->out_size, part_size);
            }

            if(part_size != part_end - part_begin)
            {
                  error(0, 0, _("%s: %s:%i: warning: Part size/range mismatch -- %lu != %lu-%lu"),
                        c->inname, f->outname, c->part, part_size, part_end, part_begin);
            }
      }

      c->status = part_intact;

      return EXIT_SUCCESS;
}

static int decode(inname, forced_outname, decoded_list)
      const char *inname;
      const char *forced_outname;
      struct decoded_file **decoded_list;
{
      int exit_status = EXIT_SUCCESS;
      static struct decoded_file *f;
      static read_enc re = NULL;
      int no_begin = 1;

      char buf[BUFSIZ];
      char name[BUFSIZ];
      char *p;

      struct decode_context c;

#define INIT_DECODE_CTX()     \
      memset(&c, 0, sizeof(c));     \
      c.inname = inname;            \
      c.status = part_broken; /* Assume broken unless we verify otherwise */

      INIT_DECODE_CTX();

      if(opt_verbose > 1)
            error(0, 0, _("%s: Processing"), inname);

      while(re || fgets_(buf, BUFSIZ, stdin))
      {
            int mode = 0644;
            const char *outname;

            if(re)
                  goto read_file;

            outname = name;
            if(!strncmp(buf, YMARKER_BEGIN, lenof(YMARKER_BEGIN)))
            {
                  /* This is parsed again in read_yenc(). Somewhat wasteful. ;_; */
                  PARSE_TAG_OPTIONAL(YTAG_PART, (int)atol, c.part);
                  if(!c.part)
                        c.part++;

                  /* parse the name tag */
                  if(!(p = strstr(buf, YTAG_NAME)))
                  {
                        error(0, 0, _("%s: No `%s' tag found"), inname, YTAG_NAME);
                        exit_status = EXIT_FAILURE;
                        continue;
                  }
                  strcpy(name, p + lenof(YTAG_NAME));
                  p = name;

                  /* yEnc 1.3 allows for trailing spaces and quoted filenames */
                  p += strlen(p) - 1;
                  while((p > outname) && ((*p == '\r') || (*p == '\n') || (*p == ' ')))
                        *p-- = '\0';
                  if((p > outname) && (*p == '"') && (*outname == '"'))
                  {
                        *p = '\0';
                        ++outname;
                  }

                  re = read_yenc;
            }
            else if(!strncmp(buf, UUMARKER_BEGIN, lenof(UUMARKER_BEGIN)))
            {
                  if(sscanf(buf + lenof(UUMARKER_BEGIN), "%o %[^\n]", &mode, name) != 2)
                  {
                        exit_status = EXIT_FAILURE;
                        continue;
                  }
                  c.part = 1;
                  re = read_stduu;
            }
            else if(!strncmp(buf, B64MARKER_BEGIN, lenof(B64MARKER_BEGIN)))
            {
                  if(sscanf(buf + lenof(B64MARKER_BEGIN), "%o %[^\n]", &mode, name) != 2)
                  {
                        exit_status = EXIT_FAILURE;
                        continue;
                  }
                  c.part = 1;
                  re = read_base64;
            }
            else continue;

            f = decoded_file_lookup(decoded_list, outname, mode, forced_outname);

            /* Don't bother decoding duplicated parts. */
            if((f->total_parts >= c.part)
                  && ((f->status[c.part - 1] == part_intact)
                        || (f->status[c.part - 1] == part_duplicated)))
            {
                  /* This is OK, because out == NULL */
                  c.status = part_duplicated;
                  exit_status = EXIT_SUCCESS;
                  goto exit_skip;
            }

            /* make sure we've got a valid handle in f->handle */
            if(decoded_file_open(f) != EXIT_SUCCESS)
            {
                  /* Only show error for the first part. */
                  if(!f->total_parts)
                        error(0, errno, _("%s: %s"), c.inname, f->outname);

                  /* NASTY HACK: total parts isn't parsed by this stage
                   * yet; but we force it nonzero so that f->total_parts
                   * will get incremented below. */
                  c.total_parts = c.part;

                  exit_status = EXIT_FAILURE;
                  goto exit_skip;
            }

read_file:

            exit_status = (*re)(&c, f, buf);

exit_skip:

            if(c.out)
            {
                  /* Commit the part to file. */
                  if((c.status == part_intact) || opt_write_broken)
                  {
                        /* f always set before out */
                        if(fwrite(c.out, c.out_size, 1, f->handle) != 1)
                        {
                              error(0, errno, _("%s: %s:%i"), c.inname, f->outname, c.part);
                              return EXIT_FAILURE;
                        }
                        f->bytes_written += c.out_size;
                  }

                  /* are we getting the parts sequentially? */
                  if(c.part == f->crc32_prev_part + 1)
                  {
                        crc32_process_bytes(c.out, c.out_size, &f->crc32_context);
                        f->crc32_prev_part = c.part;
                  }
                  else
                        f->crc32_prev_part = -1;

                  free(c.out);
                  c.out = NULL;
            }

            if(f)
            {
                  if(f->total_parts < c.total_parts)
                  {
                        enum part_status *ps;

                        ps = (enum part_status *)malloc(c.total_parts * sizeof(enum part_status));
                        memcpy(ps, f->status, f->total_parts * sizeof(enum part_status));
                        memset(ps + f->total_parts, 0, (c.total_parts - f->total_parts) * sizeof(enum part_status));
                        if(f->status)
                              free(f->status);
                        f->status = ps;
                        f->total_parts = c.total_parts;
                  }
                  f->status[c.part - 1] = c.status;

                  if(f->is_seekable && f->handle && (f->handle != stdout))
                  {
                        fclose(f->handle);
                        f->handle = NULL;
                  }
            }

            no_begin = 0;

            /* does the handler want to hold on to stdin? */
            if(exit_status == EXIT_EOF)
            {
                  exit_status = EXIT_SUCCESS;
                  break;
            }

            re = NULL;
            INIT_DECODE_CTX();
      }

      if(no_begin)
      {
            error(0, 0, _("%s: No `=ybegin' or `begin' line found"), inname);
            exit_status = EXIT_FAILURE;
      }

      return exit_status;
}


#undef PARSE_TAG
#undef PARSE_TAG_OPTIONAL
#undef SEARCH_MARKER
#undef UUDECODE
#undef B64DECODE
#undef ASSERT

static void
usage(status)
      int status;
{
      if(status)
            fprintf(stderr, _("Try `%s --help' for more information.\n"),
                  program_name);
      else
      {
#ifdef __DJGPP__
      setmode(fileno(stdout), O_TEXT);
#endif /* __DJGPP__ */
            printf(_("\
%s %s -- Copyright (C) Liyang HU 2002-2003, http://nerv.cx/liyang/\n\
Usage: %s [FILE] ...\n\
  -o, --output-file=FILE   direct all output to FILE (use - for stdout)\n\
  -D, --directory=DIR      write output files to DIR\n\
  -e, --evil-filename      allow evil characters in filename\n\
  -c, --clobber-filename   append .n to filename if it already exists\n\
  -f, --force-overwrite    overwrite output file; truncate unless -b is used.\n\
  -b, --write-broken       write decoded part even if verified to be broken,\n\
                           and don't append .broken to the filename\n\
  -i, --broken-encoder     work around broken encoders (where CRC=00000001)\n\
  -r, --remove-broken      unlink instead of renaming broken files\n\
  -l, --large-parts        expect parts larger than %uk\n\
  -h, --help               display this help and exit\n\
  -v, --verbose            detailed progress reports (repeat for more)\n\
  -V, --version            output version information and exit\n\
Refer to the man page for details.\n"),
                  PACKAGE, VERSION,
                  program_name,
                  PART_SIZE_SOFT_LIMIT >> 10);
      }
      exit(status);
}

int
main(argc, argv)
      int argc;
      char * const *argv;
{
      int opt;
      int exit_status;
      const char *outname;
      struct decoded_file *decoded_list = NULL;

      struct option longopts[] =
      {
            { "output-file", required_argument, NULL, (int)'o' },
            { "directory", required_argument, NULL, (int)'D' },
            { "evil-filename", no_argument, NULL, (int)'e' },
            { "clobber-filename", no_argument, NULL, (int)'c' },
            { "force-overwrite", no_argument, NULL, (int)'f' },
            { "write-broken", no_argument, NULL, (int)'b' },
            { "broken-encoder", no_argument, NULL, (int)'i' },
            { "remove-broken", no_argument, NULL, (int)'r' },
            { "large-parts", no_argument, NULL, (int)'l' },
            { "help", no_argument, NULL, (int)'h' },
            { "verbose", no_argument, NULL, (int)'v' },
            { "version", no_argument, NULL, (int)'V' },
            { NULL, 0, NULL, 0 }
      };

      program_name = argv[0];
      outname = NULL;

#ifdef WIN32
      /* Never underestimate stupid people in large numbers... */
      _setmode(_fileno(stdin), _O_BINARY);
      _setmode(_fileno(stdout), _O_BINARY);
#endif

#ifdef __DJGPP__
      _fmode = O_BINARY;
      setmode(fileno(stdout), O_BINARY);
      if (isatty(fileno(stdin)) == 0)
            setmode(fileno(stdin), O_BINARY);
      if (isatty(fileno(stdin)) !=0)
            setbuf(stdin, NULL);
#endif /* __DJGPP__ */

      while(opt = getopt_long(argc, argv, "o:D:ecfbirlhvV", longopts, (int *)NULL),
            opt != EOF)
      {
            switch(opt)
            {
            case 'o':
                  outname = optarg;
                  break;

            case 'D':
                  opt_directory = optarg;
                  break;

            case 'e':
                  opt_evil_filename++;
                  break;

            case 'c':
                  opt_clobber_filename = 1;
                  break;

            case 'f':
                  opt_force_overwrite = 1;
                  break;

            case 'b':
                  opt_write_broken = 1;
                  break;

            case 'i':
                  opt_broken_encoder = 1;
                  break;

            case 'r':
                  opt_remove_broken = 1;
                  break;

            case 'l':
                  opt_large_parts = 1;
                  break;

            case 'h':
                  usage(EXIT_SUCCESS);

            case 'v':
                  opt_verbose++;
                  break;

            case 'V':
                  printf("%s %s\n", PACKAGE, VERSION);
                  exit(EXIT_SUCCESS);

            case 0:
                  break;

            default:
                  usage(EXIT_FAILURE);
            }
      }

      if(optind == argc)
            exit_status = decode("stdin", outname, &decoded_list);
      else
      {
            char current_directory[BUFSIZ];

            if(opt_directory)
                  getcwd(current_directory, sizeof(current_directory));

            exit_status = EXIT_SUCCESS;
            do
            {
                  int det_return;

                  /* note the order: we want to exit this loop in
                   * opt_directory, if it is specified. */
                  if(opt_directory)
                        chdir(current_directory);

                  if(!freopen(argv[optind], "rb", stdin))
                  {
                        error(0, errno, "%s", argv[optind]);
                        exit_status = EXIT_FAILURE;
                        break;
                  }

                  if(opt_directory && chdir(opt_directory))
                  {
                        error(0, errno, "%s", opt_directory);
                        exit_status = EXIT_FAILURE;
                        break;
                  }

                  if((det_return = decode(argv[optind], outname, &decoded_list))
                              != EXIT_SUCCESS)
                        exit_status = det_return;
            }
            while(++optind < argc);
      }

      /* determine exit_status from decoded_list instead: */
      exit_status = EXIT_SUCCESS;
      while(decoded_list)
      {
            int broken_file = 0;
            struct decoded_file *f = decoded_list;
            int i;

            if(!f->previously_existed)
            {
                  /* Did we write to this file at all? Yes: */

                  for(i = 0; i < f->total_parts; i++)
                  {
                        switch(f->status[i])
                        {
                        case part_missing:
                              error(0, 0, _("%s:%i: Part missing (of %i parts)"),
                                    f->outname, i + 1, f->total_parts);
                              break;
                        case part_broken:
                              error(0, 0, _("%s:%i: Part broken (of %i parts)"),
                                    f->outname, i + 1, f->total_parts);
                              break;
                        case part_duplicated:
                              error(0, 0, _("%s:%i: warning: Part duplicated (of %i parts); ignored"),
                                    f->outname, i + 1, f->total_parts);
                        case part_intact:
                              continue;
                        }
                        broken_file = 1;
                        exit_status = EXIT_FAILURE;
                  }

                  if(f->bytes_written != f->total_size)
                  {
                        error(0, 0, _("%s: wrote %li bytes; should have been %li"),
                              f->outname, f->bytes_written, f->total_size);
                        broken_file = 1;
                        exit_status = EXIT_FAILURE;
                  }

                  /* go over the output file once again to calculate the CRC */
                  if((f->crc32_prev_part == -1) && f->is_seekable)
                  {
                        char buf[BUFSIZ];
                        size_t bytes_read;

                        decoded_file_open(f);
                        fseek(f->handle, 0, SEEK_SET);
                        crc32_init_ctx(&f->crc32_context);
                        while((bytes_read = fread(buf, 1, BUFSIZ, f->handle)))
                              crc32_process_bytes(buf, bytes_read, &f->crc32_context);

                        f->crc32_prev_part = 0;
                  }

                  if(f->handle && (f->handle != stdout))
                        fclose(f->handle);

                  crc32_finish_ctx(&f->crc32_context);
                  if((f->crc32_prev_part >= 0) && f->crc32 && (f->crc32 != crc32_read_ctx(&f->crc32_context)))
                  {
                        if (!opt_broken_encoder || f->crc32 != 0x00000001)
                        {
                              error(0, 0, _("%s: File CRC error -- got 0x%08x, should be 0x%08x"),
                                    f->outname, crc32_read_ctx(&f->crc32_context), f->crc32);
                              broken_file = 1;
                              exit_status = EXIT_FAILURE;
                        }
                        else
                              error(0, 0, _("%s: warning: File encoded with a broken encoder (fake CRC: 0x00000001, should be 0x%08x) -- please pester the sender to upgrade their software"),
                                    f->outname, crc32_read_ctx(&f->crc32_context));

                  }

                  if(broken_file)
                  {

                  /* If we have a broken file:
                   * WB RB    ACTION
                   * 0  0     rename
                   * 0  1     unlink
                   * 1  0     nothing
                   * 1  1     rename            */

            /* When both WB and RB are specified, the file is `removed' in
             * the sense that there is no longer a file with that name. */

                        if(opt_write_broken == opt_remove_broken)
                        {
                              char *outname_broken = malloc(strlen(f->outname)
                                          + lenof(YYDEC_BROKEN_SUFFIX) + 1);
                              strcpy(outname_broken, f->outname);
                              strcat(outname_broken, YYDEC_BROKEN_SUFFIX);
                              error(0, 0, "%s: renamed to %s", f->outname, outname_broken);
                              rename(f->outname, outname_broken);
                              free(outname_broken);
                        }
                        else if(!opt_write_broken && opt_remove_broken)
                              unlink(f->outname);
                  }

            } /* previously_existed == we haven't touched this file:
               *   do nothing. (except release malloc()'d storage) */

            decoded_list = f->next;
            if(f->filename)
                  free(f->filename);
            if(f->outname)
                  free(f->outname);
            if(f->status)
                  free(f->status);
            free(f);
      }

      exit(exit_status);
}


Generated by  Doxygen 1.6.0   Back to index