kostumブログ

勉強したことやノート代わりのアウトプットに使っています。

Web Storage APIを知る②

前の記事

kostum.hatenablog.jp

目的

結果

基本概念

  • Storageオブジェクトは、keyvalueの組み合わせで、ページを読み込んでも存在し続ける。
  • keyは常に文字列で、これらにアクセスするには、オブジェクトと同様に、またはStorage.getItem()Storage.setItem()メソッドを使用する。

      localStorage.colorSetting = '#a4509b';
      localStorage['colorSetting'] = '#a4509b';
      localStorage.setItem('colorSetting', '#a4509b');
    
  • Web Storage APIの使用が推奨されており、これは単純なオブジェクトがキーバリューストアとして使うという落とし穴を防ぐため。

localStorageの機能検出

  • ローカルストレージを使用するには、まずブラウザが対応済みであり、利用可能であるかを確かめるべき。
  • ただ、今はほぼ全てのブラウザで対応しているので、古いブラウザを使用している場合に確認をする必要がある。
function storageAvailable(type) {
    var storage;
    try {
        storage = window[type];
        var x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
    }
    catch(e) {
        return e instanceof DOMException && (
            // everything except Firefox
            e.code === 22 ||
            // Firefox
            e.code === 1014 ||
            // test name field too, because code might not be present
            // everything except Firefox
            e.name === 'QuotaExceededError' ||
            // Firefox
            e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
            // acknowledge QuotaExceededError only if there's something already stored
            (storage && storage.length !== 0);
    }
}

// 確認
if (storageAvailable('localStorage')) {
  // ローカルストレージ対応あり
}
else {
  // ローカルストレージ対応なし
}

Storege インターフェース

developer.mozilla.org

StorageEvent インターフェース

developer.mozilla.org

参考

developer.mozilla.org

Web Storage APIを知る①

目的

結果

Web Storege API

  • Coookieを使用するよりも直感的な方法で、keyと値のペアを保存できる仕組み
  • Web Storageには以下の2つの仕組みがある。
    • sessionStorage
      • ページのセッション中(ページの読み込みや復元を含む、ブラウザーが開いている間)に使用可能な、オリジンに区切られた保存領域を管理する。
        • セッションデータのみ保管(ブラウザもしくはタブが閉じられるまで)
        • データはサーバーには送られない
        • Cookieよりも多く保管できる(最大5M)
    • localStorage
      • sessionStorageと同じだが、ブラウザを閉じたり、再度開いたりしても持続される
      • 有効期限がなく、Javascriptで消去したり、ブラウザキャッシュ/ローカルで保持されたデータをクリアすることで消去できる
      • 保存容量は3つの中で最大
  • これらの仕組みはWindow.sessionStorageWindow.localStorageを通して使用でき、いずれかのプロパティを使用すると、Storageオブジェクトのインスタンスを生成して、データアイテムの保存、取り出し、削除ができる。
  • 同じオリジンに対して、sessionStoragelocalStorageは、別のStorageオブジェクトを使用する。これらは別々に制御されてい機能する。
  • firefoxでは、ブラウザがクラッシュまたは再起動したとき、オリジンごとに保存されるデータ量は10MBに制限される。これは、Web Storageの使用量が多すぎて発生するメモリの問題を避けるため。
  • ユーザーがサードパーティーCookieを禁止している場合、サードパーティーのiframeからWeb Storage にアクセスできない

Web Storage インターフェース

Storage
  • 特定のドメインおよびストレージの種類に対して、データの保存、取り出し、削除ができる
Window
StorageEvent
  • storageイベントは、保存領域が変更されたときにドキュメントのWindowオブジェクトで発生する。

プライベートブラウジング/シークレットモード

  • 多くのブラウザでは、プライベートブラウジングモード、シークレットモードがあり、履歴やcookieのようなデータを持たない。
  • これらは、根本的にWeb Storageとは互換性がない。
  • ほとんどのブラウザは、Storage APIを有効にして、見かけ上機能するようにしているが、保存したデータはブラウザを閉じると消去される。
  • 既存の保存済みデータをどうするかは、まだまだ議論中で、開発者はさまざまな実装を意識して、WebStorageAPIに依存するWebサイトを開発する際に考慮するべき。

    The WHATWG Blog

参考

Cookieを知る

目的

結果

Cookie

  • サーバーがブラウザに送信するデータ
  • ブラウザーに保存される
  • その後のリクエストと共に同じサーバーへ送信される
  • 一般的には2つのリクエストが同じブラウザから送信されたものであるかを知るために使用される

  • 以下の3つの用途で使用される
    • セッション管理
      • サーバーが覚えておくべきもの(ログイン、ショッピングカートなど)
    • パーソナライズ
      • ユーザー設定、テーマ、その他の設定
    • ラッキング
      • ユーザーの行動の記録
  • クライアントへデータを保存する手段としてCookieが使われていたが、現在では、ストレージAPIを使用することが推奨されている。
  • Cookieは全てのリクエストで送信されるので、性能を悪化させる必要がある。
    • Web Storage APIIndexedDBがある。
  • サーバーはレスポンスでSet-Cookieヘッダーを送信することができる。
  • 有効期限や期間を設定でき、それが切れると、その後はCookieが送信されなくなる
  • 特定のドメインやパスへの追加の制約を設定できる
  • Cookieをどこに送信するかを制限できる

Set-CookieヘッダーとCookieヘッダー

Cookieの持続時間の定義

  • 2通りの方法で定義することができる
    • セッションCookie
      • 現在のセッションが終了すると削除される。
      • ブラウザはいつ「現在のセッション」が終わったと見なすかを定義する
    • 持続的Cookie
      • Expires属性で指定された時刻、またはMax-Ageで指定された期間が経過した後に削除される
      • Expiresの日付を設定した場合、Cookieが設定されるクライアントの日時に関連する。

Cookieへのアクセス制限

  • 意図しない第三者スクリプトからアクセスされないようにするには、Secure属性とHttpOnly属性の2つの方法がある

Secure属性

  • HTTPSプロトコル上の暗号化されたリクエストでのみサーバーに送信され、安全でないHTTPでは送信されない
  • ただし、これによってCookie内の機密情報へのアクセスを全て防げない。
    • 例えば、クライアントのハードディスクへアクセスすることで読み取られる可能性がある

HttpOnly属性

  • JavascriptDocument.cookie APIにアクセスできなくなり、サーバーに送信されるだけになる
    • 例えば、サーバー側のセッションを持続させるCookieJavascriptが利用する必要はないので、HttpOnly属性をつけるべき
    • この予防策は、クロスサイトスクリプティング攻撃(XSS)を緩和するのに役立つ

Cookie送信先の定義

  • DomainおよびPath属性は、Cookieのスコープを定義する

Domain属性

  • Cookieを受信することができるホストを指定する
  • 指定されていない場合は、規定でCookieを設定したのと同じホストとなり、サブドメイン以外は除外される
  • 指定された場合、サブドメインは常に含まれる。そのため、Domainを指定すると省略時よりも制限が緩和される。ただし、サブドメイン間でユーザーに関する情報を共有する場合は有用になる。

Path属性

  • Cookieヘッダーを送信するためにリクエストされたURLの中に含む必要があるURLのパスを示す

SameSite属性(= Strict, Lax, None

Strict

  • 発生したサイトのみに送信される

Lax

  • ユーザーがCookieのオリジンのサイトに移動したときにもCookieが送信される
    • 例えば、外部サイトからリンクを辿った場合

None

  • Cookieを発生元サイトへリクエストとサイト間のリクエストの両方で送信されるが、安全なコンテキストでのみ送信される

    SameSite=NoneならSecure属性も設定する必要がある

  • SameSite=Noneが設定されていない場合、Laxとして扱われる

Cookieの接頭辞

  • Cookieの設計では、Cookieが安全なオリジンに設定されているかどうか、どこに設定されたのかをサーバーが確認することができないようになっている
  • サブドメイン上にある脆弱性のあるアプリケーションがDomain属性を使用してCookieを設定すると、他の全てのサブドメインCookieにアクセスできるようにすることができる
  • この仕組みはセッション固定攻撃で悪用される可能性がある ⇨ 対策方法: セッションの固定化
  • しかし、多層制御として、Cookieの接頭辞を使うことが可能で、以下の2つが利用可能

    • __Host-

      • Set-Cookieヘッダーが受け入れられるのは
        • Secure属性で指定
        • 安全なオリジンから送信
        • Domain属性を含んでいない
        • Path属性が/に設定されている

        場合のみ

    • __Secure-

      • Set-Cookieディレクティブが受け入れられるのは
        • Secureである
        • 安全なオリジンから送信されている

        場合のみ(__Host-接頭辞よりも弱い)

  • 上記の制約に適合していないCookieは、送られてもブラウザーが拒否する

  • アプリケーションサーバーは、ユーザーが認証されているか、あるいはCSRFトークンが正しいかどうかを判断するときに、特定のCookie名をチェックするだけなので、これはセッションの固定化に対する防御手段として効果的に機能する

JavascriptでのDocument.cookiesを使用したアクセス

  • Document.cookieプロパティを使用して新しいCookieを作成することができ、HttpOnlyフラグが設定されていない限り、既存のCookieJavascriptからアクセスすることもできる
  • しかし、Javascriptで生成されたCookieはHttpOnlyフラグを含められない
  • セキュリティの影響に注意すること。XSSによって盗まれる可能性がある。

セキュリティー

  • 全てのCookieの値がエンドユーザーから見え、変更できる
  • アプリケーションによっては、サーバー側で検索される不透明な識別子を使用するか、JSONウェブトークンのような代替の認証/機密性メカニズムを調べたほうが良いかも
  • Cookieへの攻撃を緩和する方法には以下のようなものがある
    • HttpOnly属性を使用して、JavascriptからCookieの値にアクセスするのを防ぐ
    • 認証などの機密情報のためのCookieは、持続時間を短く、SameSite属性をStrctまたはLaxに設定する

ラッキングとプライバシー

サードパーティーCookie

  • Cookieドメインやスキーム(httphttpsなど)に関連づけられる(Set-CookieDomain属性が設定された場合は、サブドメインにも関連づけられる)
  • Cookieドメインとスキームが現在のページと一致している場合、そのCookieはこのページと同じサイトからのものとみなされ、ファーストパーティーCookieと呼ばれる(Webページをホスティングしているサーバーが設定する)
  • ドメインとスキームが異なる場合、そのCookieは同じサイトのものとはみなされず、サードパーティーCookieと呼ばれる(他のドメインのサーバーに保存されている画像やその他のコンポーネント(例えば、広告バナーなど)など)

Cookieに関する規制

  • Cookieの使用を対象とした法規制には、以下のようなものがある。
  • これらの規制は、これらの管轄区域(注意事項あり)のユーザーがアクセスする全世界のウェブ上のあらゆるサイトに適用されるため、世界的な広がりを持っている
  • これらの規制の要件には、次のようなものがある
    • サイトがCookieを使用することをユーザーに通知すること
    • ユーザーが一部または全てのCookieをオプトアウトできるようにすること
    • ユーザーがCookieを受け取らなくても、サービスの大部分を利用できるようにすること

日本のCookie規制について

Cookie規制はなぜ強まるのか?取り巻く状況の変化を解説! | Priv Lab

ブラウザに情報を格納する他の方法

  • 別のアプローチとして、Web Storage APIがある
  • SessionStorageLocalStorageセッションCookie持続的Cookieに対応しているが、ストレージの容量制限がCookieより大きく、サーバーに送信されることがない
  • より構造化された大量のデータは、indexedDB API またはその上に構築されたライブラリを使用して保存することができる。
  • ゾンビCookieと呼ばれる、Cookieが削除された後に再作成されるようにするための他の技術が作成されている
    • これらの技術は、ユーザーのプライバシーとユーザー制御の原則に違反し、データプライバシー規則に違反する可能性がある

参考

HTTP Cookies - MDN

Using HTTP cookies - HTTP | MDN

Cookies, the GDPR, and the ePrivacy Directive

Cookies, the GDPR, and the ePrivacy Directive - GDPR.eu

IETF Internet-Draft

Cookies: HTTP State Management Mechanism (日本語訳)

docsifyを触る②

前の記事

kostum.hatenablog.jp

目的

  • docsifyでドキュメントのページを作成する。

結果

ページの作成

  • ディレクトリの構造に合わせて、URLパスが生成されます。

サイドバーの作成

  • 自分で作成したサイドバーを使いたい場合、_sidebar.mddocs配下に作成し、index.htmlloadSidebartrueに指定します。他の名前を使用する場合、loadSidebarの値に、その名前のファイルを指定します。
    <!-- index.html -->
    .....
    <body>
      <script>
          window.$docsify = {
            name: "",
            repo: "",
            loadSidebar: true,
            // 子要素の表示数
            subMaxLevel: 2
          };
        </script>
      <script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
      .....
    
  • 自分でサイドバーを作成する場合、デフォルトのCSSが効かなくなるので注意です。

ナビゲーションバーの作成

  • 自分で作成したナビゲーションバーを使いたい場合、_navbar.mddocs配下に作成し、index.htmlloadNavbartrueに指定します。他の名前を使用する場合、loadNavbarの値に、その名前のファイルを指定します。
    <!-- index.html -->
    .....
    <body>
      <script>
          window.$docsify = {
            name: "",
            repo: "",
            loadSidebar: true,
            loadNavbar: true,
            // 子要素の表示数
            subMaxLevel: 2
          };
        </script>
      <script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
      .....
    

絵文字の使用

  • 絵文字を使う場合は、scriptpluginを追加する必要があります。
    <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/emoji.min.js"></script>
    

カバーページの作成

  • 自分で作成したカバーページを使いたい場合、_coverpage.mddocs配下に作成し、index.htmlcoverpagetrueに指定します。他の名前を使用する場合、coverpageの値に、その名前のファイルを指定します。
    <!-- index.html -->
    .....
    <body>
      <script>
          window.$docsify = {
            name: "",
            repo: "",
            loadSidebar: true,
            // 子要素の表示数
            subMaxLevel: 2
            coverpage: true
          };
        </script>
      <script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
      .....
    

背景を変えたい

  • デフォルトでは、ランダムに背景画像が生成されますが、以下のようにすることで、背景色や画像を変更することができます。

<!-- _coverpage.md -->

# docsify <small>3.5</small>

[GitHub](https://github.com/docsifyjs/docsify/) [Get Started](#test)

<!-- background image --> ![](_media/bg.png)

<!-- background color --> ![color](#f0f0f0)

ホスティング方法

Github Pages

  • Github Pagesの設定で、ソースを/docsに指定すれば、それだけでOK。

ホスティングサービスについては、ドキュメント参考。。。

docsify.js.org

参考

docsifyを触る①

目的

  • docsifyを使って、ドキュメントをサイト生成する。

結果

docsifyとは

  • ドキュメントをウェブサイト生成する。
  • GitBookのように、静的なHTMLファイルを生成するのではなく、マークダウンを読み込み、パースし、ウェブサイト上に表示する。

特長

  • HTMLをビルドしない
  • シンプルで軽い
  • テキスト検索できる
  • 複数のテーマを指定できる
  • 絵文字をサポートしている

など.....

導入方法

npm i docsify-cli -g

初期設定

./docsに諸々配置したい場合、

docsify init ./docs

これを実行すると、./docs配下に以下のファイルが生成されます。

  • index.html
  • README.md
    • 最初に表示される画面
  • nojekyll
    • Github Pagesが、アンダースコアから始まるファイルを無視しないようにするために必要。 が生成されます。

起動方法

カレントディレクトリを先ほど設定したdocsと同じ階層に移動し、以下コマンドを実行すると、http://localhost:3000にサーバーを起動します。

docsify serve docs

バージョンについて

  • バージョンのアップデートは、手動で変える必要があります。
  • index.htmlにdocsifyのscriptcssを読み込んでいますが、@4のような書き方の場合、マイナーアップデートやバグ修正を自動的に取り込んでいるので、推奨されているようです。
  • バージョンを固定したい場合は、URLの@シンボルの数字を指定することで固定できます。

参考

project Euler 023

環境

問題

完全数とは, その数の真の約数の和がそれ自身と一致する数のことである. たとえば, 28の真の約数の和は, 1 + 2 + 4 + 7 + 14 = 28 であるので, 28は完全数である.

真の約数の和がその数よりも少ないものを不足数といい, 真の約数の和がその数よりも大きいものを過剰数と呼ぶ.

12は, 1 + 2 + 3 + 4 + 6 = 16 となるので, 最小の過剰数である. よって2つの過剰数の和で書ける最少の数は24である. 数学的な解析により, 28123より大きい任意の整数は2つの過剰数の和で書けることが知られている. 2つの過剰数の和で表せない最大の数がこの上限よりも小さいことは分かっているのだが, この上限を減らすことが出来ていない.

2つの過剰数の和で書き表せない正の整数の総和を求めよ.

考え方

  1. やりたいことは、2つの過剰数の和で書き表せない正の整数の総和を求めること
  2. 過剰数の計算の仕方は、約数の和が自身の数よりも大きい場合
  3. 「2つの過剰数の和で書き表せない正の整数」は上記2.のそれぞれを足していった数以外の正の整数になる
  4. 上記3.で分かる正の整数の総和を計算する

コード

let kajosuList = [];
let limitNum = 28123;
for (let i = 12; i < limitNum; i++) {
  let yakusuList = [];
  for (let j = 1; j < i; j++) {
    if (i % j === 0) {
      yakusuList.push(j);
    }
  }
  if (yakusuList.length > 0) {
    let yakusuSum = yakusuList.reduce((a, b) => a + b);
    if (i < yakusuSum) {
      kajosuList.push(i);
    }
  }
}
let kajosuWaList = [];
for (let i = 0; i < kajosuList.length; i++) {
  for (let j = i; j < kajosuList.length; j++) {
    kajosuWaList.push(kajosuList[i] + kajosuList[j]);
  }
}
let notKajosuWaList = [];
for (let i = 1; i < limitNum; i++) {
  if (!kajosuWaList.includes(i)) {
    notKajosuWaList.push(i);
  }
}
return notKajosuWaList.reduce((a, b) => a + b);

説明

  • 問題より、数え上げ数は、12から28123までの数字内とする
  • まず、数え上げ数の約数の和を計算する
  • 約数の和が、数え上げ数よりも大きいとき、過剰数として配列に格納しておく
  • 28123までの過剰数の配列が得られたので、この配列から、2つの過剰数の和をそれぞれ計算し、さらに配列に格納する
  • この2つの過剰数の和以外の数が、「2つの過剰数の和で書き表せない正の整数」なので、1から28123までの数に含まれない数を配列に格納する
  • 最後に得られた配列の和を計算する

project Euler 022

環境

問題

5000個以上の名前が書かれている46Kのテキストファイル names.txt を用いる. まずアルファベット順にソートせよ.

のち, 各名前についてアルファベットに値を割り振り, リスト中の出現順の数と掛け合わせることで, 名前のスコアを計算する.

たとえば, リストがアルファベット順にソートされているとすると, COLINはリストの938番目にある. またCOLINは 3 + 15 + 12 + 9 + 14 = 53 という値を持つ. よってCOLINは 938 × 53 = 49714 というスコアを持つ.

ファイル中の全名前のスコアの合計を求めよ.

考え方

  1. アルファベットに対する値を変数に設定する。
  2. 5000個以上の名前を配列に格納し、アルファベット順にソートする
  3. 2.の名前からスコア数を算出する
  4. スコアの合計を算出する

コード

const alphabet = [
  {
    alphabet: "A",
    value: 1
  },
  {
    alphabet: "B",
    value: 2
  },
  {
    alphabet: "C",
    value: 3
  },
  {
    alphabet: "D",
    value: 4
  },
  {
    alphabet: "E",
    value: 5
  },
  {
    alphabet: "F",
    value: 6
  },
  {
    alphabet: "G",
    value: 7
  },
  {
    alphabet: "H",
    value: 8
  },
  {
    alphabet: "I",
    value: 9
  },
  {
    alphabet: "J",
    value: 10
  },
  {
    alphabet: "K",
    value: 11
  },
  {
    alphabet: "L",
    value: 12
  },
  {
    alphabet: "M",
    value: 13
  },
  {
    alphabet: "N",
    value: 14
  },
  {
    alphabet: "O",
    value: 15
  },
  {
    alphabet: "P",
    value: 16
  },
  {
    alphabet: "Q",
    value: 17
  },
  {
    alphabet: "R",
    value: 18
  },
  {
    alphabet: "S",
    value: 19
  },
  {
    alphabet: "T",
    value: 20
  },
  {
    alphabet: "U",
    value: 21
  },
  {
    alphabet: "V",
    value: 22
  },
  {
    alphabet: "W",
    value: 23
  },
  {
    alphabet: "X",
    value: 24
  },
  {
    alphabet: "Y",
    value: 25
  },
  {
    alphabet: "Z",
    value: 26
  }
];
const names = [
    ...
]

const sortedNames = names.sort();
const scores: number[] = sortedNames.map((name, index) => {
  const nameList: string[] = [];
  for (let i = 0; i < name.length; i++) {
    nameList.push(name.charAt(i));
  }
  const valueList = nameList.map(name => {
    return alphabet.find(alp => name === alp.alphabet)!.value * (index + 1);
  });
  return valueList.reduce((a, b) => a + b);
});
return scores.reduce((a, b) => a + b);

説明

  • アルファベットに対応した数を設定する。
  • 名前は、配列にして格納しておく
  • まず、名前リストをソートする
  • ソートした名前リストから名前を取り出し、1文字ずつに分割し、アルファベットに対応した数に変換する
  • それらの和を算出し、配列に格納しておく
  • 最後に和の配列内の和を算出する