chiark / gitweb /
b7bf4cb80eba6f58ba954c6d484fe42d84212b10
[ypp-sc-tools.main.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 use strict (qw(vars));
38 use POSIX;
39
40 use Commods;
41
42 $CGI::POST_MAX= 3*1024*1024;
43 $CGI::DISABLE_UPLOADS= 1;
44
45 use CGI qw/:standard -private_tempfiles/;
46
47 setlocale(LC_CTYPE, "en_GB.UTF-8");
48
49 my $re_any= "^(.*)\$";
50
51 parse_masters();
52
53 sub fail ($) {
54     my ($msg) = @_;
55     print header(-status=>'400 Bad commodity update',
56                  -type=>'text/plain',
57                  -charset=>'us-ascii');
58     print "Error: $msg\n";
59     exit 0;
60 }
61
62 sub must_param ($$) {
63     my ($n,$re)= @_;
64     my $v= param($n);
65     fail("missing form parameter $n") unless defined $v;
66     fail("invalid form parameter $n ($re)") unless $v =~ m/$re/;
67     return $1;
68 }
69
70 my $clientname= must_param('clientname',$re_any);
71 my $clientinfo= $clients{$clientname};
72 fail('unknown client') unless defined $clientinfo;
73
74 my $clientfixes= must_param('clientfixes', "^([-0-9a-z ]*)\$");
75 my @clientfixes= sort grep { m/./ } split /\s+/, $clientfixes;
76 $clientfixes= "@clientfixes";
77 foreach my $bug (@$clientinfo) {
78     fail("client out of date - missing bugfix \`$bug'")
79         unless grep { $_ eq $bug } @clientfixes;
80 }
81
82 my $clientversion= must_param('clientversion', "^(\\d[-+._0-9a-zA-Z]+)\$");
83
84 my $ocean= must_param('ocean', $re_any);
85 my $island= must_param('island', $re_any);
86
87 my $arches= $oceans{$ocean};
88 fail("unknown ocean") unless $arches;
89
90 my $island_found= 0;
91 foreach my $islands (values %$arches) {
92     my $sources= $islands->{$island};
93     next unless $sources;
94     die if $island_found;
95     $island_found= $sources;
96 }
97 fail("unknown island") unless $island_found;
98
99 my $timestamp= must_param('timestamp', "^([1-9]\\d{1,20})\$");
100 my $now= time;  defined $now or die $!;
101 fail("clock skew") if $timestamp >= $now;
102
103 die if $ocean =~ m/\=/;
104 die if $island =~ m/\=/;
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 our %done;
111
112 my $mcontent= MIME::Entity->build(To => 'yarrg-commod-updates',
113                                   Type => 'multipart/mixed',
114                                   Boundary => '=',
115                                   Charset => 'utf-8');
116
117 our ($servername,$serverversion,$serverfixes);
118 get_our_version('::server');
119
120 my $clientspec= "$clientname $clientversion $clientfixes";
121 my $serverspec= "$servername $serverversion $serverfixes";
122
123 foreach my $vn (qw(ocean island timestamp
124                    clientspec clientname clientversion clientfixes
125                    serverspec servername serverversion serverfixes)) {
126     my $mpart= MIME::Entity->build(Type => 'text/plain',
127                                    Charset => 'utf-8',
128                                    Disposition => 'inline',
129                                    Data => $$vn);
130     $mcontent->add_part($mpart);
131 }
132
133 my $gunzchild= open(GZ, "-|") or die $!;
134 if (!$gunzchild) {
135     open STDIN, "<&=", $indatafh or die $!;
136     execlp 'gunzip'; die $!;
137 }
138
139 while (<GZ>) {
140     !m/\P{IsPrint}/ or die bad_data('nonprinting char(s)');
141     !m/\\/ or bad_data('data contains backslashes');
142     my @v= split /\t/;
143     @v==6 or bad_data('wrong number of fields');
144     my ($commod,$stall) = @v;
145     defined $commods{$commod} or bad_data("unknown commodity \`$commod'");
146     $stall =~ m/^\p{IsUpper}/ or bad_data("stall not capitalised");
147     !exists $done{$commod,$stall} or bad_data("repeated data");
148     $done{$commod,$stall}= 1;
149     foreach my $i (2..5) {
150         my $f= $v[$i];
151         $f =~ m/^(0|[1-9][0-9]{0,5}|\>1000)$/ or bad_data("bad field");
152         ($i % 1) or ($f !~ m/\>/) or bad_data("> in price");
153     }
154 }
155 GZ->error and die $!;
156 $?=0; close GZ; $? and fail("gunzip for check failed code $?");
157
158 my $mdatafile= MIME::Entity->build(Type => 'application/octet-stream',
159                                    Disposition => 'attachment',
160                                    Encoding => 'base64',
161                                    File => $datafile);
162 $mcontent->add_part($mdatafile);
163
164 open M, "|/usr/sbin/sendmail -t -oi -oee -odq"
165     or fail("fork sendmail failed! ($!)");
166 $mcontent->print(\*M);
167
168 M->error and fail("write sendmail failed! ($!)");
169 $?=0; close M; $? and fail("sendmail failed code $?");