3 # uscan: This program looks for watchfiles and checks upstream ftp sites
4 # for later versions of the software.
6 # Originally written by Christoph Lameter <clameter@debian.org> (I believe)
7 # Modified by Julian Gilbey <jdg@debian.org>
8 # HTTP support added by Piotr Roszatycki <dexter@debian.org>
9 # Rewritten in Perl, Copyright 2002-2006, Julian Gilbey
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 use 5.008; # uses 'our' variables and filetest
29 use File::Temp qw/tempdir/;
30 use filetest 'access';
32 use lib '/usr/share/devscripts';
33 use Devscripts::Versort;
36 eval { require LWP::UserAgent; };
38 my $progname = basename($0);
39 if ($@ =~ /^Can\'t locate LWP\/UserAgent\.pm/) {
40 die "$progname: you must have the libwww-perl package installed\nto use this script\n";
42 die "$progname: problem loading the LWP::UserAgent module:\n $@\nHave you installed the libwww-perl package?\n";
46 my $CURRENT_WATCHFILE_VERSION = 3;
48 my $progname = basename($0);
49 my $modified_conf_msg;
53 eval { require Crypt::SSLeay; };
58 # Did we find any new upstream versions on our wanderings?
61 sub process_watchline ($$$$$$);
62 sub process_watchfile ($$$$);
63 sub recursive_regex_dir ($$$);
64 sub newest_dir ($$$$$);
69 sub quoted_regex_replace ($);
70 sub safe_replace ($$);
74 Usage: $progname [options] [dir ...]
75 Process watchfiles in all .../debian/ subdirs of those listed (or the
76 current directory if none listed) to check for upstream releases.
78 --report Only report on newer or absent versions, do not download
80 Report status of packages, but do not download
81 --debug Dump the downloaded web pages to stdout for debugging
83 --destdir Path of directory to which to download.
84 --download Report on newer and absent versions, and download (default)
86 Always download the upstream release, even if up to date
87 --no-download Report on newer and absent versions, but don\'t download
88 --pasv Use PASV mode for FTP connections
89 --no-pasv Do not use PASV mode for FTP connections (default)
90 --timeout N Specifies how much time, in seconds, we give remote
91 servers to respond (default 20 seconds)
92 --symlink Make an orig.tar.gz symlink to downloaded file (default)
93 --rename Rename to orig.tar.gz instead of symlinking
94 (Both will use orig.tar.bz2 if appropriate)
95 --repack Repack downloaded archives from orig.tar.bz2 or orig.zip to
97 (does nothing if downloaded archive orig.tar.gz)
98 --no-symlink Don\'t make symlink or rename
99 --verbose Give verbose output
100 --no-verbose Don\'t give verbose output (default)
101 --check-dirname-level N
102 How much to check directory names:
104 N=1 only when program changes directory (default)
106 --check-dirname-regex REGEX
107 What constitutes a matching directory name; REGEX is
108 a Perl regular expression; the string \`PACKAGE\' will
109 be replaced by the package name; see manpage for details
110 (default: 'PACKAGE(-.+)?')
112 Specify the watchfile rather than using debian/watch;
113 no directory traversing will be done in this case
114 --upstream-version VERSION
115 Specify the current upstream version in use rather than
116 parsing debian/changelog to determine this
117 --download-version VERSION
118 Specify the version which the upstream release must
119 match in order to be considered, rather than using the
120 release with the highest version
121 --download-current-version
122 Download the currently packaged version
124 Specify the package name rather than examining
125 debian/changelog; must use --upstream-version and
126 --watchfile with this option, no directory traversing
127 will be performed, no actions (even downloading) will be
129 --no-dehs Use traditional uscan output format (default)
130 --dehs Use DEHS style output (XML-type)
131 --user-agent, --useragent
132 Override the default user agent
134 Don\'t read devscripts config files;
135 must be the first option given
136 --help Show this message
137 --version Show version information
139 Default settings modified by devscripts configuration files:
146 This is $progname, from the Debian devscripts package, version ###VERSION###
147 This code is copyright 1999-2006 by Julian Gilbey, all rights reserved.
148 Original code by Christoph Lameter.
149 This program comes with ABSOLUTELY NO WARRANTY.
150 You are free to redistribute this code under the terms of the
151 GNU General Public License, version 2 or later.
155 # What is the default setting of $ENV{'FTP_PASSIVE'}?
156 our $passive = 'default';
158 # Now start by reading configuration files and then command line
159 # The next stuff is boilerplate
163 my $download_version;
164 my $force_download = 0;
165 my $report = 0; # report even on up-to-date packages?
166 my $repack = 0; # repack .tar.bz2 or .zip to .tar.gz
167 my $symlink = 'symlink';
169 my $check_dirname_level = 1;
170 my $check_dirname_regex = 'PACKAGE(-.+)?';
173 my $dehs_end_output = 0;
174 my $dehs_start_output = 0;
175 my $pkg_report_header = '';
177 my $user_agent_string = 'Debian uscan ###VERSION###';
179 if (@ARGV and $ARGV[0] =~ /^--no-?conf$/) {
180 $modified_conf_msg = " (no configuration files read)";
183 my @config_files = ('/etc/devscripts.conf', '~/.devscripts');
185 'USCAN_TIMEOUT' => 20,
186 'USCAN_DESTDIR' => '..',
187 'USCAN_DOWNLOAD' => 'yes',
188 'USCAN_PASV' => 'default',
189 'USCAN_SYMLINK' => 'symlink',
190 'USCAN_VERBOSE' => 'no',
191 'USCAN_DEHS_OUTPUT' => 'no',
192 'USCAN_USER_AGENT' => '',
193 'USCAN_REPACK' => 'no',
194 'DEVSCRIPTS_CHECK_DIRNAME_LEVEL' => 1,
195 'DEVSCRIPTS_CHECK_DIRNAME_REGEX' => 'PACKAGE(-.+)?',
197 my %config_default = %config_vars;
201 foreach my $var (keys %config_vars) {
202 $shell_cmd .= qq[$var="$config_vars{$var}";\n];
204 $shell_cmd .= 'for file in ' . join(" ",@config_files) . "; do\n";
205 $shell_cmd .= '[ -f $file ] && . $file; done;' . "\n";
207 foreach my $var (keys %config_vars) { $shell_cmd .= "echo \$$var;\n" }
208 my $shell_out = `/bin/bash -c '$shell_cmd'`;
209 @config_vars{keys %config_vars} = split /\n/, $shell_out, -1;
212 $config_vars{'USCAN_DESTDIR'} =~ /^\s*(\S+)\s*$/
213 or $config_vars{'USCAN_DESTDIR'}='..';
214 $config_vars{'USCAN_DOWNLOAD'} =~ /^(yes|no)$/
215 or $config_vars{'USCAN_DOWNLOAD'}='yes';
216 $config_vars{'USCAN_PASV'} =~ /^(yes|no|default)$/
217 or $config_vars{'USCAN_PASV'}='default';
218 $config_vars{'USCAN_TIMEOUT'} =~ m/^\d+$/
219 or $config_vars{'USCAN_TIMEOUT'}=20;
220 $config_vars{'USCAN_SYMLINK'} =~ /^(yes|no|symlinks?|rename)$/
221 or $config_vars{'USCAN_SYMLINK'}='yes';
222 $config_vars{'USCAN_SYMLINK'}='symlink'
223 if $config_vars{'USCAN_SYMLINK'} eq 'yes' or
224 $config_vars{'USCAN_SYMLINK'} =~ /^symlinks?$/;
225 $config_vars{'USCAN_VERBOSE'} =~ /^(yes|no)$/
226 or $config_vars{'USCAN_VERBOSE'}='no';
227 $config_vars{'USCAN_DEHS_OUTPUT'} =~ /^(yes|no)$/
228 or $config_vars{'USCAN_DEHS_OUTPUT'}='no';
229 $config_vars{'USCAN_REPACK'} =~ /^(yes|no)$/
230 or $config_vars{'USCAN_REPACK'}='no';
231 $config_vars{'DEVSCRIPTS_CHECK_DIRNAME_LEVEL'} =~ /^[012]$/
232 or $config_vars{'DEVSCRIPTS_CHECK_DIRNAME_LEVEL'}=1;
234 foreach my $var (sort keys %config_vars) {
235 if ($config_vars{$var} ne $config_default{$var}) {
236 $modified_conf_msg .= " $var=$config_vars{$var}\n";
239 $modified_conf_msg ||= " (none)\n";
240 chomp $modified_conf_msg;
242 $destdir = $config_vars{'USCAN_DESTDIR'}
243 if defined $config_vars{'USCAN_DESTDIR'};
244 $download = $config_vars{'USCAN_DOWNLOAD'} eq 'no' ? 0 : 1;
245 $passive = $config_vars{'USCAN_PASV'} eq 'yes' ? 1 :
246 $config_vars{'USCAN_PASV'} eq 'no' ? 0 : 'default';
247 $timeout = $config_vars{'USCAN_TIMEOUT'};
248 $symlink = $config_vars{'USCAN_SYMLINK'};
249 $verbose = $config_vars{'USCAN_VERBOSE'} eq 'yes' ? 1 : 0;
250 $dehs = $config_vars{'USCAN_DEHS_OUTPUT'} eq 'yes' ? 1 : 0;
251 $check_dirname_level = $config_vars{'DEVSCRIPTS_CHECK_DIRNAME_LEVEL'};
252 $check_dirname_regex = $config_vars{'DEVSCRIPTS_CHECK_DIRNAME_REGEX'};
253 $user_agent_string = $config_vars{'USCAN_USER_AGENT'}
254 if $config_vars{'USCAN_USER_AGENT'};
255 $repack = $config_vars{'USCAN_REPACK'} eq 'yes' ? 1 : 0;
258 # Now read the command line arguments
260 my ($opt_h, $opt_v, $opt_destdir, $opt_download, $opt_force_download,
261 $opt_report, $opt_passive, $opt_symlink, $opt_repack);
262 my ($opt_verbose, $opt_level, $opt_regex, $opt_noconf);
263 my ($opt_package, $opt_uversion, $opt_watchfile, $opt_dehs, $opt_timeout);
264 my $opt_download_version;
266 my $opt_download_current_version;
268 GetOptions("help" => \$opt_h,
269 "version" => \$opt_v,
270 "destdir=s" => \$opt_destdir,
271 "download!" => \$opt_download,
272 "download-version=s" => \$opt_download_version,
273 "force-download" => \$opt_force_download,
274 "report" => sub { $opt_download = 0; },
275 "report-status" => sub { $opt_download = 0; $opt_report = 1; },
276 "passive|pasv!" => \$opt_passive,
277 "timeout=i" => \$opt_timeout,
278 "symlink!" => sub { $opt_symlink = $_[1] ? 'symlink' : 'no'; },
279 "rename" => sub { $opt_symlink = 'rename'; },
280 "repack" => sub { $opt_repack = 1; },
281 "package=s" => \$opt_package,
282 "upstream-version=s" => \$opt_uversion,
283 "watchfile=s" => \$opt_watchfile,
284 "dehs!" => \$opt_dehs,
285 "verbose!" => \$opt_verbose,
287 "check-dirname-level=s" => \$opt_level,
288 "check-dirname-regex=s" => \$opt_regex,
289 "user-agent=s" => \$opt_user_agent,
290 "useragent=s" => \$opt_user_agent,
291 "noconf" => \$opt_noconf,
292 "no-conf" => \$opt_noconf,
293 "download-current-version" => \$opt_download_current_version,
295 or die "Usage: $progname [options] [directories]\nRun $progname --help for more details\n";
298 die "$progname: --no-conf is only acceptable as the first command-line option!\n";
300 if ($opt_h) { usage(); exit 0; }
301 if ($opt_v) { version(); exit 0; }
303 # Now we can set the other variables according to the command line options
305 $destdir = $opt_destdir if defined $opt_destdir;
306 $download = $opt_download if defined $opt_download;
307 $force_download = $opt_force_download if defined $opt_force_download;
308 $report = $opt_report if defined $opt_report;
309 $repack = $opt_repack if defined $opt_repack;
310 $passive = $opt_passive if defined $opt_passive;
311 $timeout = $opt_timeout if defined $opt_timeout;
312 $timeout = 20 unless defined $timeout and $timeout > 0;
313 $symlink = $opt_symlink if defined $opt_symlink;
314 $verbose = $opt_verbose if defined $opt_verbose;
315 $dehs = $opt_dehs if defined $opt_dehs;
316 $user_agent_string = $opt_user_agent if defined $opt_user_agent;
317 $download_version = $opt_download_version if defined $opt_download_version;
319 $SIG{'__WARN__'} = \&dehs_warn;
320 $SIG{'__DIE__'} = \&dehs_die;
323 if (defined $opt_level) {
324 if ($opt_level =~ /^[012]$/) { $check_dirname_level = $opt_level; }
326 die "$progname: unrecognised --check-dirname-level value (allowed are 0,1,2)\n";
330 $check_dirname_regex = $opt_regex if defined $opt_regex;
332 if (defined $opt_package) {
333 die "$progname: --package requires the use of --watchfile\nas well; run $progname --help for more details\n"
334 unless defined $opt_watchfile;
335 $download = -$download unless defined $opt_download;
338 die "$progname: Can't use --verbose if you're using --dehs!\n"
339 if $verbose and $dehs;
341 die "$progname: Can't use --report-status if you're using --verbose!\n"
342 if $verbose and $report;
344 die "$progname: Can't use --report-status if you're using --download!\n"
345 if $download and $report;
347 warn "$progname: You're going to get strange (non-XML) output using --debug and --dehs together!\n"
350 # We'd better be verbose if we're debugging
353 # Net::FTP understands this
354 if ($passive ne 'default') {
355 $ENV{'FTP_PASSIVE'} = $passive;
357 elsif (exists $ENV{'FTP_PASSIVE'}) {
358 $passive = $ENV{'FTP_PASSIVE'};
360 else { $passive = undef; }
362 # if (defined $passive) { $ENV{'FTP_PASSIVE'}=$passive; }
363 # else { delete $ENV{'FTP_PASSIVE'}; }
364 # to restore $ENV{'FTP_PASSIVE'} to what it was at this point
366 # dummy subclass used to store all the redirections for later use
367 package LWP::UserAgent::UscanCatchRedirections;
369 use base 'LWP::UserAgent';
371 my @uscan_redirections;
376 if ($self->SUPER::redirect_ok(@_)) {
377 push @uscan_redirections, $request->uri;
383 sub get_redirections {
384 return \@uscan_redirections;
389 my $user_agent = LWP::UserAgent::UscanCatchRedirections->new(env_proxy => 1);
390 $user_agent->timeout($timeout);
391 $user_agent->agent($user_agent_string);
393 if (defined $opt_watchfile) {
394 die "Can't have directory arguments if using --watchfile" if @ARGV;
396 # no directory traversing then, and things are very simple
397 if (defined $opt_package) {
398 # no need to even look for a changelog!
399 process_watchfile(undef, $opt_package, $opt_uversion, $opt_watchfile);
401 # Check for debian/changelog file
402 until (-r 'debian/changelog') {
403 chdir '..' or die "$progname: can't chdir ..: $!\n";
405 die "$progname: cannot find readable debian/changelog anywhere!\nAre you in the source code tree?\n";
409 # Figure out package info we need
410 my $changelog = `dpkg-parsechangelog`;
412 die "$progname: Problems running dpkg-parsechangelog\n";
415 my ($package, $debversion, $uversion);
416 $changelog =~ /^Source: (.*?)$/m and $package=$1;
417 $changelog =~ /^Version: (.*?)$/m and $debversion=$1;
418 if (! defined $package || ! defined $debversion) {
419 die "$progname: Problems determining package name and/or version from\n debian/changelog\n";
422 # Check the directory is properly named for safety
423 my $good_dirname = 1;
424 if ($check_dirname_level == 2 or
425 ($check_dirname_level == 1 and cwd() ne $opwd)) {
426 my $re = $check_dirname_regex;
427 $re =~ s/PACKAGE/\Q$package\E/g;
429 $good_dirname = (cwd() =~ m%^$re$%);
431 $good_dirname = (basename(cwd()) =~ m%^$re$%);
434 if (! $good_dirname) {
435 die "$progname: not processing watchfile because this directory does not match the package name\n" .
436 " or the settings of the--check-dirname-level and --check-dirname-regex options if any.\n";
439 # Get current upstream version number
440 if (defined $opt_uversion) {
441 $uversion = $opt_uversion;
443 $uversion = $debversion;
444 $uversion =~ s/-[^-]+$//; # revision
445 $uversion =~ s/^\d+://; # epoch
448 process_watchfile(cwd(), $package, $uversion, $opt_watchfile);
451 # Are there any warnings to give if we're using dehs?
453 dehs_output if $dehs;
454 exit ($found ? 0 : 1);
457 # Otherwise we're scanning for watchfiles
458 push @ARGV, '.' if ! @ARGV;
459 print "-- Scanning for watchfiles in @ARGV\n" if $verbose;
461 # Run find to find the directories. We will handle filenames with spaces
462 # correctly, which makes this code a little messier than it would be
465 open FIND, '-|', 'find', @ARGV, qw(-follow -type d -name debian -print)
466 or die "$progname: couldn't exec find: $!\n";
474 die "$progname: No debian directories found\n" unless @dirs;
479 for my $dir (@dirs) {
480 unless (chdir $origdir) {
481 warn "$progname warning: Couldn't chdir back to $origdir, skipping: $!\n";
484 $dir =~ s%/debian$%%;
485 unless (chdir $dir) {
486 warn "$progname warning: Couldn't chdir $dir, skipping: $!\n";
490 # Check for debian/watch file
491 if (-r 'debian/watch' and -r 'debian/changelog') {
492 # Figure out package info we need
493 my $changelog = `dpkg-parsechangelog`;
495 warn "$progname warning: Problems running dpkg-parsechangelog in $dir, skipping\n";
499 my ($package, $debversion, $uversion);
500 $changelog =~ /^Source: (.*?)$/m and $package=$1;
501 $changelog =~ /^Version: (.*?)$/m and $debversion=$1;
502 if (! defined $package || ! defined $debversion) {
503 warn "$progname warning: Problems determining package name and/or version from\n $dir/debian/changelog, skipping\n";
507 # Check the directory is properly named for safety
508 my $good_dirname = 1;
509 if ($check_dirname_level == 2 or
510 ($check_dirname_level == 1 and cwd() ne $opwd)) {
511 my $re = $check_dirname_regex;
512 $re =~ s/PACKAGE/\Q$package\E/g;
514 $good_dirname = (cwd() =~ m%^$re$%);
516 $good_dirname = (basename(cwd()) =~ m%^$re$%);
520 print "-- Found watchfile in $dir/debian\n" if $verbose;
522 print "-- Skip watchfile in $dir/debian since it does not match the package name\n" .
523 " (or the settings of the --check-dirname-level and --check-dirname-regex options if any).\n"
528 # Get upstream version number
529 $uversion = $debversion;
530 $uversion =~ s/-[^-]+$//; # revision
531 $uversion =~ s/^\d+://; # epoch
533 push @debdirs, [$debversion, $dir, $package, $uversion];
535 elsif (-r 'debian/watch') {
536 warn "$progname warning: Found watchfile in $dir,\n but couldn't find/read changelog; skipping\n";
539 elsif (-f 'debian/watch') {
540 warn "$progname warning: Found watchfile in $dir,\n but it is not readable; skipping\n";
545 warn "$progname: no watch file found\n" if (@debdirs == 0 and $report);
547 # Was there a --uversion option?
548 if (defined $opt_uversion) {
550 $debdirs[0][3] = $opt_uversion;
552 warn "$progname warning: ignoring --uversion as more than one debian/watch file found\n";
556 # Now sort the list of directories, so that we process the most recent
557 # directories first, as determined by the package version numbers
558 @debdirs = Devscripts::Versort::deb_versort(@debdirs);
560 # Now process the watchfiles in order. If a directory d has subdirectories
561 # d/sd1/debian and d/sd2/debian, which each contain watchfiles corresponding
562 # to the same package, then we only process the watchfile in the package with
563 # the latest version number.
565 for my $debdir (@debdirs) {
566 shift @$debdir; # don't need the Debian version number any longer
567 my $dir = $$debdir[0];
568 my $parentdir = dirname($dir);
569 my $package = $$debdir[1];
570 my $version = $$debdir[2];
572 if (exists $donepkgs{$parentdir}{$package}) {
573 warn "$progname warning: Skipping $dir/debian/watch\n as this package has already been scanned successfully\n";
577 unless (chdir $origdir) {
578 warn "$progname warning: Couldn't chdir back to $origdir, skipping: $!\n";
581 unless (chdir $dir) {
582 warn "$progname warning: Couldn't chdir $dir, skipping: $!\n";
586 if (process_watchfile($dir, $package, $version, "debian/watch")
588 $donepkgs{$parentdir}{$package} = 1;
590 # Are there any warnings to give if we're using dehs?
591 dehs_output if $dehs;
594 print "-- Scan finished\n" if $verbose;
597 dehs_output if $dehs;
598 exit ($found ? 0 : 1);
601 # This is the heart of the code: Process a single watch item
603 # watch_version=1: Lines have up to 5 parameters which are:
606 # $2 = Directory on site
607 # $3 = Pattern to match, with (...) around version number part
608 # $4 = Last version we have (or 'debian' for the current Debian version)
609 # $5 = Actions to take on successful retrieval
614 # ftp://site.name/dir/path/pattern-(.*)\.tar\.gz [version [action]]
617 # http://site.name/dir/path/pattern-(.*)\.tar\.gz [version [action]]
619 # http://site.name/dir/path/base pattern-(.*)\.tar\.gz [version [action]]
621 # Lines can be prefixed with opts=<opts>.
623 # Then the patterns matched will be checked to find the one with the
624 # greatest version number (as determined by the (...) group), using the
625 # Debian version number comparison algorithm described below.
629 # Correct handling of regex special characters in the path part:
630 # ftp://ftp.worldforge.org/pub/worldforge/libs/Atlas-C++/transitional/Atlas-C\+\+-(.*)\.tar\.gz
632 # Directory pattern matching:
633 # ftp://ftp.nessus.org/pub/nessus/nessus-([\d\.]+)/src/nessus-core-([\d\.]+)\.tar\.gz
635 # The pattern in each part may contain several (...) groups and
636 # the version number is determined by joining all groups together
637 # using "." as separator. For example:
638 # ftp://site/dir/path/pattern-(\d+)_(\d+)_(\d+)\.tar\.gz
640 # This is another way of handling site with funny version numbers,
641 # this time using mangling. (Note that multiple groups will be
642 # concatenated before mangling is performed, and that mangling will
643 # only be performed on the basename version number, not any path version
645 # opts=uversionmangle=s/^/0.0./ \
646 # ftp://ftp.ibiblio.org/pub/Linux/ALPHA/wine/development/Wine-(.*)\.tar\.gz
648 # Similarly, the upstream part of the Debian version number can be
650 # opts=dversionmangle=s/\.dfsg\.\d+$// \
651 # http://some.site.org/some/path/foobar-(.*)\.tar\.gz
653 # The versionmangle=... option is a shorthand for saying uversionmangle=...
654 # and dversionmangle=... and applies to both upstream and Debian versions.
656 # The option filenamemangle can be used to mangle the name under which
657 # the downloaded file will be saved:
658 # href="http://foo.bar.org/download/?path=&download=foo-0.1.1.tar.gz"
659 # could be handled as:
660 # opts=filenamemangle=s/.*=(.*)/$1/ \
661 # http://foo.bar.org/download/\?path=&download=foo-(.*)\.tar\.gz
663 # href="http://foo.bar.org/download/?path=&download_version=0.1.1"
665 # opts=filenamemangle=s/.*=(.*)/foo-$1\.tar\.gz/ \
666 # http://foo.bar.org/download/\?path=&download_version=(.*)
668 # The option downloadurlmangle can be used to mangle the URL of the file
669 # to download. This can only be used with http:// URLs. This may be
670 # necessary if the link given on the webpage needs to be transformed in
671 # some way into one which will work automatically, for example:
672 # opts=downloadurlmangle=s/prdownload/download/ \
673 # http://developer.berlios.de/project/showfiles.php?group_id=2051 \
674 # http://prdownload.berlios.de/softdevice/vdr-softdevice-(.*).tgz
677 sub process_watchline ($$$$$$)
679 my ($line, $watch_version, $pkg_dir, $pkg, $pkg_version, $watchfile) = @_;
681 my $origline = $line;
682 my ($base, $site, $dir, $filepattern, $pattern, $lastversion, $action);
684 my (@patterns, @sites, @redirections, @basedirs);
687 my ($request, $response);
688 my ($newfile, $newversion);
691 my $headers = HTTP::Headers->new;
693 # Comma-separated list of features that sites being queried might
694 # want to be aware of
695 $headers->header('X-uscan-features' => 'enhanced-matching');
696 %dehs_tags = ('package' => $pkg);
698 if ($watch_version == 1) {
699 ($site, $dir, $pattern, $lastversion, $action) = split ' ', $line, 5;
701 if (! defined $lastversion or $site =~ /\(.*\)/ or $dir =~ /\(.*\)/) {
702 warn "$progname warning: there appears to be a version 2 format line in\n the version 1 watchfile $watchfile;\n Have you forgotten a 'version=2' line at the start, perhaps?\n Skipping the line: $line\n";
705 if ($site !~ m%\w+://%) {
706 $site = "ftp://$site";
707 if ($pattern !~ /\(.*\)/) {
708 # watch_version=1 and old style watchfile;
709 # pattern uses ? and * shell wildcards; everything from the
710 # first to last of these metachars is the pattern to match on
711 $pattern =~ s/(\?|\*)/($1/;
712 $pattern =~ s/(\?|\*)([^\?\*]*)$/$1)$2/;
713 $pattern =~ s/\./\\./g;
714 $pattern =~ s/\?/./g;
715 $pattern =~ s/\*/.*/g;
717 warn "$progname warning: Using very old style of filename pattern in $watchfile\n (this might lead to incorrect results): $3\n";
722 $base = "$site/$dir/";
723 $base =~ s%(?<!:)//%/%g;
724 $base =~ m%^(\w+://[^/]+)%;
727 # version 2/3 watchfile
728 if ($line =~ s/^opt(?:ion)?s=//) {
730 if ($line =~ s/^"(.*?)"\s+//) {
732 } elsif ($line =~ s/^(\S+)\s+//) {
735 warn "$progname warning: malformed opts=... in watchfile, skipping line:\n$origline\n";
739 my @opts = split /,/, $opts;
740 foreach my $opt (@opts) {
741 if ($opt eq 'pasv' or $opt eq 'passive') {
744 elsif ($opt eq 'active' or $opt eq 'nopasv'
745 or $opt eq 'nopassive') {
748 elsif ($opt =~ /^uversionmangle\s*=\s*(.+)/) {
749 @{$options{'uversionmangle'}} = split /;/, $1;
751 elsif ($opt =~ /^dversionmangle\s*=\s*(.+)/) {
752 @{$options{'dversionmangle'}} = split /;/, $1;
754 elsif ($opt =~ /^versionmangle\s*=\s*(.+)/) {
755 @{$options{'uversionmangle'}} = split /;/, $1;
756 @{$options{'dversionmangle'}} = split /;/, $1;
758 elsif ($opt =~ /^filenamemangle\s*=\s*(.+)/) {
759 @{$options{'filenamemangle'}} = split /;/, $1;
761 elsif ($opt =~ /^downloadurlmangle\s*=\s*(.+)/) {
762 @{$options{'downloadurlmangle'}} = split /;/, $1;
765 warn "$progname warning: unrecognised option $opt\n";
770 ($base, $filepattern, $lastversion, $action) = split ' ', $line, 4;
772 if ($base =~ s%/([^/]*\([^/]*\)[^/]*)$%/%) {
773 # Last component of $base has a pair of parentheses, so no
774 # separate filepattern field; we remove the filepattern from the
775 # end of $base and rescan the rest of the line
777 (undef, $lastversion, $action) = split ' ', $line, 3;
780 if ((!$lastversion or $lastversion eq 'debian') and not defined $pkg_version) {
781 warn "$progname warning: Unable to determine current version\n in $watchfile, skipping:\n $line\n";
786 if ($filepattern !~ /\(.*\)/) {
787 warn "$progname warning: Filename pattern missing version delimiters ()\n in $watchfile, skipping:\n $line\n";
791 # Check validity of options
792 if ($base =~ /^ftp:/ and exists $options{'downloadurlmangle'}) {
793 warn "$progname warning: downloadurlmangle option invalid for ftp sites,\n ignoring in $watchfile:\n $line\n";
796 # Handle sf.net addresses specially
797 if ($base =~ m%^http://sf\.net/%) {
798 $base =~ s%^http://sf\.net/%http://qa.debian.org/watch/sf.php/%;
799 $filepattern .= '(?:\?.*)?';
801 if ($base =~ m%^(\w+://[^/]+)%) {
804 warn "$progname warning: Can't determine protocol and site in\n $watchfile, skipping:\n $line\n";
808 # Find the path with the greatest version number matching the regex
809 $base = recursive_regex_dir($base, \%options, $watchfile);
810 if ($base eq '') { return 1; }
812 # We're going to make the pattern
813 # (?:(?:http://site.name)?/dir/path/)?base_pattern
814 # It's fine even for ftp sites
816 $basedir =~ s%^\w+://[^/]+/%/%;
817 $pattern = "(?:(?:$site)?" . quotemeta($basedir) . ")?$filepattern";
820 if (! $lastversion or $lastversion eq 'debian') {
821 if (defined $pkg_version) {
822 $lastversion=$pkg_version;
824 warn "$progname warning: Unable to determine current version\n in $watchfile, skipping:\n $line\n";
828 # And mangle it if requested
829 my $mangled_lastversion;
830 $mangled_lastversion = $lastversion;
831 foreach my $pat (@{$options{'dversionmangle'}}) {
832 if (! safe_replace(\$mangled_lastversion, $pat)) {
833 warn "$progname: In $watchfile, potentially"
834 . " unsafe or malformed dversionmangle"
835 . " pattern:\n '$pat'"
836 . " found. Skipping watchline\n"
841 if($opt_download_current_version) {
842 $download_version = $mangled_lastversion;
847 if ($pattern !~ /\(.*\)/) {
848 warn "$progname warning: Filename pattern missing version delimiters ()\n in $watchfile, skipping:\n $line\n";
852 push @patterns, $pattern;
854 push @basedirs, $basedir;
856 # What is the most recent file, based on the filenames?
857 # We first have to find the candidates, then we sort them using
858 # Devscripts::Versort::versort
859 if ($site =~ m%^http(s)?://%) {
860 if (defined($1) and !$haveSSL) {
861 die "$progname: you must have the libcrypt-ssleay-perl package installed\nto use https URLs\n";
863 print STDERR "$progname debug: requesting URL $base\n" if $debug;
864 $request = HTTP::Request->new('GET', $base, $headers);
865 $response = $user_agent->request($request);
866 if (! $response->is_success) {
867 warn "$progname warning: In watchfile $watchfile, reading webpage\n $base failed: " . $response->status_line . "\n";
871 @redirections = @{$user_agent->get_redirections};
873 print STDERR "$progname debug: redirections: @redirections\n"
876 foreach my $_redir (@redirections) {
877 my $base_dir = $_redir;
879 $base_dir =~ s%^\w+://[^/]+/%/%;
880 if ($_redir =~ m%^(\w+://[^/]+)%) {
883 push @patterns, "(?:(?:$base_site)?" . quotemeta($base_dir) . ")?$filepattern";
884 push @sites, $base_site;
885 push @basedirs, $base_dir;
887 # remove the filename, if any
888 my $base_dir_orig = $base_dir;
889 $base_dir =~ s%/[^/]*$%/%;
890 if ($base_dir ne $base_dir_orig) {
891 push @patterns, "(?:(?:$base_site)?" . quotemeta($base_dir) . ")?$filepattern";
892 push @sites, $base_site;
893 push @basedirs, $base_dir;
898 my $content = $response->content;
899 print STDERR "$progname debug: received content:\n$content\[End of received content]\n"
901 # We need this horrid stuff to handle href=foo type
902 # links. OK, bad HTML, but we have to handle it nonetheless.
904 $content =~ s/href\s*=\s*(?=[^\"\'])([^\s>]+)/href="$1"/ig;
906 $content =~ s/<!-- .*?-->//sg;
907 # Is there a base URL given?
908 if ($content =~ /<\s*base\s+[^>]*href\s*=\s*([\"\'])(.*?)\1/i) {
909 # Ensure it ends with /
911 $urlbase =~ s%//$%/%;
913 # May have to strip a base filename
914 ($urlbase = $base) =~ s%/[^/]*$%/%;
917 print STDERR "$progname debug: matching pattern(s) @patterns\n" if $debug;
919 while ($content =~ m/<\s*a\s+[^>]*href\s*=\s*([\"\'])(.*?)\1/sgi) {
922 foreach my $_pattern (@patterns) {
923 if ($href =~ m&^$_pattern$&) {
924 if ($watch_version == 2) {
925 # watch_version 2 only recognised one group; the code
926 # below will break version 2 watchfiles with a construction
927 # such as file-([\d\.]+(-\d+)?) (bug #327258)
928 push @hrefs, [$1, $href];
930 # need the map { ... } here to handle cases of (...)?
931 # which may match but then return undef values
932 my $mangled_version =
933 join(".", map { $_ if defined($_) }
934 $href =~ m&^$_pattern$&);
935 foreach my $pat (@{$options{'uversionmangle'}}) {
936 if (! safe_replace(\$mangled_version, $pat)) {
937 warn "$progname: In $watchfile, potentially"
938 . " unsafe or malformed uversionmangle"
939 . " pattern:\n '$pat'"
940 . " found. Skipping watchline\n"
945 push @hrefs, [$mangled_version, $href];
952 print "-- Found the following matching hrefs:\n";
953 foreach my $href (@hrefs) { print " $$href[1]\n"; }
955 if (defined $download_version) {
956 my @vhrefs = grep { $$_[0] eq $download_version } @hrefs;
958 ($newversion, $newfile) = @{$vhrefs[0]};
960 warn "$progname warning: In $watchfile no matching hrefs for version $download_version"
961 . " in watch line\n $line\n";
965 @hrefs = Devscripts::Versort::versort(@hrefs);
966 ($newversion, $newfile) = @{$hrefs[0]};
969 warn "$progname warning: In $watchfile,\n no matching hrefs for watch line\n $line\n";
974 # Better be an FTP site
975 if ($site !~ m%^ftp://%) {
976 warn "$progname warning: Unknown protocol in $watchfile, skipping:\n $site\n";
980 if (exists $options{'pasv'}) {
981 $ENV{'FTP_PASSIVE'}=$options{'pasv'};
983 print STDERR "$progname debug: requesting URL $base\n" if $debug;
984 $request = HTTP::Request->new('GET', $base);
985 $response = $user_agent->request($request);
986 if (exists $options{'pasv'}) {
987 if (defined $passive) { $ENV{'FTP_PASSIVE'}=$passive; }
988 else { delete $ENV{'FTP_PASSIVE'}; }
990 if (! $response->is_success) {
991 warn "$progname warning: In watchfile $watchfile, reading FTP directory\n $base failed: " . $response->status_line . "\n";
995 my $content = $response->content;
996 print STDERR "$progname debug: received content:\n$content\[End of received content]\n"
999 # FTP directory listings either look like:
1000 # info info ... info filename [ -> linkname]
1001 # or they're HTMLised (if they've been through an HTTP proxy)
1002 # so we may have to look for <a href="filename"> type patterns
1003 print STDERR "$progname debug: matching pattern $pattern\n" if $debug;
1006 # We separate out HTMLised listings from standard listings, so
1007 # that we can target our search correctly
1008 if ($content =~ /<\s*a\s+[^>]*href/i) {
1010 m/(?:<\s*a\s+[^>]*href\s*=\s*\")((?-i)$pattern)\"/gi) {
1012 my $mangled_version = join(".", $file =~ m/^$pattern$/);
1013 foreach my $pat (@{$options{'uversionmangle'}}) {
1014 if (! safe_replace(\$mangled_version, $pat)) {
1015 warn "$progname: In $watchfile, potentially"
1016 . " unsafe or malformed uversionmangle"
1017 . " pattern:\n '$pat'"
1018 . " found. Skipping watchline\n"
1023 push @files, [$mangled_version, $file];
1026 # they all look like:
1027 # info info ... info filename [ -> linkname]
1028 while ($content =~ m/\s($filepattern)(\s+->\s+\S+)?$/mg) {
1030 my $mangled_version = join(".", $file =~ m/^$filepattern$/);
1031 foreach my $pat (@{$options{'uversionmangle'}}) {
1032 if (! safe_replace(\$mangled_version, $pat)) {
1033 warn "$progname: In $watchfile, potentially"
1034 . " unsafe or malformed uversionmangle"
1035 . " pattern:\n '$pat'"
1036 . " found. Skipping watchline\n"
1041 push @files, [$mangled_version, $file];
1047 print "-- Found the following matching files:\n";
1048 foreach my $file (@files) { print " $$file[1]\n"; }
1050 if (defined $download_version) {
1051 my @vfiles = grep { $$_[0] eq $download_version } @files;
1053 ($newversion, $newfile) = @{$vfiles[0]};
1055 warn "$progname warning: In $watchfile no matching files for version $download_version"
1056 . " in watch line\n $line\n";
1060 @files = Devscripts::Versort::versort(@files);
1061 ($newversion, $newfile) = @{$files[0]};
1064 warn "$progname warning: In $watchfile no matching files for watch line\n $line\n";
1069 # The original version of the code didn't use (...) in the watch
1070 # file to delimit the version number; thus if there is no (...)
1071 # in the pattern, we will use the old heuristics, otherwise we
1074 if ($style eq 'old') {
1075 # Old-style heuristics
1076 if ($newversion =~ /^\D*(\d+\.(?:\d+\.)*\d+)\D*$/) {
1080 $progname warning: In $watchfile, couldn\'t determine a
1081 pure numeric version number from the file name for watch line
1083 and file name $newfile
1084 Please use a new style watchfile instead!
1090 my $newfile_base=basename($newfile);
1091 if (exists $options{'filenamemangle'}) {
1092 $newfile_base=$newfile;
1094 foreach my $pat (@{$options{'filenamemangle'}}) {
1095 if (! safe_replace(\$newfile_base, $pat)) {
1096 warn "$progname: In $watchfile, potentially"
1097 . " unsafe or malformed filenamemangle"
1098 . " pattern:\n '$pat'"
1099 . " found. Skipping watchline\n"
1104 # Remove HTTP header trash
1105 if ($site =~ m%^https?://%) {
1106 $newfile_base =~ s/\?.*$//;
1107 # just in case this leaves us with nothing
1108 if ($newfile_base eq '') {
1109 $newfile_base = "$pkg-$newversion.download";
1113 # So what have we got to report now?
1115 # Upstream URL? Copying code from below - ugh.
1116 if ($site =~ m%^https?://%) {
1118 if ($newfile =~ m%^\w+://%) {
1119 $upstream_url = $newfile;
1121 # absolute filename?
1122 elsif ($newfile =~ m%^/%) {
1123 # Were there any redirections? If so try using those first
1124 if ($#patterns > 0) {
1125 # replace $site here with the one we were redirected to
1126 foreach my $index (0 .. $#patterns) {
1127 if ("$sites[$index]$newfile" =~ m&^$patterns[$index]$&) {
1128 $upstream_url = "$sites[$index]$newfile";
1132 if (!defined($upstream_url)) {
1134 warn "$progname warning: Unable to determine upstream url from redirections,\n" .
1135 "defaulting to using site specified in watchfile\n";
1137 $upstream_url = "$sites[0]$newfile";
1140 $upstream_url = "$sites[0]$newfile";
1143 # relative filename, we hope
1145 # Were there any redirections? If so try using those first
1146 if ($#patterns > 0) {
1147 # replace $site here with the one we were redirected to
1148 foreach my $index (0 .. $#patterns) {
1149 # skip unless the basedir looks like a directory
1150 next unless $basedirs[$index] =~ m%/$%;
1151 my $nf = "$basedirs[$index]$newfile";
1152 if ("$sites[$index]$nf" =~ m&^$patterns[$index]$&) {
1153 $upstream_url = "$sites[$index]$nf";
1157 if (!defined($upstream_url)) {
1159 warn "$progname warning: Unable to determine upstream url from redirections,\n" .
1160 "defaulting to using site specified in watchfile\n";
1162 $upstream_url = "$urlbase$newfile";
1165 $upstream_url = "$urlbase$newfile";
1169 # mangle if necessary
1170 $upstream_url =~ s/&/&/g;
1171 if (exists $options{'downloadurlmangle'}) {
1172 foreach my $pat (@{$options{'downloadurlmangle'}}) {
1173 if (! safe_replace(\$upstream_url, $pat)) {
1174 warn "$progname: In $watchfile, potentially"
1175 . " unsafe or malformed downloadurlmangle"
1176 . " pattern:\n '$pat'"
1177 . " found. Skipping watchline\n"
1186 $upstream_url = "$base$newfile";
1189 $dehs_tags{'debian-uversion'} = $lastversion;
1190 $dehs_tags{'debian-mangled-uversion'} = $mangled_lastversion;
1191 $dehs_tags{'upstream-version'} = $newversion;
1192 $dehs_tags{'upstream-url'} = $upstream_url;
1194 # Can't just use $lastversion eq $newversion, as then 0.01 and 0.1
1195 # compare different, whereas they are treated as equal by dpkg
1196 if (system("dpkg", "--compare-versions", "$mangled_lastversion", "eq", "$newversion") == 0) {
1197 if ($verbose or ($download == 0 and $report and ! $dehs)) {
1198 print $pkg_report_header;
1199 $pkg_report_header = '';
1200 print "Newest version on remote site is $newversion, local version is $lastversion\n" .
1201 ($mangled_lastversion eq $lastversion ? "" : " (mangled local version number $mangled_lastversion)\n");
1202 print " => Package is up to date\n";
1204 $dehs_tags{'status'} = "up to date";
1205 if (! $force_download) {
1212 # In all other cases, we'll want to report information even with --report
1213 if ($verbose or ($download == 0 and ! $dehs)) {
1214 print $pkg_report_header;
1215 $pkg_report_header = '';
1216 print "Newest version on remote site is $newversion, local version is $lastversion\n" .
1217 ($mangled_lastversion eq $lastversion ? "" : " (mangled local version number $mangled_lastversion)\n");
1220 # We use dpkg's rules to determine whether our current version
1221 # is newer or older than the remote version.
1222 if (!defined $download_version) {
1223 if (system("dpkg", "--compare-versions", "$mangled_lastversion", "gt", "$newversion") == 0) {
1225 print " => remote site does not even have current version\n";
1227 $dehs_tags{'status'} = "Debian version newer than remote site";
1229 print "$pkg: remote site does not even have current version\n";
1233 # There's a newer upstream version available, which may already
1234 # be on our system or may not be
1238 # Flag that we found a newer upstream version, so that the exit status
1243 if (defined $pkg_dir) {
1244 if (! -d "$destdir") {
1245 print "Package directory '$destdir to store downloaded file is not existing\n";
1248 if (-f "$destdir/$newfile_base") {
1249 print " => $newfile_base already in package directory\n"
1250 if $verbose or ($download == 0 and ! $dehs);
1253 if (-f "$destdir/${pkg}_${newversion}.orig.tar.gz") {
1254 print " => ${pkg}_${newversion}.orig.tar.gz already in package directory '$destdir'\n"
1255 if $verbose or ($download == 0 and ! $dehs);
1258 elsif (-f "$destdir/${pkg}_${newversion}.orig.tar.bz2") {
1259 print " => ${pkg}_${newversion}.orig.tar.bz2 already in package directory '$destdir'\n"
1260 if $verbose or ($download == 0 and ! $dehs);
1265 if ($force_download and $verbose) {
1266 print " => Forcing download as requested\n";
1267 } elsif ($verbose) {
1268 print " => Newer version available from\n";
1269 print " $upstream_url\n";
1271 $dehs_tags{'status'} = "Newer version available";
1273 my $msg_header = "$pkg: ";
1274 $msg_header .= $force_download ? "Version" : "Newer version";
1275 print "$msg_header ($newversion) available on remote site:\n $upstream_url\n (local version is $lastversion" .
1276 ($mangled_lastversion eq $lastversion ? "" : ", mangled local version number $mangled_lastversion") .
1280 if ($download < 0) {
1281 my $msg = "Not downloading as --package was used. Use --download to force downloading.";
1289 return 0 unless $download;
1291 print "-- Downloading updated package $newfile_base\n" if $verbose;
1292 if (! -d "$destdir") {
1293 print "Package directory '$destdir to store downloaded file is not existing\n";
1296 # Download newer package
1297 if ($upstream_url =~ m%^http(s)?://%) {
1298 if (defined($1) and !$haveSSL) {
1299 die "$progname: you must have the libcrypt-ssleay-perl package installed\nto use https URLs\n";
1301 # substitute HTML entities
1302 # Is anything else than "&" required? I doubt it.
1303 print STDERR "$progname debug: requesting URL $upstream_url\n" if $debug;
1304 $request = HTTP::Request->new('GET', $upstream_url);
1305 $response = $user_agent->request($request, "$destdir/$newfile_base");
1306 if (! $response->is_success) {
1307 if (defined $pkg_dir) {
1308 warn "$progname warning: In directory $pkg_dir, downloading\n $upstream_url failed: " . $response->status_line . "\n";
1310 warn "$progname warning: Downloading\n $upstream_url failed:\n" . $response->status_line . "\n";
1317 if (exists $options{'pasv'}) {
1318 $ENV{'FTP_PASSIVE'}=$options{'pasv'};
1320 print STDERR "$progname debug: requesting URL $upstream_url\n" if $debug;
1321 $request = HTTP::Request->new('GET', "$upstream_url");
1322 $response = $user_agent->request($request, "$destdir/$newfile_base");
1323 if (exists $options{'pasv'}) {
1324 if (defined $passive) { $ENV{'FTP_PASSIVE'}=$passive; }
1325 else { delete $ENV{'FTP_PASSIVE'}; }
1327 if (! $response->is_success) {
1328 if (defined $pkg_dir) {
1329 warn "$progname warning: In directory $pkg_dir, downloading\n $upstream_url failed: " . $response->status_line . "\n";
1331 warn "$progname warning: Downloading\n $upstream_url failed:\n" . $response->status_line . "\n";
1337 if ($repack and $newfile_base =~ /^(.*)\.(tar\.bz2|tbz2?)$/) {
1338 print "-- Repacking from bzip2 to gzip\n" if $verbose;
1339 my $newfile_base_gz = "$1.tar.gz";
1340 system("bunzip2 -c $destdir/$newfile_base | gzip -n -9 > $destdir/$newfile_base_gz") == 0
1341 or die "repacking from bzip2 to gzip failed\n";
1342 unlink "$destdir/$newfile_base";
1343 $newfile_base = $newfile_base_gz;
1346 if ($repack and $newfile_base =~ /^(.*)\.(tar\.lzma|tlz(?:ma?)?)$/) {
1347 print "-- Repacking from lzma to gzip\n" if $verbose;
1348 my $newfile_base_gz = "$1.tar.gz";
1349 system("lzma -cd $destdir/$newfile_base | gzip -n -9 > $destdir/$newfile_base_gz") == 0
1350 or die "repacking from lzma to gzip failed\n";
1351 unlink "$destdir/$newfile_base";
1352 $newfile_base = $newfile_base_gz;
1355 if ($repack and $newfile_base =~ /^(.*)\.(tar\.xz|txz)$/) {
1356 print "-- Repacking from xz to gzip\n" if $verbose;
1357 my $newfile_base_gz = "$1.tar.gz";
1358 system("xz -cd $destdir/$newfile_base | gzip -n -9 > $destdir/$newfile_base_gz") == 0
1359 or die "repacking from xz to gzip failed\n";
1360 unlink "$destdir/$newfile_base";
1361 $newfile_base = $newfile_base_gz;
1364 if ($repack and $newfile_base =~ /^(.*)\.zip$/) {
1365 print "-- Repacking from zip to .tar.gz\n" if $verbose;
1367 system('command -v unzip >/dev/null 2>&1') >> 8 == 0
1368 or die("unzip binary not found. You need to install the package unzip to be able to repack .zip upstream archives.\n");
1370 my $newfile_base_gz = "$1.tar.gz";
1371 my $tempdir = tempdir ( "uscanXXXX", TMPDIR => 1, CLEANUP => 1 );
1372 system("unzip -q -d $tempdir $destdir/$newfile_base; GZIP=-9 tar -C $tempdir -czf $destdir/$newfile_base_gz .") == 0
1373 or die("Repacking from zip to tar.gz failed\n");
1374 unlink "$destdir/$newfile_base";
1375 $newfile_base = $newfile_base_gz;
1378 if ($newfile_base =~ /\.(tar\.gz|tgz|tar\.bz2|tbz2?)$/) {
1379 my $filetype = `file $destdir/$newfile_base`;
1380 $filetype =~ s%^\.\./\Q$newfile_base\E: %%;
1381 unless ($filetype =~ /compressed data/) {
1382 warn "$progname warning: $destdir/$newfile_base does not appear to be a compressed file;\nthe file command says: $filetype\nNot processing this file any further!\n";
1387 if ($newfile_base =~ /\.(tar\.gz|tgz)$/) {
1388 if ($symlink eq 'symlink') {
1389 symlink $newfile_base, "$destdir/${pkg}_${newversion}.orig.tar.gz";
1390 } elsif ($symlink eq 'rename') {
1391 move "$destdir/$newfile_base", "$destdir/${pkg}_${newversion}.orig.tar.gz";
1394 elsif ($newfile_base =~ /\.(tar\.bz2|tbz2?)$/) {
1395 if ($symlink eq 'symlink') {
1396 symlink $newfile_base, "$destdir/${pkg}_${newversion}.orig.tar.bz2";
1397 } elsif ($symlink eq 'rename') {
1398 move "$destdir/$newfile_base", "$destdir/${pkg}_${newversion}.orig.tar.bz2";
1403 print "-- Successfully downloaded updated package $newfile_base\n";
1404 if ($newfile_base =~ /\.(tar\.gz|tgz)$/) {
1405 if ($symlink eq 'symlink') {
1406 print " and symlinked ${pkg}_${newversion}.orig.tar.gz to it\n";
1407 } elsif ($symlink eq 'rename') {
1408 print " and renamed it as ${pkg}_${newversion}.orig.tar.gz\n";
1411 elsif ($newfile_base =~ /\.(tar\.bz2|tbz2?)$/) {
1412 if ($symlink eq 'symlink') {
1413 print " and symlinked ${pkg}_${newversion}.orig.tar.bz2 to it\n";
1414 } elsif ($symlink eq 'rename') {
1415 print " and renamed it as ${pkg}_${newversion}.orig.tar.bz2\n";
1419 my $msg = "Successfully downloaded updated package $newfile_base";
1420 if ($newfile_base =~ /\.(tar\.gz|tgz)$/) {
1421 if ($symlink eq 'symlink') {
1422 $msg .= " and symlinked ${pkg}_${newversion}.orig.tar.gz to it";
1423 } elsif ($symlink eq 'rename') {
1424 $msg .= " and renamed it as ${pkg}_${newversion}.orig.tar.gz";
1427 elsif ($newfile_base =~ /\.(tar\.bz2|tbz2?)$/) {
1428 if ($symlink eq 'symlink') {
1429 $msg .= " and symlinked ${pkg}_${newversion}.orig.tar.bz2 to it";
1430 } elsif ($symlink eq 'rename') {
1431 $msg .= " and renamed it as ${pkg}_${newversion}.orig.tar.bz2";
1436 print "$pkg: Successfully downloaded updated package $newfile_base\n";
1437 if ($newfile_base =~ /\.(tar\.gz|tgz)$/) {
1438 if ($symlink eq 'symlink') {
1439 print " and symlinked ${pkg}_${newversion}.orig.tar.gz to it\n";
1440 } elsif ($symlink eq 'rename') {
1441 print " and renamed it as ${pkg}_${newversion}.orig.tar.gz\n";
1444 elsif ($newfile_base =~ /\.(tar\.bz2|tbz2?)$/) {
1445 if ($symlink eq 'symlink') {
1446 print " and symlinked ${pkg}_${newversion}.orig.tar.bz2 to it\n";
1447 } elsif ($symlink eq 'rename') {
1448 print " and renamed it as ${pkg}_${newversion}.orig.tar.bz2\n";
1453 # Do whatever the user wishes to do
1455 my $usefile = "$destdir/$newfile_base";
1456 my @cmd = shellwords($action);
1457 if ($symlink =~ /^(symlink|rename)$/
1458 and $newfile_base =~ /\.(tar\.gz|tgz)$/) {
1459 $usefile = "$destdir/${pkg}_${newversion}.orig.tar.gz";
1461 elsif ($symlink =~ /^(symlink|rename)$/
1462 and $newfile_base =~ /\.(tar\.bz2|tbz2)$/) {
1463 $usefile = "$destdir/${pkg}_${newversion}.orig.tar.bz2";
1466 # Any symlink requests are already handled by uscan
1467 if ($action =~ /^uupdate(\s|$)/) {
1468 push @cmd, "--no-symlink";
1471 if ($watch_version > 1) {
1472 push @cmd, ("--upstream-version", "$newversion", "$usefile");
1474 push @cmd, ("$usefile", "$newversion");
1476 my $actioncmd = join(" ", @cmd);
1477 print "-- Executing user specified script\n $actioncmd\n" if $verbose;
1479 my $msg = "Executing user specified script: $actioncmd; output:\n";
1480 $msg .= `$actioncmd 2>&1`;
1491 sub recursive_regex_dir ($$$) {
1492 my ($base, $optref, $watchfile)=@_;
1494 $base =~ m%^(\w+://[^/]+)/(.*)$%;
1496 my @dirs = split /(\/)/, $2;
1499 foreach my $dirpattern (@dirs) {
1500 if ($dirpattern =~ /\(.*\)/) {
1501 print STDERR "$progname debug: dir=>$dir dirpattern=>$dirpattern\n"
1504 newest_dir($site, $dir, $dirpattern, $optref, $watchfile);
1505 print STDERR "$progname debug: newest_dir => '$newest_dir'\n"
1507 if ($newest_dir ne '') {
1508 $dir .= "$newest_dir";
1514 $dir .= "$dirpattern";
1517 return $site . $dir;
1521 # very similar to code above
1522 sub newest_dir ($$$$$) {
1523 my ($site, $dir, $pattern, $optref, $watchfile) = @_;
1524 my $base = $site.$dir;
1525 my ($request, $response);
1527 if ($site =~ m%^http(s)?://%) {
1528 if (defined($1) and !$haveSSL) {
1529 die "$progname: you must have the libcrypt-ssleay-perl package installed\nto use https URLs\n";
1531 print STDERR "$progname debug: requesting URL $base\n" if $debug;
1532 $request = HTTP::Request->new('GET', $base);
1533 $response = $user_agent->request($request);
1534 if (! $response->is_success) {
1535 warn "$progname warning: In watchfile $watchfile, reading webpage\n $base failed: " . $response->status_line . "\n";
1539 my $content = $response->content;
1540 print STDERR "$progname debug: received content:\n$content\[End of received content\]\n"
1542 # We need this horrid stuff to handle href=foo type
1543 # links. OK, bad HTML, but we have to handle it nonetheless.
1545 $content =~ s/href\s*=\s*(?=[^\"\'])([^\s>]+)/href="$1"/ig;
1547 $content =~ s/<!-- .*?-->//sg;
1549 my $dirpattern = "(?:(?:$site)?" . quotemeta($dir) . ")?$pattern";
1551 print STDERR "$progname debug: matching pattern $dirpattern\n"
1554 while ($content =~ m/<\s*a\s+[^>]*href\s*=\s*([\"\'])(.*?)\1/gi) {
1556 if ($href =~ m&^$dirpattern/?$&) {
1557 my $mangled_version = join(".", $href =~ m&^$dirpattern/?$&);
1558 push @hrefs, [$mangled_version, $href];
1562 @hrefs = Devscripts::Versort::versort(@hrefs);
1564 print "-- Found the following matching hrefs (newest first):\n";
1565 foreach my $href (@hrefs) { print " $$href[1]\n"; }
1567 my $newdir = $hrefs[0][1];
1568 # just give the final directory component
1570 $newdir =~ s%^.*/%%;
1573 warn "$progname warning: In $watchfile,\n no matching hrefs for pattern\n $site$dir$pattern";
1578 # Better be an FTP site
1579 if ($site !~ m%^ftp://%) {
1583 if (exists $$optref{'pasv'}) {
1584 $ENV{'FTP_PASSIVE'}=$$optref{'pasv'};
1586 print STDERR "$progname debug: requesting URL $base\n" if $debug;
1587 $request = HTTP::Request->new('GET', $base);
1588 $response = $user_agent->request($request);
1589 if (exists $$optref{'pasv'}) {
1590 if (defined $passive) { $ENV{'FTP_PASSIVE'}=$passive; }
1591 else { delete $ENV{'FTP_PASSIVE'}; }
1593 if (! $response->is_success) {
1594 warn "$progname warning: In watchfile $watchfile, reading webpage\n $base failed: " . $response->status_line . "\n";
1598 my $content = $response->content;
1599 print STDERR "$progname debug: received content:\n$content\[End of received content]\n"
1602 # FTP directory listings either look like:
1603 # info info ... info filename [ -> linkname]
1604 # or they're HTMLised (if they've been through an HTTP proxy)
1605 # so we may have to look for <a href="filename"> type patterns
1606 print STDERR "$progname debug: matching pattern $pattern\n" if $debug;
1609 # We separate out HTMLised listings from standard listings, so
1610 # that we can target our search correctly
1611 if ($content =~ /<\s*a\s+[^>]*href/i) {
1613 m/(?:<\s*a\s+[^>]*href\s*=\s*\")((?-i)$pattern)\"/gi) {
1615 my $mangled_version = join(".", $dir =~ m/^$pattern$/);
1616 push @dirs, [$mangled_version, $dir];
1619 # they all look like:
1620 # info info ... info filename [ -> linkname]
1621 while ($content =~ m/($pattern)(\s+->\s+\S+)?$/mg) {
1623 my $mangled_version = join(".", $dir =~ m/^$pattern$/);
1624 push @dirs, [$mangled_version, $dir];
1629 print STDERR "-- Found the following matching dirs:\n";
1630 foreach my $dir (@dirs) { print STDERR " $$dir[1]\n"; }
1632 @dirs = Devscripts::Versort::versort(@dirs);
1633 my ($newversion, $newdir) = @{$dirs[0]};
1636 warn "$progname warning: In $watchfile no matching dirs for pattern\n $base$pattern\n";
1643 # parameters are dir, package, upstream version, good dirname
1644 sub process_watchfile ($$$$)
1646 my ($dir, $package, $version, $watchfile) = @_;
1647 my $watch_version=0;
1651 unless (open WATCH, $watchfile) {
1652 warn "$progname warning: could not open $watchfile: $!\n";
1663 if (s/(?<!\\)\\$//) {
1665 warn "$progname warning: $watchfile ended with \\; skipping last line\n";
1673 if (! $watch_version) {
1674 if (/^version\s*=\s*(\d+)(\s|$)/) {
1676 if ($watch_version < 2 or
1677 $watch_version > $CURRENT_WATCHFILE_VERSION) {
1678 warn "$progname ERROR: $watchfile version number is unrecognised; skipping watchfile\n";
1683 warn "$progname warning: $watchfile is an obsolete version 1 watchfile;\n please upgrade to a higher version\n (see uscan(1) for details).\n";
1688 # Are there any warnings from this part to give if we're using dehs?
1689 dehs_output if $dehs;
1691 # Handle shell \\ -> \
1692 s/\\\\/\\/g if $watch_version==1;
1694 print "-- In $watchfile, processing watchfile line:\n $_\n";
1695 } elsif ($download == 0 and ! $dehs) {
1696 $pkg_report_header = "Processing watchfile line for package $package...\n";
1700 process_watchline($_, $watch_version, $dir, $package, $version,
1702 dehs_output if $dehs;
1706 $status=1, warn "$progname warning: problems reading $watchfile: $!\n";
1712 # Collect up messages for dehs output into a tag
1717 push @{$dehs_tags{'messages'}}, $msg;
1722 my $warning = $_[0];
1723 $warning =~ s/\s*$//;
1724 push @{$dehs_tags{'warnings'}}, $warning;
1731 %dehs_tags = ('errors' => "$msg");
1739 return unless $dehs;
1741 if (! $dehs_start_output) {
1743 $dehs_start_output=1;
1746 for my $tag (qw(package debian-uversion debian-mangled-uversion
1747 upstream-version upstream-url
1748 status messages warnings errors)) {
1749 if (exists $dehs_tags{$tag}) {
1750 if (ref $dehs_tags{$tag} eq "ARRAY") {
1751 foreach my $entry (@{$dehs_tags{$tag}}) {
1752 $entry =~ s/</</g;
1753 $entry =~ s/>/>/g;
1754 $entry =~ s/&/&/g;
1755 print "<$tag>$entry</$tag>\n";
1758 $dehs_tags{$tag} =~ s/</</g;
1759 $dehs_tags{$tag} =~ s/>/>/g;
1760 $dehs_tags{$tag} =~ s/&/&/g;
1761 print "<$tag>$dehs_tags{$tag}</$tag>\n";
1765 if ($dehs_end_output) {
1769 # Don't repeat output
1773 sub quoted_regex_parse($) {
1774 my $pattern = shift;
1775 my %closers = ('{', '}', '[', ']', '(', ')', '<', '>');
1777 $pattern =~ /^(s|tr|y)(.)(.*)$/;
1778 my ($sep, $rest) = ($2, $3 || '');
1779 my $closer = $closers{$sep};
1783 my $replacement = '';
1786 my $last_was_escape = 0;
1787 my $in_replacement = 0;
1789 for my $char (split //, $rest) {
1790 if ($char eq $sep and ! $last_was_escape) {
1793 if ($in_replacement) {
1794 # Separator after end of replacement
1798 $in_replacement = 1;
1802 if ($in_replacement) {
1803 $replacement .= $char;
1809 } elsif ($char eq $closer and ! $last_was_escape) {
1812 if ($in_replacement) {
1813 $replacement .= $char;
1817 } elsif ($open < 0) {
1822 if ($in_replacement) {
1824 $replacement .= $char;
1832 # Don't treat \\ as an escape
1833 $last_was_escape = ($char eq '\\' and ! $last_was_escape);
1836 $parsed_ok = 0 unless $in_replacement and $open == 0;
1838 return ($parsed_ok, $regexp, $replacement, $flags);
1841 sub safe_replace($$) {
1842 my ($in, $pat) = @_;
1843 $pat =~ s/^\s*(.*?)\s*$/$1/;
1845 $pat =~ /^(s|tr|y)(.)/;
1846 my ($op, $sep) = ($1, $2 || '');
1847 my $esc = "\Q$sep\E";
1848 my ($parsed_ok, $regexp, $replacement, $flags);
1850 if ($sep eq '{' or $sep eq '(' or $sep eq '[' or $sep eq '<') {
1851 ($parsed_ok, $regexp, $replacement, $flags) = quoted_regex_parse($pat);
1853 return 0 unless $parsed_ok;
1854 } elsif ($pat !~ /^(?:s|tr|y)$esc((?:\\.|[^\\$esc])*)$esc((?:\\.|[^\\$esc])*)$esc([a-z]*)$/) {
1857 ($regexp, $replacement, $flags) = ($1, $2, $3);
1860 my $safeflags = $flags;
1861 if ($op eq 'tr' or $op eq 'y') {
1862 $safeflags =~ tr/cds//cd;
1863 return 0 if $safeflags ne $flags;
1865 $regexp =~ s/\\(.)/$1/g;
1866 $replacement =~ s/\\(.)/$1/g;
1868 $regexp =~ s/([^-])/'\\x' . unpack 'H*', $1/ge;
1869 $replacement =~ s/([^-])/'\\x' . unpack 'H*', $1/ge;
1871 eval "\$\$in =~ tr<$regexp><$replacement>$flags;";
1879 $safeflags =~ tr/gix//cd;
1880 return 0 if $safeflags ne $flags;
1882 my $global = ($flags =~ s/g//);
1883 $flags = "(?$flags)" if length $flags;
1886 if ($regexp =~ /(?<!\\)(\\\\)*\\G/) {
1888 # if it's not initial, it is too dangerous
1889 return 0 if $regexp =~ /^.*[^\\](\\\\)*\\G/;
1892 # Behave like Perl and treat e.g. "\." in replacement as "."
1893 # We allow the case escape characters to remain and
1894 # process them later
1895 $replacement =~ s/(^|[^\\])\\([^luLUE])/$1$2/g;
1897 # Unescape escaped separator characters
1898 $replacement =~ s/\\\Q$sep\E/$sep/g;
1899 # If bracketing quotes were used, also unescape the
1901 $replacement =~ s/\\\Q}\E/}/g if $sep eq '{';
1902 $replacement =~ s/\\\Q]\E/]/g if $sep eq '[';
1903 $replacement =~ s/\\\Q)\E/)/g if $sep eq '(';
1904 $replacement =~ s/\\\Q>\E/>/g if $sep eq '<';
1906 # The replacement below will modify $replacement so keep
1907 # a copy. We'll need to restore it to the current value if
1908 # the global flag was set on the input pattern.
1909 my $orig_replacement = $replacement;
1911 my ($first, $last, $pos, $zerowidth, $matched, @captures) = (0, -1, 0);
1914 # handle errors due to unsafe constructs in $regexp
1918 pos($$in) = $pos if $pos;
1921 # previous match was a zero-width match, simulate it to set
1922 # the internal flag that avoids the infinite loop
1925 # Need to use /g to make it use and save pos()
1926 $matched = ($$in =~ /$flags$regexp/g);
1929 # save position and size of the match
1932 ($first, $last) = ($-[0], $+[0]);
1935 # \G in the match, weird things can happen
1936 $zerowidth = ($pos == $oldpos);
1937 # For example, matching without a match
1938 $matched = 0 if (not defined $first
1939 or not defined $last);
1941 $zerowidth = ($last - $first == 0);
1943 for my $i (0..$#-) {
1944 $captures[$i] = substr $$in, $-[$i], $+[$i] - $-[$i];
1950 # No match; leave the original string untouched but return
1951 # success as there was nothing wrong with the pattern
1952 return 1 unless $matched;
1955 $replacement =~ s/[\$\\](\d)/defined $captures[$1] ? $captures[$1] : ''/ge;
1956 $replacement =~ s/\$\{(\d)\}/defined $captures[$1] ? $captures[$1] : ''/ge;
1957 $replacement =~ s/\$&/$captures[0]/g;
1959 # Make \l etc escapes work
1960 $replacement =~ s/\\l(.)/lc $1/e;
1961 $replacement =~ s/\\L(.*?)(\\E|\z)/lc $1/e;
1962 $replacement =~ s/\\u(.)/uc $1/e;
1963 $replacement =~ s/\\U(.*?)(\\E|\z)/uc $1/e;
1965 # Actually do the replacement
1966 substr $$in, $first, $last - $first, $replacement;
1968 $pos += length($replacement) - ($last - $first);
1971 $replacement = $orig_replacement;