blob: 0be61415a1ddc386350c66cc762175936e7a584d [file]
#!/usr/bin/perl -wT
#
# write-temp-file.cgi - creates a file and its temporary subdirectory
#
# The file's desired basename is passed in the filename query parameter.
# The file's desired contents are passed in the data query parameter.
#
# The newly created temporary file will be inside a uniquely-named
# subdirectory to prevent crosstalk between parallel test runs. Once
# the test is done with the temporary file its <file_path> should be
# passed to delete-temp-file.cgi for cleanup.
#
# Must be called using the HTTP POST method.
#
# On success, outputs the following three-line plaintext UTF-8
# response:
#
# OK
# <file_path>
#
# Where <file_path> is the full file_path of the just-written
# temporary file suitable for use with wide-character Unicode APIs
# after UTF-8 decoding and UTF-16 reencoding.
#
# Any other output is an error message and indicates failure.
#
# Errors are redirected to stdout and UTF-8 encoded to aid diagnostics
# in the calling test suite.
#
# NOTE: This CGI allows writing files with filenames including
# non-ASCII Unicode characters, but note that Blink renderer crashes
# are expected on non-Windows hosts using such filenames when a
# non-UTF-8 locale is used. Use a UTF-8 locale to prevent this,
# e.g. with:
#
# bash$ export LC_ALL=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 LANG=en_US.UTF-8
#
# Any other locale should work too provided it uses UTF-8 character
# encoding.
#
# Test failures are expected when relying on this CGI on Windows when
# using any system "ANSI" codepage ("ACP" below) other than
# windows-1252. To fix this, upgrade your system's Win32.pm Perl
# module to the latest version. Any version of Win32.pm since
# Win32-0.45 (released in 2012) should have the needed method:
# https://rt.cpan.org/Public/Bug/Display.html?id=78820
#
# NOTE: "ACP" in this CGI refers to the Windows concept of "ANSI"
# Codepage, a (not actually ANSI-standardized) single- or double-byte
# non-Unicode ASCII-compatible character encoding used for some
# narrow-character Win32 APIs. It should not be confused with UTF-8
# (used for similar purposes on Linux, OS X, and other modern
# POSIX-like systems), nor with the usually-distinct OEM codepage
# used for other narrow-character Win32 APIs (for instance, console
# I/O and some filesystem data storage.)
use strict;
use warnings FATAL => 'all';
use CGI qw(-oldstyle_urls);
use Encode qw(encode decode is_utf8);
use File::Basename qw(dirname);
use File::Spec::Functions qw(catfile tmpdir);
use File::Temp qw(tempdir tempfile);
use utf8;
my $win32 = eval 'use Win32; 1' ? 1 : 0;
open STDERR, '>&STDOUT'; # let the test see die() output
binmode STDOUT, ':encoding(utf-8)';
autoflush STDOUT 1;
autoflush STDERR 1;
print "content-type: text/plain; charset=utf-8\n\n";
# Finding a suitable system temporary directory should be as simple as
# $system_tmpdir = tmpdir() but Win32 Perl CGIs got unusable
# values. Tracked in https://crbug.com/786152
# Find the logical equivalent of /tmp - however extra contortions are
# needed on Win32 under Apache with an unpopulated environment to
# avoid choosing the root directory of the active drive by default.
my $local_appdata_temp = tmpdir();
if ($win32) {
my $local_appdata = Win32::GetFolderPath(Win32::CSIDL_LOCAL_APPDATA());
if (($local_appdata ne '') && -d "$local_appdata\\Temp") {
$local_appdata_temp = "$local_appdata\\Temp";
}
}
# This fallback order works well on fairly sane "vanilla" Win32, OS X
# and Linux Apache configurations with mod_perl.
my $system_tmpdir =
$ENV{TMPDIR} || $ENV{TEMP} || $ENV{TMP} || $local_appdata_temp;
$system_tmpdir =~ /\A([^\0- ]*)\z/s
or die "untaint failed: $!";
$system_tmpdir = $1;
if ($win32) {
$system_tmpdir =
Win32::GetFullPathName(Win32::GetANSIPathName($system_tmpdir));
}
my $req = CGI->new;
if (uc 'post' ne $req->request_method) {
die 'Wrong method: ' . $req->request_method;
}
my $basename = decode utf8 => $req->url_param('filename');
$basename =~ /\A([^\0- ]*)\z/s
or die "untaint failed: $!";
$basename = $1;
my $data = decode utf8 => $req->url_param('data');
# Create a random-named subdirectory of the system temporary directory
# to hold the newly-created test file. The X's will be replaced with
# random printable characters by tempdir.
my $temp_cgi_subdir_template = 'LayoutTests_cgiXXXXX';
if ($win32) {
# On win32 this name needs to remain unique even after 8.3 shortname
# conversion so we drop the LayoutTests_ prefix.
$temp_cgi_subdir_template = 'cgiXXXXX';
}
my $temp_cgi_subdir =
tempdir($temp_cgi_subdir_template, DIR => $system_tmpdir);
my $file_path = catfile($temp_cgi_subdir, $basename);
if (dirname($file_path) ne $temp_cgi_subdir) {
die(encode utf8 => "$file_path not in $temp_cgi_subdir");
}
my $tempfile_contents_write_handle;
my $tempfile;
# $file_path_acp_utf8 is a UTF-8-encoded representation of the
# fully-qualified "ACP" file file_path. $file_path_acp is a
# logically-equivalent ACP-encoded bytestring suitable for use in a
# (narrow-character) system call. These are only distinct on Win32.
my $file_path_acp = $file_path;
my $file_path_acp_utf8 = $file_path;
if ($win32) {
# Win32::GetACP is a recently-added method and not yet available in
# some installations. Fall back to windows-1252 when GetACP is
# missing as that matches the ACP used by our Windows bots.
my $win32_ansi_codepage = 'windows-1252';
eval '$win32_ansi_codepage = "cp" . Win32::GetACP();';
Win32::CreateFile($file_path)
or die(encode utf8 => "CreateFile $file_path: $^E");
$file_path_acp = Win32::GetFullPathName(Win32::GetANSIPathName($file_path));
$file_path = Win32::GetLongPathName($file_path_acp);
$file_path_acp_utf8 = $file_path_acp;
if (!is_utf8($file_path_acp_utf8)) {
$file_path_acp_utf8 = decode($win32_ansi_codepage, $file_path_acp_utf8);
}
if (!is_utf8($file_path)) {
$file_path = decode($win32_ansi_codepage, $file_path);
}
open($tempfile_contents_write_handle, '>>', $file_path_acp)
or die(encode utf8 => "open >> $file_path_acp_utf8: $!");
$tempfile = $file_path_acp_utf8;
} else {
($tempfile_contents_write_handle, $tempfile) =
tempfile(DIR => $temp_cgi_subdir);
}
binmode $tempfile_contents_write_handle, ':encoding(utf-8)';
print $tempfile_contents_write_handle $data;
close $tempfile_contents_write_handle
or die(encode utf8 => "close $tempfile: $!");
if (!$win32) {
rename($tempfile, $file_path)
or die(encode utf8 => "rename $tempfile, $file_path: $!");
}
# Fail early to aid debugging when the newly-created file does not
# actually work with system calls or does not contain the correct
# bytes.
local $/ = undef;
my $tempfile_verification_read_handle;
open($tempfile_verification_read_handle, '<', $file_path_acp)
or die(encode utf8 => "open $file_path_acp_utf8: $!");
binmode $tempfile_verification_read_handle, ':encoding(utf-8)';
my $data2 = <$tempfile_verification_read_handle>;
close($tempfile_verification_read_handle)
or die(encode utf8 => "close $file_path_acp_utf8: $!");
if ($data ne $data2) {
die(encode utf8 => "Expected $data but got $data2");
}
print "OK\n$file_path";