diff options
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Cut text between characters, never inside one
The index description was cut at a byte count, which could split a multibyte character and leave an invalid sequence on the page. Commit subjects already backed up to the start of the character, so html_ntxt now does that for every caller.
-rw-r--r--source/html.c6
-rw-r--r--source/html.h5
-rw-r--r--source/ui-shared.c8
-rwxr-xr-xtests/t0101-index.sh14
-rwxr-xr-xtests/t0103-log.sh10
5 files changed, 34 insertions, 9 deletions
diff --git a/source/html.c b/source/html.c
index 1a6b7c5..b6e62a6 100644
--- a/source/html.c
+++ b/source/html.c
@@ -247,6 +247,12 @@ ssize_t html_ntxt(const char *txt, size_t len)
}
p++;
}
+ // A cut inside a multibyte character would leave an invalid sequence
+ // on the page, so it backs up to where that character starts. The
+ // escaped bytes are all ASCII, so no character spans one.
+ if (left < 0)
+ while (p > txt && (*p & 0xC0) == 0x80)
+ p--;
if (p != txt)
html_raw(txt, p - txt);
return left;
diff --git a/source/html.h b/source/html.h
index 095804c..c6c0e19 100644
--- a/source/html.h
+++ b/source/html.h
@@ -55,8 +55,9 @@ extern void html_attrf(const char *format, ...);
extern void html_txt(const char *txt);
/*
- * Escape at most len bytes of txt. The return is what is left of that budget,
- * and a caller prints an ellipsis when it comes back negative.
+ * Escape at most len bytes of txt, cutting short of a character the budget
+ * would split. The return is what is left of that budget, and a caller prints
+ * an ellipsis when it comes back negative.
*/
extern ssize_t html_ntxt(const char *txt, size_t len);
diff --git a/source/ui-shared.c b/source/ui-shared.c
index 7d28c39..f3d815f 100644
--- a/source/ui-shared.c
+++ b/source/ui-shared.c
@@ -1039,13 +1039,7 @@ void cgit_commit_link(const char *name, const char *title, const char *class,
html("'>");
if (name && name[0] != '\0') {
if (ctx.cfg.max_msg_len >= MIN_TRUNCATE_LEN && strlen(name) > (size_t)ctx.cfg.max_msg_len) {
- size_t len = ctx.cfg.max_msg_len - ELLIPSIS_LEN;
-
- // A cut inside a multibyte character would leave an
- // invalid sequence on the page.
- while (len > 0 && (name[len] & 0xC0) == 0x80)
- len--;
- html_ntxt(name, len);
+ html_ntxt(name, ctx.cfg.max_msg_len - ELLIPSIS_LEN);
html("...");
} else {
html_txt(name);
diff --git a/tests/t0101-index.sh b/tests/t0101-index.sh
index adad6be..e433beb 100755
--- a/tests/t0101-index.sh
+++ b/tests/t0101-index.sh
@@ -53,4 +53,18 @@ test_expect_success 'the feed link encodes the repository and the branch' '
grep "title=.Atom feed. href=.http://example.org/foo%2Bbar/atom/?h=1%2B2." tmp
'
+# The description limit counts bytes, so a cut has to land between
+# characters rather than leave half of one on the page.
+test_expect_success 'a cut description keeps whole characters' '
+ {
+ echo "max-repodesc-length=10" &&
+ sed "s/^cache-size=.*/cache-size=0/" cgitrc &&
+ echo "repo.url=mb" &&
+ echo "repo.path=$PWD/repos/foo/.git" &&
+ echo "repo.desc=日本語の説明文です"
+ } >descrc &&
+ CGIT_CONFIG="$PWD/descrc" QUERY_STRING="url=" cgit >tmp &&
+ grep ">日本語\.\.\.</a>" tmp
+'
+
test_done
diff --git a/tests/t0103-log.sh b/tests/t0103-log.sh
index ed92a76..f9612e6 100755
--- a/tests/t0103-log.sh
+++ b/tests/t0103-log.sh
@@ -91,4 +91,14 @@ test_expect_success 'the pager of a followed log keeps the name followed' '
grep "href=./fol/log/b.txt?ofs=6&amp;follow=1.>\[next\]" tmp
'
+test_expect_success 'a cut subject keeps whole characters' '
+ git -C repos/foo commit --allow-empty -m "日本語の説明文です日本語の説明文です" &&
+ {
+ echo "max-message-length=20" &&
+ sed "s/^cache-size=.*/cache-size=0/" cgitrc
+ } >subjectrc &&
+ CGIT_CONFIG="$PWD/subjectrc" QUERY_STRING="url=foo/log/" cgit >tmp &&
+ grep ">日本語の説\.\.\.</a>" tmp
+'
+
test_done