1 /*****************************************************************************/
2 /*  LibreDWG - free implementation of the DWG file format                    */
3 /*                                                                           */
4 /*  Copyright (C) 2018-2020 Free Software Foundation, Inc.                   */
5 /*                                                                           */
6 /*  This library is free software, licensed under the terms of the GNU       */
7 /*  General Public License as published by the Free Software Foundation,     */
8 /*  either version 3 of the License, or (at your option) any later version.  */
9 /*  You should have received a copy of the GNU General Public License        */
10 /*  along with this program.  If not, see <http://www.gnu.org/licenses/>.    */
11 /*****************************************************************************/
12 
13 /* dxfwrite.c: write a DXF file from various input formats.
14  * written by Reini Urban
15  */
16 
17 #include "../src/config.h"
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 // strings.h or string.h
22 #ifdef AX_STRCASECMP_HEADER
23 #  include AX_STRCASECMP_HEADER
24 #endif
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <getopt.h>
28 #ifdef HAVE_VALGRIND_VALGRIND_H
29 #  include <valgrind/valgrind.h>
30 #endif
31 
32 #include "dwg.h"
33 #include "common.h"
34 #include "bits.h"
35 #include "suffix.inc"
36 #include "decode.h"
37 #ifndef DISABLE_JSON
38 #  include "in_json.h"
39 #endif
40 #include "out_dxf.h"
41 
42 // avoid the slow fork loop, for afl-clang-fast
43 #ifdef __AFL_COMPILER
44 static volatile const char *__afl_persistent_sig = "##SIG_AFL_PERSISTENT##";
45 #endif
46 
47 static int opts = 1;
48 
49 static int help (void);
50 
51 static int
usage(void)52 usage (void)
53 {
54   printf ("\nUsage: dxfwrite [-b] [-v[0-9]] [-y] [--as rNNNN] [-I FMT] [-o DXFFILE] "
55           "INFILE\n");
56   return 1;
57 }
58 static int
opt_version(void)59 opt_version (void)
60 {
61   printf ("dxfwrite %s\n", PACKAGE_VERSION);
62   return 0;
63 }
64 static int
help(void)65 help (void)
66 {
67   printf ("\nUsage: dxfwrite [OPTION]... [-o DXFFILE] INFILE\n");
68   printf ("Writes a DXF file from various input formats.\n"
69           "\n");
70 #ifdef HAVE_GETOPT_LONG
71   printf ("  -v[0-9], --verbose [0-9]  verbosity\n");
72   printf ("  --as rNNNN                save as version\n");
73   printf ("              Valid versions:\n");
74   printf ("                r12, r13, r14, r2000, r2004, r2007, r2010, r2013, r2018\n");
75 #   ifndef DISABLE_JSON
76   printf ("  -I fmt,  --format fmt     DWG, DXF, DXFB, JSON\n");
77 #  else
78   printf ("  -I fmt,  --format fmt     DWG, DXF, DXFB\n");
79 #  endif
80   printf (
81       "           Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
82   printf ("  -o dxffile, --file        \n");
83   printf ("  -m, --minimal             only $ACADVER, HANDSEED and "
84           "ENTITIES\n");
85   printf ("  -b, --binary              create a binary DXF\n");
86   printf ("  -y, --overwrite           overwrite existing files\n");
87   printf ("           --help           display this help and exit\n");
88   printf ("           --version        output version information and exit\n"
89           "\n");
90 #else
91   printf ("  -v[0-9]     verbosity\n");
92   printf ("  -a rNNNN    save as version\n");
93   printf ("              Valid versions:\n");
94   printf ("                r12, r13, r14, r2000, r2004, r2007, r2010, r2013, r2018\n");
95   /*
96   printf ("              Planned versions:\n");
97   printf ("                r9, r10, r11, r12\n");
98   */
99 #  ifndef DISABLE_JSON
100   printf ("  -I fmt      fmt: DWG, DXF, DXFB, JSON\n");
101 #  else
102   printf ("  -I fmt      fmt: DWG, DXF, DXFB\n");
103 #  endif
104   printf ("              Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
105   printf ("  -o dxffile\n");
106   printf ("  -m          minimal, only $ACADVER, HANDSEED and ENTITIES\n");
107   printf ("  -b          create a binary DXF\n");
108   printf ("  -y          overwrite existing files\n");
109   printf ("  -h          display this help and exit\n");
110   printf ("  -i          output version information and exit\n"
111           "\n");
112 #endif
113   printf ("GNU LibreDWG online manual: "
114           "<https://www.gnu.org/software/libredwg/>\n");
115   return 0;
116 }
117 
118 int
main(int argc,char * argv[])119 main (int argc, char *argv[])
120 {
121   int i = 1;
122   int error = 0;
123   Dwg_Data dwg;
124   const char *fmt = NULL;
125   const char *infile = NULL;
126   char *outfile = NULL;
127   Bit_Chain dat = { 0 };
128   const char *version = NULL;
129   Dwg_Version_Type dwg_version = R_INVALID;
130   int c;
131   int overwrite = 0;
132   int binary = 0;
133   int minimal = 0;
134   int force_free = 0;
135   int free_outfile = 0;
136 #ifdef HAVE_GETOPT_LONG
137   int option_index = 0;
138   static struct option long_options[]
139       = { { "verbose", 1, &opts, 1 }, // optional
140           { "format", 1, 0, 'I' },    { "file", 1, 0, 'o' },
141           { "as", 1, 0, 'a' },
142           { "minimal", 0, 0, 'm' },   { "binary", 0, 0, 'b' },
143           { "overwrite", 0, 0, 'y' }, { "version", 0, 0, 0 },
144           { "force-free", 0, 0, 0 },  { "help", 0, 0, 0 },
145           { NULL, 0, NULL, 0 } };
146 #endif
147 
148   if (argc < 2)
149     return usage ();
150 
151   while
152 #ifdef HAVE_GETOPT_LONG
153       ((c
154         = getopt_long (argc, argv, "ymba:v::I:o:h", long_options, &option_index))
155        != -1)
156 #else
157       ((c = getopt (argc, argv, "ymba:v::I:o:hi")) != -1)
158 #endif
159     {
160       if (c == -1)
161         break;
162       switch (c)
163         {
164         case ':': // missing arg
165           if (optarg && !strcmp (optarg, "v"))
166             {
167               opts = 1;
168               break;
169             }
170           fprintf (stderr, "%s: option '-%c' requires an argument\n", argv[0],
171                    optopt);
172           break;
173 #ifdef HAVE_GETOPT_LONG
174         case 0:
175           /* This option sets a flag */
176           if (!strcmp (long_options[option_index].name, "verbose"))
177             {
178               if (opts < 0 || opts > 9)
179                 return usage ();
180 #  if defined(USE_TRACING) && defined(HAVE_SETENV)
181               {
182                 char v[2];
183                 *v = opts + '0';
184                 *(v + 1) = 0;
185                 setenv ("LIBREDWG_TRACE", v, 1);
186               }
187 #  endif
188               break;
189             }
190           if (!strcmp (long_options[option_index].name, "version"))
191             return opt_version ();
192           if (!strcmp (long_options[option_index].name, "help"))
193             return help ();
194           if (!strcmp (long_options[option_index].name, "force-free"))
195             force_free = 1;
196           if (!strcmp (long_options[option_index].name, "binary"))
197             binary = 1;
198           break;
199 #else
200         case 'i':
201           return opt_version ();
202 #endif
203         case 'I':
204           fmt = optarg;
205           break;
206         case 'y':
207           overwrite = 1;
208           break;
209         case 'b':
210           binary = 1;
211           break;
212         case 'm':
213           minimal = 1;
214           break;
215         case 'o':
216           outfile = optarg;
217           break;
218         case 'a': // as
219           dwg_version = dwg_version_as (optarg);
220           if (dwg_version == R_INVALID)
221             {
222               fprintf (stderr, "Invalid version '%s'\n", argv[1]);
223               return usage ();
224             }
225           version = optarg;
226           break;
227         case 'v': // support -v3 and -v
228           i = (optind > 0 && optind < argc) ? optind - 1 : 1;
229           if (!memcmp (argv[i], "-v", 2))
230             {
231               opts = argv[i][2] ? argv[i][2] - '0' : 1;
232             }
233           if (opts < 0 || opts > 9)
234             return usage ();
235 #if defined(USE_TRACING) && defined(HAVE_SETENV)
236           {
237             char v[2];
238             *v = opts + '0';
239             *(v + 1) = 0;
240             setenv ("LIBREDWG_TRACE", v, 1);
241           }
242 #endif
243           break;
244         case 'h':
245           return help ();
246         case '?':
247           fprintf (stderr, "%s: invalid option '-%c' ignored\n", argv[0],
248                    optopt);
249           break;
250         default:
251           return usage ();
252         }
253     }
254   i = optind;
255 
256 #ifdef __AFL_HAVE_MANUAL_CONTROL
257   // llvm_mode deferred init
258   __AFL_INIT();
259 #endif
260 
261 #ifdef __xxAFL_HAVE_MANUAL_CONTROL
262   while (__AFL_LOOP(1000)) { // llvm_mode persistent mode (currently broken)
263 #endif
264 
265   // get input format from INFILE, not outfile.
266   // With stdin, should -I be mandatory, or try to autodetect the format?
267   // With a file use the extension.
268   if (optind < argc) // have arg
269     {
270       infile = argv[i];
271       if (!fmt)
272         {
273           if (strstr (infile, ".dwg") || strstr (infile, ".DWG"))
274             fmt = (char *)"dwg";
275 #ifndef DISABLE_DXF
276 #  ifndef DISABLE_JSON
277           else if (strstr (infile, ".json") || strstr (infile, ".JSON"))
278             fmt = (char *)"json";
279 #  endif
280           else if (strstr (infile, ".dxfb") || strstr (infile, ".DXFB"))
281             fmt = (char *)"dxfb";
282           else if (strstr (infile, ".dxf") || strstr (infile, ".DXF"))
283             fmt = (char *)"dxf";
284           else
285 #endif
286             fprintf (stderr, "Unknown input format for '%s'\n", infile);
287         }
288     }
289 
290   // allow stdin, but require -I|--format then
291   memset (&dwg, 0, sizeof (Dwg_Data));
292   dwg.opts = opts;
293   if (version) // hint the importer
294     dwg.header.version = dat.version = dwg_version;
295 
296   if (infile)
297     {
298       struct stat attrib;
299       if (stat (infile, &attrib)) // not exists
300         {
301           fprintf (stderr, "Missing input file '%s'\n", infile);
302           exit (1);
303         }
304       dat.fh = fopen (infile, "r");
305       if (!dat.fh)
306         {
307           fprintf (stderr, "Could not read file '%s'\n", infile);
308           exit (1);
309         }
310       dat.size = attrib.st_size;
311     }
312   else
313     dat.fh = stdin;
314 
315   if ((fmt && !strcasecmp (fmt, "dwg"))
316            || (infile && !strcasecmp (infile, ".dwg")))
317     {
318       if (opts > 1)
319         fprintf (stderr, "Reading DWG file %s\n",
320                  infile ? infile : "from stdin");
321       error = dwg_read_file (infile ? infile : "-", &dwg);
322     }
323 #ifndef DISABLE_DXF
324 #  ifndef DISABLE_JSON
325   else if ((fmt && !strcasecmp (fmt, "json"))
326       || (infile && !strcasecmp (infile, ".json")))
327     {
328       if (opts > 1)
329         fprintf (stderr, "Reading JSON file %s\n",
330                  infile ? infile : "from stdin");
331       error = dwg_read_json (&dat, &dwg);
332     }
333 #  endif
334   else if ((fmt && !strcasecmp (fmt, "dxfb"))
335            || (infile && !strcasecmp (infile, ".dxfb")))
336     {
337       if (opts > 1)
338         fprintf (stderr, "Reading Binary DXF file %s\n",
339                  infile ? infile : "from stdin");
340       error = dxf_read_file (infile ? infile : "-", &dwg);
341     }
342   else if ((fmt && !strcasecmp (fmt, "dxf"))
343            || (infile && !strcasecmp (infile, ".dxf")))
344     {
345       if (opts > 1)
346         fprintf (stderr, "Reading DXF file %s\n",
347                  infile ? infile : "from stdin");
348       error = dxf_read_file (infile ? infile : "-", &dwg);
349     }
350   else
351 #endif
352     {
353       if (fmt)
354         fprintf (stderr, "Invalid or unsupported input format '%s'\n", fmt);
355       else if (infile)
356         fprintf (stderr, "Missing input format for '%s'\n", infile);
357       else
358         fprintf (stderr, "Missing input format\n");
359       if (infile)
360         fclose (dat.fh);
361       free (dat.chain);
362       exit (1);
363     }
364 
365   free (dat.chain);
366   dat.size = 0;
367   if (infile && dat.fh)
368     {
369       fclose (dat.fh);
370       dat.fh = NULL;
371     }
372   if (error >= DWG_ERR_CRITICAL)
373     goto free;
374 
375   if (!version)
376     dat.version = dwg.header.version = dwg.header.from_version;
377   if (minimal)
378     dwg.opts |= DWG_OPTS_MINIMAL;
379   dwg.opts |= opts;
380 
381   if (!outfile)
382     {
383       outfile = suffix (infile, "dxf");
384       free_outfile = 1;
385     }
386 
387   if (opts > 1)
388     {
389       fprintf (stderr, "Writing %s%sDXF file %s", minimal ? "minimal " : "",
390                binary ? "binary " : "", outfile);
391       if (version)
392         fprintf (stderr, " (from %s to %s)\n",
393                  dwg_version_type (dwg.header.from_version),
394                  dwg_version_type (dwg.header.version));
395       else
396         fprintf (stderr, " (%s)\n", dwg_version_type (dwg.header.version));
397     }
398 
399   {
400     struct stat attrib;
401     if (stat (outfile, &attrib))
402       dat.fh = fopen (outfile, "wb");
403     else // exists
404       {
405         if (!overwrite)
406           {
407             fprintf (stderr, "File not overwritten: %s, use -y.\n", outfile);
408             error |= DWG_ERR_IOERROR;
409           }
410         else
411           {
412             if (S_ISREG (attrib.st_mode) &&   // refuse to remove a directory
413                 (access (outfile, W_OK) == 0) // writable
414 #ifndef _WIN32
415                 // refuse to remove a symlink. even with overwrite. security
416                 && !S_ISLNK (attrib.st_mode)
417 #endif
418             )
419               {
420                 unlink (outfile);
421                 dat.fh = fopen (outfile, "wb");
422               }
423             else if ( // for fuzzing mainly
424 #ifdef _WIN32
425                      strEQc (outfile, "NUL")
426 #else
427                      strEQc (outfile, "/dev/null")
428 #endif
429                      )
430               {
431                 dat.fh = fopen (outfile, "wb");
432               }
433             else
434               {
435                 fprintf (stderr, "Not writable file or symlink: %s\n",
436                          outfile);
437                 error |= DWG_ERR_IOERROR;
438               }
439           }
440       }
441   }
442   if (!dat.fh)
443     {
444       fprintf (stderr, "WRITE ERROR %s\n", outfile);
445       error |= DWG_ERR_IOERROR;
446     }
447   else
448     {
449       error |= binary
450         ? dwg_write_dxfb (&dat, &dwg)
451         : dwg_write_dxf (&dat, &dwg);
452     }
453   if (dat.fh)
454     fclose (dat.fh);
455 
456   free:
457 #if defined __SANITIZE_ADDRESS__ || __has_feature(address_sanitizer)
458   {
459     char *asanenv = getenv("ASAN_OPTIONS");
460     if (!asanenv)
461       force_free = 1;
462     // detect_leaks is enabled by default. see if it's turned off
463     else if (strstr (asanenv, "detect_leaks=0") == NULL) /* not found */
464       force_free = 1;
465   }
466 #endif
467   // forget about leaks. really huge DWG's need endlessly here.
468   if ((dwg.header.version && dwg.num_objects < 1)
469       || force_free
470 #ifdef HAVE_VALGRIND_VALGRIND_H
471       || (RUNNING_ON_VALGRIND)
472 #endif
473       )
474     {
475       dwg_free (&dwg);
476     }
477 
478 #ifdef __xxAFL_HAVE_MANUAL_CONTROL
479   } // __AFL_LOOP(1000) persistent mode
480 #endif
481 
482   if (error >= DWG_ERR_CRITICAL)
483     {
484       fprintf (stderr, "ERROR 0x%x\n", error);
485       if (error && opts > 2)
486         dwg_errstrings (error);
487     }
488   else
489     {
490       if (opts > 1)
491         {
492           fprintf (stderr, "SUCCESS 0x%x\n", error);
493           if (error && opts > 2)
494             dwg_errstrings (error);
495         }
496       else
497         fprintf (stderr, "SUCCESS\n");
498     }
499 
500   if (free_outfile)
501     free (outfile);
502   return error >= DWG_ERR_CRITICAL ? 1 : 0;
503 }
504