use PerlIO::utf8_strict for decoding uploads
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Request / Upload.pm
CommitLineData
146554c5 1package Catalyst::Request::Upload;
2
e5ecd5bc 3use Moose;
531f1ab6 4with 'MooseX::Emulate::Class::Accessor::Fast';
146554c5 5
a2f2cde9 6use Catalyst::Exception;
47ae6960 7use File::Copy ();
89cb63ec 8use IO::File ();
3e22baa5 9use File::Spec::Unix;
da94772a 10use PerlIO::utf8_strict;
554c7587 11use namespace::clean -except => 'meta';
146554c5 12
5fb12dbb 13has filename => (is => 'rw');
14has headers => (is => 'rw');
15has size => (is => 'rw');
16has tempname => (is => 'rw');
17has type => (is => 'rw');
02570318 18has basename => (is => 'ro', lazy_build => 1);
6adc45cf 19has raw_basename => (is => 'ro', lazy_build => 1);
20has charset => (is=>'ro', predicate=>'has_charset');
059c085b 21
22has fh => (
23 is => 'rw',
24 required => 1,
25 lazy => 1,
26 default => sub {
27 my $self = shift;
28
29 my $fh = IO::File->new($self->tempname, IO::File::O_RDONLY);
30 unless ( defined $fh ) {
31 my $filename = $self->tempname;
32 Catalyst::Exception->throw(
33 message => qq/Can't open '$filename': '$!'/ );
34 }
059c085b 35 return $fh;
36 },
37);
38
02570318 39sub _build_basename {
6adc45cf 40 my $basename = shift->raw_basename;
41 $basename =~ s|[^\w\.-]+|_|g;
42 return $basename;
43}
44
45sub _build_raw_basename {
02570318 46 my $self = shift;
47 my $basename = $self->filename;
48 $basename =~ s|\\|/|g;
49 $basename = ( File::Spec::Unix->splitpath($basename) )[2];
02570318 50 return $basename;
51}
52
059c085b 53no Moose;
146554c5 54
965f3e35 55=for stopwords uploadtmp
56
146554c5 57=head1 NAME
58
bab3a22c 59Catalyst::Request::Upload - handles file upload requests
146554c5 60
61=head1 SYNOPSIS
62
33455c7e 63 my $upload = $c->req->upload('field');
64
3e22baa5 65 $upload->basename;
bab3a22c 66 $upload->copy_to;
67 $upload->fh;
6adc45cf 68 $upload->decoded_fh
146554c5 69 $upload->filename;
4be535b1 70 $upload->headers;
3ffaf022 71 $upload->link_to;
146554c5 72 $upload->size;
32d4bba8 73 $upload->slurp;
6adc45cf 74 $upload->decoded_slurp;
146554c5 75 $upload->tempname;
76 $upload->type;
6adc45cf 77 $upload->charset;
146554c5 78
7257e9db 79To specify where Catalyst should put the temporary files, set the 'uploadtmp'
80option in the Catalyst config. If unset, Catalyst will use the system temp dir.
81
82 __PACKAGE__->config( uploadtmp => '/path/to/tmpdir' );
83
146554c5 84See also L<Catalyst>.
85
86=head1 DESCRIPTION
87
bab3a22c 88This class provides accessors and methods to handle client upload requests.
146554c5 89
90=head1 METHODS
91
b5ecfcf0 92=head2 $upload->new
cd3bb248 93
bab3a22c 94Simple constructor.
cd3bb248 95
b5ecfcf0 96=head2 $upload->copy_to
47ae6960 97
bab3a22c 98Copies the temporary file using L<File::Copy>. Returns true for success,
99false for failure.
47ae6960 100
3ffaf022 101 $upload->copy_to('/path/to/target');
102
42d0a52a 103Please note the filename used for the copy target is the 'tempname' that
104is the actual filename on the filesystem, NOT the 'filename' that was
105part of the upload headers. This might seem counter intuitive but at this
106point this behavior is so established that its not something we can change.
107
108You can always create your own copy routine that munges the target path
109as you wish.
110
47ae6960 111=cut
112
3ffaf022 113sub copy_to {
c462faf0 114 my $self = shift;
115 return File::Copy::copy( $self->tempname, @_ );
47ae6960 116}
117
6adc45cf 118=head2 $upload->is_utf8_encoded
119
120Returns true of the upload defines a character set at that value is 'UTF-8'.
121This does not try to inspect your upload and make any guesses if the Content
122Type charset is undefined.
123
124=cut
125
126sub is_utf8_encoded {
127 my $self = shift;
128 if(my $charset = $self->charset) {
129 return $charset eq 'UTF-8' ? 1 : 0;
130 }
131 return 0;
132}
133
b5ecfcf0 134=head2 $upload->fh
146554c5 135
bab3a22c 136Opens a temporary file (see tempname below) and returns an L<IO::File> handle.
146554c5 137
6adc45cf 138This is a filehandle that is opened with no additional IO Layers.
139
140=head2 $upload->decoded_fh(?$encoding)
141
142Returns a filehandle that has binmode set to UTF-8 if a UTF-8 character set
143is found. This also accepts an override encoding value that you can use to
144force a particular L<PerlIO> layer. If neither are found the filehandle is
145set to :raw.
146
0d94e986 147This is useful if you are pulling the file into code and inspecting bits and
148maybe then sending those bits back as the response. (Please note this is not
6adc45cf 149a suitable filehandle to set in the body; use C<fh> if you are doing that).
150
151Please note that using this method sets the underlying filehandle IO layer
152so once you use this method if you go back and use the C<fh> method you
153still get the IO layer applied.
154
155=cut
156
157sub decoded_fh {
158 my ($self, $layer) = @_;
159 my $fh = $self->fh;
160
da94772a 161 $layer = ':utf8_strict' if !$layer && $self->is_utf8_encoded;
6adc45cf 162 $layer = ':raw' unless $layer;
163
164 binmode($fh, $layer);
165 return $fh;
166}
167
b5ecfcf0 168=head2 $upload->filename
146554c5 169
bab3a22c 170Returns the client-supplied filename.
146554c5 171
b5ecfcf0 172=head2 $upload->headers
4be535b1 173
bab3a22c 174Returns an L<HTTP::Headers> object for the request.
4be535b1 175
b5ecfcf0 176=head2 $upload->link_to
146554c5 177
b0ad47c1 178Creates a hard link to the temporary file. Returns true for success,
bab3a22c 179false for failure.
146554c5 180
3ffaf022 181 $upload->link_to('/path/to/target');
146554c5 182
183=cut
184
3ffaf022 185sub link_to {
47ae6960 186 my ( $self, $target ) = @_;
5c0ff128 187 return CORE::link( $self->tempname, $target );
146554c5 188}
189
b5ecfcf0 190=head2 $upload->size
146554c5 191
bab3a22c 192Returns the size of the uploaded file in bytes.
146554c5 193
6adc45cf 194=head2 $upload->slurp(?$encoding)
195
196Optionally accepts an argument to define an IO Layer (which is applied to
197the filehandle via binmode; if no layer is defined the default is set to
198":raw".
32d4bba8 199
bab3a22c 200Returns a scalar containing the contents of the temporary file.
32d4bba8 201
965f3e35 202Note that this will cause the filehandle pointed to by C<< $upload->fh >> to
203be reset to the start of the file using seek and the file handle to be put
6adc45cf 204into whatever encoding mode is applied.
0fd00e7b 205
32d4bba8 206=cut
207
208sub slurp {
209 my ( $self, $layer ) = @_;
210
4be535b1 211 unless ($layer) {
32d4bba8 212 $layer = ':raw';
213 }
214
2d48c2fc 215 my $content = '';
32d4bba8 216 my $handle = $self->fh;
217
218 binmode( $handle, $layer );
219
89cb63ec 220 $handle->seek(0, IO::File::SEEK_SET);
2d48c2fc 221
222 if ($layer eq ':raw') {
223 while ( $handle->sysread( my $buffer, 8192 ) ) {
224 $content .= $buffer;
225 }
226 }
227 else {
228 $content = do { local $/; $handle->getline };
32d4bba8 229 }
230
89cb63ec 231 $handle->seek(0, IO::File::SEEK_SET);
32d4bba8 232 return $content;
233}
234
6adc45cf 235=head2 $upload->decoded_slurp(?$encoding)
236
237Works just like C<slurp> except we use C<decoded_fh> instead of C<fh> to
238open a filehandle to slurp. This means if your upload charset is UTF8
239we binmode the filehandle to that encoding.
240
241=cut
242
243sub decoded_slurp {
244 my ( $self, $layer ) = @_;
245 my $handle = $self->decoded_fh($layer);
246
6adc45cf 247 $handle->seek(0, IO::File::SEEK_SET);
2d48c2fc 248
249 my $content = do { local $/; $handle->getline };
6adc45cf 250
251 $handle->seek(0, IO::File::SEEK_SET);
252 return $content;
253}
254
3e22baa5 255=head2 $upload->basename
256
6adc45cf 257Returns basename for C<filename>. This filters the name through a regexp
258C<basename =~ s|[^\w\.-]+|_|g> to make it safe for filesystems that don't
259like advanced characters. This will of course filter UTF8 characters.
260If you need the exact basename unfiltered use C<raw_basename>.
261
262=head2 $upload->raw_basename
263
264Just like C<basename> but without filtering the filename for characters that
265don't always write to a filesystem.
3e22baa5 266
b5ecfcf0 267=head2 $upload->tempname
146554c5 268
bab3a22c 269Returns the path to the temporary file.
146554c5 270
b5ecfcf0 271=head2 $upload->type
146554c5 272
bab3a22c 273Returns the client-supplied Content-Type.
146554c5 274
6adc45cf 275=head2 $upload->charset
276
277The character set information part of the content type, if any. Useful if you
278need to figure out any encodings on the file upload.
279
059c085b 280=head2 meta
281
282Provided by Moose
283
bab3a22c 284=head1 AUTHORS
146554c5 285
2f381252 286Catalyst Contributors, see Catalyst.pm
146554c5 287
288=head1 COPYRIGHT
289
536bee89 290This library is free software. You can redistribute it and/or modify
146554c5 291it under the same terms as Perl itself.
292
293=cut
294
e5ecd5bc 295__PACKAGE__->meta->make_immutable;
296
146554c5 2971;