[ic] Downloadable softgoods - my solution

Russell Mann tech at khouse.org
Mon Jul 14 10:20:06 EDT 2003


> > I've developed a very non-simple way of doing it.  The problems I
> > needed to solve include multiple files per product, very large files
> > to download, downloads coming from a different server than the IC
> > install.  If you need a solution addressing these needs, let me know.

Dear List,

I've had a couple requests for this info so I'm posting it to the list.  I'd
be interested in feedback for security, good coding practice, etc.  If this
is a good idea, maybe the IC developers want to use it in the demo?  If the
idea sucks, maybe someone can let me know what's wrong with it.

Thanks,

Russell

-----------------------------------------------------------------------
Here's the flow of traffic for this solution.

Download server has product files in /download_dir/<SKU>/*
This can be any number of files per product.

Products DB has a field 'download_files' which has a space delimited list of
the files found in /download_dir/<SKU>/*
When a person purchases one of these products, a record is created in a
separate table "user_downloads", which includes a secret key.

When a person goes to download the product, a symlink is made on the
download server by passing the parameters of the secret key, and the product
sku to a script which receives that information and makes a symlink from an
http:// dir to the download_dir/SKU happen.

User clicks on download link, which pops up a window which loop-lists the
files in the 'download_files' field, creating links using the secret key.

A cron on the download server cleans old symlinks out to give users a
designated number of days (example uses 7) to download the product, then cut
them off.

-----------------------------------------------------------------------
Table: User Downloads

CREATE TABLE user_downloads (
  code int(11) NOT NULL auto_increment,
  username varchar(255) NOT NULL default '',
  order_number varchar(14) NOT NULL default '',
  date_purchased date NOT NULL default '0000-00-00',
  date_key_created date default '0000-00-00',
  flag_key_created tinyint(4) default '0',
  download_sku varchar(25) NOT NULL default '',
  download_files text NOT NULL,
  secret_key varchar(7) NOT NULL default '',
  PRIMARY KEY  (code)
) TYPE=MyISAM AUTO_INCREMENT=353 ;


-----------------------------------------------------------------------
"My Downloads" page, where I display Download links:

[query st=db
list=1
sql="SELECT code, date_purchased, download_sku, download_files,
flag_key_created, secret_key
FROM user_downloads
WHERE username = '[data base=session field=username filter=sql]'
AND (
(TO_DAYS(now()) - TO_DAYS(date_key_created) <= 7)
OR
flag_key_created = '0'
OR
flag_key_created IS NULL
)
"]
[sql-alternate 2][set box_body_bgcolor]#ffffff[/set][else][set
box_body_bgcolor]#eeeeee[/set][/else][/sql-alternate]
<tr bgcolor="[scratch box_body_bgcolor]">
<td valign="top" nowrap>[convert-date fmt="%B %d, %Y"][sql-param
date_purchased][/convert-date]</td>
<td valign="top">
<a href="javascript:void(window.open('[area
	href=help/downloads_popup
	arg=|[sql-param code]|
	]','_download','toolbar=no,location=no,status=no,directories=no,menubar=no,
scrolling=auto,scrollbars=auto,width=500,height=500,resize=no'))
	"
	>[data table=products column=description key="[sql-param
download_sku]"]</a></small></td>
<!--
[if data user_downloads::flag_key_created::[sql-param code] != 1]
	[perl interpolate=1]
	$Scratch->{item_sku} = uc([sql-param download_sku]);
	[/perl]
	[tmp is_key_created][set_download_key key="[sql-param secret_key]"
sku="[scratch item_sku]"][/tmp]
	[if scratch is_key_created]
		[data table=user_downloads col=flag_key_created key="[sql-param code]"
increment=0 value="1"]
		[data table=user_downloads col=date_key_created key="[sql-param code]"
increment=0 value="[tag time]%Y-%m-%d[/tag]"]
	[/if]
[/if]
-->
</tr>
[/query]

-----------------------------------------------------------------------
Download Popup Window, which lists all files and links:

[if !session logged_in][bounce href="[area index]"][/if]
[seti user_download_code][data session arg][/seti]
[detect_browser]

<html><head><title>Product File Download</title>
<link href="/style/site.css" type="text/css" rel="stylesheet">
</head>
<body bgcolor="#ffffff" topmargin="0" leftmargin="0" marginwidth="0"
marginheight="0" bottommargin="0" text="#000000" vlink="#000000"
alink="#000000" link="#000000" onload="self.focus()">
[query st=db
list=1
sql="SELECT date_purchased, download_sku, download_files, secret_key
FROM user_downloads
WHERE code = [scratch user_download_code]
AND username = '[data base=session field=username filter=sql]'
AND flag_key_created = '1'
AND (TO_DAYS(now()) - TO_DAYS(date_key_created) <= 7)
LIMIT 1
"]
<table  border="0" cellpadding="10" cellspacing="0" width="100%">
<tr><td bgcolor="#242461" height="40" align="center" valign="middle">
<font face="verdana, helvetica, arial" color="#ffffff" size="3"><b>Product
File Download</b></font>
</td></tr>
<tr><td valign="center" align="center">
<b>Date Purchased: [convert-date fmt="%B %d, %Y"][sql-param
date_purchased][/convert-date]</b><br>
<span class="topheadtext">[data table=products column=description
key="[sql-param download_sku]"]</span>
<p>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr><td bgcolor="#185D9C" align="center" valign="middle">
<font face="verdana, helvetica, arial" color="#ffffff" size="2"><b>Download
Files</b></font>
</td></tr>
</table>
<p>
[if !scratch Is_Mac]
<b>Download Instructions:</b> <b>Right-click</b> on the link<br>
and select <b>Save Target As</b> to download the file.
[else]
<b>Download Instructions:</b> Hold down the <b>Option Key</b> and click on
the above link.  This will download the file to your
default download location (usually the desktop).
[/else]
[/if]
</p>
<table border="0" cellpadding="10" cellspacing="0" width="100%">
[loop arg="[sql-param download_files]"]
<tr><td valign="top" align="left">
<a href="http://<download_server>/store_downloads/[sql-param
secret_key]/[loop-code]"><img hspace="7" border="0"
src="/images/small_disk.gif" width="14" height="14"></a>
<a href="http://<download_server>/store_downloads/[sql-param
secret_key]/[loop-code]">[loop-code]</a>
</td></tr>
[/loop]
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr><td bgcolor="#185D9C" height="1">
<img border="0" src="/images/blank.gif" width="1" height="1">
</td></tr></table>
<p>
<a href="javascript:void(close())"><b>Click here</b></a> to close this
window.
</font>
</td></tr>
</table>
[/query]
</body></html>

-----------------------------------------------------------------------
UserTag: set_download_key.tag

UserTag set_download_key Order key sku
UserTag set_download_key addAttr
UserTag set_download_key Interpolate
UserTag set_download_key Routine <<EOR
sub {
use LWP::Simple;
my ($new_string, $sku) = @_;
my $download_site = "http://<myserver>/cgi-bin/script_that_takes_key.pl";
$sku = uc($sku);
my $url_secret_key = "$download_site?key=$new_string&sku=$sku";
my $make_secret_key;
my $counter;
my $first_server_done;

while (($make_secret_key != "200") || ($counter < 10)) {
        $make_secret_key = get $url_secret_key;
        if ($make_secret_key == "200") {
                $first_server_done = 1;
        }
$counter++;
}
return $first_server_done;
}
EOR

-----------------------------------------------------------------------

PERL script_that_takes_key.pl (located on download server)

/download_dir/<SKU>/<files go here>

#!/usr/bin/perl
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};   # Make %ENV safer
use CGI;
$query=new CGI;
my $new_string = $query->param('key');
my $product = $query->param('sku');

if ($new_string) {
system ("ln -s /download_dir/$product
/<html_directory>/store_downloads/$new_string");
}

print "Content-Type: text/html\n\n";
print "200";

-----------------------------------------------------------------------
Products DB:

Add field:
download_files - text
This field is a space delimited list of filenames used for each product.
Also, make sure there's a "download" tinyint field for flagging downloadable
products.
-----------------------------------------------------------------------
Mods to etc/log_transaction


[tag flag write]transactions orderline userdb inventory gift_certs
user_downloads[/tag]

In the Item-List loop:


[if-item-field download]
[seti seed_value][tag time]%c[/tag][item-field description][/seti]
[seti dl_passcode][generate_key_value seed="[scratch seed_value]"][/seti]
[perl interpolate=1]
$Scratch->{dl_passcode} = substr($Scratch->{dl_passcode},0,7);
$Scratch->{dl_passcode} = lc($Scratch->{dl_passcode});
$Scratch->{item_sku} = uc([item-code]);
[/perl]
[seti dl_passcode_[item-increment]][scratch dl_passcode][/seti]
[log file="products/user_downloads_log.txt" hide="1"
interpolate="1"][scratch dl_passcode]	[data session username]	[value
mv_order_number]	[value order_date]	[item-code]	[item-field
download_files][/log]
Added [item-code] to user_downloads:
[import table=user_downloads type=LINE continue=NOTES]
username: [data session username]
order_number: [value mv_order_number]
date_purchased: [value order_date]
download_sku: [scratch item_sku]
download_files: [item-field download_files]
secret_key: [scratch dl_passcode]
[/import]
[perl] Log("Past download_files."); [/perl]
[/if-item-field]

-----------------------------------------------------------------------
UserTag generate_key_value

UserTag generate_key_value Order seed
UserTag generate_key_value Routine <<EOR
sub  {
my $seed = @_[0];
if (!$seed) {
        $seed = time();
}
return generate_key($seed);
}
EOR
-----------------------------------------------------------------------




More information about the interchange-users mailing list