[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.



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)

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

[query st=db
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]'
(TO_DAYS(now()) - TO_DAYS(date_key_created) <= 7)
flag_key_created = '0'
flag_key_created IS NULL
[sql-alternate 2][set box_body_bgcolor]#ffffff[/set][else][set
<tr bgcolor="[scratch box_body_bgcolor]">
<td valign="top" nowrap>[convert-date fmt="%B %d, %Y"][sql-param
<td valign="top">
<a href="javascript:void(window.open('[area
	arg=|[sql-param code]|
	>[data table=products column=description key="[sql-param
[if data user_downloads::flag_key_created::[sql-param code] != 1]
	[perl interpolate=1]
	$Scratch->{item_sku} = uc([sql-param download_sku]);
	[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]"]

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]

<html><head><title>Product File Download</title>
<link href="/style/site.css" type="text/css" rel="stylesheet">
<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
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)
<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>
<tr><td valign="center" align="center">
<b>Date Purchased: [convert-date fmt="%B %d, %Y"][sql-param
<span class="topheadtext">[data table=products column=description
key="[sql-param download_sku]"]</span>
<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
[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.
<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).
<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
<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">
<a href="javascript:void(close())"><b>Click here</b></a> to close this

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;
return $first_server_done;


PERL script_that_takes_key.pl (located on download server)

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

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

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
Mods to etc/log_transaction

[tag flag write]transactions orderline userdb inventory gift_certs

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]);
[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
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]
[perl] Log("Past download_files."); [/perl]

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);

More information about the interchange-users mailing list