File: darcs.cgi.in

package info (click to toggle)
darcs 2.0.2-3
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 6,400 kB
  • ctags: 1,048
  • sloc: haskell: 24,937; perl: 9,736; sh: 3,369; ansic: 1,913; makefile: 17; xml: 14
file content (491 lines) | stat: -rw-r--r-- 15,562 bytes parent folder | download
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
#!/usr/bin/perl -T
#
# darcs.cgi - the darcs repository viewer
#
# Copyright (c) 2004 Will Glozer
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#
# This program calls darcs (or its own subroutines) to generate XML
# which is rendered into HTML by XSLT.  It is capable of displaying
# the files in a repository, various patch histories, annotations, etc.
#

use strict;

use CGI qw( :standard );
use CGI::Util;
use File::Basename;
use File::stat;
use IO::File;
use POSIX;

## the following variables can be customized to reflect your system
## configuration by defining them appropriately in the file
## "@sysconfdir@/darcs/cgi.conf".  The syntax accepts equals signs or simply
## blanks separating values from assignments.

$ENV{'PATH'} = read_conf('PATH', $ENV{'PATH'});

# path to executables, or just the executable if they are in $ENV{'PATH'}
my $darcs_program    = read_conf("darcs", "darcs");
my $xslt_program     = read_conf("xsltproc", "xsltproc");

# directory containing repositories
my $repository_root  = read_conf("reposdir", "/var/www");

# XSLT template locations
my $template_root = read_conf("xslt_dir", '@datadir@/darcs/xslt');

my $xslt_annotate = "$template_root/annotate.xslt";
my $xslt_browse   = "$template_root/browse.xslt";
my $xslt_patches  = "$template_root/patches.xslt";
my $xslt_repos    = "$template_root/repos.xslt";
my $xslt_rss      = "$template_root/rss.xslt";

my $xslt_errors   = "$template_root/errors.xslt";

# CSS stylesheet that XSLT templates refer to.  This is a HTTP request
# path, not a local file system path. The default will cause darcs.cgi
# to serve the stylesheet rather than the web server.
my $stylesheet = read_conf("stylesheet", "/cgi-bin/darcs.cgi/styles.css");

# location of the CSS stylesheet that darcs.cgi will serve if it
# receives a request for '/styles.css'
my $css_styles = read_conf("css_styles", '@sysconfdir@/darcs/styles.css');

# location of the favicon that darcs.cgi will serve if it
# receives a request for '/[\w\-]+/favicon.ico'
my $favicon = read_conf("favicon", "/cgi-bin/favicon.ico");

# XML source for the error pages
my $xml_errors = "$template_root/errors.xml";

# encoding to include in XML declaration
my $xml_encoding = read_conf("xml_encoding", "UTF-8");

## end customization

# ----------------------------------------------------------------------

# read a value from the cgi.conf file.
{
  my(%conf);

  sub read_conf {
    my ($flag, $val) = @_;
    $val = "" if !defined($val);
    
    if (!%conf && open(CGI_CONF, '@sysconfdir@/darcs/cgi.conf')) {
      while (<CGI_CONF>) {
        chomp;
	next if /^\s*(?:\#.*)?$/;   # Skip blank lines and comment lines
        if (/^\s*(\S+)\s*(?:\=\s*)?(\S+)\s*$/) {
           $conf{$1} = $2;
	   # print "read_conf: $1 = $2\n";
        } else {
           warn "read_conf: $_\n";
        }
      }
      close(CGI_CONF);
    }

    $val = $conf{$flag} if exists($conf{$flag});

    return $val;
  }
}

# open xsltproc to transform and output `xml' with stylesheet file `xslt'
sub transform {
    my ($xslt, $args, $content_type) = @_;

    $| = 1;
    printf "Content-type: %s\r\n\r\n", $content_type || "text/html";
    my $pipe = new IO::File "| $xslt_program $args $xslt -";
    $pipe->autoflush(0);
    return $pipe;
}

sub pristine_dir {
    my ($repo) = @_;
    my $pristine = "current";
    if (! -d "${repository_root}/${repo}/_darcs/$pristine") {
        $pristine = "pristine";
    }
    return "${repository_root}/${repo}/_darcs/$pristine";
}

# begin an XML document with a root element and the repository path
sub make_xml {
    my ($fh, $repo, $dir, $file) = @_;
    my ($full_path, $path) = '/';

    printf $fh qq(<?xml version="1.0" encoding="$xml_encoding"?>\n);

    printf $fh qq(<darcs repository="$repo" target="%s/%s%s">\n),
        $repo, ($dir ? "$dir/" : ''), ($file ? "$file" : '');

    print $fh qq(<path>\n);
    foreach $path (split('/', "$repo/$dir")) {
        $full_path .= "$path/";
        print $fh qq(<directory full-path="$full_path">$path</directory>\n);
    }
    if ($file) {
        print $fh qq(<file full-path="$full_path$file">$file</file>\n) if $file;
    }
    print $fh qq(</path>\n\n);
}

# finish XML output
sub finish_xml {
    my ($fh) = @_;
    print $fh "\n</darcs>\n";
    $fh->flush;
}

# run darcs and wrap the output in an XML document
sub darcs_xml {
    my ($fh, $repo, $cmd, $args, $dir, $file) = @_;

    make_xml($fh, $repo, $dir, $file);

    push(@$args, '--xml-output');
    darcs($fh, $repo, $cmd, $args, $dir, $file);

    finish_xml($fh);
}

# run darcs with output redirected to the specified file handle
sub darcs {
    my ($fh, $repo, $cmd, $args, $dir, $file) = @_;
    my (@darcs_argv) = ($darcs_program, $cmd, @$args);

    # push target only if there is one, otherwise darcs will get an empty param
    if ($dir || $file) {
        push(@darcs_argv, sprintf("%s%s%s", $dir, ($dir ? '/' : ''), $file));
    }

    my($pid) = fork;
    if ($pid) {
	# in the parent process
	my($status) = waitpid($pid, 0);
	die "$darcs_program exited with status $?\n" if $?;
    } elsif(defined($pid)) {
	# in the child process
	open(STDIN, '/dev/null');
	if (defined($fh)) {
	    open(STDOUT, '>&', $fh)
		|| die "can't dup to stdout: $!\n";
	}
	chdir "$repository_root/$repo"
	    || die "chdir: $repository_root/$repo: $!\n";
	exec @darcs_argv;
	die "can't exec ".$darcs_argv[0].": $!\n";
    } else {
	# fork failed
	die "can't fork: $!\n";
    }
}

# get a directory listing as XML output
sub dir_listing {
    my ($fh, $repo, $dir) = @_;
    make_xml($fh, $repo, $dir, '');

    print $fh "<files>\n";
    my $dir_ = pristine_dir ($repo) . "/$dir";
    opendir(DH, $dir_);
    while( defined (my $file_ = readdir(DH)) ) {
        next if $file_ =~ /^\.\.?$/;
        my $file = "$dir_/$file_";
        my $secs  = stat($file)->mtime;
        my $mtime = localtime($secs);
        my $ts = POSIX::strftime("%Y%m%d%H%M%S", gmtime $secs);

        my ($name, $type);

         if (-d $file) {
             ($name, $type) = (basename($file) . '/', 'directory');
         } else {
             ($name, $type) = (basename($file), 'file');
         }
         printf $fh qq(  <$type name="$name" modified="$mtime" ts="$ts" />\n);
    }
    closedir(DH);
    print $fh "</files>\n";

    finish_xml($fh);
}

# get a repository listing as XML output
sub repo_listing {
    my($fh) = @_;

    make_xml($fh, "", "", "");

    print $fh "<repositories>\n";
    opendir(DH, $repository_root);
    while( defined (my $name = readdir(DH)) ) {
        next if $name =~ /^\.\.?$/;
        if (-d "$repository_root/$name/_darcs") {
            printf $fh qq(  <repository name="$name" />\n);
        }
    }
    closedir(DH);
    print $fh "</repositories>\n";

    finish_xml($fh);
    return $fh;
}

# show an error page
sub show_error {
    my ($type, $code, $message) = @_;
    my $xml;

    # set the xslt processing arguments
    my $xslt_args = qq {
        --stringparam error-type '$type'
        --stringparam stylesheet '$stylesheet'
    };
    $xslt_args =~ s/\s+/ /gm;

    print "Status: $code $message\r\n\r\n";
    system("$xslt_program $xslt_args $xslt_errors $xml_errors");
}

# check if the requested resource has been modified since the client last
# saw it. If not send HTTP status code 304, otherwise set the Last-modified
# and Cache-control headers.
sub is_cached {
    my ($path) = @_;
    my ($stat) = stat($path);

    # stat may fail because the path was renamed or deleted but still referred
    # to by older darcs patches
    if ($stat) {
        my $last_modified = CGI::expires($stat->mtime);

        if (http('If-Modified-Since') eq $last_modified) {
            print("Status: 304 Not Modified\r\n\r\n");
            return 1;
        }

        print("Cache-control: max-age=0, must-revalidate\r\n");
        print("Last-modified: $last_modified\r\n");
    }
    return 0;
}

# safely extract a parameter from the http request.  This applies a regexp
# to the parameter which should group only the appropriate parameter value
sub safe_param {
    my ($param, $regex, $default) = @_;
    my $value = CGI::Util::unescape(param($param));
    return ($value =~ $regex) ? $1 : $default;
}

# common regular expressions for validating passed parameters
my $hash_regex = qr/^([\w\-.]+)$/;
my $path_regex = qr@^([^\\!\$\^&*()\[\]{}<>`|';"?\r\n]+)$@;

# respond to a CGI request
sub respond {
    # untaint the full URL to this CGI
    my $cgi_url = CGI::Util::unescape(url());
    $cgi_url =~ $path_regex or die qq(bad url "$cgi_url");
    $cgi_url = $1;

    # untaint script_name, reasonable to expect only \w, -, /, and . in the name
    my $script_name = CGI::Util::unescape(script_name());
    $script_name =~ qr~^([\w/.\-\~]+)$~ or die qq(bad script_name "$script_name");
    $script_name = $1;

    # untaint simple parameters, which can only have chars matching \w+
    my $cmd  = safe_param('c', '^(\w+)$', 'browse');
    my $sort = safe_param('s', '^(\w+)$', '');

    # set the xslt processing arguments
    my $xslt_args = qq {
        --stringparam cgi-program '$script_name'
        --stringparam cgi-url '$cgi_url'
        --stringparam sort-by '$sort'
        --stringparam stylesheet '$stylesheet'
    };
    $xslt_args =~ s/\s+/ /gm;

    my ($path) = CGI::Util::unescape(path_info());
    # don't allow ./ or ../ in paths
    $path =~ s|[.]+/||g;

    # check whether we're asking for styles.css
    if ($path eq '/styles.css') {
        return if is_cached($css_styles);

        open (STYLES_CSS, $css_styles) or die qq(couldn't open "${css_styles}");
        my $size = stat($css_styles)->size;

        print "Content-length: $size\r\n";
        print "Content-type: text/css\r\n\r\n";

        while (<STYLES_CSS>) {
          print $_;
        }
        close (STYLES_CSS);
        return;
    }

    # check whether we're asking for favicon.ico
    if ($path =~ '/[\w\-]+/favicon.ico') {
        return if is_cached($favicon);

        open (FAVICON, $favicon) or die qq(couldn't open "${favicon}");
        my $size = stat($favicon)->size;

        print "Content-length: $size\r\n";
        print "Content-type: image/x-icon\r\n\r\n";

        while (<FAVICON>) {
          print $_;
        }
        close (FAVICON);
        return;
    }

    # when no repository is requested display available repositories
    if (length($path) < 2) {
        my $fh = transform($xslt_repos, $xslt_args);
        repo_listing($fh);
        return;
    }

    # don't allow any shell meta characters in paths
    $path =~ $path_regex or die qq(bad path_info "$path");
    my @path = split('/', substr($1, 1));

    # split the path into a repository, directory, and file
    my ($repo, $dir, $file, @bits) = ('', '', '');
    while (@path > 0) {
        $repo = join('/', @path);
        # check if remaining path elements refer to a repo
        if (-d "${repository_root}/${repo}/_darcs") {
            if (@bits > 1) {
                $dir  = join('/', @bits[0..$#bits - 1]);
            }
            $file = $bits[$#bits];
            # check if last element of path, stored in $file, is really a dir
            if (-d (pristine_dir ($repo) . "/${dir}/${file}")) {
                $dir = ($dir ? "$dir/$file" : $file);
                $file = '';
            }
            last;
        } else {
            $repo = '';
            unshift(@bits, pop @path);
        }
    }

    # make sure the repository exists
    unless ($repo) {
        show_error('invalid-repository', '404', 'Invalid repository');
        return;
    }

    # don't generate output unless the requested path has been
    # modified since the client last saw it.
    return if is_cached(pristine_dir ($repo) . "/$dir/$file");

    # untaint patches and tags. Tags can have arbitrary values, so
    # never pass these unquoted, on pain of pain!
    my $patch = safe_param('p', $hash_regex);
    my $tag   = safe_param('t', '^(.+)$');

    my @darcs_args;
    push(@darcs_args, '--match', "hash $patch") if $patch;
    push(@darcs_args, '-t', $tag) if $tag;

    # process the requested command
    if ($cmd eq 'browse') {
        my $fh = transform($xslt_browse, $xslt_args);
        dir_listing($fh, $repo, $dir);
    } elsif ($cmd eq 'patches') {
        # patches as an option is used to support "--patches"
        if (my $patches = safe_param('patches','^(.+)$')) {
            push @darcs_args, '--patches', $patches;
        }

        my $fh = transform($xslt_patches, $xslt_args);
        darcs_xml($fh, $repo, "changes", \@darcs_args, $dir, $file);
    } elsif ($cmd eq 'annotate') {
        push(@darcs_args, '--summary');

        my $creator_hash  = safe_param('ch', $hash_regex);
        my $original_path = safe_param('o', $path_regex);
        my $fh = transform($xslt_annotate, $xslt_args);

        # use the creator hash and original file name when available so
        # annotations can span renames
        if ($creator_hash ne '' && $original_path ne '') {
            push(@darcs_args, '--creator-hash', $creator_hash);
            darcs_xml($fh, $repo, "annotate", \@darcs_args, '', $original_path);
        } else {
            darcs_xml($fh, $repo, "annotate", \@darcs_args, $dir, $file);
        }
    } elsif ($cmd eq 'diff') {
        push(@darcs_args, '-u');
        print "Content-type: text/plain\r\n\r\n";
        darcs(undef, $repo, "diff", \@darcs_args, $dir, $file);
    } elsif ($cmd eq 'rss') {
        push(@darcs_args, '--last', '25');

        my $fh = transform($xslt_rss, $xslt_args, "application/rss+xml");
        darcs_xml($fh, $repo, "changes", \@darcs_args, $dir, $file);
    } else {
        show_error('invalid-command', '400', 'Invalid command');
    }
}

# run a self-test when the --check argument is supplied
if ($ARGV[0] eq '--check') {
    (read_conf("css_styles", "abc") ne "abc") ||
        die "cannot read config file: $!\n";

    (`$darcs_program`) ||
        die "cannot execute darcs as '$darcs_program': $!\n";
    (`$xslt_program`) ||
        die "cannot execute xstlproc as '$xslt_program': $!\n";

    (-d $repository_root && -r $repository_root) ||
        die "cannot read repository root directory '$repository_root': $!\n";
    (-d $template_root && -r $template_root) ||
        die "cannot read template root directory '$template_root': $!\n";
    (-f $css_styles) ||
        die "cannot read css stylesheet '$css_styles': $!\n";
    (-f $xml_errors) ||
        die "cannot read error messages '$xml_errors': $!\n";

    exit 0;
}

# handle the CGI request
respond();