Avoiding Overengineering

How I used a sledgehammer to swat a fly on Archifolios

Avoiding Overengineering

A startup i founded Archifolios requires text to be entered into some parts of the application, to be saved to the database and later displayed to the users. These inputs are descriptions of posts, comments on posts, and reviews.

I had received a user complaint that the text they were entering was not being properly formatted when later fetched. This meant that even though the text was entered on multiple lines, to have paragraphs, when later fetched and displayed in HTML, it was showing as a single block of text.

My solution was to scour the internet for a free WYSIWYG editor, and I landed upon TinyMCE. I implemented it in the app, and converted the input text into html, which I then saved and later retrieved and displayed in the app, whose frontend was in Vue JS, using the "v-html" directive.

My reasoning was that using the editor and setting its options to exclude script tags was enough security from XSS attacks, and as for SQL injection, the backend was built on Laravel, and so Eloquent would protect me there.

I recently joined a discord server created by Danny Thompson, called "Commit Your Code", where I ran into a fellow developer seeking a way to output entered text from users as a HTML unordered list. I advised her to use my solution with TinyMCE, but then thank heavens, Pete Lamonica waded into the discussion and pointed out so many ways in which the app could get compromised by XSS if I was bypassing Vue JS output sanitization.

After a little back and forth, he advised that as my use case was only to show text as separate paragraphs instead of a single block of text, TinyMCE was overkill. This got me thinking, and after going back to inspect the actual text being passed to my backend, I found that new line characters "\n" came with the text. How I did not notice this previously, I have no idea.

My solution was then to leverage this new line character, to convert the entered text into an array using the PHP "explode" function, then using the PHP "serialize" function to convert the array into text and store it in the database. The code to do this is as shown below (note the "description" line of code):

// Save the post to database
        auth()->user()->posts()->create([
            'title' => $data['title'],
           'description' => serialize(explode("\n", $data['description'])),
            'category' => $data['category'],
            'design_fees' => $data['design_fees'],
            'proj_date' => $proj_date,
            'est_proj_cost' => $data['est_proj_cost'],
            'image_paths' => serialize($imagesArray),
        ]);

This allowed me to use the PHP "unserialize" function when retrieving the text from the database, and get an array of paragraphs, which I then output in the Vue JS template using the "v-for" directive. However, as I had previous stored text that was stored without the same formatting, I had to create a service that would try to unserialize the text, and on failing would then return the text as is. The code is shown below:

// try to unserialize
        try 
            {
                $this->text = unserialize($this->text);
                return $this->text;
            } 
            // On failing, format text if it has newline or just return as is
            catch(Exception $e) {
                if(str_contains($this->text, "\n"))
                {
                    $this->text = explode("\n", $this->text);
                    return $this->text;
                } else
                {
                    $this->text = array($this->text);
                    return $this->text;
                }
            }

The code to output the text in the template is as follows:

<h4 class="font-sans text-sm font-semibold">Description:&nbsp;</h4>
                    <span>
                        <p v-for="(paragraph, index) in descText" :key="index">{{ paragraph }}</p>
                    </span>

The lesson I learned here was to always try to distill the problem to its most basic unit, before attempting a solution. If you've read up to this point and are devCurious, I would invite you to go follow Danny Thompson and Pete Lamonica on Twitter.