3 # This script is invoked when the YPP SC PCTB client uploads to
6 # This is part of ypp-sc-tools, a set of third-party tools for assisting
7 # players of Yohoho Puzzle Pirates.
9 # Copyright (C) 2009 Ian Jackson <ijackson@chiark.greenend.org.uk>
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.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # 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.
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
37 use strict (qw(vars));
43 $CGI::POST_MAX= 3*1024*1024;
45 use CGI qw/:standard -private_tempfiles/;
47 setlocale(LC_CTYPE, "en_GB.UTF-8");
49 my $re_any= "^(.*)\$";
55 print header(-status=>'400 Bad commodity update',
57 -charset=>'us-ascii');
58 print "Error: $msg\n";
65 fail("missing form parameter $n") unless defined $v;
66 fail("invalid form parameter $n ($re)") unless $v =~ m/$re/;
72 $o{'clientname'}= must_param('clientname',$re_any);
73 my $clientinfo= $clients{$o{'clientname'}};
74 fail('unknown client') unless defined $clientinfo;
76 my $clientfixes= must_param('clientfixes', "^([-0-9a-z ]*)\$");
77 my @clientfixes= sort grep { m/./ } split /\s+/, $clientfixes;
78 $o{'clientfixes'}= "@clientfixes";
79 foreach my $bug (@$clientinfo) {
80 fail("client out of date - missing bugfix \`$bug'")
81 unless grep { $_ eq $bug } @clientfixes;
84 $o{'clientversion'}= must_param('clientversion', "^(\\d[-+._0-9a-zA-Z]+)\$");
86 $o{'ocean'}= must_param('ocean', $re_any);
87 $o{'island'}= must_param('island', $re_any);
89 my $arches= $oceans{$o{'ocean'}};
90 fail("unknown ocean") unless $arches;
93 foreach my $islands (values %$arches) {
94 my $sources= $islands->{$o{'island'}};
97 $island_found= $sources;
99 fail("unknown island") unless $island_found;
101 $o{'timestamp'}= must_param('timestamp', "^([1-9]\\d{1,20})\$");
102 my $now= time; defined $now or die $!;
103 fail("clock skew") if $o{'timestamp'} >= $now;
105 die if $o{'ocean'} =~ m/\=/;
106 die if $o{'island'} =~ m/\=/;
108 my $indatafh= upload('data');
109 defined $indatafh or fail("data is not a file");
110 my $datafile= must_param('data',"^(deduped\\.tsv\\.gz)\$");
112 my $mcontent= MIME::Entity->build(To => 'yarrg-commod-updates',
113 Type => 'multipart/mixed',
117 get_our_version(\%o, 'server');
118 foreach my $cs (qw(client server)) {
119 $o{"${cs}spec"}= join ' ', map { $o{$cs.$_} } qw(name version fixes);
122 foreach my $vn (sort keys %o) {
123 my $mpart= MIME::Entity->build(Type => 'text/plain',
125 Disposition => 'inline',
127 $mcontent->add_part($mpart);
130 my $gunzchild= open(GZ, "-|"); defined $gunzchild or die $!;
132 open STDIN, "<&=", $indatafh or die $!;
133 exec 'gunzip'; die $!;
136 sub bad_data ($) { fail("bad data: line $.: $_[0]"); }
141 !m/\P{IsPrint}/ or die bad_data('nonprinting char(s)');
142 !m/\\/ or bad_data('data contains backslashes');
144 @v==6 or bad_data('wrong number of fields');
145 my ($commod,$stall) = @v;
146 defined $commods{$commod} or bad_data("unknown commodity \`$commod'");
147 $stall =~ m/^\p{IsUpper}|^[0-9]/ or bad_data("stall not capitalised");
148 !exists $done{$commod,$stall} or bad_data("repeated data");
149 $done{$commod,$stall}= 1;
150 foreach my $i (2..5) {
152 $f =~ m/^(|0|[1-9][0-9]{0,5}|\>1000)$/ or bad_data("bad field $i");
153 ($i % 2) or ($f !~ m/\>/) or bad_data("> in field $i price");
156 GZ->error and die $!;
157 $?=0; close GZ; $? and fail("gunzip for check failed code $?");
159 my $mdatafile= MIME::Entity->build(Type => 'application/octet-stream',
160 Disposition => 'attachment',
161 Encoding => 'base64',
163 $mcontent->add_part($mdatafile);
165 open M, "|/usr/sbin/sendmail -t -oi -oee -odq"
166 or fail("fork sendmail failed! ($!)");
167 $mcontent->print(\*M);
169 M->error and fail("write sendmail failed! ($!)");
170 $?=0; close M; $? and fail("sendmail failed code $?");