convert: check for detectable errors in UTF encodings
Check that new content is valid with respect to the user defined 'working-tree-encoding' attribute. Signed-off-by: Lars Schneider <larsxschneider@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Lars Schneider committed
Apr 15, 2018 at 20:16 UTC
7a17918c34f4e83982456ffe22d880c3cda5384f
2 files changed
+123
convert.c
+61
@@ -266,6 +266,64 @@ static int will_convert_lf_to_crlf(size_t len, struct text_stat *stats,
266
267
}
268
269
+static int validate_encoding(const char *path, const char *enc,
270
+ const char *data, size_t len, int die_on_error)
271
+{
272
+ /* We only check for UTF here as UTF?? can be an alias for UTF-?? */
273
+ if (istarts_with(enc, "UTF")) {
274
+ /*
275
+ * Check for detectable errors in UTF encodings
276
+ */
277
+ if (has_prohibited_utf_bom(enc, data, len)) {
278
+ const char *error_msg = _(
279
+ "BOM is prohibited in '%s' if encoded as %s");
280
+ /*
281
+ * This advice is shown for UTF-??BE and UTF-??LE encodings.
282
+ * We cut off the last two characters of the encoding name
283
+ * to generate the encoding name suitable for BOMs.
284
+ */
285
+ const char *advise_msg = _(
286
+ "The file '%s' contains a byte order "
287
+ "mark (BOM). Please use UTF-%s as "
288
+ "working-tree-encoding.");
289
+ const char *stripped = NULL;
290
+ char *upper = xstrdup_toupper(enc);
291
+ upper[strlen(upper)-2] = '\0';
292
+ if (!skip_prefix(upper, "UTF-", &stripped))
293
+ skip_prefix(stripped, "UTF", &stripped);
294
+ advise(advise_msg, path, stripped);
295
+ free(upper);
296
+ if (die_on_error)
297
+ die(error_msg, path, enc);
298
+ else {
299
+ return error(error_msg, path, enc);
300
+ }
301
+
302
+ } else if (is_missing_required_utf_bom(enc, data, len)) {
303
+ const char *error_msg = _(
304
+ "BOM is required in '%s' if encoded as %s");
305
+ const char *advise_msg = _(
306
+ "The file '%s' is missing a byte order "
307
+ "mark (BOM). Please use UTF-%sBE or UTF-%sLE "
308
+ "(depending on the byte order) as "
309
+ "working-tree-encoding.");
310
+ const char *stripped = NULL;
311
+ char *upper = xstrdup_toupper(enc);
312
+ if (!skip_prefix(upper, "UTF-", &stripped))
313
+ skip_prefix(stripped, "UTF", &stripped);
314
+ advise(advise_msg, path, stripped, stripped);
315
+ free(upper);
316
+ if (die_on_error)
317
+ die(error_msg, path, enc);
318
+ else {
319
+ return error(error_msg, path, enc);
320
+ }
321
+ }
322
+
323
+ }
324
+ return 0;
325
+}
326
+
327
static const char *default_encoding = "UTF-8";
328
329
static int encode_to_git(const char *path, const char *src, size_t src_len,
@@ -291,6 +349,9 @@ static int encode_to_git(const char *path, const char *src, size_t src_len,
349
if (!buf && !src)
350
return 1;
351
352
+ if (validate_encoding(path, enc, src, src_len, die_on_error))
353
+ return 0;
354
+
355
dst = reencode_string_len(src, src_len, default_encoding, enc,
356
&dst_len);
357
if (!dst) {
t/t0028-working-tree-encoding.sh
+62
@@ -62,6 +62,52 @@ test_expect_success 'check $GIT_DIR/info/attributes support' '
62
63
for i in 16 32
64
do
65
+ test_expect_success "check prohibited UTF-${i} BOM" '
66
+ test_when_finished "git reset --hard HEAD" &&
67
+
68
+ echo "*.utf${i}be text working-tree-encoding=utf-${i}be" >>.gitattributes &&
69
+ echo "*.utf${i}le text working-tree-encoding=utf-${i}LE" >>.gitattributes &&
70
+
71
+ # Here we add a UTF-16 (resp. UTF-32) files with BOM (big/little-endian)
72
+ # but we tell Git to treat it as UTF-16BE/UTF-16LE (resp. UTF-32).
73
+ # In these cases the BOM is prohibited.
74
+ cp bebom.utf${i}be.raw bebom.utf${i}be &&
75
+ test_must_fail git add bebom.utf${i}be 2>err.out &&
76
+ test_i18ngrep "fatal: BOM is prohibited .* utf-${i}be" err.out &&
77
+ test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out &&
78
+
79
+ cp lebom.utf${i}le.raw lebom.utf${i}be &&
80
+ test_must_fail git add lebom.utf${i}be 2>err.out &&
81
+ test_i18ngrep "fatal: BOM is prohibited .* utf-${i}be" err.out &&
82
+ test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out &&
83
+
84
+ cp bebom.utf${i}be.raw bebom.utf${i}le &&
85
+ test_must_fail git add bebom.utf${i}le 2>err.out &&
86
+ test_i18ngrep "fatal: BOM is prohibited .* utf-${i}LE" err.out &&
87
+ test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out &&
88
+
89
+ cp lebom.utf${i}le.raw lebom.utf${i}le &&
90
+ test_must_fail git add lebom.utf${i}le 2>err.out &&
91
+ test_i18ngrep "fatal: BOM is prohibited .* utf-${i}LE" err.out &&
92
+ test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out
93
+ '
94
+
95
+ test_expect_success "check required UTF-${i} BOM" '
96
+ test_when_finished "git reset --hard HEAD" &&
97
+
98
+ echo "*.utf${i} text working-tree-encoding=utf-${i}" >>.gitattributes &&
99
+
100
+ cp nobom.utf${i}be.raw nobom.utf${i} &&
101
+ test_must_fail git add nobom.utf${i} 2>err.out &&
102
+ test_i18ngrep "fatal: BOM is required .* utf-${i}" err.out &&
103
+ test_i18ngrep "use UTF-${i}BE or UTF-${i}LE" err.out &&
104
+
105
+ cp nobom.utf${i}le.raw nobom.utf${i} &&
106
+ test_must_fail git add nobom.utf${i} 2>err.out &&
107
+ test_i18ngrep "fatal: BOM is required .* utf-${i}" err.out &&
108
+ test_i18ngrep "use UTF-${i}BE or UTF-${i}LE" err.out
109
+ '
110
+
111
test_expect_success "eol conversion for UTF-${i} encoded files on checkout" '
112
test_when_finished "rm -f crlf.utf${i}.raw lf.utf${i}.raw" &&
113
test_when_finished "git reset --hard HEAD^" &&
@@ -139,4 +185,20 @@ test_expect_success 'error if encoding round trip is not the same during refresh
185
test_i18ngrep "error: .* overwritten by checkout:" err.out
186
'
187
188
+test_expect_success 'error if encoding garbage is already in Git' '
189
+ BEFORE_STATE=$(git rev-parse HEAD) &&
190
+ test_when_finished "git reset --hard $BEFORE_STATE" &&
191
+
192
+ # Skip the UTF-16 filter for the added file
193
+ # This simulates a Git version that has no checkoutEncoding support
194
+ cp nobom.utf16be.raw nonsense.utf16 &&
195
+ TEST_HASH=$(git hash-object --no-filters -w nonsense.utf16) &&
196
+ git update-index --add --cacheinfo 100644 $TEST_HASH nonsense.utf16 &&
197
+ COMMIT=$(git commit-tree -p $(git rev-parse HEAD) -m "plain commit" $(git write-tree)) &&
198
+ git update-ref refs/heads/master $COMMIT &&
199
+
200
+ git diff 2>err.out &&
201
+ test_i18ngrep "error: BOM is required" err.out
202
+'
203
+
204
test_done