Serious race condition in Joomla BibTeX - Trying to solve it

This forum is for general questions about extensions for Joomla! version 1.5.x.

Moderator: General Support Moderators

Forum rules
Forum Rules
Absolute Beginner's Guide to Joomla! <-- please read before posting, this means YOU.
Forum Post Assistant - If you are serious about wanting help, you will use this tool to help you post.
Locked
arnoques
Joomla! Fledgling
Joomla! Fledgling
Posts: 4
Joined: Mon Aug 03, 2009 4:44 pm

Serious race condition in Joomla BibTeX - Trying to solve it

Post by arnoques » Mon Aug 03, 2009 5:57 pm

Hi everyone! I am very new to Joomla and php, so some of my questions here will probably be very simple. I am using Joomla 1.0.9, but this is relevant to all versions of this component.

A while ago, I installed joomla BibTeX (http://extensions.joomla.org/extensions/1766/details), a component to maintain a list of bibliography items. While tweaking the code to improve it, I found a serious race condition when downloading a bib file. I tried to reach the component's developer, but I've had no answer, and I need help to solve it myself.

The problematic code is the following. It gets executed when the user clicks a link to download a list of selected bibliography items.

Code: Select all

function showAllBib($database,$mainframe,$selected, $filter,$afilter,$limit,$limitstart,$catId){
	/*SNIP - I took out some code here that sets some variables*/
	$fp = fopen("components/com_jombib/download.bib", "w") or die("can't open file"); 
	foreach($result as $contid){
		$database->setQuery("SELECT content from #__bib_content where id='".$contid."';");
		$bibstringall=$database->loadResult();
		fwrite($fp, $bibstringall);
	}
	fclose($fp);
	HTML_jombib::displayBibDownload();
}
So, this function gets the bib string for all the selected items, and saves it in a file (download.bib) in the server. The HTML_jombib::displayBibDownload() function shows a different page that shows a link to download.bib, along with instructions to right-click and download.

The race condition appears because the file has always the same name in the server, and the user can take any time (seconds, minutes, days...) to download the bib file. If any other user (or even the same one, in another browser tab) selects another item list to download, the bib file is overwritten and both users get the same file. I believe this is a major usability issue.

I tried solving this replacing the function above with one that spits out the headers

Code: Select all

header("Content-type: application/octet-stream\n");
header("Content-disposition: attachment; filename=\"$file\"\n");
and then the contents of the bib file. However, the rest of the site's template is sent to the browser before this function is executed, so this is not possible, and redirecting to a different page is not easy (for me, at least) since I'm not sure how to pass the contents of the bib file to the new page.

Could you give me some pointers on how to solve this? The file should be generated on the fly (because the list of items to download is selected by the user) and, ideally, the download shouldn't take more than a single click (why clicking twice when a single click can do?). When this is solved, I'll post the changes here so all the users of this component can avoid the pitfall.

Oh, and one more thing. If the solution implies fiddling with the site's database, please do give me somewhat more detailed instructions. I am working on a production site so it's important to avoid erasing the whole database, or making it unreadable. I just began learning about Joomla and php... mySQL is somewhat of a mystery to me, right now.

arnoques
Joomla! Fledgling
Joomla! Fledgling
Posts: 4
Joined: Mon Aug 03, 2009 4:44 pm

Re: Serious race condition in Joomla BibTeX - Trying to solve it

Post by arnoques » Mon Aug 03, 2009 9:55 pm

It just occured to me, that I can add a timestamp to the filename (e. g. download_0908031849.bib) to create different files each time. It's not a nice hack, but it's simple and it'll probably work fine. Then I'd add some code to delete old files once in a while.

Any opinions?

arnoques
Joomla! Fledgling
Joomla! Fledgling
Posts: 4
Joined: Mon Aug 03, 2009 4:44 pm

[SOLVED] Serious race condition in Joomla BibTeX

Post by arnoques » Tue Aug 04, 2009 6:19 pm

Seems that I'm talking to myself here...

I've changed the code to add a timestamp to the filename. I believe the change avoids the race issue, but it still requires two clicks to get the file, and the file name is rather ugly. The directory is scanned each time a new file is generated to delete old ones.

If any other user of this component is interested, the changes are very simple:
1) open the file jombib.php (located in "components/com_jombib/")
2) replace the function showAllBib (line 353) with this one:

Code: Select all

function showAllBib($database,$mainframe,$selected, $filter,$afilter,$limit,$limitstart,$catId){
	global $database;
	if ( $selected ) {
		$orderby 				= $selected;
	} else {
		$orderby 				= 'rdate';
	}
	$orderby = _orderby_sec( $orderby );
	if($catId>0){
		$andfilter="where #__bib_categories.categories='".$catId."'";
	}else{
		$andfilter="where 1>0";
	}
	if($afilter!=""){
		$andfilter=$andfilter." AND LOWER(authorsnames) LIKE '%".$afilter."%'";
	}
	if($filter!=""){
		$andfilter=$andfilter." AND LOWER(title) LIKE '%".$filter."%'";
	}
	if((strcmp($orderby,'#__bib_auth.last')==0)||(strcmp($orderby,'#__bib_auth.last DESC')==0)){
		$orderby="authorsnames";
	}
	$database->setQuery("SELECT authorid from #__bib left join #__bib_categories on #__bib.authorid=#__bib_categories.id ".$andfilter." order by ".$orderby);
	$result = $database->loadResultArray();
	
	//Changes start here. Adds a timestamp to the download file.
	$filename = 'components/com_jombib/download_'. strtr(microtime(true), ".", "_") .'.bib';
	$fp = fopen($filename, "x") or die("can't open file");	//Changed "w" for "x" to further avoid collisions
	foreach($result as $contid){
		$database->setQuery("SELECT content from #__bib_content where id='".$contid."';");
		$bibstringall=$database->loadResult();
		fwrite($fp, $bibstringall);
	}
	fclose($fp);
	deleteOldDownloadFiles();
	HTML_jombib::displayBibDownload($filename);
}
3) add the following function

Code: Select all

function deleteOldDownloadFiles(){
//Deletes download files that are older than a certain amount of time to avoid filling the server.
	$dir = 'components/com_jombib/';
	$files = scandir($dir);
	$deletetime = 3600; //IN SECONDS. An hour, but should be configurable from the backend.
	
	foreach ($files as $file) {
		if (preg_match("/\Adownload_\d+_\d+.bib\z/", $file)) {
			preg_match("/\d+/",$file,$stamp);
			$timesince = ((int)time()) - ((int)$stamp[0]);
			if ($timesince > $deletetime) {
				unlink ($dir.$file);
			}
		}
	}
}
4) open the file jombib.html.php
5) replace the function displayBibDownload (line 13) with this one:

Code: Select all

	function displayBibDownload($file){ //Now we ask for the file to download
		global $mosConfig_live_site;
		?>
		<table class="contentpaneopen">
		<tr>
			<td class="contentheading" width="100%">
				.bib File Download
			</td>
		</tr>
		</table>
		<table>
			<tr>
			<td>
			//The link was changed to target the correct file
			<a href='<?php echo $mosConfig_live_site . "/". $file;?>'>Download File</a>(Right-click and "Save Target As..")
			</td>
			</tr>
			</table>
		<?php
	}
That's it. A very simple change. I hope this post reaches the original author so it can be integrated in the extensions directory. I've made a few other changes to improve the look of the component, and I'll be glad to contribute them back.

arnoques
Joomla! Fledgling
Joomla! Fledgling
Posts: 4
Joined: Mon Aug 03, 2009 4:44 pm

Re: Serious race condition in Joomla BibTeX [SOLVED]

Post by arnoques » Fri Aug 21, 2009 3:52 pm

Finally, I found a way to download the file with just one click.

1) open the file jombib.php (located in "components/com_jombib/")
2) replace the function showAllBib (line 353) with this one:

Code: Select all

function showAllBib($database,$mainframe,$selected,$filter,$afilter,$limit,$limitstart,$catId){
	global $database;
	if ( $selected ) {
		$orderby = $selected;
	} else {
		$orderby = 'rdate';
	}
	$orderby = _orderby_sec( $orderby );
	if($catId>0){
		$andfilter="where #__bib_categories.categories='".$catId."'";
	}else{
		$andfilter="where 1>0";
	}
	if($afilter!=""){
		$andfilter=$andfilter." AND LOWER(authorsnames) LIKE '%".$afilter."%'";
	}
	if($filter!=""){
		$andfilter=$andfilter." AND LOWER(title) LIKE '%".$filter."%'";
	}
	if((strcmp($orderby,'#__bib_auth.last')==0)||(strcmp($orderby,'#__bib_auth.last DESC')==0)){
		$orderby="authorsnames";
	}
	$database->setQuery("SELECT authorid from #__bib left join #__bib_categories on #__bib.authorid=#__bib_categories.id ".$andfilter." order by ".$orderby);
	$result = $database->loadResultArray();
	
	//Beware!! In Joomla 1.5 the use of index2 is deprecated.
	//Selects the result
	foreach($result as $contid){
		$database->setQuery("SELECT content from #__bib_content where id='".$contid."';");
		$bibstringone=$database->loadResult();
		$bibstringall=$bibstringall.$bibstringone;
	}
	//Serves the file dinamically
	$filename = 'download_'.strtr(microtime(true), ".", "_").'.bib';
	header('Content-Disposition: attachment; filename=' . $filename);   
	header('Content-Type: application/force-download');
	header('Content-Type: application/octet-stream');
	header('Content-Type: application/download');
	header('Content-Description: File Transfer');
	echo $bibstringall;
	exit;
}
3) open the file jombib.html.php
4) the function displayBibDownload (line 13) is not used any more
5) replace "index.php" for "index2.php" in line 877 (inside the function HTML_jombib::formatReference)

Step number 5 is the key to download the file, because index2.php just shows the component. In that case, the component can send the headers to tell the browser it should download the following text. However, be aware that this behaviour is deprecated in Joomla 1.5, so instead of using index2.php you should use index.php and the option "&tmpl=component".

I hope this helps anyone. In fact, if any user of this component is reading this, I'll be glad to hear from you. I can't contact the author of the component, so I think he abandoned the project. I want to contribute to it, and I'd like to know if there's any interest in keeping it alive.

Arnoques


Locked

Return to “Extensions for Joomla! 1.5”