JSFiddle - React, Tailwind, and code Playground

by gweax

HTML

<div class="message">
  <img class="avatar" src="https://randomuser.me/api/portraits/lego/2.jpg">
  <p class="content">
    Datis nepis potus colonia.
    <time class="publish-date" datetime="2017-10-16T14:01:28.817Z">Just now</time>
  </p>
</div>

CSS

.message {
  display: flex;
  margin: 1em;
  align-items: flex-end;
}

.avatar {
  width: 80px;
  height: 80px;
}

.content {
  background: deepskyblue;
  border-radius: 1em;
  padding: 1em;
  position: relative;
  margin: 0 0 0 2em;
}

.content::before {
  content: '';
  position: absolute;
  bottom: 1em;
  left: -2em;
  border-style: solid;
  border-width: 0 2em 1em 0;
  border-color: transparent deepskyblue;
}

.publish-date {
  font-style: italic;
  font-size: 0.8em;
  display: block;
}

.message:nth-of-type(odd) {
  flex-direction: row-reverse;
}

.message:nth-of-type(odd) > .content {
  background-color: peru;
  color: white;
  margin: 0 2em 0 0;
}

.message:nth-of-type(odd) > .content::before {
  border-width: 0 0 1em 2em;
  border-color: transparent peru;
  left: auto;
  right: -2em;
}

JavaScript

/*
 * Instead of displaying one message, I made an array of several messages.
 * So we can test more than one case at once.
 */
var messages = [{
  "avatarUrl": "https://randomuser.me/api/portraits/lego/6.jpg",
  "message": "Situs vi late in iset aevernit.",
  "published": "2017-11-03T13:42:00.605Z"
}, {
  "avatarUrl": "https://randomuser.me/api/portraits/lego/3.jpg",
  "message": "Never trust the user <script>alert('hacked')<\/script>",
  "published": "2017-11-03T14:55:55.123Z"
}, {
  "avatarUrl": "https://randomuser.me/api/portraits/lego/4.jpg\" onload=\"alert('hacked')",
  "message": "Never trust the user",
  "published": "2017-11-03T14:51:31.008Z"
}];

/*
 * I iterate over all messages. The inner function handles one
 * message at a time.
 */
messages.forEach(function(data) {
  var msg = document.createElement('div');
  msg.className = 'message';

  // I use the innerHTML property to build the element.
  // This shows the danger of innerHTML: The data can contain
  // HTML itself, which may be malicious.
  //
  // The embedded script element in the second message looks
  // like a danger. But script elements set with innerHTML
  // aren't executed. So we're safe.
  //
  // The avatarUrl in the third message however is malicious.
  // It contains the string
  //   " onload="alert('hacked')
  // When included in the HTML of the img element, the quotation
  // mark ends the src attribute. Then an onload attribute is
  // added. When the image loads, the JavaScript is executed.
  //
  // I should have escaped every value that may come from user
  // input. You can find a function to do so at the bottom.
  msg.innerHTML =
    '<img class="avatar" src="' + data.avatarUrl + '">' +
    '<p class="content">' +
    data.message +
    '<time class="publish-date" datetime="' + data.published + '">Just now</time>' +
    '</p>';

  document.body.appendChild(msg);
});

/**
 * Escape characters with a special meaning in HTML, so that
 * the string is not interpreted as HTML
...