IBM i Modernization - The User Interface
(Part 16)
I'd like to show an IBM i-centric utility that enables users to select multiple files from their browsers and upload them to the server. Smart phones can optionally take camera shots and upload images.
There are quite a few use cases for this type of application. Schools may need to upload student photos and attach them to student records. Email clients may need to attach files to email messages. Help desks may need to attach files to trouble tickets. Case workers may need to attach files to items in their database. And lots more.
Please click on the following screen shot to try the application.
What Does it Do?
This utility is designed for browsers that have implemented the HTML5 specification for multi-file selection.
- Users are prompted to upload files.
- The local device's file-selection dialog is displayed when a button is clicked.
- The file-selection dialog generates a collection of "file" objects including file attributes (name, size, last update date, etc.)
- An alert is displayed if the sum of the sizes exceeds a maximum.
- An RPG program generates an "approved" name for each file to be stored on the server.
- A Net.Data macro is used to receive each file, validate its name, grant *public authority, and move it to an application directory.
- A status messages is displayed for each file uploaded.
- A final completion message is displayed after the last file is received.
- A hyperlink is added to the page for each file received.
- Users may click hyperlinks to view uploaded files.
Many (perhaps most) examples on the Internet upload all files selected by users - posting just one "upload" request. The interface implemented in PHP (i.e. the $_FILES Collection) was designed with that technique in mind.
This example (in contrast) asynchronously iterates through the browser's collection of files one at a time so that status updates might be provided during the "upload" process. This enables servers to apply granular validation if needed.
How Does it Work?
The devices's file-selection dialog is linked to an <input> element, which is defined as follows:
- The type="file" attribute links the component to the device's file-selection dialog.
- The "multiple" keyword enables multiple files to be selected.
- The "this.files" refers to the "FileList" collection that is generated by the dialog.
- The onChange() event listener is triggered when users return from the file-selection dialog.
Let's review the custom JavaScript for this utility.
check_size() is invoked when users complete the file-selection dialog. The code is as follows:
function check_size(files) {
var sumSize = 0;
var maxSize = 2000000;
fileList = files;
listIndex = 0;
while (hlinks.childElementCount > 0) hlinks.removeChild(hlinks.childNodes[0]);
for (i=0; i < fileList.length; i++) {
sumSize = sumSize + fileList[i].size;
}
if (sumSize > maxSize) {
alert('Sorry, maximum size of files (2,000,000 bytes) was exceeded: bytes = ' + sumSize);
return;
}
fctl.disabled = true;
req_obj();
}
High-level explanation of check_size():
- Any hyperlinks left over from the last upload are removed from the document.
- File sizes are summed and validated against a maximum.
- An error alert is displayed if the size validation is exceeded.
- The file-selection dialog is disabled by disabling its <input> element.
- req_object() is invoked to generate a authorized server file name (including IFS directory).
The req_object() code is as follows:
function req_obj() {
reqGet('/rdcaller/toobj.shtml?rwappid=iui104&fname=' + fileList[listIndex].name);
}
High-level explanation of req_obj():
- The "toobj" action is sent to an RPG program named IUI104.
- The name of the file on the local device is included as a query-string parameter.
The upload() function is invoked in response from the IUI104 program. The code is as follows:
function upload(name) {
toobj = name;
reqUpload('/db2www/upload.ndm/doload',fileList[listIndex],toobj);
um.innerHTML='Uploading ' + fileList[listIndex].name;
}
High-level explanation of upload():
- The file "name" generated by the server is assigned to "toobj", which has document-level scope (other functions can refer to it).
- reqUpload() is a framework API that sends the file to a Net.Data macro (script).
- The status message is updated.
The cf() function is invoked in response from the Net.Data script. The code is as follows:
function cf() {
add_link();
listIndex = listIndex + 1;
if (listIndex < fileList.length) {
return req_obj();
}
um.innerHTML='Upload completed normally.';
fctl.disabled = false;
}
High-level explanation of cf():
- add_link() is invoked to add a hyperlink for the file that was just uploaded.
- listIndex is incremented to point to the next file in the fileList collection.
- req_obj() is invoked to repeat the upload process - for the "next file".
- At the end of the cycle a completion message is assigned and the file-selection dialog is re-enabled.
The add_link() code is as follows:
function add_link() {
var a = document.createElement('A');
a.className = 'fl il pd-5 mg-1 cl-1';
a.target = '_blank';
a.href = toobj;
a.innerHTML = fileList[listIndex].name;
hlinks.appendChild(a);
}
High-level explanation of add_link():
- A hyperlink is created (tagname="A").
- Attributes are assigned to the hyperlink.
- The hyperlink is appended to a <div> which is defined in the <body>.
An RPG program named IUI104 responds to the "toobj" request from browsers. The code is as follows:
fxupl100p uf a e k disk usropn //----------------------------------------------------------------- // procedure prototypes //----------------------------------------------------------------- /copy *libl/qrpglesrc,rdstrapi#1 /copy *libl/qrpglesrc,rdwtnapi#1 //----------------------------------------------------------------- // module level data //----------------------------------------------------------------- d rw e ds extname(rwpgmc) qualified d s1 s * inz(*null) d toobj s 128a varying //----------------------------------------------------------------- // program entry //----------------------------------------------------------------- c *entry plist c parm rw /free //----------------------------------------------------------------- // set references to screen and cursor //----------------------------------------------------------------- if s1 <> *null; wtnSetInst(s1); endif; //----------------------------------------------------------------- // branch to subroutines based on requested actions //----------------------------------------------------------------- select; when rw.action = 'INIT'; exsr do_init; when rw.action = 'TOOBJ'; exsr do_toobj; endsl; return; //----------------------------------------------------------------- // initialization //----------------------------------------------------------------- begsr do_init; s1 = wtnOpen('IUI104'); endsr; //----------------------------------------------------------------- // generate and return a file name //----------------------------------------------------------------- begsr do_toobj; open xupl100p; toobj = '/rd/iui/iui104/' + %trim(wtnQryGet('fname')); toobj = strReplace(toobj:' ':'_'); setll (toobj) xupl100r; if not %equal(); id = toobj; write xupl100r; endif; close xupl100p; wtnRecSet('UPLOAD'); wtnFldSet('toobj':toobj); wtnRecWrt('UPLOAD'); endsr; /end-free
High-level explanation of IUI104:
File-upload utilities that are made available on web sites may be used by hackers to upload changes to web sites that exploit users. To prevent that, IUI104 generates names for uploaded files and stores their values in a DB table (i.e. XUPL100P).
The Net.Data macro checks to ensure that the entry exists. This removes the possibility of a hacker overwriting other files that may be exposed on the site.
- Framework APIs are used to communicate with browsers.
- File names are generated and stored in table XUPL100P.
- In this case the local file name is used, but it is prefixed with an approved path. Space characters in the file name are replaced with the underscore (_) character.
The Net.Data upload() script is invoked by browsers that upload files. The code is as follows:
%function(dtw_system) chgAut() {
%exec { CHGAUT.CMD OBJ('$(fname)') USER(*PUBLIC) OBJAUT(*ALL) DTAAUT(*RX)%}
%}
%function(dtw_system) moveFile() {
%exec { MOV.CMD OBJ('$(fname)') TOOBJ('$(toobj)') %}
%}
%function(dtw_directcall) checkObj(
in varchar(128) p1,
out char(1) p2) {
%exec {/QSYS.LIB/RDPTL.LIB/CHECKUPL.PGM %}
%}
%html(doload) {
@dtw_assign(path, $(toobj))
@dtw_assign(found, " ")
@checkObj(path, found)
%if (found == "1")
%if (@dtwf_rexists(toobj)=="Y") @dtwf_remove(toobj) %endif
@chgAut()
@moveFile()
cf();
%endif
%}
High-level explanation of Net.Data upload():
Net.Data tends to have somewhat terse syntax. But it is an exceptional tool for invoking calls to ILE programs, IBM i system commands, and other language environments (i.e. SQL).
- @checkObj() is a wrapper around an RPG program that ensures that the "toobj" is authorized (exists in Table XUPL100P).
- @chgAut() is a wrapper around an IBM i command that grants *public authority to the uploaded file.
- @moveFile() is a wrapper around an IBM i command that moves uploaded files from a temporary directory to a final one.
- When complete, returns "cf();" that invokes the function in the browser.
Wrapping Up
A file-upload utility like this provides an essential element for applications that upload and "attach" various types of files to database records, email messages, SMS messages, etc. I may delve further into that in the next piece.
As an alternative to the "look and feel" of the "input element" that browsers link to file-selection dialogs, some developers create their own widgets that may have a different look and feel.
Some of the code could be "hardened" to further frustrate hacking (i.e. ensuring that browsers accept cookies and monitoring their "sessions").