A language tag longer than 35 characters is still a tag

BCP 47 sets no maximum length; 35 characters is only the least a protocol
must hold. A well formed tag such as en-US-u-ca-gregory-nu-latn-co-phonebk
was left out of both exports. The bound is now 255, kept only so a PDF's
/Lang can't put an unbounded string in the file.
This commit is contained in:
oracola91 committed 2026-11-04 23:00:11 +02:00
1 parent a699e13305
commit 0cae1c518b
2 files changed
+12 -3

No files matched your search

+6 -2
View File
@@ -8,14 +8,18 @@ pub(crate) fn allowed(c: char) -> bool {
matches!(c, '\t' | '\n' | '\r') || !(c.is_ascii_control() || c == '\u{FFFE}' || c == '\u{FFFF}')
}
/// The longest language tag the exports write. BCP 47 sets no maximum (35 characters is only
/// the least a protocol must hold), so this bounds only what a PDF's `/Lang` can put in a file.
const LONGEST_TAG: usize = 255;
/// `lang` when it is a well formed language tag (BCP 47, as a PDF's `/Lang` holds one): a primary
/// language of two or three letters, then subtags of one to eight letters or digits, joined by
/// hyphens, 35 characters at most. Anything else names no language.
/// hyphens, [`LONGEST_TAG`] characters at most. Anything else names no language.
pub(crate) fn language_tag(lang: Option<&str>) -> Option<&str> {
let tag = lang?.trim();
let mut subtags = tag.split('-');
let primary = subtags.next()?;
let well_formed = tag.len() <= 35
let well_formed = tag.len() <= LONGEST_TAG
&& (2..=3).contains(&primary.len())
&& primary.bytes().all(|b| b.is_ascii_alphabetic())
&& subtags.all(|s| (1..=8).contains(&s.len()) && s.bytes().all(|b| b.is_ascii_alphanumeric()));
+6 -1
View File
@@ -260,6 +260,11 @@ fn word_and_html_take_the_documents_language() {
assert!(styles(Some("ar-EG")).contains(&defaults("<w:lang w:bidi=\"ar-EG\"/>")), "right-to-left text");
assert!(styles(Some("HE")).contains("<w:lang w:bidi=\"HE\"/>"), "right-to-left text");
assert!(html_with_language(&pages, "Report", Some("bg-BG")).starts_with("<!DOCTYPE html>\n<html lang=\"bg-BG\">\n"));
// A tag longer than 35 characters is still a tag.
let long = "en-US-u-ca-gregory-nu-latn-co-phonebk";
assert!(styles(Some(long)).contains(&format!("<w:lang w:val=\"{long}\"/>")), "{long}");
assert!(html_with_language(&pages, "Report", Some(long)).contains(&format!("<html lang=\"{long}\">")), "{long}");
let too_long = format!("en{}", "-abcdefgh".repeat(29));
for lang in [
None,
Some(""),
@@ -270,7 +275,7 @@ fn word_and_html_take_the_documents_language() {
Some("bg--BG"),
Some("bg-"),
Some("en-US-abcdefghi"),
Some("en-abcdefgh-abcdefgh-abcdefgh-abcdefgh"),
Some(too_long.as_str()),
Some("en-\"x\""),
Some("1a-BG"),
Some("\u{431}\u{433}-BG"),