bison: tighten up output file names

Problem reported by Michał Majchrowicz.
* src/parse-gram.y: Do not allow '/' in %header and %output directives.
diff --git a/THANKS b/THANKS
index a016b28..0b3f6fa 100644
--- a/THANKS
+++ b/THANKS
@@ -131,6 +131,7 @@
 Michael Felt              mamfelt@gmail.com
 Michael Hayes             m.hayes@elec.canterbury.ac.nz
 Michael Raskin            7c6f434c@mail.ru
+Michał Majchrowicz        mmajchrowicz@afine.com
 Michel d'Hooge            michel.dhooge@gmail.com
 Michiel De Wilde          mdewilde.agilent@gmail.com
 Mickael Labau             labau_m@epita.fr
diff --git a/doc/bison.texi b/doc/bison.texi
index a72fdd6..53d14e2 100644
--- a/doc/bison.texi
+++ b/doc/bison.texi
@@ -6026,6 +6026,7 @@
 
 @deffn {Directive} %header @var{header-file}
 Same as above, but save in the file @file{@var{header-file}}.
+The @var{header-file} name should not contain slashes.
 @end deffn
 
 @deffn {Directive} %language "@var{language}"
@@ -6079,6 +6080,7 @@
 
 @deffn {Directive} %output "@var{file}"
 Generate the parser implementation in @file{@var{file}}.
+The @var{file} name should not contain slashes.
 @end deffn
 
 @deffn {Directive} %pure-parser
diff --git a/src/parse-gram.c b/src/parse-gram.c
index 9ebc1c4..14ddf5e 100644
--- a/src/parse-gram.c
+++ b/src/parse-gram.c
@@ -1,9 +1,9 @@
-/* A Bison parser, made by GNU Bison 3.8.2.30-82269.  */
+/* A Bison parser, made by GNU Bison 3.8.2.66-3169-modified.  */
 
 /* Bison implementation for Yacc-like parsers in C
 
-   Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2022, 2025-2026 Free
-   Software Foundation, Inc.
+   Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2022, 2025-2026 Free Software
+   Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -49,7 +49,7 @@
 #define YYBISON 30802
 
 /* Bison version string.  */
-#define YYBISON_VERSION "3.8.2.30-82269"
+#define YYBISON_VERSION "3.8.2.66-3169-modified"
 
 /* Skeleton name.  */
 #define YYSKELETON_NAME "yacc.c"
@@ -273,8 +273,11 @@
      string from the scanner (should be CODE). */
   static char const *translate_code_braceless (char *code, location loc);
 
+  /* Is FILE a valid output file name?  */
+  static bool valid_output_file_name (char const *file);
+
   /* Handle a %header directive.  */
-  static void handle_header (char const *value);
+  static void handle_header (location const *loc, char const *value);
 
   /* Handle a %error-verbose directive.  */
   static void handle_error_verbose (location const *loc, char const *directive);
@@ -658,19 +661,19 @@
 /* YYRLINE[YYN] -- Source line where rule number YYN was defined.  */
 static const yytype_int16 yyrline[] =
 {
-       0,   310,   310,   319,   320,   324,   325,   331,   335,   340,
-     341,   342,   343,   344,   345,   350,   355,   356,   357,   358,
-     359,   360,   360,   361,   362,   363,   364,   365,   366,   367,
-     368,   372,   373,   382,   383,   387,   398,   402,   406,   414,
-     424,   425,   435,   436,   442,   455,   455,   460,   460,   465,
-     465,   470,   480,   481,   482,   483,   488,   489,   493,   494,
-     499,   500,   504,   505,   509,   510,   511,   524,   533,   537,
-     541,   549,   550,   554,   567,   568,   573,   574,   575,   593,
-     597,   601,   609,   611,   616,   623,   633,   637,   641,   649,
-     655,   668,   669,   675,   676,   677,   684,   684,   692,   693,
-     694,   699,   702,   704,   706,   708,   710,   712,   714,   716,
-     718,   723,   724,   733,   757,   758,   759,   760,   772,   774,
-     798,   803,   804,   809,   817,   818
+       0,   314,   314,   323,   324,   328,   329,   335,   339,   344,
+     345,   346,   347,   348,   349,   354,   359,   360,   361,   362,
+     363,   372,   372,   373,   374,   375,   376,   377,   378,   379,
+     380,   384,   385,   394,   395,   399,   410,   414,   418,   426,
+     436,   437,   447,   448,   454,   467,   467,   472,   472,   477,
+     477,   482,   492,   493,   494,   495,   500,   501,   505,   506,
+     511,   512,   516,   517,   521,   522,   523,   536,   545,   549,
+     553,   561,   562,   566,   579,   580,   585,   586,   587,   605,
+     609,   613,   621,   623,   628,   635,   645,   649,   653,   661,
+     667,   680,   681,   687,   688,   689,   696,   696,   704,   705,
+     706,   711,   714,   716,   718,   720,   722,   724,   726,   728,
+     730,   735,   736,   745,   769,   770,   771,   772,   784,   786,
+     810,   815,   816,   821,   829,   830
 };
 #endif
 
@@ -2099,7 +2102,7 @@
     break;
 
   case 9: /* prologue_declaration: "%header" string.opt  */
-                                   { handle_header ((yyvsp[0].yykind_75)); }
+                                   { handle_header (&(yylsp[0]), (yyvsp[0].yykind_75)); }
     break;
 
   case 10: /* prologue_declaration: "%error-verbose"  */
@@ -2149,7 +2152,14 @@
     break;
 
   case 20: /* prologue_declaration: "%output" "string"  */
-                                { spec_outfile = unquote ((yyvsp[0].STRING)); gram_scanner_last_string_free (); }
+    {
+      char *file = unquote ((yyvsp[0].STRING));
+      if (valid_output_file_name (file))
+        spec_outfile = file;
+      else
+        complain (&(yylsp[0]), complaint, _("invalid %%output file name ignored"));
+      gram_scanner_last_string_free ();
+    }
     break;
 
   case 21: /* $@1: %empty  */
@@ -2969,14 +2979,24 @@
 }
 
 
+static bool
+valid_output_file_name (char const *file)
+{
+  return !strchr (file, '/');
+}
+
+
 static void
-handle_header (char const *value)
+handle_header (location const *loc, char const *value)
 {
   header_flag = true;
   if (value)
     {
       char *file = unquote (value);
-      spec_header_file = xstrdup (file);
+      if (valid_output_file_name (file))
+        spec_header_file = xstrdup (file);
+      else
+        complain (loc, complaint, _("invalid %%header file name ignored"));
       gram_scanner_last_string_free ();
       unquote_free (file);
     }
diff --git a/src/parse-gram.h b/src/parse-gram.h
index 5170e08..9c21d19 100644
--- a/src/parse-gram.h
+++ b/src/parse-gram.h
@@ -1,9 +1,9 @@
-/* A Bison parser, made by GNU Bison 3.8.2.30-82269.  */
+/* A Bison parser, made by GNU Bison 3.8.2.66-3169-modified.  */
 
 /* Bison interface for Yacc-like parsers in C
 
-   Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2022 Free Software Foundation,
-   Inc.
+   Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2022, 2025-2026 Free Software
+   Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
diff --git a/src/parse-gram.y b/src/parse-gram.y
index d38b16c..a775339 100644
--- a/src/parse-gram.y
+++ b/src/parse-gram.y
@@ -96,8 +96,11 @@
      string from the scanner (should be CODE). */
   static char const *translate_code_braceless (char *code, location loc);
 
+  /* Is FILE a valid output file name?  */
+  static bool valid_output_file_name (char const *file);
+
   /* Handle a %header directive.  */
-  static void handle_header (char const *value);
+  static void handle_header (location const *loc, char const *value);
 
   /* Handle a %error-verbose directive.  */
   static void handle_error_verbose (location const *loc, char const *directive);
@@ -338,7 +341,7 @@
       muscle_percent_define_insert ($2, @$, $3.kind, $3.chars,
                                     MUSCLE_PERCENT_DEFINE_GRAMMAR_FILE);
     }
-| "%header" string.opt             { handle_header ($2); }
+| "%header" string.opt             { handle_header (&@2, $2); }
 | "%error-verbose"                 { handle_error_verbose (&@$, $1); }
 | "%expect" INT_LITERAL            { expected_sr_conflicts = $2; }
 | "%expect-rr" INT_LITERAL         { expected_rr_conflicts = $2; }
@@ -357,7 +360,15 @@
 | "%name-prefix" STRING         { handle_name_prefix (&@$, $1, $2); }
 | "%no-lines"                   { no_lines_flag = true; }
 | "%nondeterministic-parser"    { nondeterministic_parser = true; }
-| "%output" STRING              { spec_outfile = unquote ($2); gram_scanner_last_string_free (); }
+| "%output" STRING
+    {
+      char *file = unquote ($2);
+      if (valid_output_file_name (file))
+        spec_outfile = file;
+      else
+        complain (&@2, complaint, _("invalid %%output file name ignored"));
+      gram_scanner_last_string_free ();
+    }
 | "%param" { current_param = $1; } params { current_param = param_none; }
 | "%pure-parser"                { handle_pure_parser (&@$, $1); }
 | "%require" STRING             { handle_require (&@2, $2); }
@@ -953,14 +964,24 @@
 }
 
 
+static bool
+valid_output_file_name (char const *file)
+{
+  return !strchr (file, '/');
+}
+
+
 static void
-handle_header (char const *value)
+handle_header (location const *loc, char const *value)
 {
   header_flag = true;
   if (value)
     {
       char *file = unquote (value);
-      spec_header_file = xstrdup (file);
+      if (valid_output_file_name (file))
+        spec_header_file = xstrdup (file);
+      else
+        complain (loc, complaint, _("invalid %%header file name ignored"));
       gram_scanner_last_string_free ();
       unquote_free (file);
     }