Uploading files to a web server can be a tricky process because a lot depends on the web server’s features, abilities, and limits. One might think that there’s a “standard” upload method, but unfortunately there’s not. How an Apache server running PHP handles things could be drastically different than a .NET server running Visual Basic or C#. Even within a family of servers sharing a common language, the rules vary from server to server and from upload script to upload script.

You must also be very careful about which files should be allowed to upload to your web server. Since you’re creating a script that anybody with the URL can run — and effectively send data to your server — you may open your server to being hacked unless you utilize caution and care. It’s quite easy with server-side scripts to allow the ability to write over critical files with malicious code, so if you don’t know what you’re doing on the server side, you may want to consider other options. In addition, your server may enforce limits on your upload abilities that are beyond your control. For example, many servers limit uploads to small files (under 2 MB), or they may limit the number of files that can be uploaded. All of these limitations must be considered before you begin implementing a file upload process.

Before You Begin

Before you proceed with this tutorial, you should read this manual to gain a clearer understanding of the entire process. This tutorial presents a “quick and dirty” method to upload files, but before you use this in production, you must make some adjustments and secure the script as necessary. You should anticipate that it’ll take some effort to get this right!

Using “network.upload()”

Corona’s network.* library contains an API called network.upload() which is a simple method for uploading files to a server, assuming that your server handles the simple method of uploading files using HTTP PUT. Most existing scripts that accept file upload probably won’t work with network.upload() because they’re looking for an HTTP POST form-based MIME multi-part upload format. Corona’s network.upload() API does not talk to these kinds of scripts, but we’ll discuss this further in a bit. First, let’s look at the Corona side…

The Corona Script

-- Callback function to handle the upload events that are generated.
-- There will be several events: one to indicate the start and end of the
-- process and several to indicate the progress (depends on the file size).
-- Always test for your error conditions!

local function uploadListener( event )
   if ( event.isError ) then
      print( "Network Error." )

      -- This is likely a time out or server being down. In other words,
      -- It was unable to communicate with the web server. Now if the
      -- connection to the web server worked, but the request is bad, this
      -- will be false and you need to look at event.status and event.response
      -- to see why the web server failed to do what you want.
   else
      if ( event.phase == "began" ) then
         print( "Upload started" )
      elseif ( event.phase == "progress" ) then
         print( "Uploading... bytes transferred ", event.bytesTransferred )
      elseif ( event.phase == "ended" ) then
         print( "Upload ended..." )
         print( "Status:", event.status )
         print( "Response:", event.response )
      end
   end
end

-- Sepcify the URL of the PHP script to upload to. Do this on your own server.
-- Also define the method as "PUT".
local url = "http://yourwebserver.com/somepath/upload.php"
local method = "PUT"

-- Set some reasonable parameters for the upload process:
local params = {
   timeout = 60,
   progress = true,
   bodyType = "binary"
}

-- Specify what file to upload and where to upload it from.
-- Also, set the MIME type of the file so that the server knows what to expect.
local filename = "image.jpg"
local baseDirectory = system.ResourceDirectory
local contentType = "image/jpeg"  --another option is "text/plain"

-- There is no standard way of using HTTP PUT to tell the remote host what
-- to name the file. We'll make up our own header here so that our PHP script
-- expects to look for that and provides the name of the file. Your PHP script
-- needs to be "hardened" because this is a security risk. For example, someone
-- could pass in a path name that might try to write arbitrary files to your
-- server and overwrite critical system files with malicious code.
-- Don't assume "This won't happen to me!" because it very well could.
local headers = {}
headers.filename = filename
params.headers = headers

network.upload( url , method, uploadListener, params, filename, baseDirectory, contentType )

The Script Demystified…

As with all network.* API calls, this operates asynchronously, meaning that it will return to your program immediately and process the upload in the background. However, you need to know the status of the upload, in particular when it completes, which is handled by the event listener function:

local function uploadListener( event )
   if ( event.isError ) then
      print( "Network Error." )
      -- This is likely a result of a timeout or the server being down.
      -- In other words, it was unable to communicate with the web server.
      -- If the connection to the web server worked, but the request is bad, this
      -- will be 'false' and you need to look at 'event.status' and 'event.response'
      -- to see why the web server failed to process your request.
   else
      if ( event.phase == "began" ) then
         print( "Upload started" )
      elseif ( event.phase == "progress" ) then
         print( "Uploading... bytes transfered ", event.bytesTransfered )
      elseif ( event.phase == "ended" ) then
         print( "Upload ended..." )
         print( "Status:", event.status )
         print( "Response:", event.response )
      end
   end
end

Let’s inspect this function in more detail. The first thing you must check is whether a network error occurred. This is returned in the event.isError attribute. In network programming, it’s important to understand that this error may indicate that the server is down, it’s unreachable, or it’s timing out. Effectively, it means that you never successfully communicated with the server.

You can successfully connect to and interact with the web server, asking it to do something that it can’t, but it will report a “success” in regards to the isError attribute. In other words, regardless of whether the server sends you a “right” or “wrong” response, you technically had a successful transaction with it. Thus, the else condition block is where you can inspect and handle the various phases of the upload. For instance, in the "began" phase, you may choose to display a widget.newProgressView(). Then in the "progress" phase, increment that widget’s status based on the amount of bytes transmitted. Finally, the "ended" phase lets you know that the web server has completed the upload process.

All finished, correct? Not so fast! The file may still have failed to upload for various reasons, for example, the file was too large, it was not a valid file name, or some permission issue on the server prevented it from uploading. Thus, you should check the event.status attribute which will hold the HTTP “result” code, such as 201 (success) or 403 (permission denied). Depending on the server, there may be additional information in the event.response attribute that can indicate where the problem resided.

Now, let’s re-inspect the various variables and tables required for the network.upload() API call. These include:

  • The URL of the server script to execute.
  • The HTTP method (in this case we’re going to use “PUT”).
  • Some parameters to configure the API Call, including the timeout, body type, and whether you want progress updates.
  • The filename to upload.
  • The source directory where the file can be found.
  • A MIME type string indicating the file type for the server’s use.
  • A custom header for the PHP script (we’ll discuss this further down).
-- Sepcify the URL of the PHP script to upload to. Do this on your own server.
-- Also define the method as "PUT".
local url = "http://yourwebserver.com/somepath/upload.php"
local method = "PUT"

-- Set some reasonable parameters for the upload process:
local params = {
   timeout = 60,
   progress = true,
   bodyType = "binary"
}

-- Specify what file to upload and where to upload it from.
-- Also, set the MIME type of the file so that the server knows what to expect.
local filename = "image.jpg"
local baseDirectory = system.ResourceDirectory
local contentType = "image/jpeg"  --another option is "text/plain"

local headers = {}
headers.filename = filename
params.headers = headers

Because this method has no way of telling the server the name of the file to save, we must create a special header named filename that will contain that name (the file we want to use on the remote server). With this step done, add the headers table to the params table and then call network.upload():

network.upload( url , method, uploadListener, params, filename, baseDirectory, contentType )

The PHP Script

For usage with this tutorial, we’re providing a sample PHP script for download. This script is fairly simple and straightforward, but unless you know PHP, it will look intimidating. Before you attempt to modify it for your own project, there are a few important things to understand:

1. Security

When you write a script online, it’s 100% your responsibility to ensure that it cannot be easily hacked. Scripts that write files to the server are the most vulnerable. You may think that only your app will use it, but once it’s on a website, anybody can execute it, figure out the parameters and the methods, and find holes to exploit. We’ve taken some basic steps to prevent this, but you need to take it the rest of the way. This script lets the caller set the filename which is inherently dangerous since people can put in tricks to create false paths that may let them write files to arbitrary locations. Your script should never run at elevated permissions, and your file system should be read-only wherever there are executable script files or system binary executables. Finally, you should do your best to scrub any provided file name before putting this code into production. The safest thing is to prevent the caller from specifying the filename, but this can create usability issues in regards to keeping the files organized.

2. Know Your Server’s Limits

Many PHP servers, if they allow uploads at all, will have very tight limits on the file size that can be uploaded. The code above has a size limit check, but it only works if the server allows larger files and you want to limit the size. In this sample, we allow up to a 5 MB file, but the server itself may only allow 1 MB. The server may not even reach your script if the file is too large, so it’s your responsibility to control the limits. Many websites live in a shared environment with other websites and you should be a good network citizen!

3. Handling Files of the Same Name

The code above uses a simple (but somewhat flawed) method of adding an increasing sequence number to the end of the string. It stops incrementing the number at 100. The while loop that checks for duplicate file names can’t run forever and after you hit 100 uploads of the same name, it will start overwriting the 100th file. Again, this is not production-ready script and you must adapt it to your needs. Another potential method is to get a list of files, find the one with the highest number, parse the name from the number, and increment it.

4. The Upload Directory

This script assumes that you’ll create a folder named upload as a child folder of where this script is located, but this may not be the best option for your website. This upload folder must have write permissions for the ID your web server runs at. Generally the web server will not run with an ID or within a group that your account has access to. This means the folder needs to be World writable. As a safety precaution, you probably don’t want the folder to have READ or EXECUTE/LIST privileges for the World user. Finally, your server may need to dump the files in a completely different location in your server tree, so you must figure out that path and adjust the PHP script accordingly.

In Summary

As you can see, the act of uploading files can be a complex task. Hopefully this tutorial has shed some light on the process. As always, please leave your questions and comments below. You may also download the PHP script based on this tutorial if you wish to use it as a foundation for your own implementation.

  1. Talk about going above and beyond the call of duty! Thanks for taking the time to share this Rob.

    Is there any particular reason why network.upload() does not support the POST form-based MIME multi-part upload format?

    Best.

        • Hey Nathan,

          I would recommend going through the Web API documentation at Microsoft if you have a better understanding of MVC type architectures as it utilizes an MVC structure that accepts web requests. If not – I would recommend looking into WCF web services as they are very versatille and you can create web services that accept practically any type of data, such as custom classes, and allows you to do whatever you’d like with it.

          This is a great starting point for Web Api – which in my opinion is very easy to use and nice! http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api

          As for the data being passed from Corona to the web service – it wouldn’t matter if it is coming from Corona or jQuery through a webpage. Web Api or WCF only care about the data it receives and that is always standard in terms of HTTP requests/responses (GET, POST, PUT, DELETE). You can easily create a custom type within a WCF web service that contains properties you pass in as an array of that “type” from a JSON request. There are tons of avenues to explore!

          Hope that helps!

  2. Hi Nathan,

    I posted this a few weeks ago – uploading files with ASP.Net – but I used network.request and HTTP “POST”. I’ve been using this as a basis for my most recent project and now, using json.encode and then decoding on the .Net side I have multiple files being uploaded in one go which is working nicely.

    http://forums.coronalabs.com/topic/44199-example-file-upload-using-aspnet-and-a-progress-bar/

    Does anyone know if there’s any reason if this approach is no longer appropriate? Should we be using PUT and network.upload instead…?

    Thanks,

    Ian

  3. Thanks for the tutorial. I am however having trouble with the event.phase check firing on iOS 7.1 devices (iphone 5S). The application seems to ignore the if-then-elseif block using event.phase. Other event triggers do fire (event.completed) on both the device and simulator, but the event.phase code only works on the corona simulator and fails to execute on the xcode simulator or ios device. any help is appreciated!

  4. yes, progress = true in the params. The block functions as expected on the corona simulator with the event.phase updates. I know the script executes to completion as the file I am uploading does appear on the server even from device side its just that any display objects I instantiate in the event.phase conditional on the device get ignored. I have tried to debug on the ios simulator but all print statements in the event.phase block get ignored as well.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>