JavaScript | DOM 操作:テキスト・内容変更 – 値の安全な出力方法

JavaScript JavaScript
スポンサーリンク

値の安全な出力方法とは何か

「安全な出力」とは、外部データやユーザー入力を画面に表示・属性へ設定する際に、HTMLとして解釈・実行されないように扱うことです。ここが重要です:構造(HTML)は固定し、可変データは“文字”として出力するのが基本方針です。これだけで HTML インジェクションや XSS の大半を防げます。


基本原則(構造とデータの責務分離)

テキストは textContent(または createTextNode)で入れ、属性は setAttribute/dataset で文字列として設定します。innerHTML の使用は、固定テンプレート+エスケープ済みの動的部分に限定します。ここが重要です:出力経路を「文字専用」にしておくと、見た目の変更があっても安全性が崩れません。


テキストの安全な出力(textContent・createTextNode・insertAdjacentText)

基本は textContent

<p id="msg"></p>
<script>
  const msg = document.getElementById("msg");
  const userText = '<script>alert(1)</script>'; // 外部入力の想定
  msg.textContent = userText; // タグとして実行されず、文字として表示(安全)
</script>
HTML

ここが重要です:textContent は“文字だけ”を扱うため、最も安全で高速です。UI の文言差し替えは原則 textContent に寄せます。

部分的に差し込みたいなら createTextNode

<p id="line"><b>重要:</b> </p>
<script>
  const line = document.getElementById("line");
  const t = document.createTextNode("更新があります");
  line.appendChild(t); // 既存構造を壊さず、文字だけ追加
</script>
HTML

ここが重要です:構造はそのまま、文字だけ追加。イベント消失などの副作用を避けられます。

その場に文字を挿入する insertAdjacentText

<p id="log"></p>
<script>
  const log = document.getElementById("log");
  log.insertAdjacentText("beforeend", "処理が完了しました"); // 文字のみ(安全)
</script>
HTML

ここが重要です:insertAdjacentText は “HTMLではなく文字” を挿入します。小さな追記に便利です。


属性の安全な出力(setAttribute・dataset・boolean属性)

setAttribute で文字として設定

<a id="link">詳細</a>
<script>
  const a = document.getElementById("link");
  const title = 'ようこそ "新規" さん';
  a.setAttribute("title", String(title)); // 文字列として属性に設定
</script>
HTML

ここが重要です:属性は最終的には文字列。String(…) で明示的に文字列化すると、null/undefined 由来の事故を防げます。

data-* は文字専用、複合データは JSON に

<button id="buy">購入</button>
<script>
  const btn = document.getElementById("buy");
  const payload = { id: 42, tags: ["new", "sale"] };
  btn.setAttribute("data-payload", JSON.stringify(payload));
  const parsed = JSON.parse(btn.getAttribute("data-payload") ?? "{}");
</script>
HTML

ここが重要です:配列やオブジェクトを dataset に直接入れない。JSON.stringify/parse のペアで扱うと一貫して安全です。

boolean属性は「有無」で管理(”false”は付けない)

<input id="agree" type="checkbox">
<script>
  const el = document.getElementById("agree");
  el.toggleAttribute("disabled", true);  // 付ける → 有効
  el.toggleAttribute("disabled", false); // 外す → 無効化しない
</script>
HTML

ここが重要です:disabled/checked/required などは値の文字列ではなく有無が真偽。setAttribute(“disabled”, “false”) でも “存在する” ため有効になります。


HTMLをどうしても差し込む場合の限定策(エスケープとテンプレート)

動的部分を必ずエスケープしてから innerHTML

<p id="notice"></p>
<script>
  const notice = document.getElementById("notice");
  const user = '田中<script>alert(1)</script>';

  function escapeHTML(s) {
    return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]);
  }

  notice.innerHTML = `ようこそ、<strong>${escapeHTML(user)}</strong> さん`;
</script>
HTML

ここが重要です:構造(p/strong)は固定、動的部分は全面エスケープ。可能なら innerHTML を避け、構造は HTML/DOM、文字は textContent で。

template 要素で安全に構造を用意

<template id="row">
  <li><span class="name"></span> <span class="qty"></span></li>
</template>
<ul id="list"></ul>
<script>
  const tpl = document.getElementById("row");
  const list = document.getElementById("list");
  const frag = document.createDocumentFragment();
  const data = [{name:"Apple",qty:3},{name:"Banana",qty:0}];

  data.forEach(d => {
    const node = tpl.content.cloneNode(true);
    node.querySelector(".name").textContent = d.name;
    node.querySelector(".qty").textContent  = String(d.qty);
    frag.appendChild(node);
  });
  list.appendChild(frag);
</script>
HTML

ここが重要です:テンプレートは実行されず安全。可変データは textContent で埋めると、XSS を避けつつ柔軟にレンダリングできます。


URLやクエリの安全な生成(エンコードとAPIの活用)

ユーザー入力は必ずエンコード

<a id="search">検索</a>
<script>
  const search = document.getElementById("search");
  const q = "赤い 果物&人気";
  search.setAttribute("href", `/find?q=${encodeURIComponent(q)}`);
</script>
HTML

ここが重要です:未エンコードの混入は壊れたURLや意図しない解釈の原因。encodeURIComponent を徹底します。

複数パラメータは URLSearchParams

const params = new URLSearchParams({ q: "バナナ", page: 2, sort: "asc" });
link.setAttribute("href", `/items?${params}`);
JavaScript

ここが重要です:文字列連結より URLSearchParams の方が確実・読みやすい。


実践例:安全なメッセージとリンクの出力

<p id="message"></p>
<a id="help">ヘルプ</a>
<script>
  const message = document.getElementById("message");
  const help = document.getElementById("help");

  function putText(el, value) {
    el.textContent = String(value ?? ""); // null/undefined を "" に
  }
  function safeHref(el, base, params) {
    const qs = new URLSearchParams(params);
    el.setAttribute("href", `${base}?${qs}`);
  }

  const userInput = '<b>太郎</b>'; // 外部データ
  putText(message, `ようこそ ${userInput} さん`); // 文字として安全に表示
  safeHref(help, "/help", { q: "表示", lang: "ja" }); // 安全にURL生成
</script>
HTML

ここが重要です:小さなユーティリティで“常に安全に出力する”癖を標準化すると、プロジェクト全体で事故が激減します。


まとめ

安全な出力の要点は、構造は固定、可変データは“文字”で扱うこと。テキストは textContent/createTextNode/insertAdjacentText、属性は setAttribute/dataset(複合は JSON)、URL は encodeURIComponent/URLSearchParams。boolean属性は有無で管理し、innerHTML は固定テンプレート+全面エスケープに限定。この方針を守れば、初心者でも直感的に書けて、XSSや表示事故を確実に避けられます。

タイトルとURLをコピーしました