libredwg-sys 0.16.0

Raw FFI bindings to the LibreDWG C sources (a vendored subset of upstream carrying two local patches, see NOTICE.md), built natively via cc + bindgen (no Emscripten/WASM).
Documentation
--- a/src/common.c
+++ b/src/common.c
@@ -530,6 +530,38 @@
   return -1; // not found
 }
 
+/* --- uncad local patch, 2026-09-23 (NOTICE.md at the root of this crate,
+   "The two changes"; the full reasoning is in the project's docs/CAVEATS.md,
+   "Local patches to the vendored LibreDWG", which is not in the published
+   tarball) ---------------------------------------------------------------
+
+   Every caller of cvt_TIMEBLL() hands the struct tm straight to strftime()
+   (dec_macros.h's FIELD_TIMEBLL/FIELD_TIMERLL and the DECODER block in
+   header_variables.spec, which runs at any log level). Microsoft's UCRT
+   strftime() *validates* its struct tm: measured against ucrtbase.dll, a
+   tm_year outside [-1900, 8099], tm_mon outside [0, 11], tm_mday outside
+   [1, 31], tm_hour outside [0, 23], tm_min outside [0, 59] or tm_sec
+   outside [0, 60] trips the invalid-parameter handler, which fail-fasts the
+   whole process with 0xC0000409 -- reported as STATUS_STACK_BUFFER_OVERRUN
+   although nothing overran. That is below the FFI boundary, so no Rust
+   guard can catch it.
+
+   A TIMEBLL is two raw BLs off the bit stream (BITCODE_BL, unsigned 32-bit
+   days and milliseconds), so a corrupt or truncated DWG -- or any mutation
+   that shifts the header bit stream before TDUCREATE/TDUUPDATE -- reaches
+   here with a date the Julian-day arithmetic below turns into an
+   out-of-range tm_year/tm_mon/tm_hour. Upstream additionally leaves
+   tm_wday/tm_yday/tm_isdst at whatever the caller's stack held.
+
+   So: zero the struct first, and clamp every field into the range strftime
+   accepts before returning. A real drawing's date already satisfies every
+   bound, so no valid file's parse changes. The one visible difference is
+   the debug string for a >24h TDINDWG/TDUSRTIMER *duration*, whose hour is
+   now capped at 23 -- a LOG_TRACE line only (this crate never raises
+   loglevel), and strftime cannot print an hour above 23 anyway. */
+#define UNCAD_TM_CLAMP(v, lo, hi)                                             \
+  ((v) < (lo) ? (lo) : ((v) > (hi) ? (hi) : (v)))
+
 /* from my dwg11.c, 1995 - rurban */
 struct tm *
 cvt_TIMEBLL (struct tm *tm, BITCODE_TIMEBLL date)
@@ -539,6 +571,7 @@
 
 #define TRUNC(n) (long)floor (n)
 
+  memset (tm, 0, sizeof (struct tm)); /* uncad local patch */
   t = date.ms / 1000.0; // ms within the day, in seconds
   if (date.days > 2299161)
     {
@@ -583,6 +616,16 @@
   ss = t - (tm->tm_min * 60.0);
   tm->tm_sec = (int)ss;
   // sprintf (s, "%02d.%02d.%4d  %02d:%02d:%05.2f", d, m, y, hh, mm, ss);
+  /* uncad local patch: keep the result inside strftime's accepted ranges */
+  tm->tm_sec = UNCAD_TM_CLAMP (tm->tm_sec, 0, 60);
+  tm->tm_min = UNCAD_TM_CLAMP (tm->tm_min, 0, 59);
+  tm->tm_hour = UNCAD_TM_CLAMP (tm->tm_hour, 0, 23);
+  tm->tm_mday = UNCAD_TM_CLAMP (tm->tm_mday, 1, 31);
+  tm->tm_mon = UNCAD_TM_CLAMP (tm->tm_mon, 0, 11);
+  tm->tm_year = UNCAD_TM_CLAMP (tm->tm_year, -1900, 8099);
+  tm->tm_wday = UNCAD_TM_CLAMP (tm->tm_wday, 0, 6);
+  tm->tm_yday = UNCAD_TM_CLAMP (tm->tm_yday, 0, 365);
+  tm->tm_isdst = 0;
   return tm;
 }