From: jason Date: Wed, 28 May 2008 23:01:23 +0000 (-0400) Subject: added progress-bar uploader.php X-Git-Url: https://jasonwoof.com/gitweb/?p=wfpl.git;a=commitdiff_plain;h=ed5b82e026ec9a6f0fd844ba42fff3f268d949d3 added progress-bar uploader.php --- diff --git a/format.php b/format.php index ae66713..f63dfba 100644 --- a/format.php +++ b/format.php @@ -81,12 +81,20 @@ function format_zip($str) { return $str; } -function format_filename($str) { - $str = strtolower($str); - $str = ereg_replace('[^a-z0-9_.-]', '_', $str); +function format_filename($str, $allow_uppercase = false) { + if(!$allow_uppercase) { + $str = strtolower($str); + } + $str = ereg_replace('[^a-zA-Z0-9_.-]', '_', $str); return ereg_replace('^[.-]', '_', $str); } +function client_path_to_filename($path) { + $filename = ereg_replace(".*[:/\\]", '', $path); + return format_filename($filename, true); +} + + function format_h_w_image($str) { $fields = explode(' ', $str); if(count($fields) != 3) { diff --git a/http.php b/http.php index e325fc1..042e1b9 100644 --- a/http.php +++ b/http.php @@ -45,6 +45,22 @@ function this_url_sans_path() { return $url; } +# just the hostname, no port number +function this_host() { + if($_SERVER['HTTP_HOST']) { + $host = $_SERVER['HTTP_HOST']; + $p = strpos($host, ':'); + if($p) { + $host = substr($host, 0, $p); + } + return $host; + } else { + return $_SERVER['SERVER_NAME']; + } +} + + + # return our best guess at the url used to access this page function this_url() { $url = this_url_sans_path(); diff --git a/session.php b/session.php index 4f1cb67..c61be6f 100644 --- a/session.php +++ b/session.php @@ -18,18 +18,8 @@ # along with wfpl; if not, write to the Free Software Foundation, Inc., 51 # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -# you'll need this file that calls db_connect() -if(!isset($GLOBALS['wfpl_db_handle'])) { - if(file_exists('db_connect.php')) { - require_once('db_connect.php'); - } elseif(file_exists('code/db_connect.php')) { - require_once('code/db_connect.php'); - } else { - die("session.php requires a file db_connect.php or that you call db_connect() first. See code/wfpl/db.php for more information."); - } -} -# and these database tables: +# you'll need these database tables: # create table wfpl_sessions (id int unique auto_increment, session_key varchar(16), length int, expires int); # create table wfpl_session_data (id int unique auto_increment, session_id int, name varchar(100), value text); # run this command to install/clear the tables: diff --git a/uploader.php b/uploader.php new file mode 100644 index 0000000..0c962c2 --- /dev/null +++ b/uploader.php @@ -0,0 +1,111 @@ +load('code/wfpl/uploader/uploader.html'); + $html->set('filename', $filename); + $html->set('host', $GLOBALS['wfpl_uploader_host']); + $html->set('port', $GLOBALS['wfpl_uploader_port']); + $html->show('main'); + $html = $html->get('main'); + + $css = read_whole_file('code/wfpl/uploader/uploader.css'); + + $javascript = new tem(); + $javascript->load('code/wfpl/uploader/progress.js'); + $javascript->set('url', $progress_url); + $javascript = $javascript->run(); + + uploader_daemon_start($GLOBALS['wfpl_uploader_port']); + + return array($html, $css, $javascript, $filename); +} + +function uploader_move($tmp_filename, $filename) { + $tmp_path = $GLOBALS['wfpl_uploader_path'] . '/partial/' . $tmp_filename; + $out_path = $GLOBALS['wfpl_uploader_path'] . '/' . $filename; + unlink($GLOBALS['wfpl_uploader_path'] . '/progress/' . $tmp_filename); + rename($tmp_path, $out_path); +} + +# start a daemon to accept file uploads and give progress indicators +# if the port is used (eg if the daemon is already running) this will do nothing. +function uploader_daemon_start($port) { + exec(path_to('tcpserver') . " -q -R -H -llocalhost 0 $port " . path_to('perl') . ' code/wfpl/uploader/daemon.pl ' . $GLOBALS['wfpl_uploader_path'] . ' >/dev/null 2>/dev/null < /dev/null &'); +} + +/* call this to respond to the javascript async request for progress on the upload */ +function wfpl_uploader_progress() { + if(!isset($_REQUEST['wfpl_upload_progress'])) { + return; + } + + # allow this script to run for 8 hours + set_time_limit(28800); + + $file = $_REQUEST['wfpl_upload_progress']; + $file = strtolower($file); + $file = ereg_replace('[^a-z0-9.-]', '_', $file); + $file = ereg_replace('^[.-]', '_', $file); + $file = $GLOBALS['wfpl_uploader_path'] . "/progress/$file"; + + $waited = 0; + while(!file_exists($file)) { + usleep(500000); + ++$waited; + if($waited > 100) { + return; + } + } + + $progress_sent = 0; + while(true) { + clearstatcache(); + $stats = stat($file); + if($stats !== false) { + $progress = $stats['size']; + if($progress > $progress_sent) { + print(substr('............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................', 0, $progress - $progress_sent)); + flush(); + $progress_sent = $progress; + if($progress == 1000) { + return; + } + } + } + usleep(500000); # wait half a second + } +} diff --git a/uploader/daemon.pl b/uploader/daemon.pl new file mode 100755 index 0000000..e0e8000 --- /dev/null +++ b/uploader/daemon.pl @@ -0,0 +1,260 @@ +#!/usr/bin/perl + +# FIXME rewrite to use non-blocking IO and put limits on waiting + +#use Fcntl; + +#$flags = ''; +#fcntl(HANDLE, F_GETFL, $flags) +# or die "Couldn't get flags for HANDLE : $!\n"; +#$flags |= O_NONBLOCK; +#fcntl(HANDLE, F_SETFL, $flags) +# or die "Couldn't set flags for HANDLE: $!\n"; +# +#Once a filehandle is set for non-blocking I/O, the sysread or syswrite calls that would block will instead return undef and +#set $! to EAGAIN: + + +use strict; + +use vars qw($output_path $g_filename $flags $buffer $the_end $refills_at_end $content_length $bytes_written $progress_written $bytes_left $boundary); + +$output_path = $ARGV[0]; + +$the_end = 0; +$content_length = -1; +$boundary = ''; +$refills_at_end = 0; +$bytes_left = 4000; # if the headers are bigger than this... too bad +# FIXME if the entire request (including file contents) is less than 4000 this causes the program to hang. This happens with firefox when the file is deleted before hitting submit + +sub refill_buffer { + my $ret; + my $size; + my $max_read; + $size = length $buffer; + if($the_end == 1 || $bytes_left - $size < 1) { + $refills_at_end += 1; + if($refills_at_end > 10) { + die('refill_buffer called too many times (11) after EOF was reached'); + } + return; + } + return unless $size < 1000; + $max_read = (1100 - $size); + if($max_read > ($bytes_left - $size)) { + $max_read = ($bytes_left - $size); + } + $ret = sysread STDIN, $buffer, $max_read, $size; + if($ret == 0) { + $the_end = 1; + } elsif($ret == undef) { + die("read returned: " . $!); + } +} + +# remove x bytes from buffer and return them +# read_line doesn't use this, but keeps bytes_count anyway +sub read_buff { + my $count = shift; + my $str; + $str = substr $buffer, 0, $count; + $buffer = substr $buffer, $count; + $bytes_left -= $count; + return $str; +} + +# mark the entire buffer as used +sub buffer_used { + $bytes_left -= length $buffer; + $buffer = ''; +} + +# returns the next line from the input stream (not including the trailing crlf) +sub read_line { + my $size; + my $crlf_index; + my $line; + refill_buffer(); + $size = length $buffer; + $crlf_index = index $buffer, "\r\n"; + if($crlf_index < 0) { + die("expected a line, but didn't find a CRLF for $size characters (bytes_left: $bytes_left)"); + } + $line = substr $buffer, 0, $crlf_index; + $buffer = substr $buffer, ($crlf_index + 2); + $bytes_left -= $crlf_index + 2; + return $line; +} + +sub parse_main_headers { + my $line; + my $i; + + $line = read_line; + $i = index($line, '/'); + die(500) if $i < 0; + $line = substr($line, $i + 1); + $i = index($line, ' '); + die(501) if $i < 0; + $line = substr($line, 0, $i); + + if($line eq '') { + # FIXME return 404? + die('no filename passed'); + } + + $line = lc($line); + $line =~ s/[^a-z0-9.-]/_/g; + $line =~ s/^[.-]/_/; + + $g_filename = $line; + + + + while(1) { + $line = read_line; + if(substr(lc($line), 0, 16) eq 'content-length: ') { + $content_length = substr($line, 16); + } elsif(substr(lc($line), 0, 14) eq 'content-type: ') { + $i = index(lc($line), 'boundary='); + if($i < 0) { + die('no boundary= in content-type header'); + } + $boundary = substr $line, ($i + 9); + } elsif($line eq '') { + if($content_length == -1) { + die('No Content-Length header'); + } + if($boundary eq "") { + die('No boundary found in headers'); + } + $boundary = '--' . $boundary; + $bytes_left = $content_length; + return; + } + } +} + +# pass int from 0-1000 +sub progress_bar_update { + my $pct = shift; + my $dots; + if($pct > $progress_written) { + syswrite(PROGRESS_FD, '............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................', $pct - $progress_written); + $progress_written = $pct; + } +} + +sub progress_bar_start { + my $progress_filename = shift; # global + $progress_written = 0; + $bytes_written = 0; + open PROGRESS_FD, ">$progress_filename"; +} + +sub progress_bar_finish { + progress_bar_update(1000); + close PROGRESS_FD; +} + + +# save bytes past and update progress bar +sub output { + my $out = shift; + my $prog; + print FD $out; + + # update progressbar + $bytes_written += length($out); + $prog = $bytes_written / $content_length; # FIXME off by size of headers. do we care? + $prog = int($prog * 999 + .99); + progress_bar_update($prog); +} + +sub save_to_next_boundary { + my $filename = shift; + my $i; + my $crlfboundary = "\r\n$boundary"; + open FD, ">$output_path/partial/$filename"; + progress_bar_start("$output_path/progress/$filename"); + while(1) { + refill_buffer; + $i = index $buffer, $crlfboundary; + if($i < 0) { + output $buffer; + buffer_used; + } else { + if ($i > 0) { + output(read_buff($i)); + } + read_buff(2); # remove crlf between content and boundary #FIXME make sure this exists + close FD; + progress_bar_finish(); + return; + } + } +} + + +sub parse_sub { + my $sub_length = -1; + my $line; + my $i; + my $i2; + + while(1) { + $line = lc(read_line()); + if($line eq "") { + return save_to_next_boundary($g_filename); + } + #if(substr($line, 0, 21) eq 'content-disposition: ') { + # $i = index($line, 'filename="'); + # if($i < 0) { + # die('no filename=" in content-disposition sub-header'); + # } + # $i2 = index($line, '"', ($i + 10)); + # if($i2 < 0) { + # die('no filename=" in content-disposition sub-header'); + # } + # $filename = lc(substr($line, ($i + 10), ($i2 - ($i + 10)))); + # $filename =~ s/[^a-z0-9.-]/_/g; + # $filename =~ s/^[.-]/_/; + #} elsif($line eq '') { + # if($filename eq "") { + # die('No filename found in headers on part'); + # } + # return save_to_next_boundary($filename); + #} + } +} + +sub reply_and_quit { + print "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 8\r\n\r\nReceived"; + exit 0; +} + +sub parse_body { + my $line; + + while(1) { + $line = read_line; + if($line eq $boundary) { + parse_sub; + } elsif($line eq ($boundary . '--')) { + reply_and_quit; + } else { + die("Expecting boundary \"$boundary\" but got: \"$line\""); + } + } +} + + +#$flags = ''; +#fcntl(STDIN, F_GETFL, $flags) +# or die "Couldn't get flags for STDIN : $!\n"; +#$flags |= O_NONBLOCK; +#fcntl(STDIN, F_SETFL, $flags) +# or die "Couldn't set flags for STDIN: $!\n"; +parse_main_headers; +parse_body; diff --git a/uploader/progress.js b/uploader/progress.js new file mode 100644 index 0000000..6c1b304 --- /dev/null +++ b/uploader/progress.js @@ -0,0 +1,101 @@ +function tag(name) { + return document.getElementById(name); +} + +var dbg_url; +function sendRequest(url,callback,postData) { + var req = createXMLHTTPObject(); + if (!req) return; + var method = (postData) ? "POST" : "GET"; + req.open(method,url,true); + req.setRequestHeader('User-Agent','XMLHTTP/1.0'); + if (postData) + req.setRequestHeader('Content-type','application/x-www-form-urlencoded'); + dbg_url = url; + req.onreadystatechange = function () { + if(req.readyState != 4) { + callback(req); + return; + } + if (req.status != 200 && req.status != 304) { + /* alert('url:' + dbg_url + ' HTTP error ' + req.status); */ + progress_start_delayed(); + return; + } + callback(req); + } + if (req.readyState == 4) return; + req.send(postData); +} + +var XMLHttpFactories = [ + function () {return new XMLHttpRequest()}, + function () {return new ActiveXObject("Msxml2.XMLHTTP")}, + function () {return new ActiveXObject("Msxml3.XMLHTTP")}, + function () {return new ActiveXObject("Microsoft.XMLHTTP")} +]; + +function createXMLHTTPObject() { + var xmlhttp = false; + for (var i=0;i + + + + + + + + +
+
+

Upload a file:

+ +

+
+
+ +

+ +
+

Uploading...

+ +
+
+
+
+ + +