// v0.1 Initial public release // v0.2 Fixed download counter. Added (tentative) interface messages that may break in later versions of TXP if (txpinterface === 'admin') { register_callback('smd_remote_file_replace', 'file', 'file_replace'); register_callback('smd_remote_file_replace', 'file', 'file_save'); register_callback('smd_remote_file_create', 'file', 'smd_remote_file_create', 1); register_callback('smd_remote_file', 'file'); } if (txpinterface === 'public') { register_callback('smd_remote_download', 'file_download'); } // Yuk :-( global $smd_remote_file_vars; $smd_remote_file_vars = array( 'msg' => '', ); // Blow-by-blow equivalent of file_download_link, just a remote-aware version. // Adds the choose option. Defaults to 0 ( = random, for load balancing). Specify // any higher integer to grab that particular entry from the file (if it exists, else use 1st) function smd_file_download_link($atts, $thing) { global $thisfile; extract(lAtts(array( 'filename' => '', 'id' => '', 'choose' => '0', ), $atts)); unset($atts['choose']); $keys = array("smd_choose" => $choose); $out = file_download_link($atts, $thing); if (strpos($thisfile['filename'], ".link")) { $origLink = explode('"', $out); $origLink[1] .= join_qs($keys); // Will ignore if choose is 0 $out = implode('"', $origLink); } return $out; } // Add an image to the download form which, by default, is based on the filename of the download itself function smd_file_download_image($atts) { global $thisfile; assert_file(); extract(lAtts(array( 'filename' => '', 'id' => '', 'extension' => 'jpg', 'ifmissing' => '?file', 'thumb' => '0', 'class' => '', 'wraptag' => '', ), $atts)); $filelink = $thisfile['filename']; if ($filename == "") { $filename = $filelink; } $filename = str_replace(".link", "", $filename) . (($extension=="") ? '' : '.'.$extension); $img = ''; if ($id) { $img = ($thumb==0) ? @image(array("id" => $id, "class" => $class, "wraptag" => $wraptag)) : @thumbnail(array("id" => $id, "class" => $class, "wraptag" => $wraptag)); } else if ((strpos($filename, "http://") === 0) || (strpos($filename, "/") === 0)) { $img = (($wraptag=="") ? '' : '<'.$wraptag. (($class=="") ? '' : ' class="'.$class.'"') .'>') . ''. (($wraptag=="") ? '' : ''); } else { $img = ($thumb==0) ? @image(array("name" => $filename, "class" => $class, "wraptag" => $wraptag)) : @thumbnail(array("name" => $filename, "class" => $class, "wraptag" => $wraptag)); } $display = ($id) ? $id : $filename; $wrapper = (($wraptag=="") ? '@@REPL' : '<'.$wraptag. (($class=="") ? '' : ' class="'.$class.'"') .'>@@REPL'); return ($img) ? $img : (($ifmissing=="?file") ? str_replace("@@REPL", $display, $wrapper) : (($ifmissing=="") ? '' : str_replace("@@REPL", $ifmissing, $wrapper))); } // Generic callback which is fired _after_ the Files page has loaded. // Performs display cleanup/insertion and post processing of any file inserts/edits function smd_remote_file($event, $step) { global $smd_remote_file_vars; $helpLink = '?'; $ul_form = trim(form(eInput('file'). sInput('smd_remote_file_create'). graf('or URL'.sp.$helpLink.sp.fInput('text', 'smd_remote_url', '', 'edit', 'Enter a URL to upload to TXP', '', '32').sp. fInput('submit', '', 'Upload', 'smallerbox')) , 'text-align: center;')); echo smd_remote_js('$(".upload-form").append(\''.$ul_form.'\');'); if ($smd_remote_file_vars['msg'] != "") { echo smd_remote_js('$("#nav-primary table td:eq(0)").append(\''.$smd_remote_file_vars['msg'].'\');'); } } // Every time a file is saved/edited, TXP recalculates its size from the real file (grrr). // This is undesirable so it is replaced with the size of the remote URL file instead. function smd_remote_file_replace() { extract(gpsa(array('id'))); smd_remote_set_size($id); } // Sets the size of the given TXP database file to that of its corresponding "real" remote URL file size function smd_remote_set_size($id_or_file) { global $file_base_path; if (is_numeric($id_or_file)) { $filename = trim(safe_field("filename", "txp_file", "id=".intval($id_or_file))); } else { $filename = trim(doSlash($id_or_file)); } if (strpos($filename, ".link")) { $filepath = build_file_path($file_base_path, $filename); $url = smd_remote_file_list($filepath, 1, 1); if (count($url) > 0) { $hdrs = smd_get_headers($url[0], 1); $size = ($hdrs === false || !isset($hdrs['content-length'])) ? 1 : $hdrs['content-length']; safe_update("txp_file", "size=".$size, "filename='".$filename."'"); } } } // Callback for uploading a URL from the Files tab function smd_remote_file_create() { global $file_base_path, $smd_remote_file_vars; extract(doSlash(gpsa(array('smd_remote_url','category','permissions','description')))); $url = trim($smd_remote_url); // Only intercept remote files; leave everything else for TXP to manage if (strpos($url, "http") === 0) { $hdrs = smd_get_headers($url, 1); $size = ($hdrs === false || !isset($hdrs['content-length'])) ? 1 : $hdrs['content-length']; // Make a filename and full path: unencoded $dest_filename = basename(urldecode($url)).".link"; $dest_filepath = build_file_path($file_base_path, $dest_filename); if (file_exists($dest_filepath)) { // Read the whole file because we only want to add the URL if it's not there already $lines = smd_remote_file_list($dest_filepath, 0, 1); if (!in_array($url, $lines)) { $handle = fopen($dest_filepath, "a"); fwrite($handle, $url.n); fclose($handle); $smd_remote_file_vars['msg'] = gTxt('file_uploaded', array('{name}' => urldecode($url))); } else { $smd_remote_file_vars['msg'] = gTxt('file_already-exists', array('{name}' => urldecode($url))); } // Set the size just in case smd_remote_set_size($dest_filename); } else { // File doesn't exist so create it and put the URL inside $tmp = tempnam("/tmp", "smd_"); $handle = fopen($tmp, "w"); fwrite($handle, $url.n); fclose($handle); rename($tmp, $dest_filepath); // Add the file to TXP $ret = safe_insert("txp_file", "filename = '$dest_filename', category = '$category', permissions = '$permissions', description = '$description', size = '$size', created = now(), modified = now() "); $smd_remote_file_vars['msg'] = gTxt('file_uploaded', array('{name}' => urldecode($url))); } } else { $smd_remote_file_vars['msg'] = gTxt('file_upload_failed'); } } // Read the contents of the chosen file (full path required) and get lines from within, adding them to an array. // $offset is where to start from. Normally 1 = 1st row, 2 = 2nd row and so on. 0 = a random row. // $qty specifies how many rows to pull. 0 = unlimited. function smd_remote_file_list($fname, $qty=1, $offset=0) { $out = array(); if (file_exists($fname)) { $fd = fopen($fname, "r"); // Read the whole file in (yes there's the file() call, but fgets() is supposedly quicker on txt files) while (!feof($fd)) { $line = rtrim(fgets($fd)); if ($line != "") { $lines[] = $line; } } fclose ($fd); if ($offset == 0) { shuffle($lines); $offset = 1; } $offset = ($offset > count($lines)) ? 1 : $offset; $out = ($qty == 0) ? $lines : array_slice($lines, $offset-1, $qty); } return $out; } function smd_remote_download($event, $step) { global $pretext, $filename, $id, $file_base_path; if (strpos($filename, ".link") > 0) { $choose = 0; // Get any overriding value of smd_choose from the query string if ($pretext['qs']) { list($qkey, $qval) = explode("=", $pretext['qs']); if ($qkey == "smd_choose") { if ($qval > 0) { $choose = intval($qval); } } } // Get the "true" filename (unfortunately from the database = 1 extra query). // The file size, however, is that of the remote file $real_file = safe_row("filename, size, status", "txp_file", "id=".intval($id)); if ($real_file['status'] == getStatusNum("live")) { $remoteURL = smd_remote_file_list(build_file_path($file_base_path, $real_file['filename']), 1, $choose); if (count($remoteURL) > 0) { $url = $remoteURL[0]; // Test the file exists $hdrs = smd_get_headers($url, 0); if (strpos($hdrs[0], "200") > 0 && strpos($hdrs[0], "OK") > 0) { header('Content-Description: File Download'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="' . basename($filename) . '"; size = "'.$real_file['size'].'"'); // Fix for lame IE 6 pdf bug on servers configured to send cache headers header('Cache-Control: private'); @ini_set("zlib.output_compression", "Off"); @set_time_limit(0); @ignore_user_abort(true); header('Location: ' . $url); // record download if ((connection_status()==0) and !connection_aborted() ) { safe_update("txp_file", "downloads=downloads+1", 'id='.intval($id)); log_hit('200'); } else { $pretext['request_uri'] .= '#aborted'; log_hit('200'); } } else { $file_error = 404; } } else { $file_error = 404; } } else { $file_error = 403; } // deal with error - kinda pointless since nothing is displayed to the user if (isset($file_error)) { switch($file_error) { case 403: header('HTTP/1.0 403 Forbidden'); break; case 404: header('HTTP/1.0 404 File Not Found'); break; default: header('HTTP/1.0 500 Internal Server Error'); break; } } // remote download done exit(0); } } function smd_remote_js($content) { $out = ''.n; return $out; } // PHP4 emulation(ish) of PHP5's get_headers(). Stolen from php.net and modded function smd_get_headers($url, $format=0) { if (!$url) { return false; } $uinfo=parse_url($url); $headers = array(); if (is_callable('checkdnsrr') && !checkdnsrr($uinfo['host'].'.','MX') && !checkdnsrr($uinfo['host'].'.','A')) { return false; } $port = isset($uinfo['port']) ? $uinfo['port'] : 80; $fp=fsockopen($uinfo['host'], $port, $errno, $errstr, 15); if($fp) { $head = "GET ".@$uinfo['path']."?".@$uinfo['query']." HTTP/1.0\r\n"; $head .= "Host: ".@$uinfo['host'].":".$port."\r\n"; $head .= "Connection: Close\r\n"; $head .= "Accept: */*\r\n"; if (@$uinfo['user']) { $head .= "Authorization: Basic ".base64_encode($uinfo['user'].':'.$uinfo['pass'])."\r\n"; } $head .= "\r\n"; fputs($fp, $head); $eoheader = false; while(!feof($fp) or ($eoheader==true)) { if($header=fgets($fp, 1024)) { if ($header == "\r\n") { $eoheader = true; break; } else { $header = trim($header); } if($format == 1) { $key = strtolower(array_shift(explode(': ',$header))); if($key == $header) { $headers[] = $header; } else { $headers[$key]=substr($header,strlen($key)+2); } unset($key); } else { $headers[] = $header; } } } return $headers; } else { return false; } }