Tuesday, November 24, 2009

Uploading Files to a webserver from CKEditor under mod_perl

Just got a request to elaborate on this topic which I had mentioned in a recent tweet. So here's some more details. Note that there is no file-type checking here. In my case the uploads are only being allowed from logged-in administrators, so I can assume they are not uploading malicious files.

1) Of course, remember to use enctype="multipart/form-data" in the HTML form. Obvious but I've forgotten it more than once over the years...

2) To tell CKEditor what URL to send the uploaded file to, I pass the target URL to the CKEditor command which replaces the form <textarea> with a rich text editor:

CKEDITOR.replace( 'form_textarea', { filebrowserUploadUrl : '/upload_file' } );

In my case I did this inside a jQuery "ready" handler:


$(document).ready(
function() {
CKEDITOR.replace( 'form_textarea', { filebrowserUploadUrl : '/upload_file' } );
}
);


3) Map the URL to a mod_perl handler

I'm using object oriented perl handlers, so the relevant portion of my httpd.conf file looks something like:


<Location ~ "^/upload_file$">
SetHandler perl-script
PerlHandler NacreData::Controllers::FormHandler->upload_file
</Location>


This will look different if you're using a procedural handler or mod_request or some such.

4) Require/Use a perl module to do the uploading

The one I got to work is Apache::Upload::Slurp

I installed this with CPAN and used a PerlModule directive in httpd.conf to make sure it was loaded at runtime.

5) Write the mod_perl handler

Here's a cleaned up version of what mine looks like:


sub upload_file( $$ ) {
my ( $self, $r ) = @_;

use Apache::Constants qw( :common );
my $apr = Apache::Request->instance( $r );
$r->content_type('text/html');
$r->send_http_header;

require Apache::Upload::Slurp;
my $Uploader = new Apache::Upload::Slurp;
my $uploads = $Uploader->uploads();
my $upload = $uploads->[0];
my $dir = '/upload_files'; # directory where the uploaded files will go
return unless $upload;
return unless $upload->{data};

# make this into a nice filename, no special chars, no spaces, etc.
my $filename = $upload->{filename};
$filename =~ m{([^\\/]+$)};
$filename = $1;
$filename =~ s/[^.\w]//g;
$filename =~ s/__+/_/g;
$filename =~ s/^_//;
$filename =~ s/_$//;
$filename ||= 'file';

# make sure we don't overwrite another file by the same name
while( -f $dir.'/'.$filename || -d $dir.'/'.$filename ) {
my $ext = '';
if( $filename =~ m/(\.[^.]+$)/ ) {
$ext = $1;
$filename =~ s/\.[^.]+$//;
}
my $d = 0;
if( $filename =~ m/(\d+)$/ ) {
$d = $1;
$filename =~ s/\d+$//;
}
$d++;
$filename = $filename . $d . $ext;
}

my $fh;
$filename = $dir.'/'.$filename;
open ( $fh, '>', $filename ) || die "can't open $filename";
print {$fh} $upload->{data};
close $fh;

# now we return the information about the uploaded file to CKEditor.

# reference passed in from CKEditor. We use this to call back, letting
# the editor know we've don the upload.
my $funcNum = $apr->param('CKEditorFuncNum');

$filename =~ s|^/htdocs||; # path under document root to uploaded file
# you probably need ot change this line -- in my case, since I'm
# running under chroot, the webroot is just "/" and docroot /htdocs

my $hostname = $r->server->server_hostname;
$hostname =~ m{(\w+\.\w{2,4})$};
my $url = 'http://' . $hostname . $filename;

print "<script>parent.CKEDITOR.tools.callFunction( $funcNum, '$url' );</script>";
}

Tuesday, September 2, 2008

BBEdit 9.0 -- First Impressions

After a little more than one working day using the newly released BBEdit 9.0, my impressions is positive.

First of all, I am glad to see that a trusted tool works largely as expected as it keeps pace. This is to me much more important than flashy new interfaces or features. The search interface is cleaner, which is nice, but over all I had no problem at all getting right back to work after the upgrade.

My favorite new feature is the variable auto-completion I noticed this evening while working on an approximately 400-line perl script with many, many variables. The auto-completion gave me great assurance without any split screens or searching that I had the spelling and capitalization of previously used variables correct.

Thanks, BareBones for the continued good work.

Saturday, August 2, 2008

Some links about current DNS attack


Is your ISP using a correctly patched DNS server
?

Or are you open to attack? Could the websites you see not really be what they appear to be?

Listen to a podcast with a very complete explanation of DNS and the attack.

Friday, August 1, 2008

Product Review: Tone 12 GB USB 2.0 Mini Hard Drive

Very cute, casing matches my MacBook Pro. Works reliably so far. Took formatting to Mac (Extended, case-insensitive, journaled) no problem. Slow as molasses, though. The speed really cuts into it's utility for my intended use in loading virtual machines.

Launch VMWare and open a suspended WinXP virtual machine:

* Over the network from G5 dual 1.8GHz PowerPC to MacBook Pro: 58 seconds
(54 seconds to suspend, saving state back to disk)

* Same virtual machine from Tone 12 GB attached to MacBook Pro directly: 1:02 minute. Saving state: 1:41 - 3:09 minutes.

* Same virtual machine loading directly from MacBook Pro HD: 17 seconds; suspend: 4.4 seconds

Saturday, July 5, 2008

Green Business Leadership

We'd heard that some rental car companies are renting hybrids. Calling around today, my wife was frustrated to learn that the options are few and far between in the Triangle area. Seems that just like the car manufactures, most rental companies are followers, looking behind, instead of business leaders looking forward.

Which gets me thinking—how can NacreData LLC do a better job of being a green business leader? Feel free to leave your suggestions in the comments.

Our transportation costs are largely non-existent — I work in a home office and everyone else currently doing NacreData work is also a home-based.

We're not a bulk hosting company, but I've read a few times (see http://blogs.business2.com/greenwombat/2007/02/photo_originall.html for instance) that server farms are power hogs. We're beginning to move more of the sites we manage onto virtual-machine based servers, where several server environments can share the resources of one physical machine.

Friday, December 28, 2007

Getting SpamAssassin's spamc to work with Qmail

One of the recommended ways of setting up SpamAssassin to work with Qmail is to move the binary "qmail-queue" to a different name, say qmail-queue.orig, then create a shell script named qmail-queue which passes the email through SpamAssassin then on to the original qmail-queue. 

One problem with that set up which I've been noticing is that it passes every email through the spam filter, even though the vast majority of email coming into the server is addressed to non-existent users. The time taken to scan all those emails was noticeably slowing down the system. 

So what I really wanted to do was call the spam filter from the .qmail files for real users. 

Qmail's default delivery mechanism, set to contain just "#" in .qmail-default, will then drop all emails sent to non-existent users without calling SpamAssassin. 

I wanted to use the spamc utility, written in C, as a filter to call the already running spamd deamon from the user's spamc with the "-c" switch so that non-spam messages will return 0 (zero)  and thus pass on to delivery. One small problem, though, is that the exit value for an email determined to be spam is 1. Qmail will interpret an exit value of 1 as a temporary failure and keep trying to deliver the message. This will create a backlog of undelivered messages and slow down the email server. 

To get around this, I changed the exit value for spam emails in spamc. First I downloaded the latest SpamAssassin and untarred the download file. In the "spamc" subfolder I found a file named "libspamc.h". On or around line 85 is the line:

#define EX_ISSPAM   1

I simply changed this to 

#define EX_ISSPAM   99

and then completed the installation steps:

perl Makefile.PL
make
sudo make install

which installed SpamAssasissin including spamc with the altered exit value.

Lastly, from my user .qmail files, I call spamc. For example for .qmail-devin:

|/usr/local/bin/spamc -c
./devin/Maildir/

and now SpamAssassin is run only for email coming to real users and all messages determined to be spam are silently dropped. The return value of 99 tells Qmail that the message has been completely processed and no further delivery steps need be taken. 


Thursday, August 9, 2007

MySQL: To Good to Be True

In the back of my mind for some time now I've planned to move all NacreData projects away from MySQL to a more truly open source solution (almost surely PostgreSQL), and today's news pretty much cinches it.