830a0ac824f7dfd2eec6d8b6ef3fad411c8a1697
1 #! /usr/bin/perl -w
3 # uscan: This program looks for watchfiles and checks upstream ftp sites
4 # for later versions of the software.
5 #
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
10 #
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.
15 #
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.
20 #
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
25 use strict;
26 use Cwd;
27 use Cwd 'abs_path';
28 use Dpkg::IPC;
29 use Try::Tiny;
30 use File::Basename;
31 use File::Copy;
32 use File::Temp qw/tempfile tempdir/;
33 use filetest 'access';
34 use Getopt::Long qw(:config gnu_getopt);
35 use lib '/usr/share/devscripts';
36 use Devscripts::Versort;
37 use Text::ParseWords;
38 BEGIN {
39 eval { require LWP::UserAgent; };
40 if ($@) {
41 my $progname = basename($0);
42 if ($@ =~ /^Can\'t locate LWP\/UserAgent\.pm/) {
43 die "$progname: you must have the libwww-perl package installed\nto use this script\n";
44 } else {
45 die "$progname: problem loading the LWP::UserAgent module:\n $@\nHave you installed the libwww-perl package?\n";
46 }
47 }
48 }
49 # Dpkg::Control::Hash prefered by James McCoy (who did the last three uscan.pl edits using a debian.org e-mail address)
50 use Dpkg::Control::Hash;
51 # Parse::DebControl suggested by Jonas Smedegaard
52 # use Parse::DebControl;
54 my $CURRENT_WATCHFILE_VERSION = 3;
56 my $progname = basename($0);
57 my $modified_conf_msg;
58 my $opwd = cwd();
60 my $haveSSL = 1;
61 eval { require Crypt::SSLeay; };
62 if ($@) {
63 $haveSSL = 0;
64 }
66 # Did we find any new upstream versions on our wanderings?
67 our $found = 0;
69 sub process_watchline ($$$$$$);
70 sub process_watchfile ($$$$);
71 sub recursive_regex_dir ($$$);
72 sub newest_dir ($$$$$);
73 sub dehs_msg ($);
74 sub dehs_warn ($);
75 sub dehs_die ($);
76 sub dehs_output ();
77 sub quoted_regex_replace ($);
78 sub safe_replace ($$);
79 sub get_main_source_dir($$$$$);
80 sub compress_archive($$$);
82 sub usage {
83 print <<"EOF";
84 Usage: $progname [options] [dir ...]
85 Process watchfiles in all .../debian/ subdirs of those listed (or the
86 current directory if none listed) to check for upstream releases.
87 Options:
88 --report Only report on newer or absent versions, do not download
89 --report-status
90 Report status of packages, but do not download
91 --debug Dump the downloaded web pages to stdout for debugging
92 your watch file.
93 --destdir Path of directory to which to download.
94 --download Report on newer and absent versions, and download (default)
95 --force-download
96 Always download the upstream release, even if up to date
97 --no-download Report on newer and absent versions, but don\'t download
98 --pasv Use PASV mode for FTP connections
99 --no-pasv Do not use PASV mode for FTP connections (default)
100 --timeout N Specifies how much time, in seconds, we give remote
101 servers to respond (default 20 seconds)
102 --symlink Make an orig.tar.gz symlink to downloaded file (default)
103 --rename Rename to orig.tar.gz instead of symlinking
104 (Both will use orig.tar.bz2, orig.tar.lzma, or orig.tar.xz
105 if appropriate)
106 --repack Repack downloaded archives from orig.tar.bz2, orig.tar.lzma,
107 orig.tar.xz or orig.zip to orig.tar.gz
108 (does nothing if downloaded archive orig.tar.gz)
109 --repack-compression COMP
110 When some repackaging is done use compression COMP for
111 the resulting tarball
112 --no-symlink Don\'t make symlink or rename
113 --verbose Give verbose output
114 --no-verbose Don\'t give verbose output (default)
115 --check-dirname-level N
116 How much to check directory names:
117 N=0 never
118 N=1 only when program changes directory (default)
119 N=2 always
120 --check-dirname-regex REGEX
121 What constitutes a matching directory name; REGEX is
122 a Perl regular expression; the string \`PACKAGE\' will
123 be replaced by the package name; see manpage for details
124 (default: 'PACKAGE(-.+)?')
125 --watchfile FILE
126 Specify the watchfile rather than using debian/watch;
127 no directory traversing will be done in this case
128 --upstream-version VERSION
129 Specify the current upstream version in use rather than
130 parsing debian/changelog to determine this
131 --download-version VERSION
132 Specify the version which the upstream release must
133 match in order to be considered, rather than using the
134 release with the highest version
135 --download-current-version
136 Download the currently packaged version
137 --package PACKAGE
138 Specify the package name rather than examining
139 debian/changelog; must use --upstream-version and
140 --watchfile with this option, no directory traversing
141 will be performed, no actions (even downloading) will be
142 carried out
143 --no-dehs Use traditional uscan output format (default)
144 --dehs Use DEHS style output (XML-type)
145 --user-agent, --useragent
146 Override the default user agent
147 --no-conf, --noconf
148 Don\'t read devscripts config files;
149 must be the first option given
150 --no-exclusion no automatic exclusion of files mentioned in
151 debian/copyright field Files-Excluded
152 --help Show this message
153 --version Show version information
155 Default settings modified by devscripts configuration files:
156 $modified_conf_msg
157 EOF
158 }
160 sub version {
161 print <<"EOF";
162 This is $progname, from the Debian devscripts package, version ###VERSION###
163 This code is copyright 1999-2006 by Julian Gilbey, all rights reserved.
164 Original code by Christoph Lameter.
165 This program comes with ABSOLUTELY NO WARRANTY.
166 You are free to redistribute this code under the terms of the
167 GNU General Public License, version 2 or later.
168 EOF
169 }
171 # What is the default setting of $ENV{'FTP_PASSIVE'}?
172 our $passive = 'default';
174 # Now start by reading configuration files and then command line
175 # The next stuff is boilerplate
177 my $destdir = "..";
178 my $download = 1;
179 my $download_version;
180 my $force_download = 0;
181 my $report = 0; # report even on up-to-date packages?
182 my $repack = 0; # repack .tar.bz2, .tar.lzma, .tar.xz or .zip to .tar.gz
183 my $default_compression = 'gz' ;
184 my $repack_compression = $default_compression;
185 my $symlink = 'symlink';
186 my $verbose = 0;
187 my $check_dirname_level = 1;
188 my $check_dirname_regex = 'PACKAGE(-.+)?';
189 my $dehs = 0;
190 my %dehs_tags;
191 my $dehs_end_output = 0;
192 my $dehs_start_output = 0;
193 my $pkg_report_header = '';
194 my $timeout = 20;
195 my $user_agent_string = 'Debian uscan ###VERSION###';
196 my $no_exclusion = 0;
198 if (@ARGV and $ARGV[0] =~ /^--no-?conf$/) {
199 $modified_conf_msg = " (no configuration files read)";
200 shift;
201 } else {
202 my @config_files = ('/etc/devscripts.conf', '~/.devscripts');
203 my %config_vars = (
204 'USCAN_TIMEOUT' => 20,
205 'USCAN_DESTDIR' => '..',
206 'USCAN_DOWNLOAD' => 'yes',
207 'USCAN_PASV' => 'default',
208 'USCAN_SYMLINK' => 'symlink',
209 'USCAN_VERBOSE' => 'no',
210 'USCAN_DEHS_OUTPUT' => 'no',
211 'USCAN_USER_AGENT' => '',
212 'USCAN_REPACK' => 'no',
213 'USCAN_NO_EXCLUSION' => 'no',
214 'DEVSCRIPTS_CHECK_DIRNAME_LEVEL' => 1,
215 'DEVSCRIPTS_CHECK_DIRNAME_REGEX' => 'PACKAGE(-.+)?',
216 );
217 my %config_default = %config_vars;
219 my $shell_cmd;
220 # Set defaults
221 foreach my $var (keys %config_vars) {
222 $shell_cmd .= qq[$var="$config_vars{$var}";\n];
223 }
224 $shell_cmd .= 'for file in ' . join(" ",@config_files) . "; do\n";
225 $shell_cmd .= '[ -f $file ] && . $file; done;' . "\n";
226 # Read back values
227 foreach my $var (keys %config_vars) { $shell_cmd .= "echo \$$var;\n" }
228 my $shell_out = `/bin/bash -c '$shell_cmd'`;
229 @config_vars{keys %config_vars} = split /\n/, $shell_out, -1;
231 # Check validity
232 $config_vars{'USCAN_DESTDIR'} =~ /^\s*(\S+)\s*$/
233 or $config_vars{'USCAN_DESTDIR'}='..';
234 $config_vars{'USCAN_DOWNLOAD'} =~ /^(yes|no)$/
235 or $config_vars{'USCAN_DOWNLOAD'}='yes';
236 $config_vars{'USCAN_PASV'} =~ /^(yes|no|default)$/
237 or $config_vars{'USCAN_PASV'}='default';
238 $config_vars{'USCAN_TIMEOUT'} =~ m/^\d+$/
239 or $config_vars{'USCAN_TIMEOUT'}=20;
240 $config_vars{'USCAN_SYMLINK'} =~ /^(yes|no|symlinks?|rename)$/
241 or $config_vars{'USCAN_SYMLINK'}='yes';
242 $config_vars{'USCAN_SYMLINK'}='symlink'
243 if $config_vars{'USCAN_SYMLINK'} eq 'yes' or
244 $config_vars{'USCAN_SYMLINK'} =~ /^symlinks?$/;
245 $config_vars{'USCAN_VERBOSE'} =~ /^(yes|no)$/
246 or $config_vars{'USCAN_VERBOSE'}='no';
247 $config_vars{'USCAN_DEHS_OUTPUT'} =~ /^(yes|no)$/
248 or $config_vars{'USCAN_DEHS_OUTPUT'}='no';
249 $config_vars{'USCAN_REPACK'} =~ /^(yes|no)$/
250 or $config_vars{'USCAN_REPACK'}='no';
251 $config_vars{'USCAN_NO_EXCLUSION'} =~ /^(yes|no)$/
252 or $config_vars{'USCAN_NO_EXCLUSION'}='no';
253 $config_vars{'DEVSCRIPTS_CHECK_DIRNAME_LEVEL'} =~ /^[012]$/
254 or $config_vars{'DEVSCRIPTS_CHECK_DIRNAME_LEVEL'}=1;
256 foreach my $var (sort keys %config_vars) {
257 if ($config_vars{$var} ne $config_default{$var}) {
258 $modified_conf_msg .= " $var=$config_vars{$var}\n";
259 }
260 }
261 $modified_conf_msg ||= " (none)\n";
262 chomp $modified_conf_msg;
264 $destdir = $config_vars{'USCAN_DESTDIR'}
265 if defined $config_vars{'USCAN_DESTDIR'};
266 $download = $config_vars{'USCAN_DOWNLOAD'} eq 'no' ? 0 : 1;
267 $passive = $config_vars{'USCAN_PASV'} eq 'yes' ? 1 :
268 $config_vars{'USCAN_PASV'} eq 'no' ? 0 : 'default';
269 $timeout = $config_vars{'USCAN_TIMEOUT'};
270 $symlink = $config_vars{'USCAN_SYMLINK'};
271 $verbose = $config_vars{'USCAN_VERBOSE'} eq 'yes' ? 1 : 0;
272 $dehs = $config_vars{'USCAN_DEHS_OUTPUT'} eq 'yes' ? 1 : 0;
273 $check_dirname_level = $config_vars{'DEVSCRIPTS_CHECK_DIRNAME_LEVEL'};
274 $check_dirname_regex = $config_vars{'DEVSCRIPTS_CHECK_DIRNAME_REGEX'};
275 $user_agent_string = $config_vars{'USCAN_USER_AGENT'}
276 if $config_vars{'USCAN_USER_AGENT'};
277 $repack = $config_vars{'USCAN_REPACK'} eq 'yes' ? 1 : 0;
278 }
280 # Now read the command line arguments
281 my $debug = 0;
282 my ($opt_h, $opt_v, $opt_destdir, $opt_download, $opt_force_download,
283 $opt_report, $opt_passive, $opt_symlink, $opt_repack,
284 $opt_repack_compression, $opt_no_exclusion);
285 my ($opt_verbose, $opt_level, $opt_regex, $opt_noconf);
286 my ($opt_package, $opt_uversion, $opt_watchfile, $opt_dehs, $opt_timeout);
287 my $opt_download_version;
288 my $opt_user_agent;
289 my $opt_download_current_version;
291 GetOptions("help" => \$opt_h,
292 "version" => \$opt_v,
293 "destdir=s" => \$opt_destdir,
294 "download!" => \$opt_download,
295 "download-version=s" => \$opt_download_version,
296 "force-download" => \$opt_force_download,
297 "report" => sub { $opt_download = 0; },
298 "report-status" => sub { $opt_download = 0; $opt_report = 1; },
299 "passive|pasv!" => \$opt_passive,
300 "timeout=i" => \$opt_timeout,
301 "symlink!" => sub { $opt_symlink = $_[1] ? 'symlink' : 'no'; },
302 "rename" => sub { $opt_symlink = 'rename'; },
303 "repack" => sub { $opt_repack = 1; },
304 "repack-compression=s" => \$opt_repack_compression,
305 "package=s" => \$opt_package,
306 "upstream-version=s" => \$opt_uversion,
307 "watchfile=s" => \$opt_watchfile,
308 "dehs!" => \$opt_dehs,
309 "verbose!" => \$opt_verbose,
310 "debug" => \$debug,
311 "check-dirname-level=s" => \$opt_level,
312 "check-dirname-regex=s" => \$opt_regex,
313 "user-agent=s" => \$opt_user_agent,
314 "useragent=s" => \$opt_user_agent,
315 "noconf" => \$opt_noconf,
316 "no-conf" => \$opt_noconf,
317 "no-exclusion" => \$opt_no_exclusion,
318 "download-current-version" => \$opt_download_current_version,
319 )
320 or die "Usage: $progname [options] [directories]\nRun $progname --help for more details\n";
322 if ($opt_noconf) {
323 die "$progname: --no-conf is only acceptable as the first command-line option!\n";
324 }
325 if ($opt_h) { usage(); exit 0; }
326 if ($opt_v) { version(); exit 0; }
328 # Now we can set the other variables according to the command line options
330 $destdir = $opt_destdir if defined $opt_destdir;
331 $download = $opt_download if defined $opt_download;
332 $force_download = $opt_force_download if defined $opt_force_download;
333 $report = $opt_report if defined $opt_report;
334 $repack = $opt_repack if defined $opt_repack;
335 $passive = $opt_passive if defined $opt_passive;
336 $timeout = $opt_timeout if defined $opt_timeout;
337 $timeout = 20 unless defined $timeout and $timeout > 0;
338 $symlink = $opt_symlink if defined $opt_symlink;
339 $verbose = $opt_verbose if defined $opt_verbose;
340 if ( defined $opt_repack_compression ) {
341 if ( $opt_repack_compression =~ /^gz$/ or
342 $opt_repack_compression =~ /^bz2$/ or
343 $opt_repack_compression =~ /^xz$/ or
344 $opt_repack_compression =~ /^lzma$/ ) {
345 $repack_compression = $opt_repack_compression;
346 } else {
347 print "-- Invalid compression $opt_repack_compression given. Use default $default_compression instead.\n" if $verbose ;
348 }
349 }
350 $dehs = $opt_dehs if defined $opt_dehs;
351 $no_exclusion = $opt_no_exclusion if defined $opt_no_exclusion;
352 $user_agent_string = $opt_user_agent if defined $opt_user_agent;
353 $download_version = $opt_download_version if defined $opt_download_version;
354 if ($dehs) {
355 $SIG{'__WARN__'} = \&dehs_warn;
356 $SIG{'__DIE__'} = \&dehs_die;
357 }
359 if (defined $opt_level) {
360 if ($opt_level =~ /^[012]$/) { $check_dirname_level = $opt_level; }
361 else {
362 die "$progname: unrecognised --check-dirname-level value (allowed are 0,1,2)\n";
363 }
364 }
366 $check_dirname_regex = $opt_regex if defined $opt_regex;
368 if (defined $opt_package) {
369 die "$progname: --package requires the use of --watchfile\nas well; run $progname --help for more details\n"
370 unless defined $opt_watchfile;
371 $download = -$download unless defined $opt_download;
372 }
374 die "$progname: Can't use --verbose if you're using --dehs!\n"
375 if $verbose and $dehs;
377 die "$progname: Can't use --report-status if you're using --verbose!\n"
378 if $verbose and $report;
380 die "$progname: Can't use --report-status if you're using --download!\n"
381 if $download and $report;
383 warn "$progname: You're going to get strange (non-XML) output using --debug and --dehs together!\n"
384 if $debug and $dehs;
386 # We'd better be verbose if we're debugging
387 $verbose |= $debug;
389 # Net::FTP understands this
390 if ($passive ne 'default') {
391 $ENV{'FTP_PASSIVE'} = $passive;
392 }
393 elsif (exists $ENV{'FTP_PASSIVE'}) {
394 $passive = $ENV{'FTP_PASSIVE'};
395 }
396 else { $passive = undef; }
397 # Now we can say
398 # if (defined $passive) { $ENV{'FTP_PASSIVE'}=$passive; }
399 # else { delete $ENV{'FTP_PASSIVE'}; }
400 # to restore $ENV{'FTP_PASSIVE'} to what it was at this point
402 # dummy subclass used to store all the redirections for later use
403 package LWP::UserAgent::UscanCatchRedirections;
405 use base 'LWP::UserAgent';
407 my @uscan_redirections;
409 sub redirect_ok {
410 my $self = shift;
411 my ($request) = @_;
412 if ($self->SUPER::redirect_ok(@_)) {
413 push @uscan_redirections, $request->uri;
414 return 1;
415 }
416 return 0;
417 }
419 sub get_redirections {
420 return \@uscan_redirections;
421 }
423 package main;
425 my $user_agent = LWP::UserAgent::UscanCatchRedirections->new(env_proxy => 1);
426 $user_agent->timeout($timeout);
427 $user_agent->agent($user_agent_string);
429 if (defined $opt_watchfile) {
430 die "Can't have directory arguments if using --watchfile" if @ARGV;
432 # no directory traversing then, and things are very simple
433 if (defined $opt_package) {
434 # no need to even look for a changelog!
435 process_watchfile(undef, $opt_package, $opt_uversion, $opt_watchfile);
436 } else {
437 # Check for debian/changelog file
438 until (-r 'debian/changelog') {
439 chdir '..' or die "$progname: can't chdir ..: $!\n";
440 if (cwd() eq '/') {
441 die "$progname: cannot find readable debian/changelog anywhere!\nAre you in the source code tree?\n";
442 }
443 }
445 # Figure out package info we need
446 my $changelog = `dpkg-parsechangelog`;
447 unless ($? == 0) {
448 die "$progname: Problems running dpkg-parsechangelog\n";
449 }
451 my ($package, $debversion, $uversion);
452 $changelog =~ /^Source: (.*?)$/m and $package=$1;
453 $changelog =~ /^Version: (.*?)$/m and $debversion=$1;
454 if (! defined $package || ! defined $debversion) {
455 die "$progname: Problems determining package name and/or version from\n debian/changelog\n";
456 }
458 # Check the directory is properly named for safety
459 my $good_dirname = 1;
460 if ($check_dirname_level == 2 or
461 ($check_dirname_level == 1 and cwd() ne $opwd)) {
462 my $re = $check_dirname_regex;
463 $re =~ s/PACKAGE/\Q$package\E/g;
464 if ($re =~ m%/%) {
465 $good_dirname = (cwd() =~ m%^$re$%);
466 } else {
467 $good_dirname = (basename(cwd()) =~ m%^$re$%);
468 }
469 }
470 if (! $good_dirname) {
471 die "$progname: not processing watchfile because this directory does not match the package name\n" .
472 " or the settings of the--check-dirname-level and --check-dirname-regex options if any.\n";
473 }
475 # Get current upstream version number
476 if (defined $opt_uversion) {
477 $uversion = $opt_uversion;
478 } else {
479 $uversion = $debversion;
480 $uversion =~ s/-[^-]+$//; # revision
481 $uversion =~ s/^\d+://; # epoch
482 }
484 process_watchfile(cwd(), $package, $uversion, $opt_watchfile);
485 }
487 # Are there any warnings to give if we're using dehs?
488 $dehs_end_output=1;
489 dehs_output if $dehs;
490 exit ($found ? 0 : 1);
491 }
493 # Otherwise we're scanning for watchfiles
494 push @ARGV, '.' if ! @ARGV;
495 print "-- Scanning for watchfiles in @ARGV\n" if $verbose;
497 # Run find to find the directories. We will handle filenames with spaces
498 # correctly, which makes this code a little messier than it would be
499 # otherwise.
500 my @dirs;
501 open FIND, '-|', 'find', @ARGV, qw(-follow -type d -name debian -print)
502 or die "$progname: couldn't exec find: $!\n";
504 while (<FIND>) {
505 chomp;
506 push @dirs, $_;
507 }
508 close FIND;
510 die "$progname: No debian directories found\n" unless @dirs;
512 my @debdirs = ();
514 my $origdir = cwd;
515 for my $dir (@dirs) {
516 unless (chdir $origdir) {
517 warn "$progname warning: Couldn't chdir back to $origdir, skipping: $!\n";
518 next;
519 }
520 $dir =~ s%/debian$%%;
521 unless (chdir $dir) {
522 warn "$progname warning: Couldn't chdir $dir, skipping: $!\n";
523 next;
524 }
526 # Check for debian/watch file
527 if (-r 'debian/watch' and -r 'debian/changelog') {
528 # Figure out package info we need
529 my $changelog = `dpkg-parsechangelog`;
530 unless ($? == 0) {
531 warn "$progname warning: Problems running dpkg-parsechangelog in $dir, skipping\n";
532 next;
533 }
535 my ($package, $debversion, $uversion);
536 $changelog =~ /^Source: (.*?)$/m and $package=$1;
537 $changelog =~ /^Version: (.*?)$/m and $debversion=$1;
538 if (! defined $package || ! defined $debversion) {
539 warn "$progname warning: Problems determining package name and/or version from\n $dir/debian/changelog, skipping\n";
540 next;
541 }
543 # Check the directory is properly named for safety
544 my $good_dirname = 1;
545 if ($check_dirname_level == 2 or
546 ($check_dirname_level == 1 and cwd() ne $opwd)) {
547 my $re = $check_dirname_regex;
548 $re =~ s/PACKAGE/\Q$package\E/g;
549 if ($re =~ m%/%) {
550 $good_dirname = (cwd() =~ m%^$re$%);
551 } else {
552 $good_dirname = (basename(cwd()) =~ m%^$re$%);
553 }
554 }
555 if ($good_dirname) {
556 print "-- Found watchfile in $dir/debian\n" if $verbose;
557 } else {
558 print "-- Skip watchfile in $dir/debian since it does not match the package name\n" .
559 " (or the settings of the --check-dirname-level and --check-dirname-regex options if any).\n"
560 if $verbose;
561 next;
562 }
564 # Get upstream version number
565 $uversion = $debversion;
566 $uversion =~ s/-[^-]+$//; # revision
567 $uversion =~ s/^\d+://; # epoch
569 push @debdirs, [$debversion, $dir, $package, $uversion];
570 }
571 elsif (-r 'debian/watch') {
572 warn "$progname warning: Found watchfile in $dir,\n but couldn't find/read changelog; skipping\n";
573 next;
574 }
575 elsif (-f 'debian/watch') {
576 warn "$progname warning: Found watchfile in $dir,\n but it is not readable; skipping\n";
577 next;
578 }
579 }
581 warn "$progname: no watch file found\n" if (@debdirs == 0 and $report);
583 # Was there a --uversion option?
584 if (defined $opt_uversion) {
585 if (@debdirs == 1) {
586 $debdirs[0][3] = $opt_uversion;
587 } else {
588 warn "$progname warning: ignoring --uversion as more than one debian/watch file found\n";
589 }
590 }
592 # Now sort the list of directories, so that we process the most recent
593 # directories first, as determined by the package version numbers
594 @debdirs = Devscripts::Versort::deb_versort(@debdirs);
596 # Now process the watchfiles in order. If a directory d has subdirectories
597 # d/sd1/debian and d/sd2/debian, which each contain watchfiles corresponding
598 # to the same package, then we only process the watchfile in the package with
599 # the latest version number.
600 my %donepkgs;
601 for my $debdir (@debdirs) {
602 shift @$debdir; # don't need the Debian version number any longer
603 my $dir = $$debdir[0];
604 my $parentdir = dirname($dir);
605 my $package = $$debdir[1];
606 my $version = $$debdir[2];
608 if (exists $donepkgs{$parentdir}{$package}) {
609 warn "$progname warning: Skipping $dir/debian/watch\n as this package has already been scanned successfully\n";
610 next;
611 }
613 unless (chdir $origdir) {
614 warn "$progname warning: Couldn't chdir back to $origdir, skipping: $!\n";
615 next;
616 }
617 unless (chdir $dir) {
618 warn "$progname warning: Couldn't chdir $dir, skipping: $!\n";
619 next;
620 }
622 if (process_watchfile($dir, $package, $version, "debian/watch")
623 == 0) {
624 $donepkgs{$parentdir}{$package} = 1;
625 }
626 # Are there any warnings to give if we're using dehs?
627 dehs_output if $dehs;
628 }
630 print "-- Scan finished\n" if $verbose;
632 $dehs_end_output=1;
633 dehs_output if $dehs;
634 exit ($found ? 0 : 1);
637 # This is the heart of the code: Process a single watch item
638 #
639 # watch_version=1: Lines have up to 5 parameters which are:
640 #
641 # $1 = Remote site
642 # $2 = Directory on site
643 # $3 = Pattern to match, with (...) around version number part
644 # $4 = Last version we have (or 'debian' for the current Debian version)
645 # $5 = Actions to take on successful retrieval
646 #
647 # watch_version=2:
648 #
649 # For ftp sites:
650 # ftp://site.name/dir/path/pattern-(.+)\.tar\.gz [version [action]]
651 #
652 # For http sites:
653 # http://site.name/dir/path/pattern-(.+)\.tar\.gz [version [action]]
654 # or
655 # http://site.name/dir/path/base pattern-(.+)\.tar\.gz [version [action]]
656 #
657 # Lines can be prefixed with opts=<opts>.
658 #
659 # Then the patterns matched will be checked to find the one with the
660 # greatest version number (as determined by the (...) group), using the
661 # Debian version number comparison algorithm described below.
662 #
663 # watch_version=3:
664 #
665 # Correct handling of regex special characters in the path part:
666 # ftp://ftp.worldforge.org/pub/worldforge/libs/Atlas-C++/transitional/Atlas-C\+\+-(.+)\.tar\.gz
667 #
668 # Directory pattern matching:
669 # ftp://ftp.nessus.org/pub/nessus/nessus-([\d\.]+)/src/nessus-core-([\d\.]+)\.tar\.gz
670 #
671 # The pattern in each part may contain several (...) groups and
672 # the version number is determined by joining all groups together
673 # using "." as separator. For example:
674 # ftp://site/dir/path/pattern-(\d+)_(\d+)_(\d+)\.tar\.gz
675 #
676 # This is another way of handling site with funny version numbers,
677 # this time using mangling. (Note that multiple groups will be
678 # concatenated before mangling is performed, and that mangling will
679 # only be performed on the basename version number, not any path version
680 # numbers.)
681 # opts=uversionmangle=s/^/0.0./ \
682 # ftp://ftp.ibiblio.org/pub/Linux/ALPHA/wine/development/Wine-(.+)\.tar\.gz
683 #
684 # Similarly, the upstream part of the Debian version number can be
685 # mangled:
686 # opts=dversionmangle=s/\.dfsg\.\d+$// \
687 # http://some.site.org/some/path/foobar-(.+)\.tar\.gz
688 #
689 # The versionmangle=... option is a shorthand for saying uversionmangle=...
690 # and dversionmangle=... and applies to both upstream and Debian versions.
691 #
692 # The option filenamemangle can be used to mangle the name under which
693 # the downloaded file will be saved:
694 # href="http://foo.bar.org/download/?path=&download=foo-0.1.1.tar.gz"
695 # could be handled as:
696 # opts=filenamemangle=s/.*=(.*)/$1/ \
697 # http://foo.bar.org/download/\?path=&download=foo-(.+)\.tar\.gz
698 # and
699 # href="http://foo.bar.org/download/?path=&download_version=0.1.1"
700 # as:
701 # opts=filenamemangle=s/.*=(.*)/foo-$1\.tar\.gz/ \
702 # http://foo.bar.org/download/\?path=&download_version=(.+)
703 #
704 # The option downloadurlmangle can be used to mangle the URL of the file
705 # to download. This can only be used with http:// URLs. This may be
706 # necessary if the link given on the webpage needs to be transformed in
707 # some way into one which will work automatically, for example:
708 # opts=downloadurlmangle=s/prdownload/download/ \
709 # http://developer.berlios.de/project/showfiles.php?group_id=2051 \
710 # http://prdownload.berlios.de/softdevice/vdr-softdevice-(.+).tgz
713 sub process_watchline ($$$$$$)
714 {
715 my ($line, $watch_version, $pkg_dir, $pkg, $pkg_version, $watchfile) = @_;
717 my $origline = $line;
718 my ($base, $site, $dir, $filepattern, $pattern, $lastversion, $action);
719 my $basedir;
720 my (@patterns, @sites, @redirections, @basedirs);
721 my %options = ();
723 my ($request, $response);
724 my ($newfile, $newversion);
725 my $style='new';
726 my $urlbase;
727 my $headers = HTTP::Headers->new;
729 # Comma-separated list of features that sites being queried might
730 # want to be aware of
731 $headers->header('X-uscan-features' => 'enhanced-matching');
732 %dehs_tags = ('package' => $pkg);
734 if ($watch_version == 1) {
735 ($site, $dir, $filepattern, $lastversion, $action) = split ' ', $line, 5;
737 if (! defined $lastversion or $site =~ /\(.*\)/ or $dir =~ /\(.*\)/) {
738 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";
739 return 1;
740 }
741 if ($site !~ m%\w+://%) {
742 $site = "ftp://$site";
743 if ($filepattern !~ /\(.*\)/) {
744 # watch_version=1 and old style watchfile;
745 # pattern uses ? and * shell wildcards; everything from the
746 # first to last of these metachars is the pattern to match on
747 $filepattern =~ s/(\?|\*)/($1/;
748 $filepattern =~ s/(\?|\*)([^\?\*]*)$/$1)$2/;
749 $filepattern =~ s/\./\\./g;
750 $filepattern =~ s/\?/./g;
751 $filepattern =~ s/\*/.*/g;
752 $style='old';
753 warn "$progname warning: Using very old style of filename pattern in $watchfile\n (this might lead to incorrect results): $3\n";
754 }
755 }
757 # Merge site and dir
758 $base = "$site/$dir/";
759 $base =~ s%(?<!:)//%/%g;
760 $base =~ m%^(\w+://[^/]+)%;
761 $site = $1;
762 $pattern = $filepattern;
763 } else {
764 # version 2/3 watchfile
765 if ($line =~ s/^opt(?:ion)?s=//) {
766 my $opts;
767 if ($line =~ s/^"(.*?)"\s+//) {
768 $opts=$1;
769 } elsif ($line =~ s/^(\S+)\s+//) {
770 $opts=$1;
771 } else {
772 warn "$progname warning: malformed opts=... in watchfile, skipping line:\n$origline\n";
773 return 1;
774 }
776 my @opts = split /,/, $opts;
777 foreach my $opt (@opts) {
778 if ($opt eq 'pasv' or $opt eq 'passive') {
779 $options{'pasv'}=1;
780 }
781 elsif ($opt eq 'active' or $opt eq 'nopasv'
782 or $opt eq 'nopassive') {
783 $options{'pasv'}=0;
784 }
785 elsif ($opt =~ /^uversionmangle\s*=\s*(.+)/) {
786 @{$options{'uversionmangle'}} = split /;/, $1;
787 }
788 elsif ($opt =~ /^dversionmangle\s*=\s*(.+)/) {
789 @{$options{'dversionmangle'}} = split /;/, $1;
790 }
791 elsif ($opt =~ /^versionmangle\s*=\s*(.+)/) {
792 @{$options{'uversionmangle'}} = split /;/, $1;
793 @{$options{'dversionmangle'}} = split /;/, $1;
794 }
795 elsif ($opt =~ /^filenamemangle\s*=\s*(.+)/) {
796 @{$options{'filenamemangle'}} = split /;/, $1;
797 }
798 elsif ($opt =~ /^downloadurlmangle\s*=\s*(.+)/) {
799 @{$options{'downloadurlmangle'}} = split /;/, $1;
800 }
801 else {
802 warn "$progname warning: unrecognised option $opt\n";
803 }
804 }
805 }
807 ($base, $filepattern, $lastversion, $action) = split ' ', $line, 4;
809 if ($base =~ s%/([^/]*\([^/]*\)[^/]*)$%/%) {
810 # Last component of $base has a pair of parentheses, so no
811 # separate filepattern field; we remove the filepattern from the
812 # end of $base and rescan the rest of the line
813 $filepattern = $1;
814 (undef, $lastversion, $action) = split ' ', $line, 3;
815 }
817 if ((!$lastversion or $lastversion eq 'debian') and not defined $pkg_version) {
818 warn "$progname warning: Unable to determine current version\n in $watchfile, skipping:\n $line\n";
819 return 1;
820 }
822 # Check all's OK
823 if (not $filepattern or $filepattern !~ /\(.*\)/) {
824 warn "$progname warning: Filename pattern missing version delimiters ()\n in $watchfile, skipping:\n $line\n";
825 return 1;
826 }
828 # Check validity of options
829 if ($base =~ /^ftp:/ and exists $options{'downloadurlmangle'}) {
830 warn "$progname warning: downloadurlmangle option invalid for ftp sites,\n ignoring in $watchfile:\n $line\n";
831 }
833 # Handle sf.net addresses specially
834 if ($base =~ m%^http://sf\.net/%) {
835 $base =~ s%^http://sf\.net/%http://qa.debian.org/watch/sf.php/%;
836 $filepattern .= '(?:\?.*)?';
837 }
838 if ($base =~ m%^(\w+://[^/]+)%) {
839 $site = $1;
840 } else {
841 warn "$progname warning: Can't determine protocol and site in\n $watchfile, skipping:\n $line\n";
842 return 1;
843 }
845 # Find the path with the greatest version number matching the regex
846 $base = recursive_regex_dir($base, \%options, $watchfile);
847 if ($base eq '') { return 1; }
849 # We're going to make the pattern
850 # (?:(?:http://site.name)?/dir/path/)?base_pattern
851 # It's fine even for ftp sites
852 $basedir = $base;
853 $basedir =~ s%^\w+://[^/]+/%/%;
854 $pattern = "(?:(?:$site)?" . quotemeta($basedir) . ")?$filepattern";
855 }
857 if (! $lastversion or $lastversion eq 'debian') {
858 if (defined $pkg_version) {
859 $lastversion=$pkg_version;
860 } else {
861 warn "$progname warning: Unable to determine current version\n in $watchfile, skipping:\n $line\n";
862 return 1;
863 }
864 }
865 # And mangle it if requested
866 my $mangled_lastversion;
867 $mangled_lastversion = $lastversion;
868 foreach my $pat (@{$options{'dversionmangle'}}) {
869 if (! safe_replace(\$mangled_lastversion, $pat)) {
870 warn "$progname: In $watchfile, potentially"
871 . " unsafe or malformed dversionmangle"
872 . " pattern:\n '$pat'"
873 . " found. Skipping watchline\n"
874 . " $line\n";
875 return 1;
876 }
877 }
878 if($opt_download_current_version) {
879 $download_version = $mangled_lastversion;
880 $force_download = 1;
881 }
883 # Check all's OK
884 if ($pattern !~ /\(.*\)/) {
885 warn "$progname warning: Filename pattern missing version delimiters ()\n in $watchfile, skipping:\n $line\n";
886 return 1;
887 }
889 push @patterns, $pattern;
890 push @sites, $site;
891 push @basedirs, $basedir;
893 # What is the most recent file, based on the filenames?
894 # We first have to find the candidates, then we sort them using
895 # Devscripts::Versort::versort
896 if ($site =~ m%^http(s)?://%) {
897 if (defined($1) and !$haveSSL) {
898 die "$progname: you must have the libcrypt-ssleay-perl package installed\nto use https URLs\n";
899 }
900 print STDERR "$progname debug: requesting URL $base\n" if $debug;
901 $request = HTTP::Request->new('GET', $base, $headers);
902 $response = $user_agent->request($request);
903 if (! $response->is_success) {
904 warn "$progname warning: In watchfile $watchfile, reading webpage\n $base failed: " . $response->status_line . "\n";
905 return 1;
906 }
908 @redirections = @{$user_agent->get_redirections};
910 print STDERR "$progname debug: redirections: @redirections\n"
911 if $debug;
913 foreach my $_redir (@redirections) {
914 my $base_dir = $_redir;
916 $base_dir =~ s%^\w+://[^/]+/%/%;
917 if ($_redir =~ m%^(\w+://[^/]+)%) {
918 my $base_site = $1;
920 push @patterns, "(?:(?:$base_site)?" . quotemeta($base_dir) . ")?$filepattern";
921 push @sites, $base_site;
922 push @basedirs, $base_dir;
924 # remove the filename, if any
925 my $base_dir_orig = $base_dir;
926 $base_dir =~ s%/[^/]*$%/%;
927 if ($base_dir ne $base_dir_orig) {
928 push @patterns, "(?:(?:$base_site)?" . quotemeta($base_dir) . ")?$filepattern";
929 push @sites, $base_site;
930 push @basedirs, $base_dir;
931 }
932 }
933 }
935 my $content = $response->content;
936 print STDERR "$progname debug: received content:\n$content\[End of received content]\n"
937 if $debug;
939 if ($content =~ m%^<[?]xml%i &&
940 $content =~ m%xmlns="http://s3.amazonaws.com/doc/2006-03-01/"%) {
941 # this is an S3 bucket listing. Insert an 'a href' tag
942 # into the content for each 'Key', so that it looks like html (LP: #798293)
943 print STDERR "$progname debug: fixing s3 listing\n" if $debug;
944 $content =~ s%<Key>([^<]*)</Key>%<Key><a href="$1">$1</a></Key>%g
945 }
947 # We need this horrid stuff to handle href=foo type
948 # links. OK, bad HTML, but we have to handle it nonetheless.
949 # It's bug #89749.
950 $content =~ s/href\s*=\s*(?=[^\"\'])([^\s>]+)/href="$1"/ig;
951 # Strip comments
952 $content =~ s/<!-- .*?-->//sg;
953 # Is there a base URL given?
954 if ($content =~ /<\s*base\s+[^>]*href\s*=\s*([\"\'])(.*?)\1/i) {
955 # Ensure it ends with /
956 $urlbase = "$2/";
957 $urlbase =~ s%//$%/%;
958 } else {
959 # May have to strip a base filename
960 ($urlbase = $base) =~ s%/[^/]*$%/%;
961 }
963 print STDERR "$progname debug: matching pattern(s) @patterns\n" if $debug;
964 my @hrefs;
965 while ($content =~ m/<\s*a\s+[^>]*href\s*=\s*([\"\'])(.*?)\1/sgi) {
966 my $href = $2;
967 $href =~ s/\n//g;
968 foreach my $_pattern (@patterns) {
969 if ($href =~ m&^$_pattern$&) {
970 if ($watch_version == 2) {
971 # watch_version 2 only recognised one group; the code
972 # below will break version 2 watchfiles with a construction
973 # such as file-([\d\.]+(-\d+)?) (bug #327258)
974 push @hrefs, [$1, $href];
975 } else {
976 # need the map { ... } here to handle cases of (...)?
977 # which may match but then return undef values
978 my $mangled_version =
979 join(".", map { $_ if defined($_) }
980 $href =~ m&^$_pattern$&);
981 foreach my $pat (@{$options{'uversionmangle'}}) {
982 if (! safe_replace(\$mangled_version, $pat)) {
983 warn "$progname: In $watchfile, potentially"
984 . " unsafe or malformed uversionmangle"
985 . " pattern:\n '$pat'"
986 . " found. Skipping watchline\n"
987 . " $line\n";
988 return 1;
989 }
990 }
991 push @hrefs, [$mangled_version, $href];
992 }
993 }
994 }
995 }
996 if (@hrefs) {
997 if ($verbose) {
998 print "-- Found the following matching hrefs:\n";
999 foreach my $href (@hrefs) { print " $$href[1]\n"; }
1000 }
1001 if (defined $download_version) {
1002 my @vhrefs = grep { $$_[0] eq $download_version } @hrefs;
1003 if (@vhrefs) {
1004 ($newversion, $newfile) = @{$vhrefs[0]};
1005 } else {
1006 warn "$progname warning: In $watchfile no matching hrefs for version $download_version"
1007 . " in watch line\n $line\n";
1008 return 1;
1009 }
1010 } else {
1011 @hrefs = Devscripts::Versort::versort(@hrefs);
1012 ($newversion, $newfile) = @{$hrefs[0]};
1013 }
1014 } else {
1015 warn "$progname warning: In $watchfile,\n no matching hrefs for watch line\n $line\n";
1016 return 1;
1017 }
1018 }
1019 else {
1020 # Better be an FTP site
1021 if ($site !~ m%^ftp://%) {
1022 warn "$progname warning: Unknown protocol in $watchfile, skipping:\n $site\n";
1023 return 1;
1024 }
1026 if (exists $options{'pasv'}) {
1027 $ENV{'FTP_PASSIVE'}=$options{'pasv'};
1028 }
1029 print STDERR "$progname debug: requesting URL $base\n" if $debug;
1030 $request = HTTP::Request->new('GET', $base);
1031 $response = $user_agent->request($request);
1032 if (exists $options{'pasv'}) {
1033 if (defined $passive) { $ENV{'FTP_PASSIVE'}=$passive; }
1034 else { delete $ENV{'FTP_PASSIVE'}; }
1035 }
1036 if (! $response->is_success) {
1037 warn "$progname warning: In watchfile $watchfile, reading FTP directory\n $base failed: " . $response->status_line . "\n";
1038 return 1;
1039 }
1041 my $content = $response->content;
1042 print STDERR "$progname debug: received content:\n$content\[End of received content]\n"
1043 if $debug;
1045 # FTP directory listings either look like:
1046 # info info ... info filename [ -> linkname]
1047 # or they're HTMLised (if they've been through an HTTP proxy)
1048 # so we may have to look for <a href="filename"> type patterns
1049 print STDERR "$progname debug: matching pattern $pattern\n" if $debug;
1050 my (@files);
1052 # We separate out HTMLised listings from standard listings, so
1053 # that we can target our search correctly
1054 if ($content =~ /<\s*a\s+[^>]*href/i) {
1055 while ($content =~
1056 m/(?:<\s*a\s+[^>]*href\s*=\s*\")((?-i)$pattern)\"/gi) {
1057 my $file = $1;
1058 my $mangled_version = join(".", $file =~ m/^$pattern$/);
1059 foreach my $pat (@{$options{'uversionmangle'}}) {
1060 if (! safe_replace(\$mangled_version, $pat)) {
1061 warn "$progname: In $watchfile, potentially"
1062 . " unsafe or malformed uversionmangle"
1063 . " pattern:\n '$pat'"
1064 . " found. Skipping watchline\n"
1065 . " $line\n";
1066 return 1;
1067 }
1068 }
1069 push @files, [$mangled_version, $file];
1070 }
1071 } else {
1072 # they all look like:
1073 # info info ... info filename [ -> linkname]
1074 for my $ln (split(/\n/, $content)) {
1075 if ($ln =~ m/\s($filepattern)(\s+->\s+\S+)?$/) {
1076 my $file = $1;
1077 my $mangled_version = join(".", $file =~ m/^$filepattern$/);
1078 foreach my $pat (@{$options{'uversionmangle'}}) {
1079 if (! safe_replace(\$mangled_version, $pat)) {
1080 warn "$progname: In $watchfile, potentially"
1081 . " unsafe or malformed uversionmangle"
1082 . " pattern:\n '$pat'"
1083 . " found. Skipping watchline\n"
1084 . " $line\n";
1085 return 1;
1086 }
1087 }
1088 push @files, [$mangled_version, $file];
1089 }
1090 }
1091 }
1093 if (@files) {
1094 if ($verbose) {
1095 print "-- Found the following matching files:\n";
1096 foreach my $file (@files) { print " $$file[1]\n"; }
1097 }
1098 if (defined $download_version) {
1099 my @vfiles = grep { $$_[0] eq $download_version } @files;
1100 if (@vfiles) {
1101 ($newversion, $newfile) = @{$vfiles[0]};
1102 } else {
1103 warn "$progname warning: In $watchfile no matching files for version $download_version"
1104 . " in watch line\n $line\n";
1105 return 1;
1106 }
1107 } else {
1108 @files = Devscripts::Versort::versort(@files);
1109 ($newversion, $newfile) = @{$files[0]};
1110 }
1111 } else {
1112 warn "$progname warning: In $watchfile no matching files for watch line\n $line\n";
1113 return 1;
1114 }
1115 }
1117 # The original version of the code didn't use (...) in the watch
1118 # file to delimit the version number; thus if there is no (...)
1119 # in the pattern, we will use the old heuristics, otherwise we
1120 # use the new.
1122 if ($style eq 'old') {
1123 # Old-style heuristics
1124 if ($newversion =~ /^\D*(\d+\.(?:\d+\.)*\d+)\D*$/) {
1125 $newversion = $1;
1126 } else {
1127 warn <<"EOF";
1128 $progname warning: In $watchfile, couldn\'t determine a
1129 pure numeric version number from the file name for watch line
1130 $line
1131 and file name $newfile
1132 Please use a new style watchfile instead!
1133 EOF
1134 return 1;
1135 }
1136 }
1138 my $newfile_base=basename($newfile);
1139 if (exists $options{'filenamemangle'}) {
1140 $newfile_base=$newfile;
1141 }
1142 foreach my $pat (@{$options{'filenamemangle'}}) {
1143 if (! safe_replace(\$newfile_base, $pat)) {
1144 warn "$progname: In $watchfile, potentially"
1145 . " unsafe or malformed filenamemangle"
1146 . " pattern:\n '$pat'"
1147 . " found. Skipping watchline\n"
1148 . " $line\n";
1149 return 1;
1150 }
1151 }
1152 # Remove HTTP header trash
1153 if ($site =~ m%^https?://%) {
1154 $newfile_base =~ s/\?.*$//;
1155 # just in case this leaves us with nothing
1156 if ($newfile_base eq '') {
1157 $newfile_base = "$pkg-$newversion.download";
1158 }
1159 }
1161 # So what have we got to report now?
1162 my $upstream_url;
1163 # Upstream URL? Copying code from below - ugh.
1164 if ($site =~ m%^https?://%) {
1165 # absolute URL?
1166 if ($newfile =~ m%^\w+://%) {
1167 $upstream_url = $newfile;
1168 }
1169 elsif ($newfile =~ m%^//%) {
1170 $upstream_url = $site;
1171 $upstream_url =~ s/^(https?:).*/$1/;
1172 $upstream_url .= $newfile;
1173 }
1174 # absolute filename?
1175 elsif ($newfile =~ m%^/%) {
1176 # Were there any redirections? If so try using those first
1177 if ($#patterns > 0) {
1178 # replace $site here with the one we were redirected to
1179 foreach my $index (0 .. $#patterns) {
1180 if ("$sites[$index]$newfile" =~ m&^$patterns[$index]$&) {
1181 $upstream_url = "$sites[$index]$newfile";
1182 last;
1183 }
1184 }
1185 if (!defined($upstream_url)) {
1186 if ($debug) {
1187 warn "$progname warning: Unable to determine upstream url from redirections,\n" .
1188 "defaulting to using site specified in watchfile\n";
1189 }
1190 $upstream_url = "$sites[0]$newfile";
1191 }
1192 } else {
1193 $upstream_url = "$sites[0]$newfile";
1194 }
1195 }
1196 # relative filename, we hope
1197 else {
1198 # Were there any redirections? If so try using those first
1199 if ($#patterns > 0) {
1200 # replace $site here with the one we were redirected to
1201 foreach my $index (0 .. $#patterns) {
1202 # skip unless the basedir looks like a directory
1203 next unless $basedirs[$index] =~ m%/$%;
1204 my $nf = "$basedirs[$index]$newfile";
1205 if ("$sites[$index]$nf" =~ m&^$patterns[$index]$&) {
1206 $upstream_url = "$sites[$index]$nf";
1207 last;
1208 }
1209 }
1210 if (!defined($upstream_url)) {
1211 if ($debug) {
1212 warn "$progname warning: Unable to determine upstream url from redirections,\n" .
1213 "defaulting to using site specified in watchfile\n";
1214 }
1215 $upstream_url = "$urlbase$newfile";
1216 }
1217 } else {
1218 $upstream_url = "$urlbase$newfile";
1219 }
1220 }
1222 # mangle if necessary
1223 $upstream_url =~ s/&/&/g;
1224 if (exists $options{'downloadurlmangle'}) {
1225 foreach my $pat (@{$options{'downloadurlmangle'}}) {
1226 if (! safe_replace(\$upstream_url, $pat)) {
1227 warn "$progname: In $watchfile, potentially"
1228 . " unsafe or malformed downloadurlmangle"
1229 . " pattern:\n '$pat'"
1230 . " found. Skipping watchline\n"
1231 . " $line\n";
1232 return 1;
1233 }
1234 }
1235 }
1236 }
1237 else {
1238 # FTP site
1239 $upstream_url = "$base$newfile";
1240 }
1242 $dehs_tags{'debian-uversion'} = $lastversion;
1243 $dehs_tags{'debian-mangled-uversion'} = $mangled_lastversion;
1244 $dehs_tags{'upstream-version'} = $newversion;
1245 $dehs_tags{'upstream-url'} = $upstream_url;
1247 # Can't just use $lastversion eq $newversion, as then 0.01 and 0.1
1248 # compare different, whereas they are treated as equal by dpkg
1249 if (system("dpkg", "--compare-versions", "$mangled_lastversion", "eq", "$newversion") == 0) {
1250 if ($verbose or ($download == 0 and $report and ! $dehs)) {
1251 print $pkg_report_header;
1252 $pkg_report_header = '';
1253 print "Newest version on remote site is $newversion, local version is $lastversion\n" .
1254 ($mangled_lastversion eq $lastversion ? "" : " (mangled local version number $mangled_lastversion)\n");
1255 print " => Package is up to date\n";
1256 }
1257 $dehs_tags{'status'} = "up to date";
1258 if (! $force_download) {
1259 return 0;
1260 } else {
1261 $download = 1;
1262 }
1263 }
1265 # In all other cases, we'll want to report information even with --report
1266 if ($verbose or ($download == 0 and ! $dehs)) {
1267 print $pkg_report_header;
1268 $pkg_report_header = '';
1269 print "Newest version on remote site is $newversion, local version is $lastversion\n" .
1270 ($mangled_lastversion eq $lastversion ? "" : " (mangled local version number $mangled_lastversion)\n");
1271 }
1273 # We use dpkg's rules to determine whether our current version
1274 # is newer or older than the remote version.
1275 if (!defined $download_version) {
1276 if (system("dpkg", "--compare-versions", "$mangled_lastversion", "gt", "$newversion") == 0) {
1277 if ($verbose) {
1278 print " => remote site does not even have current version\n";
1279 } elsif ($dehs) {
1280 $dehs_tags{'status'} = "Debian version newer than remote site";
1281 } else {
1282 print "$pkg: remote site does not even have current version\n";
1283 }
1284 return 0;
1285 } else {
1286 # There's a newer upstream version available, which may already
1287 # be on our system or may not be
1288 $found++;
1289 }
1290 } else {
1291 # Flag that we found a newer upstream version, so that the exit status
1292 # is set correctly
1293 $found++;
1294 }
1296 if (defined $pkg_dir) {
1297 if (! -d "$destdir") {
1298 print "Package directory '$destdir to store downloaded file is not existing\n";
1299 return 1;
1300 }
1301 if (-f "$destdir/$newfile_base") {
1302 print " => $newfile_base already in package directory\n"
1303 if $verbose or ($download == 0 and ! $dehs);
1304 return 0;
1305 }
1306 foreach my $suffix (qw(gz bz2 lzma xz)) {
1307 if (-f "$destdir/${pkg}_${newversion}.orig.tar.$suffix") {
1308 print " => ${pkg}_${newversion}.orig.tar.$suffix already in package directory '$destdir'\n"
1309 if $verbose or ($download == 0 and ! $dehs);
1310 return 0;
1311 }
1312 }
1313 }
1315 if ($force_download and $verbose) {
1316 print " => Forcing download as requested\n";
1317 } elsif ($verbose) {
1318 print " => Newer version available from\n";
1319 print " $upstream_url\n";
1320 } elsif ($dehs) {
1321 $dehs_tags{'status'} = "Newer version available";
1322 } else {
1323 my $msg_header = "$pkg: ";
1324 $msg_header .= $force_download ? "Version" : "Newer version";
1325 print "$msg_header ($newversion) available on remote site:\n $upstream_url\n (local version is $lastversion" .
1326 ($mangled_lastversion eq $lastversion ? "" : ", mangled local version number $mangled_lastversion") .
1327 ")\n";
1328 }
1330 if ($download < 0) {
1331 my $msg = "Not downloading as --package was used. Use --download to force downloading.";
1332 if ($dehs) {
1333 dehs_msg($msg);
1334 } else {
1335 print "$msg\n";
1336 }
1337 return 0;
1338 }
1339 return 0 unless $download;
1341 print "-- Downloading updated package $newfile_base\n" if $verbose;
1342 if (! -d "$destdir") {
1343 print "Package directory '$destdir to store downloaded file is not existing\n";
1344 return 1;
1345 }
1346 # Download newer package
1347 if ($upstream_url =~ m%^http(s)?://%) {
1348 if (defined($1) and !$haveSSL) {
1349 die "$progname: you must have the libcrypt-ssleay-perl package installed\nto use https URLs\n";
1350 }
1351 # substitute HTML entities
1352 # Is anything else than "&" required? I doubt it.
1353 print STDERR "$progname debug: requesting URL $upstream_url\n" if $debug;
1354 $request = HTTP::Request->new('GET', $upstream_url);
1355 $response = $user_agent->request($request, "$destdir/$newfile_base");
1356 if (! $response->is_success) {
1357 if (defined $pkg_dir) {
1358 warn "$progname warning: In directory $pkg_dir, downloading\n $upstream_url failed: " . $response->status_line . "\n";
1359 } else {
1360 warn "$progname warning: Downloading\n $upstream_url failed:\n" . $response->status_line . "\n";
1361 }
1362 return 1;
1363 }
1364 }
1365 else {
1366 # FTP site
1367 if (exists $options{'pasv'}) {
1368 $ENV{'FTP_PASSIVE'}=$options{'pasv'};
1369 }
1370 print STDERR "$progname debug: requesting URL $upstream_url\n" if $debug;
1371 $request = HTTP::Request->new('GET', "$upstream_url");
1372 $response = $user_agent->request($request, "$destdir/$newfile_base");
1373 if (exists $options{'pasv'}) {
1374 if (defined $passive) { $ENV{'FTP_PASSIVE'}=$passive; }
1375 else { delete $ENV{'FTP_PASSIVE'}; }
1376 }
1377 if (! $response->is_success) {
1378 if (defined $pkg_dir) {
1379 warn "$progname warning: In directory $pkg_dir, downloading\n $upstream_url failed: " . $response->status_line . "\n";
1380 } else {
1381 warn "$progname warning: Downloading\n $upstream_url failed:\n" . $response->status_line . "\n";
1382 }
1383 return 1;
1384 }
1385 }
1387 if ($repack and $newfile_base =~ /^(.*)\.(tar\.bz2|tbz2?)$/ and
1388 $repack_compression !~ /^bz2$/ ) {
1389 print "-- Repacking from bzip2 to gzip\n" if $verbose;
1390 my $newfile_base_compression = "$1.tar.".$repack_compression;
1391 my (undef, $fname) = tempfile(UNLINK => 1);
1392 spawn(exec => ['bunzip2', '-c', "$destdir/$newfile_base"],
1393 to_file => $fname,
1394 wait_child => 1);
1395 spawn(exec => ['gzip', '-n', '-9'],
1396 from_file => $fname,
1397 to_file => "$destdir/$newfile_base_compression",
1398 wait_child => 1);
1399 unlink "$destdir/$newfile_base";
1400 $newfile_base = $newfile_base_compression;
1401 }
1403 if ($repack and $newfile_base =~ /^(.*)\.(tar\.lzma|tlz(?:ma?)?)$/ and
1404 $repack_compression !~ /^lzma$/ ) {
1405 print "-- Repacking from lzma to $repack_compression\n" if $verbose;
1406 my $newfile_base_compression = "$1.tar.".$repack_compression;
1407 my (undef, $fname) = tempfile(UNLINK => 1);
1408 spawn(exec => ['xz', '-F', 'lzma', '-cd', "$destdir/$newfile_base"],
1409 to_file => $fname,
1410 wait_child => 1);
1411 compress_archive("$fname", "$destdir/$newfile_base_compression", $repack_compression);
1412 $newfile_base = $newfile_base_compression;
1413 }
1415 if ($repack and $newfile_base =~ /^(.*)\.(tar\.xz|txz)$/ and
1416 $repack_compression !~ /^xz$/ ) {
1417 print "-- Repacking from xz to $repack_compression\n" if $verbose;
1418 my $newfile_base_compression = "$1.tar.".$repack_compression;
1419 my (undef, $fname) = tempfile(UNLINK => 1);
1420 spawn(exec => ['xz', '-cd', "$destdir/$newfile_base"],
1421 to_file => $fname,
1422 wait_child => 1);
1423 compress_archive("$fname", "$destdir/$newfile_base_compression", $repack_compression);
1424 $newfile_base = $newfile_base_compression;
1425 }
1427 if ($repack and $newfile_base =~ /^(.*)\.(zip|jar)$/) {
1428 print "-- Repacking from zip to .tar.$repack_compression\n" if $verbose;
1430 system('command -v unzip >/dev/null 2>&1') >> 8 == 0
1431 or die("unzip binary not found. You need to install the package unzip to be able to repack .zip upstream archives.\n");
1433 my $compress_file_base = "$1.tar" ;
1434 my $newfile_base_compression = "$compress_file_base.".$repack_compression;
1435 my $tempdir = tempdir ( "uscanXXXX", TMPDIR => 1, CLEANUP => 1 );
1436 my $globpattern = "*";
1437 my $hidden = ".[!.]*";
1438 my $absdestdir = abs_path($destdir);
1439 system('unzip', '-q', '-a', '-d', $tempdir, "$destdir/$newfile_base") == 0
1440 or die("Repacking from zip or jar to tar.$repack_compression failed (could not unzip)\n");
1441 if (defined glob("$tempdir/$hidden")) {
1442 $globpattern .= " $hidden";
1443 }
1444 system("cd $tempdir; tar --owner=root --group=root --mode=a+rX -cf \"$absdestdir/$compress_file_base\" $globpattern") == 0
1445 or die("Repacking from zip or jar to tar.$repack_compression failed (could not create tarball)\n");
1446 compress_archive("$absdestdir/$compress_file_base", "$absdestdir/$newfile_base_compression", $repack_compression);
1447 $newfile_base = $newfile_base_compression;
1448 }
1450 if ($newfile_base =~ /\.(tar\.gz|tgz
1451 |tar\.bz2|tbz2?
1452 |tar.lzma|tlz(?:ma?)?
1453 |tar.xz|txz)$/x) {
1454 my $filetype = `file -b -k \"$destdir/$newfile_base\"`;
1455 unless ($filetype =~ /compressed data/) {
1456 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";
1457 return 1;
1458 }
1459 }
1461 my $excludesuffix = '+dfsg';
1462 if ( !$no_exclusion ) {
1463 my $data ;
1464 $data = Dpkg::Control::Hash->new();
1465 try {
1466 $data->load('debian/copyright');
1467 } catch {
1468 print "-- No machine readable debian/copyright file.\n" if ( $verbose ) ;
1469 $data->{'format'} = '' ;
1470 } ;
1471 # my $parser = new Parse::DebControl(1);
1472 # my $data = $parser->parse_file('debian/copyright', {discardCase=>1,singleBlock=>1,});
1473 my $okformat = qr'http://www.debian.org/doc/packaging-manuals/copyright-format/[.\d]+';
1474 print "-- Wrong format of debian/copyright file to profit from Files-Excluded.\n" if ( $data->{'files-excluded'} and $data->{'format'} !~ m{^$okformat/?$} and $verbose ) ;
1475 if ($data->{'format'} =~ m{^$okformat/?$} and $data->{'files-excluded'} ) {
1476 my $tempdir = tempdir ( "uscanXXXX", TMPDIR => 1, CLEANUP => 1 );
1477 my $globpattern = "*";
1478 my $hidden = ".[!.]*";
1479 if (defined glob("$tempdir/$hidden")) {
1480 $globpattern .= " $hidden";
1481 }
1482 my $absdestdir = abs_path($destdir);
1483 unless ( system("cd $tempdir; tar -xaf \"$absdestdir/$newfile_base\" 2>/dev/null") == 0 ) {
1484 print "-- $newfile_base is no tarball. Try unzip.\n" if $verbose;
1485 # try unzip if tar fails - we do want to do something sensible even if no --repack was specified
1486 system('command -v unzip >/dev/null 2>&1') >> 8 == 0
1487 or die("unzip binary not found. This would serve as fallback because tar just failed.\n");
1488 # system('unzip', '-q', '-a', '-d', $tempdir, "$destdir/$newfile_base") == 0
1489 # When using -a option (text) files could be changed in size (some whitespace encoding can change
1490 # While it makes sense to get proper UNIX whitespaces in the repackaged source it does not help
1491 # when verifying the repackaging result via diff. While `diff -b` helps here this is disabled
1492 # for the moment.
1493 system('unzip', '-q', '-d', $tempdir, "$destdir/$newfile_base") == 0
1494 or die("Repacking from zip or jar to tar.gz failed (could not unzip)\n");
1495 }
1496 # Some source archives contain a useless __MACOSX dir which would prevent a reasonable
1497 # normalising of the +dfsg.orig archive - so removing it in advance in case it should
1498 # be removed anyway helps creating normalised source archives.
1499 my $exclude__MACOSX = grep( /\s*\/?__MACOSX\/?\s*/, $data->{"files-excluded"} );
1500 my $main_source_dir = get_main_source_dir($tempdir, $pkg, $newversion, $excludesuffix, $exclude__MACOSX);
1501 unless ( -d $main_source_dir ) {
1502 print STDERR "Error: $main_source_dir is no directory";
1503 }
1504 my $nfiles_before = `find "$main_source_dir" | wc -l`;
1505 foreach (split /\s+/, $data->{"files-excluded"}) {
1506 # delete trailing '/' because otherwise find -path will fail
1507 s?/+$?? ;
1508 # use -depth to enable deleting directories
1509 system('find',$main_source_dir,'-depth','-path',"$main_source_dir/$_",qw(-exec rm -rf {} ;))==0 or
1510 die "failure to run find properly";
1511 };
1512 my $nfiles_after = `find "$main_source_dir" | wc -l`;
1513 if ( $nfiles_before == $nfiles_after && ! $exclude__MACOSX ) {
1514 print "-- Source tree remains identical - no need for repacking.\n" if $verbose;
1515 } else {
1516 my $newfile_base_dfsg = "${pkg}_${newversion}${excludesuffix}.orig.tar" ;
1517 system("cd $tempdir; tar --owner=root --group=root --mode=a+rX --exclude-vcs -cf \"$absdestdir/$newfile_base_dfsg\" $globpattern") == 0
1518 or die("Excluding files failed (could not create tarball)\n");
1519 compress_archive("$absdestdir/$newfile_base_dfsg", "$absdestdir/$newfile_base_dfsg.$repack_compression", $repack_compression);
1520 $symlink = 'files-excluded' # prevent symlinking or renaming
1521 }
1522 }
1523 }
1525 my @renames = (
1526 [qr/\.(tar\.gz|tgz)$/, 'gz'],
1527 [qr/\.(tar\.bz2|tbz2?)$/, 'bz2'],
1528 [qr/\.tar\.lzma|tlz(?:ma?)?$/, 'lzma'],
1529 [qr/\.(tar\.xz|txz)$/, 'xz'],
1530 );
1532 my ($renamed_base);
1533 foreach my $pair (@renames) {
1534 if ($newfile_base !~ $pair->[0]) {
1535 next;
1536 }
1538 my ($pattern, $suffix) = @{$pair};
1539 $renamed_base = "${pkg}_${newversion}.orig.tar.$suffix";
1540 if ($symlink eq 'symlink') {
1541 symlink $newfile_base, "$destdir/$renamed_base";
1542 } elsif ($symlink eq 'rename') {
1543 move "$destdir/$newfile_base", "$destdir/$renamed_base";
1544 }
1545 if ($verbose) {
1546 print "-- Successfully downloaded updated package $newfile_base\n";
1547 if ($symlink eq 'symlink') {
1548 print " and symlinked $renamed_base to it\n";
1549 } elsif ($symlink eq 'rename') {
1550 print " and renamed it as $renamed_base\n";
1551 } elsif ($symlink eq 'files-excluded') {
1552 print " and removed files from it in ${pkg}_${newversion}${excludesuffix}.orig.tar.$suffix\n";
1553 }
1554 } elsif ($dehs) {
1555 my $msg = "Successfully downloaded updated package $newfile_base";
1556 $dehs_tags{'target'} = "$renamed_base";
1557 if ($symlink eq 'symlink') {
1558 $msg .= " and symlinked $renamed_base to it";
1559 } elsif ($symlink eq 'rename') {
1560 $msg .= " and renamed it as $renamed_base";
1561 } elsif ($symlink eq 'files-excluded') {
1562 $msg .= " and removed files from it in ${pkg}_${newversion}${excludesuffix}.orig.tar.$suffix\n";
1563 } else {
1564 $dehs_tags{'target'} = $newfile_base;
1565 }
1566 dehs_msg($msg);
1567 } else {
1568 print "$pkg: Successfully downloaded updated package $newfile_base\n";
1569 if ($symlink eq 'symlink') {
1570 print " and symlinked $renamed_base to it\n";
1571 } elsif ($symlink eq 'rename') {
1572 print " and renamed it as $renamed_base\n";
1573 } elsif ($symlink eq 'files-excluded') {
1574 print " and removed files from it in ${pkg}_${newversion}${excludesuffix}.orig.tar.$suffix\n";
1575 }
1576 }
1577 last;
1578 }
1580 # Do whatever the user wishes to do
1581 if ($action) {
1582 my $usefile = "$destdir/$newfile_base";
1583 my @cmd = shellwords($action);
1584 if ($symlink =~ /^(symlink|rename)$/ && $renamed_base) {
1585 $usefile = "$destdir/$renamed_base";
1586 }
1588 # Any symlink requests are already handled by uscan
1589 if ($action =~ /^uupdate(\s|$)/) {
1590 push @cmd, "--no-symlink";
1591 }
1593 if ($watch_version > 1) {
1594 push @cmd, ("--upstream-version", "$newversion", "$usefile");
1595 } else {
1596 push @cmd, ("$usefile", "$newversion");
1597 }
1598 my $actioncmd = join(" ", @cmd);
1599 print "-- Executing user specified script\n $actioncmd\n" if $verbose;
1600 if ($dehs) {
1601 my $msg = "Executing user specified script: $actioncmd; output:\n";
1602 $msg .= `$actioncmd 2>&1`;
1603 dehs_msg($msg);
1604 } else {
1605 system(@cmd);
1606 }
1607 }
1609 return 0;
1610 }
1613 sub recursive_regex_dir ($$$) {
1614 my ($base, $optref, $watchfile)=@_;
1616 $base =~ m%^(\w+://[^/]+)/(.*)$%;
1617 my $site = $1;
1618 my @dirs = ();
1619 if (defined $2) {
1620 @dirs = split /(\/)/, $2;
1621 }
1622 my $dir = '/';
1624 foreach my $dirpattern (@dirs) {
1625 if ($dirpattern =~ /\(.*\)/) {
1626 print STDERR "$progname debug: dir=>$dir dirpattern=>$dirpattern\n"
1627 if $debug;
1628 my $newest_dir =
1629 newest_dir($site, $dir, $dirpattern, $optref, $watchfile);
1630 print STDERR "$progname debug: newest_dir => '$newest_dir'\n"
1631 if $debug;
1632 if ($newest_dir ne '') {
1633 $dir .= "$newest_dir";
1634 }
1635 else {
1636 return '';
1637 }
1638 } else {
1639 $dir .= "$dirpattern";
1640 }
1641 }
1642 return $site . $dir;
1643 }
1646 # very similar to code above
1647 sub newest_dir ($$$$$) {
1648 my ($site, $dir, $pattern, $optref, $watchfile) = @_;
1649 my $base = $site.$dir;
1650 my ($request, $response);
1652 if ($site =~ m%^http(s)?://%) {
1653 if (defined($1) and !$haveSSL) {
1654 die "$progname: you must have the libcrypt-ssleay-perl package installed\nto use https URLs\n";
1655 }
1656 print STDERR "$progname debug: requesting URL $base\n" if $debug;
1657 $request = HTTP::Request->new('GET', $base);
1658 $response = $user_agent->request($request);
1659 if (! $response->is_success) {
1660 warn "$progname warning: In watchfile $watchfile, reading webpage\n $base failed: " . $response->status_line . "\n";
1661 return 1;
1662 }
1664 my $content = $response->content;
1665 print STDERR "$progname debug: received content:\n$content\[End of received content\]\n"
1666 if $debug;
1667 # We need this horrid stuff to handle href=foo type
1668 # links. OK, bad HTML, but we have to handle it nonetheless.
1669 # It's bug #89749.
1670 $content =~ s/href\s*=\s*(?=[^\"\'])([^\s>]+)/href="$1"/ig;
1671 # Strip comments
1672 $content =~ s/<!-- .*?-->//sg;
1674 my $dirpattern = "(?:(?:$site)?" . quotemeta($dir) . ")?$pattern";
1676 print STDERR "$progname debug: matching pattern $dirpattern\n"
1677 if $debug;
1678 my @hrefs;
1679 while ($content =~ m/<\s*a\s+[^>]*href\s*=\s*([\"\'])(.*?)\1/gi) {
1680 my $href = $2;
1681 if ($href =~ m&^$dirpattern/?$&) {
1682 my $mangled_version = join(".", map { $_ || '' } $href =~ m&^$dirpattern/?$&);
1683 push @hrefs, [$mangled_version, $href];
1684 }
1685 }
1686 if (@hrefs) {
1687 @hrefs = Devscripts::Versort::versort(@hrefs);
1688 if ($debug) {
1689 print "-- Found the following matching hrefs (newest first):\n";
1690 foreach my $href (@hrefs) { print " $$href[1]\n"; }
1691 }
1692 my $newdir = $hrefs[0][1];
1693 # just give the final directory component
1694 $newdir =~ s%/$%%;
1695 $newdir =~ s%^.*/%%;
1696 return $newdir;
1697 } else {
1698 warn "$progname warning: In $watchfile,\n no matching hrefs for pattern\n $site$dir$pattern";
1699 return 1;
1700 }
1701 }
1702 else {
1703 # Better be an FTP site
1704 if ($site !~ m%^ftp://%) {
1705 return 1;
1706 }
1708 if (exists $$optref{'pasv'}) {
1709 $ENV{'FTP_PASSIVE'}=$$optref{'pasv'};
1710 }
1711 print STDERR "$progname debug: requesting URL $base\n" if $debug;
1712 $request = HTTP::Request->new('GET', $base);
1713 $response = $user_agent->request($request);
1714 if (exists $$optref{'pasv'}) {
1715 if (defined $passive) { $ENV{'FTP_PASSIVE'}=$passive; }
1716 else { delete $ENV{'FTP_PASSIVE'}; }
1717 }
1718 if (! $response->is_success) {
1719 warn "$progname warning: In watchfile $watchfile, reading webpage\n $base failed: " . $response->status_line . "\n";
1720 return '';
1721 }
1723 my $content = $response->content;
1724 print STDERR "$progname debug: received content:\n$content\[End of received content]\n"
1725 if $debug;
1727 # FTP directory listings either look like:
1728 # info info ... info filename [ -> linkname]
1729 # or they're HTMLised (if they've been through an HTTP proxy)
1730 # so we may have to look for <a href="filename"> type patterns
1731 print STDERR "$progname debug: matching pattern $pattern\n" if $debug;
1732 my (@dirs);
1734 # We separate out HTMLised listings from standard listings, so
1735 # that we can target our search correctly
1736 if ($content =~ /<\s*a\s+[^>]*href/i) {
1737 while ($content =~
1738 m/(?:<\s*a\s+[^>]*href\s*=\s*\")((?-i)$pattern)\"/gi) {
1739 my $dir = $1;
1740 my $mangled_version = join(".", $dir =~ m/^$pattern$/);
1741 push @dirs, [$mangled_version, $dir];
1742 }
1743 } else {
1744 # they all look like:
1745 # info info ... info filename [ -> linkname]
1746 foreach my $ln (split(/\n/, $content)) {
1747 if ($ln =~ m/($pattern)(\s+->\s+\S+)?$/) {
1748 my $dir = $1;
1749 my $mangled_version = join(".", $dir =~ m/^$pattern$/);
1750 push @dirs, [$mangled_version, $dir];
1751 }
1752 }
1753 }
1754 if (@dirs) {
1755 if ($debug) {
1756 print STDERR "-- Found the following matching dirs:\n";
1757 foreach my $dir (@dirs) { print STDERR " $$dir[1]\n"; }
1758 }
1759 @dirs = Devscripts::Versort::versort(@dirs);
1760 my ($newversion, $newdir) = @{$dirs[0]};
1761 return $newdir;
1762 } else {
1763 warn "$progname warning: In $watchfile no matching dirs for pattern\n $base$pattern\n";
1764 return '';
1765 }
1766 }
1767 }
1770 # parameters are dir, package, upstream version, good dirname
1771 sub process_watchfile ($$$$)
1772 {
1773 my ($dir, $package, $version, $watchfile) = @_;
1774 my $watch_version=0;
1775 my $status=0;
1776 %dehs_tags = ();
1778 unless (open WATCH, $watchfile) {
1779 warn "$progname warning: could not open $watchfile: $!\n";
1780 return 1;
1781 }
1783 while (<WATCH>) {
1784 next if /^\s*\#/;
1785 next if /^\s*$/;
1786 s/^\s*//;
1788 CHOMP:
1789 chomp;
1790 if (s/(?<!\\)\\$//) {
1791 if (eof(WATCH)) {
1792 warn "$progname warning: $watchfile ended with \\; skipping last line\n";
1793 $status=1;
1794 last;
1795 }
1796 $_ .= <WATCH>;
1797 goto CHOMP;
1798 }
1800 if (! $watch_version) {
1801 if (/^version\s*=\s*(\d+)(\s|$)/) {
1802 $watch_version=$1;
1803 if ($watch_version < 2 or
1804 $watch_version > $CURRENT_WATCHFILE_VERSION) {
1805 warn "$progname ERROR: $watchfile version number is unrecognised; skipping watchfile\n";
1806 last;
1807 }
1808 next;
1809 } else {
1810 warn "$progname warning: $watchfile is an obsolete version 1 watchfile;\n please upgrade to a higher version\n (see uscan(1) for details).\n";
1811 $watch_version=1;
1812 }
1813 }
1815 # Are there any warnings from this part to give if we're using dehs?
1816 dehs_output if $dehs;
1818 # Handle shell \\ -> \
1819 s/\\\\/\\/g if $watch_version==1;
1820 if ($verbose) {
1821 print "-- In $watchfile, processing watchfile line:\n $_\n";
1822 } elsif ($download == 0 and ! $dehs) {
1823 $pkg_report_header = "Processing watchfile line for package $package...\n";
1824 }
1826 $status +=
1827 process_watchline($_, $watch_version, $dir, $package, $version,
1828 $watchfile);
1829 dehs_output if $dehs;
1830 }
1832 close WATCH or
1833 $status=1, warn "$progname warning: problems reading $watchfile: $!\n";
1835 return $status;
1836 }
1839 # Collect up messages for dehs output into a tag
1840 sub dehs_msg ($)
1841 {
1842 my $msg = $_[0];
1843 $msg =~ s/\s*$//;
1844 push @{$dehs_tags{'messages'}}, $msg;
1845 }
1847 sub dehs_warn ($)
1848 {
1849 my $warning = $_[0];
1850 $warning =~ s/\s*$//;
1851 push @{$dehs_tags{'warnings'}}, $warning;
1852 }
1854 sub dehs_die ($)
1855 {
1856 my $msg = $_[0];
1857 $msg =~ s/\s*$//;
1858 %dehs_tags = ('errors' => "$msg");
1859 $dehs_end_output=1;
1860 dehs_output;
1861 exit 1;
1862 }
1864 sub dehs_output ()
1865 {
1866 return unless $dehs;
1868 if (! $dehs_start_output) {
1869 print "<dehs>\n";
1870 $dehs_start_output=1;
1871 }
1873 for my $tag (qw(package debian-uversion debian-mangled-uversion
1874 upstream-version upstream-url
1875 status target messages warnings errors)) {
1876 if (exists $dehs_tags{$tag}) {
1877 if (ref $dehs_tags{$tag} eq "ARRAY") {
1878 foreach my $entry (@{$dehs_tags{$tag}}) {
1879 $entry =~ s/</</g;
1880 $entry =~ s/>/>/g;
1881 $entry =~ s/&/&/g;
1882 print "<$tag>$entry</$tag>\n";
1883 }
1884 } else {
1885 $dehs_tags{$tag} =~ s/</</g;
1886 $dehs_tags{$tag} =~ s/>/>/g;
1887 $dehs_tags{$tag} =~ s/&/&/g;
1888 print "<$tag>$dehs_tags{$tag}</$tag>\n";
1889 }
1890 }
1891 }
1892 if ($dehs_end_output) {
1893 print "</dehs>\n";
1894 }
1896 # Don't repeat output
1897 %dehs_tags = ();
1898 }
1900 sub quoted_regex_parse($) {
1901 my $pattern = shift;
1902 my %closers = ('{', '}', '[', ']', '(', ')', '<', '>');
1904 $pattern =~ /^(s|tr|y)(.)(.*)$/;
1905 my ($sep, $rest) = ($2, $3 || '');
1906 my $closer = $closers{$sep};
1908 my $parsed_ok = 1;
1909 my $regexp = '';
1910 my $replacement = '';
1911 my $flags = '';
1912 my $open = 1;
1913 my $last_was_escape = 0;
1914 my $in_replacement = 0;
1916 for my $char (split //, $rest) {
1917 if ($char eq $sep and ! $last_was_escape) {
1918 $open++;
1919 if ($open == 1) {
1920 if ($in_replacement) {
1921 # Separator after end of replacement
1922 $parsed_ok = 0;
1923 last;
1924 } else {
1925 $in_replacement = 1;
1926 }
1927 } else {
1928 if ($open > 1) {
1929 if ($in_replacement) {
1930 $replacement .= $char;
1931 } else {
1932 $regexp .= $char;
1933 }
1934 }
1935 }
1936 } elsif ($char eq $closer and ! $last_was_escape) {
1937 $open--;
1938 if ($open) {
1939 if ($in_replacement) {
1940 $replacement .= $char;
1941 } else {
1942 $regexp .= $char;
1943 }
1944 } elsif ($open < 0) {
1945 $parsed_ok = 0;
1946 last;
1947 }
1948 } else {
1949 if ($in_replacement) {
1950 if ($open) {
1951 $replacement .= $char;
1952 } else {
1953 $flags .= $char;
1954 }
1955 } else {
1956 $regexp .= $char;
1957 }
1958 }
1959 # Don't treat \\ as an escape
1960 $last_was_escape = ($char eq '\\' and ! $last_was_escape);
1961 }
1963 $parsed_ok = 0 unless $in_replacement and $open == 0;
1965 return ($parsed_ok, $regexp, $replacement, $flags);
1966 }
1968 sub safe_replace($$) {
1969 my ($in, $pat) = @_;
1970 $pat =~ s/^\s*(.*?)\s*$/$1/;
1972 $pat =~ /^(s|tr|y)(.)/;
1973 my ($op, $sep) = ($1, $2 || '');
1974 my $esc = "\Q$sep\E";
1975 my ($parsed_ok, $regexp, $replacement, $flags);
1977 if ($sep eq '{' or $sep eq '(' or $sep eq '[' or $sep eq '<') {
1978 ($parsed_ok, $regexp, $replacement, $flags) = quoted_regex_parse($pat);
1980 return 0 unless $parsed_ok;
1981 } elsif ($pat !~ /^(?:s|tr|y)$esc((?:\\.|[^\\$esc])*)$esc((?:\\.|[^\\$esc])*)$esc([a-z]*)$/) {
1982 return 0;
1983 } else {
1984 ($regexp, $replacement, $flags) = ($1, $2, $3);
1985 }
1987 my $safeflags = $flags;
1988 if ($op eq 'tr' or $op eq 'y') {
1989 $safeflags =~ tr/cds//cd;
1990 return 0 if $safeflags ne $flags;
1992 $regexp =~ s/\\(.)/$1/g;
1993 $replacement =~ s/\\(.)/$1/g;
1995 $regexp =~ s/([^-])/'\\x' . unpack 'H*', $1/ge;
1996 $replacement =~ s/([^-])/'\\x' . unpack 'H*', $1/ge;
1998 eval "\$\$in =~ tr<$regexp><$replacement>$flags;";
2000 if ($@) {
2001 return 0;
2002 } else {
2003 return 1;
2004 }
2005 } else {
2006 $safeflags =~ tr/gix//cd;
2007 return 0 if $safeflags ne $flags;
2009 my $global = ($flags =~ s/g//);
2010 $flags = "(?$flags)" if length $flags;
2012 my $slashg;
2013 if ($regexp =~ /(?<!\\)(\\\\)*\\G/) {
2014 $slashg = 1;
2015 # if it's not initial, it is too dangerous
2016 return 0 if $regexp =~ /^.*[^\\](\\\\)*\\G/;
2017 }
2019 # Behave like Perl and treat e.g. "\." in replacement as "."
2020 # We allow the case escape characters to remain and
2021 # process them later
2022 $replacement =~ s/(^|[^\\])\\([^luLUE])/$1$2/g;
2024 # Unescape escaped separator characters
2025 $replacement =~ s/\\\Q$sep\E/$sep/g;
2026 # If bracketing quotes were used, also unescape the
2027 # closing version
2028 $replacement =~ s/\\\Q}\E/}/g if $sep eq '{';
2029 $replacement =~ s/\\\Q]\E/]/g if $sep eq '[';
2030 $replacement =~ s/\\\Q)\E/)/g if $sep eq '(';
2031 $replacement =~ s/\\\Q>\E/>/g if $sep eq '<';
2033 # The replacement below will modify $replacement so keep
2034 # a copy. We'll need to restore it to the current value if
2035 # the global flag was set on the input pattern.
2036 my $orig_replacement = $replacement;
2038 my ($first, $last, $pos, $zerowidth, $matched, @captures) = (0, -1, 0);
2039 while (1) {
2040 eval {
2041 # handle errors due to unsafe constructs in $regexp
2042 no re 'eval';
2044 # restore position
2045 pos($$in) = $pos if $pos;
2047 if ($zerowidth) {
2048 # previous match was a zero-width match, simulate it to set
2049 # the internal flag that avoids the infinite loop
2050 $$in =~ /()/g;
2051 }
2052 # Need to use /g to make it use and save pos()
2053 $matched = ($$in =~ /$flags$regexp/g);
2055 if ($matched) {
2056 # save position and size of the match
2057 my $oldpos = $pos;
2058 $pos = pos($$in);
2059 ($first, $last) = ($-[0], $+[0]);
2061 if ($slashg) {
2062 # \G in the match, weird things can happen
2063 $zerowidth = ($pos == $oldpos);
2064 # For example, matching without a match
2065 $matched = 0 if (not defined $first
2066 or not defined $last);
2067 } else {
2068 $zerowidth = ($last - $first == 0);
2069 }
2070 for my $i (0..$#-) {
2071 $captures[$i] = substr $$in, $-[$i], $+[$i] - $-[$i];
2072 }
2073 }
2074 };
2075 return 0 if $@;
2077 # No match; leave the original string untouched but return
2078 # success as there was nothing wrong with the pattern
2079 return 1 unless $matched;
2081 # Replace $X
2082 $replacement =~ s/[\$\\](\d)/defined $captures[$1] ? $captures[$1] : ''/ge;
2083 $replacement =~ s/\$\{(\d)\}/defined $captures[$1] ? $captures[$1] : ''/ge;
2084 $replacement =~ s/\$&/$captures[0]/g;
2086 # Make \l etc escapes work
2087 $replacement =~ s/\\l(.)/lc $1/e;
2088 $replacement =~ s/\\L(.*?)(\\E|\z)/lc $1/e;
2089 $replacement =~ s/\\u(.)/uc $1/e;
2090 $replacement =~ s/\\U(.*?)(\\E|\z)/uc $1/e;
2092 # Actually do the replacement
2093 substr $$in, $first, $last - $first, $replacement;
2094 # Update position
2095 $pos += length($replacement) - ($last - $first);
2097 if ($global) {
2098 $replacement = $orig_replacement;
2099 } else {
2100 last;
2101 }
2102 }
2104 return 1;
2105 }
2106 }
2108 sub get_main_source_dir($$$$$) {
2109 my ($tempdir, $pkg, $newversion, $excludesuffix, $exclude__MACOSX) = @_;
2110 my $fcount = 0;
2111 my $main_source_dir = '' ;
2112 my $any_dir = '' ;
2113 opendir DIR, $tempdir or die "opendir $tempdir: $!";
2114 my @files = readdir DIR ;
2115 closedir DIR ;
2116 foreach my $file (@files) {
2117 unless ($file =~ /^\.\.?/) {
2118 if ( $exclude__MACOSX && $file =~ /^__MACOSX$/ ) {
2119 `rm -rf ${tempdir}/__MACOSX` ;
2120 next ;
2121 }
2122 $fcount++;
2123 if ( -d $tempdir.'/'.$file ) {
2124 $any_dir = $tempdir . '/' . $file ;
2125 # check whether there is some dir in upstream source which looks reasonable
2126 # If such dir exists, we do not try to undirty the directory structure
2127 $main_source_dir = $any_dir if ( $file =~ /^$pkg\w*$newversion$/i ) ;
2128 }
2129 }
2130 }
2131 if ( $fcount == 1 and $main_source_dir ) {
2132 return $main_source_dir ;
2133 }
2134 if ( $fcount == 1 and $any_dir ) {
2135 # Unusual base dir in tarball - should be rather something like ${pkg}-${newversion}
2136 $main_source_dir = $tempdir . '/' . $pkg . '-' . $newversion . $excludesuffix . '.orig';
2137 move($any_dir, $main_source_dir) or die("Unable to move $any_dir directory $main_source_dir\n");
2138 return $main_source_dir ;
2139 }
2140 print "-- Dirty tarball found.\n" if $verbose;
2141 if ( $main_source_dir ) { # if tarball is dirty but does contain a $pkg-$newversion dir we will not undirty but leave it as is
2142 print "-- No idea how to create proper tarball structure - leaving as is.\n" if $verbose;
2143 return $tempdir;
2144 }
2145 print "-- Move files to subdirectory $pkg-$newversion.\n" if $verbose;
2146 $main_source_dir = $tempdir . '/' . $pkg . '-' . $newversion . $excludesuffix . '.orig';
2147 mkdir($main_source_dir) or die("Unable to create temporary source directory $main_source_dir\n");
2148 foreach my $file (@files) {
2149 unless ($file =~ /^\.\.?/) {
2150 if ( -d "${tempdir}/$file" ) {
2151 # HELP: why can't perl move not move directories????
2152 system( "mv ${tempdir}/$file $main_source_dir" ) ;
2153 } else {
2154 move("${tempdir}/$file", $main_source_dir) or die("Unable to move ${tempdir}/$file directory $main_source_dir\n");
2155 }
2156 }
2157 }
2158 return $main_source_dir;
2159 }
2162 sub compress_archive($$$) {
2163 my ($from_file, $to_file, $compression) = @_;
2164 if ( $compression =~ /^gz$/ ) {
2165 spawn(exec => ['gzip', '-n', '-9'],
2166 from_file => $from_file,
2167 to_file => $to_file,
2168 wait_child => 1);
2169 } elsif ( $compression =~ /^bz2$/ ) {
2170 # The actual options should be discussed - supporting small memory seems reasonable
2171 spawn(exec => ['bzip2', '--small'],
2172 from_file => $from_file,
2173 to_file => $to_file,
2174 wait_child => 1);
2175 } elsif ( $compression =~ /^xz$/ ) {
2176 # The actual options should be discussed - supporting small memory seems reasonable
2177 spawn(exec => ['xz', '--memlimit=150MiB'],
2178 from_file => $from_file,
2179 to_file => $to_file,
2180 wait_child => 1);
2181 } elsif ( $compression =~ /^lzma$/ ) {
2182 # The actual options should be discussed - no idea what might be reasonable here
2183 spawn(exec => ['lzma'],
2184 from_file => $from_file,
2185 to_file => $to_file,
2186 wait_child => 1);
2187 } else {
2188 die("Unknown compression method $compression.");
2189 }
2190 unlink "$from_file";
2191 }
