2
#Ethan Jackosn found this at http://www.softlights.net/projects/mexumgen/
3
#*******************************************************************************
4
# Mozilla Extension Update Manifest Generator, version 1.1
5
# Copyright (C) 2008 Sergei Zhirikov (sfzhi@yahoo.com)
6
# This software is available under the GNU General Public License v3.0
7
# (http://www.gnu.org/licenses/gpl-3.0.txt)
8
#*******************************************************************************
15
use RDF::Core::Parser;
16
use File::Spec::Functions qw(catfile tmpdir curdir);
17
#*******************************************************************************
18
use constant NSMOZ => 'http://www.mozilla.org/2004/em-rdf#';
19
use constant NSRDF => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
20
use constant sha512WithRSAEncryption => ':1.2.840.113549.1.1.13';
21
#*******************************************************************************
22
@ARGV or pod2usage(-exitval => 1, -verbose => 99, -sections => 'NAME|SYNOPSIS');
23
#*******************************************************************************
24
$Getopt::Std::STANDARD_HELP_VERSION = 1;
26
pod2usage(-exitval => 'NOEXIT', -verbose => 99, -sections => 'NAME');
29
pod2usage(-exitval => 'NOEXIT', -verbose => 1);
31
#*******************************************************************************
33
getopts('i:o:k:p:hw', \%opt) or die "Use '--help' to see available options\n";
34
our ($rdf, $out, $pem, $pwd) = @opt{qw[i o k p]};
35
our $grp = $opt{w}? 3: 2;
36
#*******************************************************************************
37
@ARGV % $grp == 0 or die "The number of arguments must be multiple of $grp\n";
40
my ($xpi, $url, $inf) = splice(@ARGV, 0, $grp);
41
push(@xpi, {xpi => $xpi, url => $url, $inf? (inf => $inf): ()});
44
$rdf || @xpi or die "At least one input file is required\n";
45
#*******************************************************************************
46
our $tmp = catfile(tmpdir() || curdir(), "update.rdf.mexumgen.$$.tmp");
47
#*******************************************************************************
50
(new RDF::Core::Parser(Assert => sub {
52
push(@{$tree->{$item{'subject_uri'}}->
53
{$item{'predicate_ns'}}->{$item{'predicate_name'}}},
54
{uri => $item{'object_uri'}, lit => $item{'object_literal'}});
58
#*******************************************************************************
60
my $txt = qx[unzip -jnpq "$xpi->{xpi}" install.rdf];
61
$? == 0 or die "Could not extract install manifest from '$xpi->{xpi}'\n";
63
my $all = $rdf->{'urn:mozilla:install-manifest'}->{NSMOZ()};
64
my ($ext, $ver) = map {
65
(defined($_) && (@{$_} == 1))? $_->[0]->{lit}: undef;
66
} @$all{'id', 'version'};
69
(defined($uri) && exists($rdf->{$uri}))? (sub {
70
my ($app, $min, $max) = map {
71
(defined($_) && (@{$_} == 1))? $_->[0]->{lit}: undef;
72
} @{$rdf->{$uri}->{NSMOZ()}}{'id', 'minVersion', 'maxVersion'};
73
{app => $app, min => $min, max => $max};
75
} @{$all->{'targetApplication'}};
80
#*******************************************************************************
81
if (@xpi && ($pem || $opt{h})) {
82
open(SHA, '-|', 'openssl sha1 -hex '.join(' ', map {qq["$_->{xpi}"]} @xpi))
83
or die "Failed to start OpenSSL to calculate SHA1 hashes: $!\n";
85
if (/^SHA1\((.*?)\)=\s*([[:xdigit:]]{40})\s*$/) {
86
$xpi[$xpi{$1}]->{sha} = "sha1:$2" if (exists($xpi{$1}));
90
$? == 0 or die "OpenSSL failed to calculate SHA1 hashes\n";
92
#*******************************************************************************
96
if (my $ext = $xpi->{ext}) {
97
push(@ext, $ext) unless (exists($ext{$ext}));
98
push(@{$ext{$ext}}, $xpi);
101
#*******************************************************************************
104
my ($file, $tree, $offs, $incr, $mode) = @_;
105
my ($name, $data, $attr) = @$tree;
106
my $sort = $mode && $name =~ s/^rdf:/RDF:/;
108
$attr = $attr? ' '.($mode? '': 'rdf:').'about="'.$attr.'"': '';
109
print $file "$offs<$name$attr>\n";
110
for my $item ($sort? sort({$a->[0] cmp $b->[0]} @$data): @$data) {
111
ser($file, $item, $offs.$incr, $incr, $mode);
113
print $file "$offs</$name>\n";
114
} elsif ($name ne 'em:signature') {
115
print $file "$offs<$name>$data</$name>\n";
117
print $file "$offs<$name>\n";
118
print $file "$offs$incr", substr($data, 0, 64, ''), "\n" while ($data);
119
print $file "$offs</$name>\n";
122
#*******************************************************************************
123
use constant BUFFER => 32768; # Must be BUFFER > max(length($signature))
125
open(RDF, '>', $tmp) or die "Failed to create a temporary file: $!\n";
127
ser(*RDF, shift, '', ' ', 1);
129
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
130
open(SIG, '-|', qq[openssl dgst -sha512 -sign "$pem" -binary "$tmp"])
131
or die "Failed to start OpenSSL to generate the signature: $!\n";
134
my $size = read(SIG, $body, BUFFER);
136
$? == 0 or die "OpenSSL failed to generate the signature\n";
137
#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
138
if (($size > 0) && ($size < BUFFER) && ($size == length($body))) {
139
my $asn1 = Convert::ASN1->new(encoding => 'DER');
141
Algorithm ::= SEQUENCE {
142
oid OBJECT IDENTIFIER,
145
Signature ::= SEQUENCE {
150
my $data = $asn1->encode(sig => $body,
151
alg => {oid => sha512WithRSAEncryption()});
152
if (defined($data)) {
153
return encode_base64($data, '');
155
die "Failed to encode the generated signature: ".$asn1->error."\n";
158
die "Failed to obtain the generated signature from OpenSSL\n";
161
#*******************************************************************************
163
die "Signing an existing update manifest is not supported yet\n";
165
#*******************************************************************************
166
for my $ext (values(%ext)) {
168
my @ab = map {$_->{ver}} ($a, $b);
169
my ($ax, $bx) = map {[map {($_ eq '*')? '*': [('0') x !/^-?\d/,
170
split /(?<=\d)(?=\D)|(?<=[^-\d])(?=-?\d)/]} split /\./]} @ab;
171
push(@$ax, (['0']) x ($#$bx - $#$ax)) if ($#$ax < $#$bx);
172
push(@$bx, (['0']) x ($#$ax - $#$bx)) if ($#$bx < $#$ax);
176
if (ref($ay) && ref($by)) {
177
foreach my $i (0..(($#$ay > $#$by)? $#$ay: $#$by)) {
178
my ($az, $bz) = ($ay->[$i], $by->[$i]);
179
$cmp = ($i % 2)? ((defined($bz) <=> defined($az)) ||
180
($az cmp $bz)): (($az || 0) <=> ($bz || 0));
184
$cmp = !ref($ay) <=> !ref($by);
189
} @$ext if (@$ext > 1);
191
#*******************************************************************************
193
['rdf:Description' => [
199
['rdf:Description' => [
200
['em:version' => $upd->{ver}],
202
['em:targetApplication' => [
203
['rdf:Description' => [
204
['em:id' => $_->{app}],
205
['em:minVersion' => "3.0"],
206
['em:maxVersion' => $_->{max}],
207
['em:updateLink' => $upd->{url}],
209
['em:updateHash' => $upd->{sha}]: (),
211
['em:updateInfoURL' => $upd->{inf}]: ()
220
], 'urn:mozilla:extension:'.$_]
222
#*******************************************************************************
224
push(@{$_->[1]}, ['em:signature' => sig($_)]) for (@rdf);
226
#*******************************************************************************
227
use constant INDENT => ' ' x 4;
229
open(RDF, '>', $out) or die "Failed to open the output file '$out': $!\n";
231
open(RDF, '>&', \*STDOUT) or die "Failed to open output stream: $!\n";
234
<?xml version="1.0"?>
235
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
236
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
238
ser(*RDF, $_, INDENT(), INDENT(), 0) for (@rdf);
239
print RDF "</rdf:RDF>\n";
241
#*******************************************************************************
242
#END { unlink($tmp) if (defined($tmp) && (-e $tmp)); }
243
#*******************************************************************************
248
mexumgen - Mozilla Extension Update Manifest Generator, version 1.1
252
menumgen [-h | -k key.pem] [-w] [-o out.rdf] etc...
260
Calculate SHA1 hash of every installation package and include it in the update
261
manifest. If B<-k> option is present this is implied.
265
The private key to sign the update manifest with. Typically it is 1024-bit RSA
266
key in PEM format. If this parameter is omitted the update manifest will not be
271
Indicates that C<updateInfoURL> (a.k.a "What's new") should be included in the
272
generated update manifest. This changes the meaning of the tail of the command
273
line (see B<etc> below).
277
The output update manifest file (generated and signed). This can be the same
278
file as specified with B<-i> option. If this parameter is omitted the resulting
279
update manifest will be written to the standard output.
283
The remaining command line arguments specify the installation packages to be
284
used and the corresponding URLs.
286
If B<-w> option is present, the number of remaining
287
arguments must be multiple of 3. Each group of 3 arguments specifies (in that
288
order): the path to the installation package in the local file system, the URL
289
where that package is going to be available, the URL of the "What's new" page
292
If B<-w> option is not present, the number
293
of remaining arguments must be multiple of 2. Each pair of arguments specifies
294
the path to the installation package in the local file system and the URL where
295
that package is going to be available.
297
In other words, the B<-w> option indicates whether "What's new" URLs must be
298
present in the list of the installation packages.
304
The F<install.rdf> file found in each installation package specified in the
305
command line is parsed to retrieve the information about the extension (I<id>,
306
I<version>) and about the target application(s) (I<id>, I<minVersion>,
307
I<maxVersion>). Also, if the update manifest is to be signed or if B<-h>
308
command line option is present, the SHA1 hash of each installation package
309
(xpi file) is calculated. That information is used to construct a new update
314
menumgen -k key.pem -w -o update.rdf extension.xpi
315
http://www.example.com/download/extension.xpi
316
http://www.example.com/extension/update.xhtml
320
The I<targetPlatform> from the installation packages is not taken into account.
322
If the key is encrypted the password must be entered interactively, there is
323
no way to specify it on the command line.
325
Signing an existing update manifest is not supported in the current version.
329
Convert::ASN1, XML::Parser, RDF::Core, openssl, unzip
333
L<http://www.softlights.net/projects/mexumgen/>
337
L<http://www.softlights.net/projects/mexumgen/mexumgen.zip>
341
Copyright (C) 2008 Sergei Zhirikov (sfzhi@yahoo.com)