JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
added progress-bar uploader.php
authorjason <jason@lappy.(none)>
Wed, 28 May 2008 23:01:23 +0000 (19:01 -0400)
committerjason <jason@lappy.(none)>
Wed, 28 May 2008 23:01:23 +0000 (19:01 -0400)
format.php
http.php
session.php
uploader.php [new file with mode: 0644]
uploader/daemon.pl [new file with mode: 0755]
uploader/progress.js [new file with mode: 0644]
uploader/uploader.css [new file with mode: 0644]
uploader/uploader.html [new file with mode: 0644]

index ae66713..f63dfba 100644 (file)
@@ -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) {
index e325fc1..042e1b9 100644 (file)
--- 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();
index 4f1cb67..c61be6f 100644 (file)
 #  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 (file)
index 0000000..0c962c2
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+
+require_once('code/wfpl/template.php');
+require_once('code/wfpl/encode.php');
+require_once('code/wfpl/session.php');
+require_once('code/wfpl/upload.php'); # FIXME for path_to() which should be somewhere else
+
+# This function is for making an uploader with a progress bar.
+#
+# Parameter: (optional)
+#     progress_url: URL javascript should use to get progress updates (defaults to this_url() with the query string replaced with ?wfpl_upload_progress=FILENAME (where FILENAME is the first parameter.))
+#
+# You must also set $GLOBALS['wfpl_uploader_port'] to an available port for the upload receiver to run on.
+#
+# Returns: (an array containing)
+#     html
+#     css
+#     javascript
+#     filename
+
+function uploader($progress_url = '') {
+       if(!$filename) {
+               $filename = strtolower(session_generate_key());
+       }
+       if(!$progress_url) {
+               $progress_url = this_url();
+               $q = strpos($progress_url, '?');
+               if($q) {
+                       $progress_url = substr($progress_url, 0, $q);
+               }
+               $progress_url .= '?wfpl_upload_progress=' . enc_url_val($filename);
+       }
+       if(!$GLOBALS['wfpl_uploader_host']) {
+               $GLOBALS['wfpl_uploader_host'] = this_host();
+       }
+
+       $html = new tem();
+       $html->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 (executable)
index 0000000..e0e8000
--- /dev/null
@@ -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 (file)
index 0000000..6c1b304
--- /dev/null
@@ -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<XMLHttpFactories.length;i++) {
+               try {
+                       xmlhttp = XMLHttpFactories[i]();
+               }
+               catch (e) {
+                       continue;
+               }
+               break;
+       }
+       return xmlhttp;
+}
+
+
+function progress_start() {
+       sendRequest('~url~', progress_update_with);
+}
+
+function progress_start_delayed() {
+       setTimeout(progress_start, 1500);
+}
+
+
+function progress_finished() {
+       var appears;
+       tag('wfpl_progress_header').innerHTML = 'Upload Finished';
+       appears = tag('wfpl_upload_finished');
+       if(appears) {
+               appears.style.position = 'static';
+       }
+       if(wfpl_upload_finished) {
+               wfpl_upload_finished();
+       }
+}
+
+function progress_update_with(rec) {
+       length = rec.responseText.length;
+       bar = tag('wfpl_progress_bar');
+       bar.style.backgroundPosition = (Math.floor(length / 5) - 200) + 'px 0';
+
+       whole = Math.floor(length/10);
+       pct = '' + whole + '.' + (length - (whole * 10));
+       bar.innerHTML = pct + '%';
+
+       if(length == 1000) {
+               progress_finished();
+       }
+}
+
+function submitting() {
+       if(wfpl_upload_starting) {
+               wfpl_upload_starting();
+       }
+       tag('wfpl_progress_form').style.display = 'none';
+       tag('wfpl_progress_section').style.position = 'static';
+       progress_start_delayed();
+}
+
+
+
+
+
+
diff --git a/uploader/uploader.css b/uploader/uploader.css
new file mode 100644 (file)
index 0000000..05d07c4
--- /dev/null
@@ -0,0 +1,4 @@
+#wfpl_progress_post_response { display: none; }
+#wfpl_progress_section { position: absolute; left: -2000px; top: 0px; }
+#wfpl_progress_bar_border { width: 200px; height: 16px; border: 1px solid #ddd; font: 11px Verdana; }
+#wfpl_progress_bar { text-align: center; width: 200px; height: 16px; margin: 0 auto 0 0; background: transparent url(images/wfpl_uploader_bar.png) no-repeat -200px 0px; }
diff --git a/uploader/uploader.html b/uploader/uploader.html
new file mode 100644 (file)
index 0000000..2ad8695
--- /dev/null
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title></title>
+</head>
+
+<body>
+<!--~main start~-->
+  <div id="wfpl_progress_form">
+    <form action="http://~host~:~port~/~filename~" enctype="multipart/form-data" target="wfpl_progress_post_response" method="post">
+      <p>Upload a file: <input type="file" name="wfpl_uploader_file" id="wfpl_uploader_file" /></p>
+
+      <p><input type="submit" onclick="submitting()" value="Submit" /></p>
+    </form>
+  </div>
+
+  <p><iframe id="wfpl_progress_post_response" name="wfpl_progress_post_response"></iframe></p>
+
+  <div id="wfpl_progress_section">
+    <h2 id="wfpl_progress_header">Uploading...</h2>
+
+    <div id="wfpl_progress_bar_border">
+      <div id="wfpl_progress_bar"></div>
+    </div>
+  </div>
+<!--~end~-->
+</body>
+</html>