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