Loosen what std/gif allows as the trailer byte
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 4467fdc..76de842 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -5644,7 +5644,6 @@
 
 // ---------------- Status Codes
 
-extern const char* wuffs_gif__error__bad_block;
 extern const char* wuffs_gif__error__bad_extension_label;
 extern const char* wuffs_gif__error__bad_frame_size;
 extern const char* wuffs_gif__error__bad_graphic_control;
@@ -16874,7 +16873,6 @@
 
 // ---------------- Status Codes Implementations
 
-const char* wuffs_gif__error__bad_block = "#gif: bad block";
 const char* wuffs_gif__error__bad_extension_label = "#gif: bad extension label";
 const char* wuffs_gif__error__bad_frame_size = "#gif: bad frame size";
 const char* wuffs_gif__error__bad_graphic_control = "#gif: bad graphic control";
@@ -18055,7 +18053,7 @@
           goto suspend;
         }
         goto label__0__break;
-      } else if (v_block_type == 59) {
+      } else {
         if (self->private_impl.f_delayed_num_decoded_frames) {
           self->private_impl.f_delayed_num_decoded_frames = false;
           wuffs_base__u64__sat_add_indirect(
@@ -18063,9 +18061,6 @@
         }
         self->private_impl.f_end_of_data = true;
         goto label__0__break;
-      } else {
-        status = wuffs_base__make_status(wuffs_gif__error__bad_block);
-        goto exit;
       }
     }
   label__0__break:;
@@ -19928,7 +19923,7 @@
           goto suspend;
         }
         goto label__0__break;
-      } else if (v_block_type == 59) {
+      } else {
         if (self->private_impl.f_delayed_num_decoded_frames) {
           self->private_impl.f_delayed_num_decoded_frames = false;
           wuffs_base__u64__sat_add_indirect(
@@ -19936,9 +19931,6 @@
         }
         self->private_impl.f_end_of_data = true;
         goto label__0__break;
-      } else {
-        status = wuffs_base__make_status(wuffs_gif__error__bad_block);
-        goto exit;
       }
     }
   label__0__break:;
diff --git a/std/gif/common_consts.wuffs b/std/gif/common_consts.wuffs
index 7e9a816..e412b7e 100644
--- a/std/gif/common_consts.wuffs
+++ b/std/gif/common_consts.wuffs
@@ -14,7 +14,6 @@
 
 use "std/lzw"
 
-pub status "#bad block"
 pub status "#bad extension label"
 pub status "#bad frame size"
 pub status "#bad graphic control"
diff --git a/std/gif/decode_config.wuffs b/std/gif/decode_config.wuffs
index b61ebae..0061d00 100644
--- a/std/gif/decode_config.wuffs
+++ b/std/gif/decode_config.wuffs
@@ -423,15 +423,25 @@
 			}
 			this.decode_id_part0?(src: args.src)
 			break
-		} else if block_type == 0x3B {  // The spec calls 0x3B the "Trailer".
+		} else {
+			// If we don't have 0x21 or 0x2C then, according to the spec, the
+			// only valid byte is 0x3B, called the "Trailer". In practice, some
+			// other popular decoders allow anything (other than 0x21 or 0x2C)
+			// to be equivalent to 0x3B, which ends the animated GIF image, and
+			// we do likewise here. Some real world GIF files that exhibit this
+			// are at https://github.com/golang/go/issues/38853
+			//
+			// Chromium's decoder
+			// https://skia.googlesource.com/libgifcodec/+/c002ec500aba1e1b0189547629787cb02db78193/SkGifImageReader.cpp#563
+			//
+			// Firefox's decoder
+			// https://dxr.mozilla.org/mozilla-central/rev/93a33cb7f2369ac4f1d1f2ac97ec14ba60e1e7d7/image/decoders/nsGIFDecoder2.cpp#569
 			if this.delayed_num_decoded_frames {
 				this.delayed_num_decoded_frames = false
 				this.num_decoded_frames_value ~sat+= 1
 			}
 			this.end_of_data = true
 			break
-		} else {
-			return "#bad block"
 		}
 	} endwhile
 }
diff --git a/std/gif/decode_gif.wuffs b/std/gif/decode_gif.wuffs
index 87d779f..235a167 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -498,15 +498,25 @@
 			}
 			this.decode_id_part0?(src: args.src)
 			break
-		} else if block_type == 0x3B {  // The spec calls 0x3B the "Trailer".
+		} else {
+			// If we don't have 0x21 or 0x2C then, according to the spec, the
+			// only valid byte is 0x3B, called the "Trailer". In practice, some
+			// other popular decoders allow anything (other than 0x21 or 0x2C)
+			// to be equivalent to 0x3B, which ends the animated GIF image, and
+			// we do likewise here. Some real world GIF files that exhibit this
+			// are at https://github.com/golang/go/issues/38853
+			//
+			// Chromium's decoder
+			// https://skia.googlesource.com/libgifcodec/+/c002ec500aba1e1b0189547629787cb02db78193/SkGifImageReader.cpp#563
+			//
+			// Firefox's decoder
+			// https://dxr.mozilla.org/mozilla-central/rev/93a33cb7f2369ac4f1d1f2ac97ec14ba60e1e7d7/image/decoders/nsGIFDecoder2.cpp#569
 			if this.delayed_num_decoded_frames {
 				this.delayed_num_decoded_frames = false
 				this.num_decoded_frames_value ~sat+= 1
 			}
 			this.end_of_data = true
 			break
-		} else {
-			return "#bad block"
 		}
 	} endwhile
 }
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index 095b5ff..ca2ea1c 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -491,11 +491,13 @@
   CHECK_STATUS("set_from_slice", wuffs_base__pixel_buffer__set_from_slice(
                                      &pb, &ic.pixcfg, g_pixel_slice_u8));
 
-  wuffs_base__status status = wuffs_gif__decoder__decode_frame(
-      &dec, &pb, &src, WUFFS_BASE__PIXEL_BLEND__SRC, g_work_slice_u8, NULL);
-  if (status.repr != want_status) {
-    RETURN_FAIL("decode_frame: have \"%s\", want \"%s\"", status.repr,
-                want_status);
+  {
+    wuffs_base__status status = wuffs_gif__decoder__decode_frame(
+        &dec, &pb, &src, WUFFS_BASE__PIXEL_BLEND__SRC, g_work_slice_u8, NULL);
+    if (status.repr != want_status) {
+      RETURN_FAIL("decode_frame #0: have \"%s\", want \"%s\"", status.repr,
+                  want_status);
+    }
   }
 
   wuffs_base__rect_ie_u32 r = wuffs_gif__decoder__frame_dirty_rect(&dec);
@@ -505,6 +507,16 @@
                 dirty_rect_is_empty ? "true" : "false",
                 want_dirty_rect_is_empty ? "true" : "false");
   }
+
+  if (want_status == NULL) {
+    wuffs_base__status status = wuffs_gif__decoder__decode_frame(
+        &dec, &pb, &src, WUFFS_BASE__PIXEL_BLEND__SRC, g_work_slice_u8, NULL);
+    if (status.repr != wuffs_base__note__end_of_data) {
+      RETURN_FAIL("decode_frame #1: have \"%s\", want \"%s\"", status.repr,
+                  wuffs_base__note__end_of_data);
+    }
+  }
+
   return NULL;
 }
 
@@ -703,18 +715,14 @@
       .data = g_src_slice_u8,
   });
   CHECK_STRING(read_file(&src, "test/data/animated-red-blue.gif"));
-  if (src.meta.wi < 1) {
-    return "src file is too short";
-  }
 
-  // A GIF image should end with the 0x3B Trailer byte.
-  if (src.data.ptr[src.meta.wi - 1] != 0x3B) {
-    RETURN_FAIL("final byte: have 0x%02X, want 0x%02X",
-                src.data.ptr[src.meta.wi - 1], 0x3B);
+  // Truncate the final 0x3B trailer byte.
+  if (src.meta.wi <= 0) {
+    return "src file is too short";
+  } else if (src.data.ptr[src.meta.wi - 1] != 0x3B) {
+    return "src file does not end with 0x3B";
   }
-  // Replace that final byte with something invalid: neither 0x21 (Extension
-  // Introducer), 0x2C (Image Separator) or 0x3B (Trailer).
-  src.data.ptr[src.meta.wi - 1] = 0x99;
+  src.meta.wi--;
 
   int q;
   for (q = 0; q < 2; q++) {
@@ -1594,6 +1602,25 @@
 }
 
 const char*  //
+test_wuffs_gif_decode_nul_byte_trailer() {
+  CHECK_FOCUS(__func__);
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = g_src_slice_u8,
+  });
+  CHECK_STRING(read_file(&src, "test/data/pjw-thumbnail.gif"));
+
+  // Change the final 0x3B trailer byte to 0x00.
+  if (src.meta.wi <= 0) {
+    return "src file is too short";
+  } else if (src.data.ptr[src.meta.wi - 1] != 0x3B) {
+    return "src file does not end with 0x3B";
+  }
+  src.data.ptr[src.meta.wi - 1] = 0x00;
+
+  return do_test_wuffs_gif_decode_expecting(src, 0, NULL, false);
+}
+
+const char*  //
 test_wuffs_gif_decode_pixel_data_none() {
   CHECK_FOCUS(__func__);
   wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
@@ -2409,6 +2436,7 @@
     test_wuffs_gif_decode_missing_two_src_bytes,
     test_wuffs_gif_decode_multiple_graphic_controls,
     test_wuffs_gif_decode_multiple_loop_counts,
+    test_wuffs_gif_decode_nul_byte_trailer,
     test_wuffs_gif_decode_pixel_data_none,
     test_wuffs_gif_decode_pixel_data_not_enough,
     test_wuffs_gif_decode_pixel_data_too_much_sans_quirk,