chiark / gitweb /
2c02c11bcb74603ada8f65b062988699d66ec00a
[ypp-sc-tools.db-test.git] / pctb / commod-update-receiver
1 #!/usr/bin/perl -w
2 #
3 # This script is invoked when the YPP SC PCTB client uploads to
4 # the chiark database.
5
6 # This is part of ypp-sc-tools, a set of third-party tools for assisting
7 # players of Yohoho Puzzle Pirates.
8 #
9 # Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
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 3 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/>.
23 #
24 # Yohoho and Puzzle Pirates are probably trademarks of Three Rings and
25 # are used without permission.  This program is not endorsed or
26 # sponsored by Three Rings.
27
28
29 # Uploads contain:
30 #  ocean                        canonical mixed case
31 #  island                       canonical mixed case
32 #  clientname                   "ypp-sc-tools"
33 #  clientversion                2.1-g2e06a26  [from git-describe --tags HEAD]
34 #  clientfixes                  "lastpage"  [space separated list]
35 #  data filename=deduped.tsv.gz output of ypp-commodities --tsv
36
37
38 use strict (qw(vars));
39 use POSIX;
40 use MIME::Entity;
41
42 use Commods;
43
44 $CGI::POST_MAX= 3*1024*1024;
45
46 use CGI qw/:standard -private_tempfiles/;
47
48 setlocale(LC_CTYPE, "en_GB.UTF-8");
49
50 my $re_any= "^(.*)\$";
51
52 parse_masters();
53
54 sub fail ($) {
55     my ($msg) = @_;
56     print header(-status=>'400 Bad commodity update',
57                  -type=>'text/plain',
58                  -charset=>'us-ascii');
59     print "Error: $msg\n";
60     exit 0;
61 }
62
63 sub must_param ($$) {
64     my ($n,$re)= @_;
65     my $v= param($n);
66     fail("missing form parameter $n") unless defined $v;
67     fail("invalid form parameter $n ($re)") unless $v =~ m/$re/;
68     return $1;
69 }
70
71 my %o;
72
73 $o{'clientname'}= must_param('clientname',$re_any);
74 my $clientinfo= $clients{$o{'clientname'}};
75 fail('unknown client') unless defined $clientinfo;
76
77 my $clientfixes= must_param('clientfixes', "^([-0-9a-z ]*)\$");
78 my @clientfixes= sort grep { m/./ } split /\s+/, $clientfixes;
79 $o{'clientfixes'}= "@clientfixes";
80 foreach my $bug (@$clientinfo) {
81     fail("client out of date - missing bugfix \`$bug'")
82         unless grep { $_ eq $bug } @clientfixes;
83 }
84
85 $o{'clientversion'}= must_param('clientversion', "^(\\d[-+._0-9a-zA-Z]+)\$");
86
87 $o{'ocean'}= must_param('ocean', $re_any);
88 $o{'island'}= must_param('island', $re_any);
89
90 my $arches= $oceans{$o{'ocean'}};
91 fail("unknown ocean") unless $arches;
92
93 my $island_found= 0;
94 foreach my $islands (values %$arches) {
95     my $sources= $islands->{$o{'island'}};
96     next unless $sources;
97     die if $island_found;
98     $island_found= $sources;
99 }
100 fail("unknown island") unless $island_found;
101
102 $o{'timestamp'}= must_param('timestamp', "^([1-9]\\d{1,20})\$");
103 my $now= time;  defined $now or die $!;
104 fail("clock skew") if $o{'timestamp'} >= $now;
105
106 my $indatafh= upload('data');
107 defined $indatafh or fail("data is not a file");
108 my $datafile= must_param('data',"^(deduped\\.tsv\\.gz)\$");
109
110 my $mcontent= MIME::Entity->build(To => 'yarrg-commod-updates',
111                                   Type => 'multipart/mixed',
112                                   Boundary => '=',
113                                   Charset => 'utf-8');
114
115 get_our_version(\%o, 'server');
116 foreach my $cs (qw(client server)) {
117     $o{"${cs}spec"}= join "\t", map { $o{$cs.$_} } qw(name version fixes);
118 }
119
120 my $metadata= '';
121
122 sub ksmap ($) {
123     my ($v) = @_;
124     my $i=0; grep { return $i if $_ eq $v; $i++ } qw(ocean island timestamp);
125     sprintf "z %d %s", (length $v) / 8, $v;
126 }
127
128 foreach my $vn (sort { ksmap($a) cmp ksmap($b) } keys %o) {
129     my $val= $o{$vn};
130     die if $val =~ m/\n|\r/;
131     $metadata .= "$vn\t$o{$vn}\n";
132 }
133
134 my $mdpart= MIME::Entity->build(Top => 0,
135                                 Type => 'text/plain',
136                                 Charset => 'utf-8',
137                                 Disposition => 'inline',
138                                 Encoding => 'quoted-printable',
139                                 Filename => 'metadata',
140                                 Data => $metadata);
141 $mcontent->add_part($mdpart);
142
143 my $gunzchild= open(GZ, "-|"); defined $gunzchild or die $!;
144 if (!$gunzchild) {
145     open STDIN, "<&=", $indatafh or die $!;
146     exec 'gunzip'; die $!;
147 }
148
149 my $dedupedtsv= pipethrough_prep();
150
151 while (<GZ>) {
152     my @v= check_tsv_line($_,\&fail);
153     print $dedupedtsv join('\t',@v),"\n" or die $!;
154 }
155
156 GZ->error and die $!;
157 $?=0; close GZ; $? and fail("gunzip for check failed code $?");
158
159 my $launderedgz= pipethrough_run($dedupedtsv,undef,'gzip','gzip');
160
161 my $mdatafile= MIME::Entity->build(Top => 0,
162                                    Type => 'application/octet-stream',
163                                    Disposition => 'attachment',
164                                    Encoding => 'base64',
165                                    Filename => 'deduped.tsv.gz',
166                                    Data => $launderedgz);
167 $mcontent->add_part($mdatafile);
168
169 open M, "|/usr/sbin/sendmail -t -oi -oee -odb"
170     or fail("fork sendmail failed! ($!)");
171 $mcontent->print(\*M);
172
173 M->error and fail("write sendmail failed! ($!)");
174 $?=0; close M; $? and fail("sendmail failed code $?");
175
176 print header(-type=>'text/plain', -charset=>'us-ascii'),
177       "OK\n"
178     or die $!;