Skip to content

Commit

Permalink
Item14240: protect parallel content paths as well
Browse files Browse the repository at this point in the history
... organized in parallel to the normal pub directory tree

Item13665: support for rev parameter to access previous versions of an attachment

also:

- fixed encoding under Foswiki-2.x
- detect 404 file not found properly
- allow to specify content disposition (inline or attachment)
  • Loading branch information
MichaelDaum committed Nov 30, 2016
1 parent bf3cfcf commit 4e9a4c0
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 53 deletions.
14 changes: 7 additions & 7 deletions .gitignore
@@ -1,8 +1,8 @@
*.swp
XSendFileContrib.md5
XSendFileContrib.sha1
XSendFileContrib.tgz
XSendFileContrib.txt
XSendFileContrib.zip
XSendFileContrib_installer
XSendFileContrib_installer.pl
/XSendFileContrib.md5
/XSendFileContrib.sha1
/XSendFileContrib.tgz
/XSendFileContrib.txt
/XSendFileContrib.zip
/XSendFileContrib_installer
/XSendFileContrib_installer.pl
37 changes: 21 additions & 16 deletions data/System/XSendFileContrib.txt
@@ -1,5 +1,7 @@
%META:TOPICINFO{author="ProjectContributor" comment="" date="1437154671" format="1.1" version="1"}%
---+!! %TOPIC%
%FORMFIELD{"Description"}%

%TOC%

---++ Introduction
Expand Down Expand Up @@ -107,20 +109,15 @@ See https://tn123.org/mod_xsendfile/ for more information on how to compile =mod
---++ Installation
%$INSTALL_INSTRUCTIONS%

See above for more information on how to configure Foswiki and your web server.

---++ Info
<!--
One line description, required for extensions repository catalog.
* Set SHORTDESCRIPTION = %$SHORTDESCRIPTION%
-->
---++ Dependencies
%$DEPENDENCIES%

| Author: | Michael Daum |
| Copyright &copy;: | 2013-2015, Michael Daum http://michaeldaumconsulting.com |
| License: | GPL ([[http://www.gnu.org/copyleft/gpl.html][GNU General Public License]]) |
| Dependencies: | %$DEPENDENCIES% |
| Version: | %$VERSION% |
| Change History: | <!-- versions below in reverse order -->&nbsp; |
---++ Change History
| 30 Nov 2016 | fixed encoding under Foswiki-2.x; \
detect 404 file not found properly; \
added support for rev parameter to access previous versions of an attachment; \
allow to specify content disposition (inline or attachment); \
allow to deliver and protect other content paths organized in parallel to the normal pub directory tree, for instance =/pub/images= for thumbnails |
| 17 Jul 2015 | added support for Foswiki-2.0 |
| 29 Aug 2014 | improved decoding and untainting url components |
| 28 May 2014 | fixed file check on wrong location |
Expand All @@ -130,6 +127,14 @@ One line description, required for extensions repository catalog.
| 01 Nov 2013 | added support for if-modified-since http headers |
| 22 May 2013 | using mime-magic as a fallback in case file extensions don't unveil the mime-type |
| 28 Mar 2013 | implemented {<nop>AccessRules} to allow any kind of access control list for attachments |
| 1.00: | Initial version |
| Home: | http://foswiki.org/Extensions/%TOPIC% |
| Support: | http://foswiki.org/Support/%TOPIC% |

%META:FORM{name="PackageForm"}%
%META:FIELD{name="Author" title="Author" value="Michael Daum"}%
%META:FIELD{name="Copyright" title="Copyright" value="2013-2016, Michael Daum http://michaeldaumconsulting.com"}%
%META:FIELD{name="Description" title="Description" value="%25$SHORTDESCRIPTION%25"}%
%META:FIELD{name="Home" title="Home" value="https://foswiki.org/Extensions/%TOPIC%"}%
%META:FIELD{name="License" title="License" value="GPL ([[http://www.gnu.org/copyleft/gpl.html][GNU General Public License]])"}%
%META:FIELD{name="Release" title="Release" value="%$RELEASE%"}%
%META:FIELD{name="Repository" title="Repository" value="https://github.com/foswiki/%TOPIC%"}%
%META:FIELD{name="Support" title="Support" value="https://foswiki.org/Support/%TOPIC%"}%
%META:FIELD{name="Version" title="Version" value="%$VERSION%"}%
116 changes: 89 additions & 27 deletions lib/Foswiki/Contrib/XSendFileContrib.pm
@@ -1,6 +1,6 @@
# Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
#
# Copyright (C) 2013-2015 Michael Daum http://michaeldaumconsulting.com
# Copyright (C) 2013-2016 Michael Daum http://michaeldaumconsulting.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
Expand All @@ -23,17 +23,18 @@ use Foswiki::Sandbox ();
use Foswiki::Func ();
use Foswiki::Time ();
use File::MMagic ();
use File::Spec ();

our $VERSION = '4.00';
our $RELEASE = '17 Jul 2015';
our $VERSION = '5.00';
our $RELEASE = '30 Nov 2016';
our $SHORTDESCRIPTION = 'A viewfile replacement to send static files efficiently';
our $mimeTypeInfo;
our $mmagic;

sub _decodeUntaint {
my ($text, $sub) = @_;

$text = Encode::decode_utf8($text);
$text = Encode::decode_utf8($text) if $Foswiki::UNICODE;
$text = Foswiki::Sandbox::untaint($text, $sub) if $sub;

return $text;
Expand All @@ -45,12 +46,27 @@ sub xsendfile {
my $request = $session->{request};
my $response = $session->{response};

# remove cookie
$response->cookies([]);

my $web = $session->{webName};
my $topic = $session->{topicName};
my $fileName = $request->param('filename');
my $dispositionMode = $request->param('mode') || 'inline';

my $pathInfo = $request->path_info();
my $pathPrefix = "";

my $headerName = $Foswiki::cfg{XSendFileContrib}{Header} || 'X-LIGHTTPD-send-file';
my $location = $Foswiki::cfg{XSendFileContrib}{Location} || $Foswiki::cfg{PubDir};
my $fileLocation;

my $filePath;
my $foundOnDisk = 0;

if (defined $Foswiki::cfg{XSendFileContrib}{PathPrefix} && $pathInfo =~ s/^($Foswiki::cfg{XSendFileContrib}{PathPrefix})//) {
$pathPrefix = $1;
}

my @path = split(/\/+/, $pathInfo);
shift(@path) unless $path[0];

Expand All @@ -65,6 +81,7 @@ sub xsendfile {
}

$web = join('/', @web);

unless ($web) {
$response->status(404);
$response->print("404 - no web found\n");
Expand All @@ -78,6 +95,21 @@ sub xsendfile {
# The next element on the path has to be the topic name
$topic = _decodeUntaint(shift @path, \&Foswiki::Sandbox::validateTopicName);

# check whether this is a file already
$filePath = File::Spec->catfile("/", $Foswiki::cfg{PubDir}, $pathPrefix, $web, $topic);
if (-f $filePath) {
$foundOnDisk = 1;
$fileLocation = $location . $pathPrefix . '/' . $web . '/' . $topic;

# test for a file extension, e.g. System/WebHome.html
if ($topic =~ /^(.*)\.([^\.]+)$/) {
$fileName = $topic;
$topic = $1;
} else {
$fileName = $topic;
}
}

unless ($topic) {
$response->status(404);
$response->print("404 - no topic found\n");
Expand All @@ -89,11 +121,20 @@ sub xsendfile {

unless (defined $fileName) {
# What's left in the path is the attachment name.
$fileName = join('/', @path);
$fileName = File::Spec->catfile(@path);
} else {
$fileName = Foswiki::urlDecode($fileName);
}

# check whether this is a file already
if (!$foundOnDisk && $fileName) {
$filePath = File::Spec->catfile("/", $Foswiki::cfg{PubDir}, $pathPrefix, $web, $topic, $fileName);
if (-f $filePath) {
$foundOnDisk = 1;
$fileLocation = $location . $pathPrefix . '/' . $web . '/' . $topic . '/' . $fileName;
}
}

# not found
if (!defined($fileName) || $fileName eq '') {
$response->status(404);
Expand All @@ -105,25 +146,24 @@ sub xsendfile {

#print STDERR "web=$web, topic=$topic, fileName=$fileName\n";

# invalid
# invalid
unless (defined $fileName) {
$response->status(404);
$response->print("404 - file not valid\n");
return;
}

my $topicObject = Foswiki::Meta->new($session, $web, $topic);
my $topicObject = Foswiki::Meta->load($session, $web, $topic);

# not found
unless ($topicObject->existsInStore()) {
if (!$foundOnDisk && !$topicObject->existsInStore()) {
$response->status(404);
$response->print("404 - topic $web.$topic does not exist\n");
return;
}

# not found

unless ($topicObject->hasAttachment($fileName)) {
if (!$foundOnDisk && !$topicObject->hasAttachment($fileName)) {
$response->status(404);
$response->print("404 - attachment $fileName not found at $web.$topic\n");
return;
Expand All @@ -136,37 +176,58 @@ sub xsendfile {
return;
}

# construct file path to protected location
my $location = $Foswiki::cfg{XSendFileContrib}{Location} || $Foswiki::cfg{PubDir};
my $fileLocation = $location.'/'.$web.'/'.$topic.'/'.$fileName;
my $filePath = $Foswiki::cfg{PubDir}.'/'.$web.'/'.$topic.'/'.$fileName;
# check whether we can return a 304 not modified
$filePath = File::Spec->catfile($Foswiki::cfg{PubDir}, $pathPrefix, $web, $topic, $fileName)
unless defined $filePath;

my @stat = stat($filePath);
my $lastModified = Foswiki::Time::formatTime($stat[9] || $stat[10] || 0, '$http', 'gmtime');
my $ifModifiedSince = $request->header('If-Modified-Since') || '';
my $mimeType = mimeTypeOfFile($fileName);

my $headerName = $Foswiki::cfg{XSendFileContrib}{Header} || 'X-LIGHTTPD-send-file';

unless ($Foswiki::UNICODE) {
$fileName = Encode::encode_utf8($fileName);
$fileLocation = Encode::encode_utf8($fileLocation);
if ($lastModified eq $ifModifiedSince) {
$response->header(-status => 304,);
return;
}

if ($lastModified eq $ifModifiedSince) {
$response->header(
-status => 304,
);
# check for rev parameter and fallback if not current
my $rev = $request->param('rev');
if (defined $rev) {

my $fileMeta = $topicObject->get('FILEATTACHMENT', $fileName);
if ($fileMeta && $fileMeta->{version} > $rev) {

$session->{response}->header(
-status => 200,
-type => $mimeType,
-content_disposition => "inline; filename=\"$fileName\"",
-last_modified => $lastModified,
);

my $fh = $topicObject->openAttachment($fileName, '<', version => $rev);
$session->{response}->body(<$fh>);
}
} else {

my $dispositionMode = $request->param('disposition');

unless (defined $dispositionMode) {
# SMELL: Force office documents into a save-as-dialog using "attachment".
# this is mostly needed for Internet Explorers as other browser do just fine with those type of files in "inline" mode...
$dispositionMode = ($fileName =~ /(?:(?:(?:xlt|xls|csv|ppt|pps|pot|doc|dot)(x|m)?)|odc|odb|odf|odg|otg|odi|odp|otp|ods|ots|odt|odm|ott|oth|mpp|rtf|txt|vsd)$/)?"attachment":"inline";
}

$fileLocation = $location . $pathPrefix . '/' . $web . '/' . $topic . '/' . $fileName unless defined $fileLocation;

$response->header(
-status => 200,
-type => mimeTypeOfFile($filePath),
-type => $mimeType,
-content_disposition => "$dispositionMode; filename=\"$fileName\"",
-last_modified => $lastModified,
$headerName => $fileLocation,
);
}

# $response->print("OK");

return;
}

Expand All @@ -189,6 +250,7 @@ sub checkAccess {
}

# fallback
#print STDERR "checking ACLS for user $user\n";
return $topicObject->haveAccess("VIEW", $user);
}

Expand Down
16 changes: 14 additions & 2 deletions lib/Foswiki/Contrib/XSendFileContrib/Config.spec
@@ -1,8 +1,12 @@
# ---+ Extensions
# ---++ XSendFileContrib
# **PERL HIDDEN**
# **PERL EXPERT**
# This setting is required to enable executing the xsendfile service from the bin directory
$Foswiki::cfg{SwitchBoard}{xsendfile} = ['Foswiki::Contrib::XSendFileContrib', 'xsendfile', {xsendfile => 1}];
$Foswiki::cfg{SwitchBoard}{xsendfile} = {
package => 'Foswiki::Contrib::XSendFileContrib',
function => 'xsendfile',
context => {xsendfile => 1}
};

# **SELECT none,X-Sendfile,X-LIGHTTPD-send-file,X-Accel-Redirect**
# Enable efficient delivery of static files
Expand All @@ -21,6 +25,14 @@ $Foswiki::cfg{XSendFileContrib}{Header} = 'none';
# as configured for an Nginx.
$Foswiki::cfg{XSendFileContrib}{Location} = '';

# **REGEX**
# prefix of a url path to be removed before parsing the rest of it as a web.topic
# For examples thumbnails generated by ImageGalleryPlugin are stored in '/pub/images/'.
# A PathPrefix '/images' covers these kind of urls. ExportPlugin by default stores
# exported files in '/export/assets' and '/export/html'. Below example setting
# covers the three of them.
$Foswiki::cfg{XSendFileContrib}{PathPrefix} = '/(images|export/assets|export/html)';

# **PERL**
# By default view rights of the topic are controlling the access rights to download
# all attachments on this topic. In some cases you might want to use <i>change</i>
Expand Down
2 changes: 1 addition & 1 deletion lib/Foswiki/Contrib/XSendFileContrib/build.pl
@@ -1,4 +1,4 @@
#!/usr/bin/perl -w
#!/usr/bin/env perl
use strict;
use warnings;

Expand Down

0 comments on commit 4e9a4c0

Please sign in to comment.