Penggunaan fungsi SANTITIZE pada PHP

Dalam mengembangkan aplikasi, salah satu bagian yang paling penting adalah bagian antarmuka pengguna. Antarmuka pengguna penting karena bagian inilah yang paling banyak dilihat dan berinteraksi dengan pengguna. Pada kebanyakan kasus, kualitas dari antarmuka merupakan penentu dari keberhasilan perangkat lunak tersebut.

Dalam dunia web, antarmuka pengguna direpresentasikan dengan HTML dan CSS. Tampilan yang dihasilkan dengan HTML dan CSS kemudian dapat kita kembangkan menjadi interaktif dengan Javascript. Pada bagian Request dan Response kita telah melihat bagaimana aplikasi web pada dasarnya hanya merupakan pembentuk HTTP Response dinamis, berdasarkan sumber daya tertentu. Bentuk paling umum dari HTTP Response yang diberikan ke pengguna adalah kombinasi antara HTML, CSS, dan Javascript.

CSS dan Javascript umumnya bersifat statis, karena kedua bagian ini hanya berisi kode untuk mengatur tampilan dan interaksi. HTML, sebagai bagian yang mendefinisikan tampilan lah yang selalu bersifat dinamis, berubah sesuai dengan data atau informasi yang ingin ditampilkan. Perubahan yang ada pada HTML juga seringkali tidak 100% sesuai dengan data. Misalnya, bagian logo dan menu aplikasi umumnya selalu sama pada setiap halaman, tetapi bagian isi selalu berbeda-beda.

Untuk dapat menghasilkan HTML yang bersifat setengah dinamis, kita dapat memanfaatkan sebuah sistem yang dikenal dengan sistem templating. Sistem templating memanfaatkan Template Engine untuk menghasilkan keluaran yang diinginkan. Apa itu template engine? Perhatikan gmabar berikut:

Berdasarkan gambar di atas, kita dapat mendefinisikan template engine sebagai sebuah (komponen) perangkat lunak yang dirancang untuk menggabungkan sebuah template (contoh) dengan data untuk dijadikan dokumen hasil. Pada umumnya di dunia web, dokumen yang dihasilkan oleh template engine merupakan dokumen HTML, walaupun tidak jarang juga kita menghasilkan data lain seperti PDF atau JSON. Pada beberapa kasus ekstrim, template engine bahkan digunakan untuk menghasilkan kode program tertentu berdasarkan masukan pengguna.

Fitur Umum Template Engine¶

Sebagai komponen perangkat lunak yang kerap digunakan dalam berbagai lingkungan dan situasi, template engine memiliki beberapa fitur umum, yaitu:

  1. Perulangan dan percabangan.
  2. Pembuatan fungsi.
  3. Fungsi bawaan (biasanya berhubungan dengan teks).
  4. Blok dokumen.
  5. Penggabungan file (include).
  6. Pewarisan template.

Seperti yang dapat dilihat di atas, template engine pada umumnya memiliki fitur yang mirip dengan sebuah bahasa pemrograman (perulangan dan percabangan, pembuatan fungsi, dan fungsi bawaan). Hal ini disebabkan karena pada pembuatan template seringkali kita akan memerlukan cara untuk menampilkan data secara berulang. Misalnya, kita dapat saja ingin menampilkan data di dalam tabel, dan baris dari tabel merupakan data yang diambil dari basis data. Metode paling efektif untuk menampilkan data tersebut tentunya dengan melakukan perulangan yang mengeluarkan baris tabel (tr).

Untuk lebih jelasnya, perhatikan contoh kode berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

<!DOCTYPE html>
<html>
    <head>
        <title>My Webpage</title>
    </head>
    <body>
        <ul id="navigation">
        {% for item in navigation %}
            <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
        {% endfor %}
        </ul>

        <h2>My Webpage</h2>
        {{ a_variable }}
    </body>
</html>

Contoh kode di atas merupakan contoh kode yang kita ambil dari halaman dokumentasi Twig, yang merupakan sebuah templating engine populer dari PHP. Pada contoh di atas, kita melihat bagaimana Twig digunakan untuk menampilkan menu navigasi, yang melakuakn perulangan terhadap variabel navigation untuk menghasilkan elemen li.

Beberapa hal lagi yang dapat diperhatikan pada kode di atas:

  1. Terdapat campuran antara HTML dengan kode Twig yang dibungkus dalam {% %} ataupun {{ }}.
  2. Data dapat disimpan ke dalam sebuah variabel.
  3. Tidak ada perbedaan pada bagian dinamis dan statik dari HTML.
  4. Jika kita membaca dan menjalankan kode dari atas sampai bawah, kita akan mendapatkan kode HTML lengkap.

Sehingga dapat kita simpulkan bahwa sebuah template pada template engine umumnya merupakan kode program yang jika dieksekusi akan menghasilkan keluaran tertentu. Karena memiliki bagian yang statis dan dinamis, template engine juga harus memiliki penanda antara bagian statis dan dinamis ({% %} dan {{ }} pada contoh). Pembaca yang telah berpengalaman dalam pengembangan PHP mungkin akan melihat kesamaan antara PHP dengan templating engine. Dugaan ini tidak dapat dikatakan salah, meskipun PHP kurang optimal sebagai bahasa template.

Seperti ketiga fitur awal, fitur-fitur tambahan lain dari template engine juga pada dasarnya dibuat untuk mempermudah pengembangan halaman dinamis. Blok dokumen dibuat agar kita dapat dengan mudah mengganti hanya bagian spesifik dari dokumen, penggabungan file digunakan agar kita dapat mengorganisasikan bagian statik dan dinamis dengan lebih rapi, dan pewarisan (inheritance) dapat digunakan untuk menyederhanakan pengembangan template yang didasarkan pada template lain. Kita akan melihat fitur-fitur ini dan kenapa fitur ini sangat menyenangkan nantinya.

Komponen Template Engine¶

Sebuah template engine umumnya memiliki beberapa komponen utama, yaitu:

  1. Komponen model data, misalnya: basis data atau file.
  2. Komponen template, merupakan kode yang merepresentasikan dokumen keluaran.
  3. Komponen template engine, yang menggabungkan komponen no 1 dan 2 menjadi no 4.
  4. Komponen keluaran, dokumen hasil yang diinginkan.

Karena fungsi pada bahasa pemrograman sering ditemui juga pada template engine, biasanya pengembang template engine akan menyediakan sistem khusus untuk ini. Beberapa pendekatan umum yang digunakan untuk bahasa pada template engine yaitu:

  1. Menggunakan bahasa pemrograman yang sudah ada (misal: PHP, Javascript, Python).
  2. Mengguankan bahasa sederhana yang khusus dikembangkan (contoh: Haml, Twig, JADE).
  3. Mengguanakan antarmuka pengguna untuk bagian dinamis (contoh: Mail Merge pada Microsoft Word).

Karena spesifiknya sebuah template engine ini, sistem template engine umumnya berdiri sendiri tanpa terikat kepada sistem lainnya. Kebanyakan template engine dirancang untuk diintegrasikan ke dalam aplikasi atau framework lain.

Implementasi Template Engine Sederhana¶

Untuk melihat bagaimana sebuah template engine bekerja, kita akan mencoba untuk mengimplementasikan sebuah template engine sederhana, dan kemudian mengintegrasikan template engine ini ke sistem routing dan server yang telah kita kembangkan pada bagian sebelumnya.

Template engine yang akan kita kembangkan bersifat sederhana, hanya memiliki sebuah fitur yaitu menampilkan data secara dinamis. Misalnya, untuk template seperti berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>{{ PageTitle }}</title>
    </head>
    <body>
        <h2>{{ PageTitle }}</h2>

        <p>
            {{ Content }}
        </p>
    </body>
    </html>

dapat kita panggil dengan mengisikan data seperti berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

server.AddRoute("GET", /^\/$/, function (params) {
    var that = this;
    var content = 'Hello, world!';
    var data = { 'PageTitle': 'Template!', 'Content': content };

    that.view.Render('./temp.html', data)
        .then(function (result) {
            that.response.writeHead(200, { 'Content-Type': 'text/html', 'Content-Length': result.length });
            that.response.write(result);
            that.response.end();
        });
});

Perhatikan bagaimana tetap harus melakukan penulisan HTTP Response secara manual. Pada integrasi template engine yang ideal, hal ini harus telah dilakukan secara otomatis.

Langsung saja, kita akan memulai implementasi dengan membuat objek kosong yang akan kita kembangkan:

var Template = function () {
    var TemplateObject = {};

    TemplateObject.Render = function (template, data) {
    };

    return TemplateObject;
};

module.exports = Template();

Seperti yang dapat di lihat pada kode di atas, objek kita memiliki hanya satu buah method, Render, yang menerima dua buah parameter, yaitu:

  1. template berisi lokasi dari file template yang akan diolah.
  2. data, yaitu data yang akan diberikan kepada file template.

Pembacaan file akan kita lakukan dengan modul fs dari NodeJS. Karenanya kita akan menambahkan modul ini ke dalam modul kita:

var fs = require('fs');

var Template = function () {
    // lanjutan...

Selanjutnya, kita dapat menggunakan fungsi fs.readFile untuk membaca file. Jika kita lihat dari dokumentasi fs.readFile, kita dapat melihat bagaimana fungsi ini menggunakan sistem asinkron. Untuk mempermudah kode, kita akan menggunakan sistem Promise untuk menyederhanakan kode kita.

Sayangnya, NodeJS tidak mendukung Promise secara langsung. Kita harus menambahkan pustaka pihak ketiga untuk mendapatkan fitur ini. Langsung saja, tambahkan devDependencies pada package.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

{
    "name": "NodeJSTemplating",
    "version": "0.0.0",
    "description": "NodeJSTemplating",
    "main": "server.js",
    "author": {
        "name": "berts",
        "email": ""
    },
    "devDependencies": {
        "promise": "7.0.0",
    }
}

dan jalankan perintah npm install untuk melakukan instalasi modul tersebut. Setelah selesai, kita kemudian dapat menggunakan Promise dengan memasukkan modul Promise:

var fs = require('fs'),
    Promise = require('promise');

Selanjutnya, sebagai langkah awal kita akan membuat fungsi yang membaca file dengan Promise, untuk nanti digunakan oleh Template.Render:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

_ReadFile = function (path) {
    var _readFile = function (path, success, fail) {
        fs.readFile(path, { 'encoding': 'utf8' }, function (err, data) {
            if (err) fail(err);

            success(data);
        });
    };

    return new Promise(function (resolve, reject) {
        _readFile(path, resolve, reject);
    });
};

Kode di atas cukup sederhana, hanya menggabungkan konsep dasar dari Promise dengan fungsi pembacaan file fs.readFile. Pada dasarnya kita melakukan dua hal pada fungsi ini:

  1. Membuat sebuah fungsi _readFile yang bertugas membaca file dengan menggunakan fungsi fs.readFile, dan
  2. Memasukkan fungsi tersebut di dalam Promise untuk dieksekusi nantinya.

Jika anda tidak mengerti kod di atas, silahkan baca pembahasan tentang Promise dan dokumentasi fungsi fs.readFile.

Setelah memiliki fungsi khusus untuk membaca file, kita kemudian dapat mengimplementasikan fungsi yang akan mengganti seluruh variabel yang ada dalam template dengan nilai yang ada dalam data. Berikut adalah kode yang kita gunakan:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17

TemplateObject.Render = function (template, data) {
    var _tag, _tagValue, _match,
        _parseRE = /{{([^}}]+)?}}/g,
        _result = '';

    return _ReadFile(template).then(function (templateContent) {
        _result = templateContent;

        while (_match = _parseRE.exec(_result)) {
            _tag = _match[0];
            _tagValue = data[_match[1].trim()];
            _result = _result.replace(_tag, _tagValue);
        }

        return _result;
    });
};

Penjelasan dari kode di atas (bagian inisialisasi variabel kita lewatkan):

Bagian return _ReadFile(template).then(function (templateContent) {

Pertama, kita memanggil _ReadFile untuk membaca file pada template.

Bagian _result = templateContent

Setelah file selesai dibaca dan disimpan pada templateContent, kita akan melakukan kopi isi templateContent ke _result.

Bagian while (_match = _parseRE.exec(_result)) {

Selanjutnya kita melakukan pencarian pola di dalam file dengan menggunakan regular expression (RE). RE yang kita gunakan ada pada variabel _parseRE. RE ini pada dasarnya melakukan pencocokan terhadap segala pola string yang berbentuk {{ [nilai-apapun] }}. Pada dokumentasi fungsi RegExp.prototype.exec kita dapat melihat bahwa fungsi ini mengembalikan beberapa nilai:

  1. Seluruh string yang cocok dengan pola (misal: {{ Content }}).
  2. Substring pada bagian () (capture) dari RE. Misalnya :code:` Content . Dapat berupa banyak nilai jika terdapat banyak :code:`().
  3. Indeks dari lokasi string yang sesuai pola.
  4. String asli.

Nilai-nilai yang kita dapatkan dan simpan pada _match ini nantinya akan kita gunakan untuk memperbaharui template. Setelah diproses, kita akan melakukan perulangan pada pengisian _match ini, sampai tidak ada lagi pola yang cocok pada template.

Bagian berikutnya, yaitu 3 baris kode dalam while:

_tag = _match[0];
_tagValue = data[_match[1].trim()];
_result = _result.replace(_tag, _tagValue);

Pada bagian ini kita melakukan 3 hal sekaligus:

  1. Memasukkan pola yang tepat (misal: {{ Content }}) ke dalam _tag.
  2. Membersihkan dan mengambil isi dalam capture (misal: Content) dari data. Jika data berisi { 'Content': 'Hello!' } maka _tagValue akan berisi "Hello!" sekarang.
  3. Menggantikan {{ Content }} dengan Hello! pada _result dan menyimpannya kembali ke _result.

Singkatnya, pada tiga baris di atas kita akan mengisikan nilai variabel dalam template dengan data yang diberikan. Hal ini dilakukan secara berulang, sampai seluruh variabel yang ada tergantikan oleh data.

Pada bagian terakhir, yaitu return _result kita mengembalikan hasil akhir dari template, yang telah dapat langsung dicetak seperti pada kode penggunaan di awal. Kita kemudian dapat mencoba menggunakan template engine ini dengan cara seperti berikut:

var view = require('./Template'),
    data = {'Title': 'Template!', 'Content': 'Hello, world!'};

view.Render('./contoh.tmpl', data).then(function (result) {
    console.log(result);
});

dengan isi file contoh.tmpl seperti berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

<!DOCTYPE html>
<html>
    <head>
        <title>{{ Title }}</title>
    </head>
    <body>
        <h2>{{ Title }}</h2>
        <p>
            {{ Content }}
        </p>
    </body>
</html>

Jika dijalankan, HTML yang dihasilkan adalah seperti berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

<!DOCTYPE html>
<html>
    <head>
        <title>Template!</title>
    </head>
    <body>
        <h2>Template!</h2>
        <p>
            Hello, world!
        </p>
    </body>
</html>

yang kemudian dapat kita format menjadi sebuah HTTP Response jika diinginkan.

Sampai pada titik ini, template engine yang telah kita kembangkan sudah dapat bekerja dengan cukup baik, tetapi terdapat satu hal yang kita lewatkan dalam implementasi ini, yaitu keamanan.

Keamanan pada Template Engine¶

Pada kode template engine sebelumnya, ketika menerima sebuah data dalam variabel kita langsung menggantikan data tersebut menjadi kode HTML. Hal ini mengakibatkan ketika data string yang dikirimkan memiliki kode di dalamnya, seperti:

var data = {
    'Content': '<script type="text/javascript">alert("DING");</script>'
};

maka kode javascript yang ada di dalam akan dieksekusi. Hal ini merupakan lubang keamanan yang sangat besar, karena jika kita mengembangkan aplikasi yang dapat menerima masukan pengguna seperti kotak komentar, pengguna akan dapat memasukkan kode yang akan dieksekusi oleh pengguna lain. Kode yang dimasukkan ini kemudian dapat digunakan untuk mencuri identitas pengguna lain, atau bahkan menutupi akses ke website kita dengan mengirimkan pengguna ke web lain begitu kode dieksekusi.

Lubang keamanan yang dieksploitasi pada contoh di atas kita kenal dengan nama Cross Site Scripting (XSS). XSS merupakan lubang keamanan yang cukup kompleks dengan berbagai area penyerangan, yang tidak akan kita bahas secara mendalam pada bagian ini. Ingat bahwa contoh yang diberikan hanya merupakan area penyerangan XSS yang spesifik. XSS masih bisa dieksploitasi dengan cara lain!.

Untuk menanggulangi XSS pada contoh, kita dapat menggunakan dua buah pendekatan:

  1. HTML Sanitizing, proses di mana kita melakukan pembersihan terhadap teks yang akan dikeluarkan dari template. Proses ini akan membersihkan HTML dari tag yang membahayakan seperti <script> sehingga kalaupun pengguna mengisikan <script> ke dalam konten, data tersebut tidak akan dieksekusi.
  2. Input / Output Encoding, merupakan proses konversi data masukan ataupun keluaran menjadi nilai yang aman. Misalkan tag <script> akan dikonversi menjadi &lt;script&gt; agar tidak dianggap sebagai kode HTML yang dapat dieksekusi.

Kedua pendekatan di atas umumnya sudah cukup efektif untuk mengantisipasi XSS, walaupun pendekatan ini bergantung kepada kemampuan dari kdoe HTML Sanitizing atau Input / Output Encoding yang kita gunakan. Integrasi penggunaan pustaka sendiri sangat mudah. Kita cukup melakukan HTML Sanitizing atau Input / Output Encoding ketika akan menggantikan nilai variabel.

Misalnya, untuk kode sebelumnya, kita dapat menambahkan paket sanitize-html. Tambahkan paket ke dalam package.json sehingga package.json berisi:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

{
    "name": "NodeJSTemplating",
    "version": "0.0.0",
    "description": "NodeJSTemplating",
    "main": "server.js",
    "author": {
        "name": "berts",
        "email": ""
    },
    "devDependencies": {
        "promise": "7.0.0",
        "sanitize-html": "1.6.*"
    }
}

Kemudian jalankan npm instlall untuk memasang sanitize-html. Kita kemudian dapat mengubah isi dari Template.Render menjadi:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

// File: Template.js

// bagian require
var fs = require('fs'),
    sanitizer = require('sanitize-html'),
    Promise = require('promise');

// bagian Template.Render
while (_match = _parseRE.exec(_result)) {
    _tag = _match[0];
    _tagValue = sanitizer(data[_match[1].trim()]);
    _result = _result.replace(_tag, _tagValue);
}

Perhatikan bagaimana kita hanya mengganti satu baris kode, di bagian _tagValue. Pemanggilan fungsi sanitizer akan memastikan kode HTML yang akan masuk ke template hanya merupakan kode HTML yang aman saja.

Sampai di sini kita telah berhasil mengembangkan sebuah template engine sederhana dan juga menutupi sedikit lubang keamanan. Pada sebuah template engine yang lengkap, hal ini akan sedikit lebih sulit dilakukan karena umumnya kita akan mengikutkan komponen kode yang dapat dieksekusi seperti fungsi dan perulangan. Begitupun, sedikit mengetahui cara kerja template engine dapat membantu kita dalam menulis kode yang lebih baik karena kita melihat berbagai pertimbangan seperti keamanan dan pencocokan pola yang ada dalam sebuah template engine.

Pada bagian berikutnya, kita akan mencoba sebuah alternatif yang lebih dapat digunakan di dunia nyata: mengintegrasikan aplikasi web kita dengan template engine yang telah ada.

Template Engine Menggunakan JADE¶

Pada kebanyakan waktu, menggunakan template engine yang telah ada akan jauh lebih menguntungkan kita. Waktu pengembangan akan sangat banyak dihemat karena fitur-fitur yang telah dikembangkan oleh template engine lain sebelumnya. Sistem keamanan juga biasanya telah lebih teruji, karena template engine telah digunakan oleh banyak orang.

Salah satu template engine yang paling populer pada NodeJS adalah JADE. Untuk menggunakan JADE, kita dapat memasukkan JADE ke dalam package.json terlebih dahulu:

"devDependencies": {
    "jade": "1.9.*"
}

dan kemudian kita dapat memperbaharui kode pada Template.Render menjadi jauh lebih sederhana. Keseluruhan kode dari Template.js bahkan akan menjadi sangat sederhana, seperti berikut:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

var jade = require('jade');

var Template = function () {
    var TemplateObject = {};

    TemplateObject.Render = function (path, data) {
        var _temp, _result;

        _temp = jade.compileFile(path, { locals: data });
        _result = _temp(data);

        return _result;
    };

    return TemplateObject;
};

module.exports = Template();

dan JADE dapat kita gunakan dengan cara yang sama dengan ketika kita mengembangkan template engine kita sendiri:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

server.AddRoute("GET", /^\/$/, function (params) {
    var locals, result;

    locals = {
        'PageTitle': 'Jade!',
        'Content': 'Hello, World! <script type="text/javascript">alert("XSS!");</script>'
    };

    result = this.view.Render('./temp.jade', locals);

    this.response.writeHead(200, { 'Content-Type': 'text/html', 'Content-Length': result.length });
    this.response.write(result);
    this.response.end();
});

Perlu dicatat bahwa karena sekarang kita telah menggunakan JADE, file template yang akan kita bangun akan memiliki ekstensi .jade dan dituliskan sesuai dengan aturan JADE. Misalnya pada kode di atas kita dapat mengisikan kode pada temp.jade seperti berikut:

doctype html
html
    head
        title= PageTitle
    body
        h2= Content

jauh lebih sederhana, dan kita dapat menggunakan berbagai fitur dari JADE tanpa perlu menambahkan terlalu banyak kode. Jika ingin mengintegrasikan JADE lebih dalam, silahkan rujuk ke dokumentasi API JADE.

Penutup¶

Sampai titik ini kita telah melihat bagaimana sebuah template engine bekerja, dan apa saja yang ditawarkan oleh sebuah template engine secara umum. Berbagai kode yang kita tuliskan pada bagain ini dapat diakses pada versi lengkapnya di Github:

  1. Template Engine Standar
  2. Template Engine yang mengintegrasikan JADE

Di bagian selanjutnya kita akan melihat tentang basis data dan berbagai hal yang berhubungan dengan basis data pada NodeJS.