Parent Directory
|
Revision Log
fixed some security checks in Hardcopy. -sam
1 ################################################################################ 2 # WeBWorK mod_perl (c) 2000-2002 WeBWorK Project 3 # $Id$ 4 ################################################################################ 5 6 package WeBWorK::ContentGenerator::Hardcopy; 7 8 =head1 NAME 9 10 WeBWorK::ContentGenerator::Hardcopy - generate a PDF version of one or more 11 problem sets. 12 13 =cut 14 15 use strict; 16 use warnings; 17 use base qw(WeBWorK::ContentGenerator); 18 use CGI qw(); 19 use File::Path qw(rmtree); 20 use File::Temp qw(tempdir); 21 use WeBWorK::DB::Classlist; 22 use WeBWorK::DB::WW; 23 use WeBWorK::Form; 24 use WeBWorK::Utils qw(readFile); 25 26 sub go { 27 my ($self, $singleSet) = @_; 28 29 my $r = $self->{r}; 30 my $ce = $self->{courseEnvironment}; 31 my @sets = $r->param("hcSet"); 32 my @users = $r->param("hcUser"); 33 34 # add singleSet to the list of sets 35 if (length $singleSet > 0) { 36 $singleSet =~ s/^set//; 37 unshift @sets, $singleSet unless grep { $_ eq $singleSet } @sets; 38 } 39 40 # default user is the effectiveUser 41 unless (@users) { 42 unshift @users, $r->param("effectiveUser"); 43 } 44 45 $self->{cldb} = WeBWorK::DB::Classlist->new($ce); 46 $self->{authdb} = WeBWorK::DB::Auth->new($ce); 47 $self->{wwdb} = WeBWorK::DB::WW->new($ce); 48 $self->{user} = $self->{cldb}->getUser($r->param("user")); 49 $self->{permissionLevel} = $self->{authdb}->getPermissions($r->param("user")); 50 $self->{effectiveUser} = $self->{cldb}->getUser($r->param("effectiveUser")); 51 $self->{sets} = \@sets; 52 $self->{users} = \@users; 53 $self->{errors} = []; 54 $self->{warnings} = []; 55 56 # security checks - these have to be put somewhere 57 my $multiSet = $self->{permissionLevel} > 0; 58 my $multiUser = $self->{permissionLevel} > 0; 59 if (@sets > 1 and not $multiSet) { 60 $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple sets. Please select a single set and try again."]; 61 } 62 if (@users > 1 and not $multiUser) { 63 $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for multiple users. Please select a single user and try again."]; 64 } 65 if ($users[0] ne $self->{effectiveUser}->id and not $multiUser) { 66 $self->{generationError} = ["SIMPLE", "You are not permitted to generate hardcopy for other users."]; 67 } 68 69 unless ($self->{generationError}) { 70 if ($r->param("generateHardcopy")) { 71 my ($tempDir, $fileName) = eval { $self->generateHardcopy() }; 72 if ($@) { 73 $self->{generationError} = $@; 74 } else { 75 my $filePath = "$tempDir/$fileName"; 76 77 $r->content_type("application/x-pdf"); 78 # as per RFC2183: 79 $r->header_out("Content-Disposition", "attachment; filename=$fileName"); 80 $r->send_http_header(); 81 82 local *INPUTFILE; 83 open INPUTFILE, "<", $filePath 84 or die "Failed to read $filePath: $!"; 85 my $buf; 86 while (read INPUTFILE, $buf, 16384) { 87 print $buf; 88 } 89 close INPUTFILE; 90 91 return; 92 } 93 } 94 } 95 96 $r->content_type("text/html"); 97 $r->send_http_header(); 98 $self->template($ce->{templates}->{system}, $singleSet); 99 } 100 101 # ----- 102 103 sub path { 104 my ($self, undef, $args) = @_; 105 106 my $ce = $self->{courseEnvironment}; 107 my $root = $ce->{webworkURLs}->{root}; 108 my $courseName = $ce->{courseName}; 109 return $self->pathMacro($args, 110 "Home" => "$root", 111 $courseName => "$root/$courseName", 112 "Hardcopy Generator" => "", 113 ); 114 } 115 116 sub title { 117 return "Hardcopy Generator"; 118 } 119 120 sub body { 121 my $self = shift; 122 123 if ($self->{generationError}) { 124 if (ref $self->{generationError} eq "ARRAY") { 125 my ($disposition, @rest) = @{$self->{generationError}}; 126 if ($disposition eq "PGFAIL") { 127 print $self->multiErrorOutput(@{$self->{errors}}); 128 return ""; 129 } elsif ($disposition eq "FAIL") { 130 print $self->errorOutput(@rest); 131 return ""; 132 } elsif ($disposition eq "RETRY") { 133 print $self->errorOutput(@rest); 134 } else { # a "simple" error 135 print CGI::p(CGI::font({-color=>"red"}, @rest)); 136 } 137 } else { 138 # not something we were expecting... 139 die $self->{generationError}; 140 } 141 } 142 $self->displayForm(); 143 } 144 145 sub multiErrorOutput($@) { 146 my ($self, @errors) = @_; 147 148 print CGI::h2("Software Errors"); 149 print CGI::p(<<EOF); 150 WeBWorK has encountered one or more software errors while attempting to process these sets. 151 It is likely that there are error(s) in the problem itself. 152 If you are a student, contact your professor to have the error(s) corrected. 153 If you are a professor, please consut the error output below for more informaiton. 154 EOF 155 foreach my $error (@errors) { 156 print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}); 157 print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message})); 158 print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context})); 159 } 160 } 161 162 # ----- 163 164 sub displayForm($) { 165 my $self = shift; 166 my $r = $self->{r}; 167 168 print CGI::start_p(), "Select the problem sets for which to generate hardcopy versions."; 169 if ($self->{permissionLevel} > 0) { 170 print "You may also select multiple users from the users list. You will receive hardcopy for each (set, user) pair."; 171 } 172 print CGI::end_p(); 173 174 print CGI::start_form(-method=>"POST", -action=>$r->uri); 175 print $self->hidden_authen_fields(); 176 print CGI::start_table({-width=>"100%"}), CGI::start_Tr({-valign=>"top"}); 177 178 my $multiSet = $self->{permissionLevel} > 0; 179 my $multiUser = $self->{permissionLevel} > 0; 180 181 # set selection menu 182 { 183 print CGI::start_td(); 184 print CGI::h3("Sets"); 185 print CGI::start_table(); 186 my @sets; 187 push @sets, $self->{wwdb}->getSet($self->{effectiveUser}->id, $_) 188 foreach ($self->{wwdb}->getSets($self->{effectiveUser}->id)); 189 @sets = sort { $a->id cmp $b->id } @sets; 190 foreach my $set (@sets) { 191 my $checked = grep { $_ eq $set->id } @{$self->{sets}}; 192 my $control; 193 if (time < $set->open_date) { 194 $control = ""; 195 } else { 196 if ($multiSet) { 197 $control = CGI::checkbox( 198 -name=>"hcSet", 199 -value=>$set->id, 200 -label=>"", 201 -checked=>$checked 202 ); 203 } else { 204 $control = CGI::radio_group( 205 -name=>"hcSet", 206 -values=>[$set->id], 207 -default=>($checked ? $set->id : "-"), 208 -labels=>{$set->id => ""} 209 ); 210 } 211 } 212 print CGI::Tr(CGI::td([ 213 $control, 214 $set->id, 215 ])); 216 } 217 print CGI::end_table(); 218 print CGI::end_td(); 219 } 220 221 # user selection menu 222 if ($multiUser) { 223 print CGI::start_td(); 224 print CGI::h3("Users"); 225 print CGI::start_table(); 226 #print CGI::Tr( 227 # CGI::td(CGI::checkbox(-name=>"hcAllUsers", -value=>"1", -label=>"")), 228 # CGI::td({-colspan=>"2"}, "All Users"), 229 #); 230 #print CGI::Tr(CGI::td({-colspan=>"3"}, " ")); 231 my @users; 232 push @users, $self->{cldb}->getUser($_) 233 foreach ($self->{cldb}->getUsers()); 234 @users = sort { $a->last_name cmp $b->last_name } @users; 235 foreach my $user (@users) { 236 my $checked = grep { $_ eq $user->id } @{$self->{users}}; 237 print CGI::Tr(CGI::td([ 238 CGI::checkbox(-name=>"hcUser", -value=>$user->id, -label=>"", -checked=>$checked), 239 $user->id, 240 $user->last_name.", ".$user->first_name, 241 ])); 242 } 243 print CGI::end_table(); 244 print CGI::end_td(); 245 } 246 247 print CGI::end_Tr(), CGI::end_table(); 248 print CGI::p({-align=>"center"}, 249 CGI::submit(-name=>"generateHardcopy", -label=>"Generate Hardcopy")); 250 print CGI::end_form(); 251 252 return ""; 253 } 254 255 sub generateHardcopy($) { 256 my $self = shift; 257 my @sets = @{$self->{sets}}; 258 my @users = @{$self->{users}}; 259 my $multiSet = $self->{permissionLevel} > 0; 260 my $multiUser = $self->{permissionLevel} > 0; 261 # sanity checks 262 unless (@sets) { 263 die ["RETRY", "No sets were specified."]; 264 } 265 unless (@users) { 266 die ["RETRY", "No users were specified."]; 267 } 268 269 # determine where hardcopy is going to go 270 #my $tempDir = $self->{courseEnvironment}->{courseDirs}->{html_temp} . "/hardcopy"; 271 my $tempDir = tempdir("webwork-hardcopy-XXXXXXXX", TMPDIR => 1); 272 273 # make sure tempDir exists 274 #unless (-e $tempDir) { 275 # if (system "mkdir", "-p", $tempDir) { 276 # die ["FAIL", "Failed to mkdir $tempDir", $!]; 277 # } 278 #} 279 280 # determine name of PDF file 281 my $courseName = $self->{courseEnvironment}->{courseName}; 282 my $fileNameSet = (@sets > 1 ? "multiset" : $sets[0]); 283 my $fileNameUser = (@users > 1 ? "multiuser" : $users[0]); 284 my $fileName = "$courseName.$fileNameUser.$fileNameSet.pdf"; 285 286 # for each user ... generate TeX for each set 287 my $tex; 288 foreach my $user (@users) { 289 $tex .= $self->getMultiSetTeX(@sets); 290 } 291 292 # deal with PG errors 293 if (@{$self->{errors}}) { 294 die ["PGFAIL"]; 295 } 296 297 # "try" to generate pdf 298 eval { $self->latex2pdf($tex, $tempDir, $fileName) }; 299 if ($@) { 300 die ["FAIL", "Failed to generate PDF from tex", $@]; 301 } 302 303 return $tempDir, $fileName; 304 } 305 306 # ----- 307 308 sub latex2pdf { 309 # this is a little ad-hoc function which I will replace with a LaTeX 310 # module at some point (or put it in Utils). 311 my ($self, $tex, $fileBase, $fileName) = @_; 312 my $finalFile = "$fileBase/$fileName"; 313 my $ce = $self->{courseEnvironment}; 314 315 # create a temporary directory for tex to shit in 316 my $wd = tempdir("webwork-hardcopy-XXXXXXXX", TMPDIR => 1); 317 my $texFile = "$wd/hardcopy.tex"; 318 my $pdfFile = "$wd/hardcopy.pdf"; 319 my $logFile = "$wd/hardcopy.log"; 320 321 # write the tex file 322 local *TEX; 323 open TEX, ">", $texFile or die "Failed to open $texFile: $!\n"; 324 print TEX $tex; 325 close TEX; 326 327 # call pdflatex - we don't want to chdir in the mod_perl process, as 328 # that might step on the feet of other things (esp. in Apache 2.0) 329 my $pdflatex = $ce->{externalPrograms}->{pdflatex}; 330 system "cd $wd && $pdflatex $texFile" and die "Failed to call pdflatex: $!\n"; 331 332 if (-e $pdfFile) { 333 # move resulting PDF file to appropriate location 334 system "/bin/mv", $pdfFile, $finalFile and die "Failed to mv: $!\n"; 335 } 336 337 # remove temporary directory 338 rmtree($wd, 0, 1); 339 340 -e $finalFile or die "Failed to create $finalFile for no apparent reason.\n"; 341 } 342 343 # ----- 344 345 sub texBlockComment(@) { return "\n".("%"x80)."\n%% ".join("", @_)."\n".("%"x80)."\n\n"; } 346 347 sub getMultiSetTeX { 348 my ($self, @sets) = @_; 349 my $ce = $self->{courseEnvironment}; 350 my $tex = ""; 351 352 # the document preamble 353 $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{preamble}); 354 355 while (defined (my $setName = shift @sets)) { 356 $tex .= $self->getSetTeX($setName); 357 if (@sets) { 358 # divide sets, but not after the last set 359 $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{setDivider}); 360 } 361 } 362 363 # the document postamble 364 $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{postamble}); 365 366 return $tex; 367 } 368 369 sub getSetTeX { 370 my ($self, $setName) = @_; 371 my $ce = $self->{courseEnvironment}; 372 my $wwdb = $self->{wwdb}; 373 my $effectiveUserName = $self->{effectiveUser}->id; 374 my @problemNumbers = sort { $a <=> $b } $wwdb->getProblems($effectiveUserName, $setName); 375 376 # get header and footer 377 my $setHeader = $wwdb->getSet($effectiveUserName, $setName)->set_header 378 || $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}; 379 # database doesn't support the following yet :( 380 #my $setFooter = $wwdb->getSet($effectiveUserName, $setName)->set_footer 381 # || $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; 382 # so we don't allow per-set customization, which is probably okay :) 383 my $setFooter = $ce->{webworkFiles}->{hardcopySnippets}->{setFooter}; 384 385 my $tex = ""; 386 387 # render header 388 $tex .= texBlockComment("BEGIN $setName : $setHeader"); 389 $tex .= $self->getProblemTeX($setName, 0, $setHeader); 390 391 # render each problem 392 while (my $problemNumber = shift @problemNumbers) { 393 $tex .= texBlockComment("BEGIN $setName : $problemNumber"); 394 $tex .= $self->getProblemTeX($setName, $problemNumber); 395 if (@problemNumbers) { 396 # divide problems, but not after the last problem 397 $tex .= $self->texInclude($ce->{webworkFiles}->{hardcopySnippets}->{problemDivider}); 398 } 399 } 400 401 # render footer 402 $tex .= texBlockComment("BEGIN $setName : $setFooter"); 403 $tex .= $self->getProblemTeX($setName, 0, $setFooter); 404 405 return $tex; 406 } 407 408 sub getProblemTeX { 409 my ($self, $setName, $problemNumber, $pgFile) = @_; 410 my $r = $self->{r}; 411 my $ce = $self->{courseEnvironment}; 412 413 my $wwdb = $self->{wwdb}; 414 my $cldb = $self->{cldb}; 415 my $effectiveUser = $self->{effectiveUser}; 416 my $set = $wwdb->getSet($effectiveUser->id, $setName); 417 my $psvn = $wwdb->getPSVN($effectiveUser->id, $setName); 418 419 # decide what to do about problem number 420 my $problem; 421 if ($problemNumber) { 422 $problem = $wwdb->getProblem($effectiveUser->id, $setName, $problemNumber); 423 } elsif ($pgFile) { 424 $problem = WeBWorK::Problem->new( 425 id => 0, 426 set_id => $set->id, 427 login_id => $effectiveUser->id, 428 source_file => $pgFile, 429 # the rest of Problem's fields are not needed, i think 430 ); 431 } 432 433 my $pg = WeBWorK::PG->new( 434 $ce, 435 $effectiveUser, 436 $r->param('key'), 437 $set, 438 $problem, 439 $psvn, 440 {}, # no form fields! 441 { # translation options 442 displayMode => "tex", 443 showHints => 0, 444 showSolutions => 0, 445 processAnswers => 0, 446 }, 447 ); 448 449 if ($pg->{warnings} ne "") { 450 push @{$self->{warnings}}, { 451 set => $setName, 452 problem => $problemNumber, 453 message => $pg->{warnings}, 454 }; 455 } 456 457 if ($pg->{flags}->{error_flag}) { 458 push @{$self->{errors}}, { 459 set => $setName, 460 problem => $problemNumber, 461 message => $pg->{errors}, 462 context => $pg->{body_text}, 463 }; 464 # if there was an error, body_text contains 465 # the error context, not TeX code 466 $pg->{body_text} = undef; 467 } 468 469 return $pg->{body_text}; 470 } 471 472 sub texInclude { 473 my ($self, $texFile) = @_; 474 my $tex = ""; 475 476 $tex .= texBlockComment("BEGIN: $texFile"); 477 eval { 478 $tex .= readFile($texFile) 479 }; 480 if ($@) { 481 $tex .= texBlockComment($@); 482 } 483 484 return $tex; 485 } 486 487 1; 488 489 __END__ 490 491 sub body { 492 my $self = shift; 493 494 STUFF: { 495 my $courseName = $self->{courseEnvironment}->{courseName}; 496 my $effectiveUserName = $self->{r}->param("effectiveUser"); 497 my @sets = @{$self->{sets}}; 498 499 unless (@sets) { 500 print CGI::p("No problem sets were specified."); 501 last STUFF; 502 } 503 504 # determine where hardcopy is going to go 505 my $tempDir = $self->{courseEnvironment}->{courseDirs}->{html_temp} 506 . "/hardcopy"; 507 my $tempURL = $self->{courseEnvironment}->{courseURLs}->{html_temp} 508 . "/hardcopy"; 509 510 # make sure tempDir exists 511 unless (-e $tempDir) { 512 if (system "mkdir", "-p", $tempDir) { 513 print CGI::p("An error occured while trying to generate your PDF hardcopy:"); 514 print CGI::blockquote(CGI::pre("Failed to mkdir $tempDir: $!\n")); 515 } 516 } 517 518 # determine name of PDF file 519 my $fileName; 520 if (@sets > 1) { 521 # multiset output 522 $fileName = "$courseName.$effectiveUserName.multiset.pdf" 523 } elsif (@sets == 1) { 524 # only one set 525 my $setName = $sets[0]; 526 $fileName = "$courseName.$effectiveUserName.$setName.pdf"; 527 } else { 528 $fileName = "$courseName.$effectiveUserName.pdf"; 529 } 530 531 # determine full URL 532 my $fullURL = "$tempURL/$fileName"; 533 534 # generate TeX from sets 535 my $tex = $self->getMultiSetTeX(@sets); 536 #print CGI::pre($tex); 537 538 # check for PG errors (fatal) 539 if (@{$self->{errors}}) { 540 my @errors = @{$self->{errors}}; 541 print CGI::h2("Software Errors"); 542 print CGI::p(<<EOF); 543 WeBWorK has encountered one or more software errors while attempting to process these sets. 544 It is likely that there are error(s) in the problem itself. 545 If you are a student, contact your professor to have the error(s) corrected. 546 If you are a professor, please consut the error output below for more informaiton. 547 EOF 548 foreach my $error (@errors) { 549 print CGI::h3("Set: ", $error->{set}, ", Problem: ", $error->{problem}); 550 print CGI::h4("Error messages"), CGI::blockquote(CGI::pre($error->{message})); 551 print CGI::h4("Error context"), CGI::blockquote(CGI::pre($error->{context})); 552 } 553 554 last STUFF; 555 } 556 557 # "try" to generate hardcopy 558 eval { $self->latex2pdf($tex, $tempDir, $fileName) }; 559 if ($@) { 560 print CGI::p("An error occured while trying to generate your PDF hardcopy:"); 561 print CGI::blockquote(CGI::pre($@)); 562 last STUFF; 563 } else { 564 print CGI::p({-align=>"center"}, 565 CGI::big(CGI::a({-href=>$fullURL}, "Download PDF Hardcopy")) 566 ); 567 } 568 569 # check for PG warnings (non-fatal) 570 if (@{$self->{warnings}}) { 571 my @warnings = @{$self->{warnings}}; 572 print CGI::h2("Software Warnings"); 573 print CGI::p(<<EOF); 574 WeBWorK has encountered warnings while attempting to process these sets. 575 It is likely that this indicates an error or ambiguity in the problem(s) themselves. 576 If you are a student, contact your professor to have the problem(s) corrected. 577 If you are a professor, please consut the error output below for more informaiton. 578 EOF 579 foreach my $warning (@warnings) { 580 print CGI::h3("Set: ", $warning->{set}, ", Problem: ", $warning->{problem}); 581 print CGI::h4("Warning messages"), CGI::blockquote(CGI::pre($warning->{message})); 582 } 583 } 584 } 585 586 # feedback form 587 my $ce = $self->{courseEnvironment}; 588 my $root = $ce->{webworkURLs}->{root}; 589 my $courseName = $ce->{courseName}; 590 my $feedbackURL = "$root/$courseName/feedback/"; 591 print 592 CGI::startform("POST", $feedbackURL), 593 $self->hidden_authen_fields, 594 CGI::hidden("module", __PACKAGE__), 595 CGI::p({-align=>"right"}, 596 CGI::submit(-name=>"feedbackForm", -label=>"Send Feedback") 597 ), 598 CGI::endform(); 599 600 return ""; 601 }
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |