1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
|
test_description='Check the audit regressions'
. ./setup.sh
test_expect_success 'set up a repository and a config to vary' '
mkrepo repos/rob 3 &&
sha=$(git -C repos/rob rev-parse HEAD~1) &&
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "snapshots=tar.gz" &&
echo "repo.url=rob" &&
echo "repo.path=$PWD/repos/rob/.git"
} >robrc
'
robq() { CGIT_CONFIG="$PWD/robrc" QUERY_STRING="$1" cgit; }
test_expect_success 'a repository without a path answers 404' '
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "repo.url=nopath" &&
echo "repo.url=emptypath" &&
echo "repo.path="
} >nopathrc &&
for r in nopath emptypath
do
CGIT_CONFIG="$PWD/nopathrc" QUERY_STRING="url=$r/log/" cgit >tmp &&
grep "^Status: 404 Not Found" tmp &&
grep "Failed to open $r: Not a valid git repository" tmp || return 1
done
'
test_expect_success 'a repository with an empty url is skipped with a warning' '
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "repo.url=" &&
echo "repo.path=$PWD/repos/rob/.git" &&
echo "repo.url=rob" &&
echo "repo.path=$PWD/repos/rob/.git"
} >nourlrc &&
CGIT_CONFIG="$PWD/nourlrc" QUERY_STRING="url=rob/" cgit >tmp 2>err &&
grep "^Status: 200" tmp &&
grep "Ignoring repository with an empty url" err
'
test_expect_success 'a scan path that is itself a repository is named after it' '
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "remove-suffix=1" &&
echo "scan-path=$PWD/repos/rob"
} >rootrc &&
CGIT_CONFIG="$PWD/rootrc" QUERY_STRING="url=" cgit >tmp &&
grep "toplevel-repo.><a href=./rob/.>rob</a>" tmp
'
test_expect_success 'a repo key after scan-path is reported instead of applied' '
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "repo.url=first" &&
echo "repo.path=$PWD/repos/rob/.git" &&
echo "scan-path=$PWD/repos" &&
echo "repo.desc=misplaced"
} >stalerc &&
CGIT_CONFIG="$PWD/stalerc" QUERY_STRING="url=" cgit >tmp 2>err &&
! grep "misplaced" tmp &&
grep "Ignoring repo.desc before any repo.url" err
'
test_expect_success 'a valueless or broken git config in a scanned repository is skipped' '
git clone -q --bare repos/rob/.git scan/a.git &&
printf "[cgit]\n\thide\n[cgit\n" >>scan/a.git/config &&
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "enable-git-config=1" &&
echo "scan-path=$PWD/scan"
} >gitcfgrc &&
CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=" cgit >tmp 2>err &&
grep "toplevel-repo.><a href=./a.git/.>" tmp &&
grep "Ignoring unreadable config in $PWD/scan/a.git/config" err
'
test_expect_success 'only descriptive keys are taken from a scanned repository' '
git clone -q --bare repos/rob/.git scan/b.git &&
{
echo "desc=from the repo" &&
echo "readme=master:file-1" &&
echo "head-content=<script>alert(1)</script>" &&
echo "logo-link=javascript:alert(2)"
} >scan/b.git/cgitrc &&
CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=b.git/about/" cgit >tmp 2>err &&
grep "from the repo" tmp &&
grep "pre class=.plaintext." tmp &&
! grep "alert(" tmp &&
grep "Ignoring head-content in $PWD/scan/b.git/: trust-scan-config is not set" err &&
grep "Ignoring logo-link in " err
'
test_expect_success 'a filesystem readme from a scanned repository is refused' '
echo "readme=/etc/hosts" >scan/b.git/cgitrc &&
CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=b.git/about/" cgit >tmp 2>err &&
grep "Ignoring readme in " err
'
test_expect_success 'html serving from a scanned repository waits on trust' '
(
cd repos/rob &&
printf "<p>page</p>\n" >page.html &&
git add page.html &&
git commit -m html
) &&
mkdir -p scan2 &&
git clone -q --bare repos/rob/.git scan2/c.git &&
git -C scan2/c.git config cgit.enable-html-serving 1 &&
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "enable-git-config=1" &&
echo "mimetype.html=text/html" &&
echo "scan-path=$PWD/scan2"
} >htmlrc &&
CGIT_CONFIG="$PWD/htmlrc" QUERY_STRING="url=c.git/plain/page.html" cgit >tmp 2>err &&
grep "^Content-Type: text/plain" tmp &&
grep "^X-Content-Type-Options: nosniff" tmp &&
grep "Ignoring enable-html-serving in $PWD/scan2/c.git/: trust-scan-config is not set" err
'
test_expect_success 'with trust-scan-config the same repository serves html' '
{
echo "trust-scan-config=1" &&
cat htmlrc
} >htmltrustrc &&
CGIT_CONFIG="$PWD/htmltrustrc" QUERY_STRING="url=c.git/plain/page.html" cgit >tmp &&
grep "^Content-Type: text/html" tmp &&
! grep "^X-Content-Type-Options" tmp
'
test_expect_success SYMLINKS 'a symlink cycle under the scan path is entered once' '
ln -s . scan/loop &&
CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=" cgit >tmp &&
test $(grep -c "toplevel-repo" tmp) = 2
'
test_expect_success 'a bare query parameter does not swallow the next one' '
robq "url=rob/commit/&x&id=$sha" >tmp &&
grep "<div class=.commit-subject.>commit 2" tmp
'
test_expect_success 'a filter that cannot run answers one error page' '
{
echo "commit-filter=exec:$PWD/no/such/filter" &&
cat robrc
} >badfilterrc &&
CGIT_CONFIG="$PWD/badfilterrc" QUERY_STRING="url=rob/commit/" cgit >tmp 2>err &&
test $(grep -c "^Status:" tmp) = 1 &&
grep "Unable to complete the request" tmp &&
grep "Unable to run filter $PWD/no/such/filter" err &&
grep "Unable to exec filter" err
'
test_expect_success 'a filter that exits without reading does not end cgit' '
{
echo "email-filter=exec:/usr/bin/true" &&
cat robrc
} >truerc &&
CGIT_CONFIG="$PWD/truerc" QUERY_STRING="url=rob/log/" cgit >tmp &&
grep "^Status:" tmp
'
test_expect_success 'a commit encoding header is read without its newline' '
(
cd repos/rob &&
echo more >file-1 &&
git add file-1 &&
git -c i18n.commitEncoding=ISO-8859-1 commit -m "$(printf "caf\351")"
) &&
robq "url=rob/commit/" >tmp &&
grep "<div class=.commit-subject.>caf$(printf "\303\251")<" tmp
'
test_expect_success 'a submodule below the about path does not end the request' '
(
cd repos/rob &&
git update-index --add --cacheinfo 160000,$sha,sub &&
git commit -m gitlink
) &&
{
echo "mimetype.png=image/png" &&
cat robrc &&
echo "repo.readme=master:file-1"
} >aboutrc &&
CGIT_CONFIG="$PWD/aboutrc" QUERY_STRING="url=rob/about/sub" cgit >tmp 2>err &&
grep "^Status:" tmp &&
! grep "fatal" err
'
test_expect_success 'a missing image on the about page errors as html' '
CGIT_CONFIG="$PWD/aboutrc" QUERY_STRING="url=rob/about/missing.png" cgit >tmp &&
grep "^Status: 404" tmp &&
grep "^Content-Type: text/html" tmp
'
test_expect_success 'a snapshot name holding a slash is refused' '
robq "url=rob/snapshot/:/../../...tar.gz" >tmp &&
grep "^Status: 404" tmp &&
robq "url=rob/snapshot/rob-master.tar.gz" >tmp &&
grep "^Status: 200" tmp
'
test_expect_success 'a path opening with a colon is refused on the log and patch pages' '
robq "url=rob/log/:%25x" >tmp &&
grep "^Status: 400" tmp &&
robq "url=rob/patch/:%25x" >tmp &&
grep "^Status: 400" tmp &&
test $(grep -c "^Status:" tmp) = 1
'
test_expect_success 'a revision spelled like an option is refused' '
git -C repos/rob update-ref refs/heads/--stdin HEAD &&
robq "url=rob/log/&h=--stdin" >tmp &&
grep "^Status: 404" tmp &&
robq "url=rob/blame/file-1&id=--stdin" >tmp &&
grep "^Status: 400" tmp &&
robq "url=rob/atom/&h=--stdin" >tmp &&
grep "^Status: 404" tmp
'
test_expect_success 'the blob page reads a file through an annotated tag' '
git -C repos/rob -c tag.gpgsign=false tag -a -m note ann HEAD &&
robq "url=rob/blob/&h=ann&path=file-1" >tmp &&
strip_headers <tmp >body &&
printf "more\n" >want &&
test_cmp want body
'
test_expect_success 'the commit page shows a message whole when text follows its trailers' '
(
cd repos/rob &&
echo again >file-1 &&
git add file-1 &&
git commit -F - <<-\EOF
Subject
Body text.
Signed-off-by: A U Thor <author@example.com>
Conflicts:
file-1
EOF
) &&
{
echo "enable-commit-trailers=1" &&
cat robrc
} >trailrc &&
CGIT_CONFIG="$PWD/trailrc" QUERY_STRING="url=rob/commit/" cgit >tmp &&
! grep "commit-trailers" tmp &&
grep "Conflicts:" tmp
'
test_expect_success 'a revision expression that walks the history is refused' '
robq "url=rob/log/&h=:/zzzz" >tmp &&
grep "^Status: 404" tmp &&
robq "url=rob/commit/&id=HEAD^{/zzzz}" >tmp &&
grep "^Status: 400" tmp &&
robq "url=rob/log/&qt=range&q=:/zzzz" >tmp &&
grep "^Status: 400" tmp &&
robq "url=rob/log/&qt=range&q=master~1..master" >tmp &&
grep "^Status: 400" tmp &&
robq "url=rob/log/&qt=range&q=$sha..master" >tmp &&
grep "^Status: 200" tmp
'
test_expect_success 'the log search is literal' '
robq "url=rob/log/&qt=grep&q=commit.1" >tmp &&
! grep ">commit 1</a>" tmp &&
robq "url=rob/log/&qt=grep&q=commit%201" >tmp &&
grep ">commit 1</a>" tmp
'
test_expect_success 'a commit with a broken tree line does not crash the blob page' '
git clone -q repos/rob broken &&
(
cd broken &&
bad=$(git cat-file commit HEAD | sed "s/^tree .*/tree not-a-hash/" |
git hash-object -t commit -w --stdin --literally) &&
echo $bad >.git/refs/heads/bad
) &&
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "repo.url=broken" &&
echo "repo.path=$PWD/broken/.git" &&
echo "repo.readme=:file-1"
} >brokenrc &&
CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/blob/&h=bad&path=file-1" cgit >tmp &&
grep "^Status: 404" tmp
'
test_expect_success 'a die inside git answers one 500 page and logs the reason' '
printf "[core\n" >>broken/.git/config &&
CGIT_CONFIG="$PWD/brokenrc" QUERY_STRING="url=broken/log/" cgit >tmp 2>err &&
grep "^Status: 500" tmp &&
test $(grep -c "^Status:" tmp) = 1 &&
grep "bad config line" err &&
! grep "bad config line" tmp
'
test_expect_success 'a history with a missing parent renders up to the gap' '
git clone -q repos/rob gap &&
older=$(git -C gap rev-parse HEAD~3) &&
parent=$(git -C gap rev-parse HEAD~2) &&
rm gap/.git/objects/$(echo $parent | cut -c1-2)/$(echo $parent | cut -c3-) &&
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "repo.url=gap" &&
echo "repo.path=$PWD/gap/.git"
} >gaprc &&
CGIT_CONFIG="$PWD/gaprc" QUERY_STRING="url=gap/log/" cgit >tmp 2>err &&
test $(grep -c "^Status:" tmp) = 1 &&
grep "Unable to complete the request" tmp &&
grep "Failed to traverse parents" err &&
CGIT_CONFIG="$PWD/gaprc" QUERY_STRING="url=gap/patch/&id2=$older" cgit >tmp &&
test $(grep -c "^Status:" tmp) = 1 &&
grep "^Status: 500" tmp
'
test_expect_success 'a filter pipeline sees the default SIGPIPE disposition' '
cat >pipe.sh <<-\EOF &&
#!/bin/sh
n=0
while test $n -lt 20000
do
echo line
n=$((n + 1))
done | head -c 5
EOF
chmod +x pipe.sh &&
{
echo "source-filter=exec:$PWD/pipe.sh" &&
cat robrc
} >piperc &&
CGIT_CONFIG="$PWD/piperc" QUERY_STRING="url=rob/tree/file-1" cgit >tmp 2>err &&
grep "^Status: 200" tmp &&
! grep -i "broken pipe" err
'
test_expect_success 'the fallback branch skips a name spelled like an option' '
git -C repos/rob update-ref refs/heads/-first HEAD &&
git -C repos/rob symbolic-ref HEAD refs/heads/gone &&
robq "url=rob/" >tmp &&
git -C repos/rob symbolic-ref HEAD refs/heads/master &&
grep "^Status: 200" tmp
'
test_expect_success 'a blank line in the mimetype file is skipped' '
(
cd repos/rob &&
echo note >note.cgt &&
git add note.cgt &&
git commit -m note
) &&
printf "\n \nimage/x-cgit cgt\n" >mime.types &&
{
echo "mimetype-file=$PWD/mime.types" &&
cat robrc
} >mimerc &&
CGIT_CONFIG="$PWD/mimerc" QUERY_STRING="url=rob/plain/note.cgt" cgit >tmp &&
grep "^Content-Type: image/x-cgit" tmp
'
test_expect_success 'a refs page named with an id is not cached for ever' '
mkdir -p cache &&
{
echo "cache-size=10" &&
echo "cache-root=$PWD/cache" &&
echo "cache-static-ttl=-1" &&
echo "cache-dynamic-ttl=0" &&
echo "cache-summary-ttl=0" &&
grep -v "^cache-size" robrc
} >ttlrc &&
full=$(git -C repos/rob rev-parse HEAD) &&
CGIT_CONFIG="$PWD/ttlrc" QUERY_STRING="url=rob/refs/&id=$full" cgit >tmp &&
git -C repos/rob tag later HEAD &&
CGIT_CONFIG="$PWD/ttlrc" QUERY_STRING="url=rob/refs/&id=$full" cgit >tmp &&
grep ">later<" tmp
'
test_expect_success 'the diff caps hold when the path names a directory' '
(
cd repos/rob &&
mkdir -p dir &&
for n in 1 2 3
do
echo $n >dir/f$n || return 1
done &&
git add dir &&
git commit -m dir
) &&
{
echo "max-diff-files=2" &&
cat robrc
} >caprc &&
CGIT_CONFIG="$PWD/caprc" QUERY_STRING="url=rob/diff/dir" cgit >tmp &&
grep "too large to be rendered inline" tmp &&
CGIT_CONFIG="$PWD/caprc" QUERY_STRING="url=rob/diff/dir/f1" cgit >tmp &&
! grep "too large to be rendered inline" tmp
'
test_expect_success 'a snapshot of a tag spelled like an option is archived' '
git -C repos/rob update-ref refs/tags/-l HEAD &&
robq "url=rob/snapshot/-l.tar.gz" >tmp &&
grep "^Status: 200" tmp &&
strip_headers <tmp | gzip -dc | tar -tf - >list &&
grep "file-1" list
'
test -n "$CGIT_VALGRIND" || test_set_prereq NO_VALGRIND
test_expect_success NO_VALGRIND 'a snapshot format whose compressor is missing answers 500' '
{
echo "snapshots=tar.xz" &&
grep -v "^snapshots" robrc
} >xzrc &&
cgitbin=$(command -v cgit) &&
mkdir -p nobin &&
PATH="$PWD/nobin" CGIT_CONFIG="$PWD/xzrc" \
QUERY_STRING="url=rob/snapshot/rob-master.tar.xz" $cgitbin >tmp &&
grep "^Status: 500" tmp &&
test $(grep -c "^Status:" tmp) = 1
'
test_expect_success 'a disk readme in the repository directory serves no sibling' '
echo hello >repos/rob/.git/README &&
{
cat robrc &&
echo "repo.readme=README"
} >diskrc &&
CGIT_CONFIG="$PWD/diskrc" QUERY_STRING="url=rob/about/" cgit >tmp &&
grep "hello" tmp &&
CGIT_CONFIG="$PWD/diskrc" QUERY_STRING="url=rob/about/config" cgit >tmp &&
! grep "repositoryformatversion" tmp
'
test_expect_success 'a tag pointing at a tag links the inner tag by id' '
git -C repos/rob -c tag.gpgsign=false tag -a -m outer outer ann &&
inner=$(git -C repos/rob rev-parse ann) &&
robq "url=rob/tag/&id=$inner" >tmp &&
grep "^Status: 200" tmp &&
grep "tagged object" tmp
'
test_expect_success 'the dumb transport serves an empty repository' '
git init -q --bare empty.git &&
{
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "repo.url=empty" &&
echo "repo.path=$PWD/empty.git"
} >emptyrc &&
CGIT_CONFIG="$PWD/emptyrc" QUERY_STRING="url=empty/info/refs" cgit >tmp &&
grep "^Status: 200" tmp &&
grep "^Content-Type: text/plain" tmp &&
CGIT_CONFIG="$PWD/emptyrc" QUERY_STRING="url=empty/objects/info" cgit >tmp &&
grep "^Status: 404" tmp
'
test_expect_success 'the dumb transport sends a file with its size' '
git -C repos/rob repack -q -d &&
robq "url=rob/objects/info/packs" >tmp &&
pack=$(strip_headers <tmp | sed -n "s/^P //p" | head -1) &&
test -n "$pack" &&
robq "url=rob/objects/pack/$pack" >tmp &&
grep "^Status: 200" tmp &&
size=$(wc -c <repos/rob/.git/objects/pack/$pack | tr -d " ") &&
grep "^Content-Length: $size$" tmp &&
strip_headers <tmp >body &&
cmp body repos/rob/.git/objects/pack/$pack
'
test_expect_success 'a symlink whose target is a large blob is listed without it' '
big=$(head -c 5000 /dev/zero | tr "\0" a | git -C repos/rob hash-object -w --stdin) &&
(
cd repos/rob &&
git update-index --add --cacheinfo 120000,$big,biglink &&
git commit -m biglink
) &&
robq "url=rob/tree/" >tmp &&
grep "biglink" tmp &&
! grep "aaaaaaaaaa" tmp
'
test_expect_success 'a chain of single directories is followed a bounded number of levels' '
(
cd repos/rob &&
deep=$(printf "d/%.0s" $(seq 1 40))leaf &&
mkdir -p $(dirname $deep) &&
echo x >$deep &&
git add d &&
git commit -m deep
) &&
robq "url=rob/tree/" >tmp &&
test $(grep -o "class=.ls-dir." tmp | wc -l) -le 17
'
test_done
|