std/jpeg: add QUIRK_REJECT_PROGRESSIVE_JPEGS
diff --git a/doc/note/quirks.md b/doc/note/quirks.md
index 4606c52..1afa484 100644
--- a/doc/note/quirks.md
+++ b/doc/note/quirks.md
@@ -76,6 +76,7 @@
 Package-specific quirks:
 
 - [GIF image decoder quirks](/std/gif/decode_quirks.wuffs)
+- [JPEG decoder quirks](/std/jpeg/decode_quirks.wuffs)
 - [JSON decoder quirks](/std/json/decode_quirks.wuffs)
 - [LZMA decoder quirks](/std/lzma/decode_quirks.wuffs)
 - [LZW decoder quirks](/std/lzw/decode_quirks.wuffs)
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 8750fab..b3c4abb 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -9664,6 +9664,7 @@
 extern const char wuffs_jpeg__error__bad_scan_count[];
 extern const char wuffs_jpeg__error__missing_huffman_table[];
 extern const char wuffs_jpeg__error__missing_quantization_table[];
+extern const char wuffs_jpeg__error__rejected_progressive_jpeg[];
 extern const char wuffs_jpeg__error__truncated_input[];
 extern const char wuffs_jpeg__error__unsupported_arithmetic_coding[];
 extern const char wuffs_jpeg__error__unsupported_color_model[];
@@ -9681,6 +9682,8 @@
 
 #define WUFFS_JPEG__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 51552191232u
 
+#define WUFFS_JPEG__QUIRK_REJECT_PROGRESSIVE_JPEGS 1220532224u
+
 // ---------------- Struct Declarations
 
 typedef struct wuffs_jpeg__decoder__struct wuffs_jpeg__decoder;
@@ -9908,6 +9911,7 @@
     bool f_bitstream_is_closed;
     bool f_expect_multiple_scans;
     bool f_use_lower_quality;
+    bool f_reject_progressive_jpegs;
     bool f_swizzle_immediately;
     wuffs_base__status f_swizzle_immediately_status;
     uint32_t f_swizzle_immediately_b_offsets[10];
@@ -44300,6 +44304,7 @@
 const char wuffs_jpeg__error__bad_scan_count[] = "#jpeg: bad scan count";
 const char wuffs_jpeg__error__missing_huffman_table[] = "#jpeg: missing Huffman table";
 const char wuffs_jpeg__error__missing_quantization_table[] = "#jpeg: missing Quantization table";
+const char wuffs_jpeg__error__rejected_progressive_jpeg[] = "#jpeg: rejected progressive JPEG";
 const char wuffs_jpeg__error__truncated_input[] = "#jpeg: truncated input";
 const char wuffs_jpeg__error__unsupported_arithmetic_coding[] = "#jpeg: unsupported arithmetic coding";
 const char wuffs_jpeg__error__unsupported_color_model[] = "#jpeg: unsupported color model";
@@ -44538,6 +44543,8 @@
   248u, 249u, 250u,
 };
 
+#define WUFFS_JPEG__QUIRKS_BASE 1220532224u
+
 // ---------------- Private Initializer Prototypes
 
 // ---------------- Private Function Prototypes
@@ -46455,6 +46462,10 @@
     if (self->private_impl.f_use_lower_quality) {
       return 18446744073709551615u;
     }
+  } else if (a_key == 1220532224u) {
+    if (self->private_impl.f_reject_progressive_jpegs) {
+      return 1u;
+    }
   }
   return 0u;
 }
@@ -46480,6 +46491,9 @@
   if (a_key == 2u) {
     self->private_impl.f_use_lower_quality = (a_value >= 9223372036854775808u);
     return wuffs_base__make_status(NULL);
+  } else if (a_key == 1220532224u) {
+    self->private_impl.f_reject_progressive_jpegs = (a_value != 0u);
+    return wuffs_base__make_status(NULL);
   }
   return wuffs_base__make_status(wuffs_base__error__unsupported_option);
 }
@@ -46689,7 +46703,10 @@
         goto exit;
       } else if (v_marker < 208u) {
         if (v_marker <= 194u) {
-          if (self->private_impl.f_sof_marker != 0u) {
+          if ((v_marker == 194u) && self->private_impl.f_reject_progressive_jpegs) {
+            status = wuffs_base__make_status(wuffs_jpeg__error__rejected_progressive_jpeg);
+            goto exit;
+          } else if (self->private_impl.f_sof_marker != 0u) {
             status = wuffs_base__make_status(wuffs_jpeg__error__bad_sof_marker);
             goto exit;
           }
diff --git a/std/jpeg/decode_jpeg.wuffs b/std/jpeg/decode_jpeg.wuffs
index 5616ec1..12e7a8d 100644
--- a/std/jpeg/decode_jpeg.wuffs
+++ b/std/jpeg/decode_jpeg.wuffs
@@ -18,6 +18,7 @@
 pub status "#bad scan count"
 pub status "#missing Huffman table"
 pub status "#missing Quantization table"
+pub status "#rejected progressive JPEG"
 pub status "#truncated input"
 pub status "#unsupported arithmetic coding"
 pub status "#unsupported color model"
@@ -281,7 +282,8 @@
 
         expect_multiple_scans : base.bool,
 
-        use_lower_quality : base.bool,
+        use_lower_quality        : base.bool,
+        reject_progressive_jpegs : base.bool,
 
         swizzle_immediately           : base.bool,
         swizzle_immediately_status    : base.status,
@@ -342,40 +344,21 @@
         if this.use_lower_quality {
             return 0xFFFF_FFFF_FFFF_FFFF
         }
+    } else if args.key == QUIRK_REJECT_PROGRESSIVE_JPEGS {
+        if this.reject_progressive_jpegs {
+            return 1
+        }
     }
     return 0
 }
 
-// If key is base.QUIRK_QUALITY then, when re-interpreting the value as a
-// signed i64 value, a negative quirk value opts the decoder in to a slightly
-// worse quality but leaner (less memory required) chroma upsampling algorithm.
-//
-// Negative (in i64 space) means >= 0x8000_0000_0000_0000 (in u64 space).
-//
-// Specifically, a negative quirk value uses a box filter. A non-negative quirk
-// value (including the default value, zero) uses the same triangle filter that
-// libjpeg-turbo also uses by default, which requires chroma upsampling on the
-// edges of a MCU (Minimum Coded Unit) to remember adjacent MCU's values.
-//
-// Leaner means that for common (sequential) JPEG images, the range returned by
-// decoder.workbuf_len can have a minimum value of zero, meaning that no work
-// buffer memory needs to be allocated. Decoding rarer (progressive) JPEGs will
-// still require a positive amount of work buffer memory. The exact amount
-// required depends on factors like the JPEG image's width, height, component
-// count and subsampling ratios.
-//
-// Regardless of the quirk value, as always, the workbuf_len method returns a
-// range, whose min and max can be different. The caller is able to choose
-// between providing the minimal amount of work buffer memory (for slightly
-// slower decoding) or the maximal amount (for slightly faster decoding).
-//
-// Callers that never wish to allocate work buffer memory can simply reject
-// progressive JPEGs (where workbuf_len().min_incl > 0 despite setting the
-// base.QUIRK_QUALITY key to a negative quirk value) as unsupported.
 pub func decoder.set_quirk!(key: base.u32, value: base.u64) base.status {
     if args.key == base.QUIRK_QUALITY {
         this.use_lower_quality = args.value >= 0x8000_0000_0000_0000
         return ok
+    } else if args.key == QUIRK_REJECT_PROGRESSIVE_JPEGS {
+        this.reject_progressive_jpegs = args.value <> 0
+        return ok
     }
     return base."#unsupported option"
 }
@@ -454,7 +437,9 @@
 
         } else if marker < 0xD0 {  // SOFn (Start of Frame) and friends.
             if marker <= 0xC2 {
-                if this.sof_marker <> 0 {
+                if (marker == 0xC2) and this.reject_progressive_jpegs {
+                    return "#rejected progressive JPEG"
+                } else if this.sof_marker <> 0 {
                     return "#bad SOF marker"
                 }
                 this.sof_marker = marker
diff --git a/std/jpeg/decode_quirks.wuffs b/std/jpeg/decode_quirks.wuffs
new file mode 100644
index 0000000..1676947
--- /dev/null
+++ b/std/jpeg/decode_quirks.wuffs
@@ -0,0 +1,77 @@
+// Copyright 2024 The Wuffs Authors.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+//
+// SPDX-License-Identifier: Apache-2.0 OR MIT
+
+// --------
+
+// Quirks are discussed in (/doc/note/quirks.md).
+//
+// The base38 encoding of "jpeg" is 0x122FF6. Left shifting by 10 gives
+// 0x48BF_D800.
+pri const QUIRKS_BASE : base.u32 = 0x48BF_D800
+
+// --------
+
+// When this quirk value is non-zero, the decoder will not support progressive
+// JPEG images. When combined with setting base.QUIRK_QUALITY to "lower
+// quality", this means that decoding will never require any temporary "work
+// buffer" memory: decoder.workbuf_len will always return a range (a min and
+// max pair) whose minimum value is zero.
+//
+// The maximum value can still be positive, as using more memory might lead to
+// faster decoding, but a zero minimum means that it's valid for the caller to
+// not allocate any memory for a work buffer.
+//
+// When opted into lower quality decoding, the workbuf_len minimum will be zero
+// and positive for sequential and progressive JPEGs respectively, regardless
+// of this QUIRK_REJECT_PROGRESSIVE_JPEGS key's value. But setting this quirk
+// explicitly will produce a "#rejected progressive JPEG" error when
+// encountering a progressive JPEG. This results in more readable calling code,
+// instead of commenting about "positive work buffer minimum length implies a
+// progressive JPEG".
+pub const QUIRK_REJECT_PROGRESSIVE_JPEGS : base.u32 = 0x48BF_D800 | 0x00
+
+// --------
+
+// The base.QUIRK_QUALITY key is defined in the base package, not this package.
+// Still, here's some documentation on how this package responds to that (key,
+// value) quirk pair.
+//
+// If decoder.set_quirk is passed a base.QUIRK_QUALITY as the key argument
+// then, when re-interpreting the u64 value argument as a signed i64 number, a
+// negative number opts the decoder in to a slightly worse quality but leaner
+// (less memory required) chroma upsampling algorithm.
+//
+// Negative (in i64 space) means >= 0x8000_0000_0000_0000 (in u64 space).
+// base.QUIRK_QUALITY__VALUE__LOWER_QUALITY is one such value.
+//
+// Specifically, a negative quirk value uses a box filter. A non-negative quirk
+// value (including the default value, zero) uses the same triangle filter that
+// libjpeg-turbo also uses by default, which requires chroma upsampling on the
+// edges of a MCU (Minimum Coded Unit) to remember adjacent MCU's values.
+//
+// Leaner means that for common (sequential) JPEG images, the range returned by
+// decoder.workbuf_len will have a minimum value of zero, meaning that no work
+// buffer memory needs to be allocated. Decoding rarer (progressive) JPEGs will
+// still always require a positive amount of work buffer memory, for reasons
+// unrelated to chroma upsampling. The exact amount required depends on factors
+// like the JPEG image's width, height, component count and subsampling ratios.
+//
+// Regardless of the quirk value, as always, the workbuf_len method returns a
+// range, whose min and max can be different. The caller is able to choose
+// between providing the minimal amount of work buffer memory (for slightly
+// slower decoding) or the maximal amount (for slightly faster decoding).
+//
+// Callers that never wish to allocate work buffer memory can simply reject
+// progressive JPEGs (where "workbuf_len().min_incl > 0" despite setting the
+// base.QUIRK_QUALITY key to a negative quirk value) as unsupported. See also
+// QUIRK_REJECT_PROGRESSIVE_JPEGS.
+//
+// For some pictures comparing box and triangle filter decodings, see
+// https://nigeltao.github.io/blog/2024/jpeg-chroma-upsampling.html