File uploads - Large Objects

HTTP provides for uploading files, with the input type="file". You need to specify the enctype for the form, use POST, and you may limit the file size. You may also include any other data you wish on the form, even other files. This example is from the PHP documentation.

<!-- The data encoding type, enctype, MUST be specified as below -->
<form enctype="multipart/form-data" action="upload.php" method="POST">
<!-- MAX_FILE_SIZE must precede the file input field -->
<input type="hidden" name="MAX_FILE_SIZE" value="30000" />
<!-- Name of input element determines name in $_FILES array -->
<br> Send this file: <input name="mugshot" type="file" />
<input type="submit" value="Send File" align="center" />

Receiving the file

In PHP, the $_FILES array, indexed by the name(s) given in the form, contains array(s) of information about each uploaded file. The upload may fail for a number of reasons. The browser may prevent an overly large upload, if it does not, PHP will. PHP puts the file in a temporary location. You need to:

  1. Check that the upload was successful:  $_FILES['mugshot']['error'] == UPLOAD_ERR_OK
  2. Check that it was in fact an uploaded file (in case someone is trying to trick you), the functions is_uploaded_file() or move_uploaded_file() will do this.
  3. Store the file either as a file, or in your database. In the example, I move it to a subdirectory.
   $file = $_FILES['mugshot'];
if ($file['size'] > 31000 || $file['size'] < 1000)
die ("File too large (or small)\n");
if ($file['error'] != UPLOAD_ERR_OK) die ("File upload failed");
if (!move_uploaded_file($file['tmp_name'],"upload/{$file['name']}"))
die ('Possible attack! File: ' . $file['name']."\n") ;

Moving to your subdirectory - permission

In the example, I have trusted the client to pick an appropriate name, you may wish to generate your own name. There is a problem of permission for apache to create a file. Either

  1. create named files, to which you give "everyone" write access, so apache can rewrite them, or
  2. create a directory, to store only uploaded files, and give "everyone" write access to the directory (effectively, file creation privilege).  Apache will then become owner of the files it creates, however you as owner of the directory can remove such files.
However, "everyone" is a bad idea. Let's at least limit the permission to "apache" This can be done by changing the group of the directory (or files) to "apache", and then granting write permission to the group. I will be happy to change the group (you can't do it yourself). Just ask me. You can change permissions (the "mod" bits) as follows:
chmod g+w upload

PostgreSQL Large Objects

Large objects (LO or BLOB for Binary Large OBject) are database objects that can be created, opened and read or written like files. In particular, very large objects can be read in chunks, and sent gradually to a client. On creation, you get an OID (object identifier, a number) which you need to remember. You will also need to know the "mime type" of the file if it is an image, so you can send the proper header. In these code examples, assume that a connection to the database has been made.
if (!is_uploaded_file($file['tmp_name'])
die ('Possible attack! File: ' . $file['name']."\n") ;
pg_query("begin"); // start transaction block, essential for LO
$oid = pg_lo_import($file['tmp_name']);
if ($oid==FALSE) die ("Import failed!");
$res = pg_query_params("update students set mug=$1, type=$2 where linux=$3",
array($oid, $imagetype, $linux));
if (pg_affected_rows($res) < 1)
pg_lo_unlink($oid); // get rid of large object we can't store oid of

Retrieving a large object (image)

Now, to retrieve the large object, let's suppose it is an image file, we will need to write a script that will return the whole file, with the content-type specified, this means sending a header, followed by all of the large object. Fortunately, there are PHP functions to do this easily. Possible problem: It was up to the original client to send us the correct type.

$linux = pg_escape_string($linux); // double any quote that comes in before db use
$database = pg_connect('dbname=timetable host=localhost user=mickey password=MoUsE') or die('Connect failed');
pg_query($database, "begin");
$res = pg_query($database, "select mug, type from students where linux='$linux'");
$row = pg_fetch_row($res);
if (!$row) die("We can'f find you, $linux");
$image_oid = $row[0];
if (!$image_oid) die ("We don't have your picture yet, $linux");
header("Content-type: $type");
$handle = pg_lo_open($database, $image_oid, "r");
pg_query($database, "commit");

Store in table as bytea

The type bytea is a binary version of text, an unlimited size of bytes. However, to insert such a binary value, many bytes that are not printing characters must be escaped. Likewise, when retreiving bytea fields, all the bytes are returned as escaped values. NOTE: You cannot pass the already-escaped string as a parameter, since that would cause further escaping!

Going in:

   $allbytes = file_get_contents($file['tmp_name']);
$allbytes = pg_escape_bytea($allbytes);
$res = pg_query_params("update students set mugb='$allbytes', typeb=$1 where linux=$2",
array($imagetype, $linux));
Getting out:
   $res = pg_query($database, "select typeb, mugb from students where linux='$linux'");
$row = pg_fetch_row($res);
if (!$row) die("We can'f find you, $linux");
$image = $row[1];
if (!$image) die ("We don't have your picture yet, $linux");
header("Content-type: $type");
print pg_unescape_bytea($image);

See this in action, (use second form)

Lin Jensen,