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