読者です 読者をやめる 読者になる 読者になる

素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: すかいぷ:javascripter_  か javascripter あっと tsukkun.net skypeのほうがいいです

英文法の話パート3、which,thatの使い方についてなど

パート1: 英文法の話、時勢と動詞のニュアンスについてなど - 素人がプログラミングを勉強していたブログ

パート2: 英文法の話パート2、to doとsomething to doの見分け方と使い方についてなど - 素人がプログラミングを勉強していたブログ

質問者
The rumor that he got married is false.
The rumor that he heard is false.
この違い

javascripter
The rumor of him having got married
The rumor which he heard 
まあこれは明らかに違うな

質問者
前者はThe rumor = got marriedで後者はthe rumor≠he heeard
後者のthe rumorはheardの目的語でrumorを修飾しているから形容詞節で前者は=で結びつける接続詞で同格を表す名詞節
この辺りの見分け方がむずいけどなんとなく分かってきた

質問者
つまり最初に言ったI had a dream that I climbed Mt.Fuji.もa dream=I climbed Mt.Fujiが成立するから名詞の同格になり名詞節でthatはただの接続詞。This is the house where I was born.もthe house ≠ I was born.なので同格ではなく形容詞節か。

質問者
Climbing Mt.Fuji is a dreamでSVCが成り立つってことか。

質問者
つまりClimbing Mt.Fujiとa dreamの関係は=。being bornとthe houseは=で結べない。being born is the houseにはならないつまりSVCの関係ではないってことか

javascripter
The rumor is false. He got married. 
Then rumor is false. He heard 

javascripter
後者は抜き出すと単語が抜けてて文章が未完成になる
これが見分け方

質問者
hearの目的語がないとこが見分けるポイント?

javascripter
そう

質問者
get marriedは自動詞だから独り立ちできる文でSVで完全自動しだけどHe heardはSVOをとるものだからSVにはならないってことか

javascripter
Whichと同じで
The rumor which he heard is falseは
He heard the rumor. The rumor is false. 
みたいに
二文にわかれてるから
このタイプは、探すと抜けてる部分があるから
分離して完成しないのが後者

質問者
そっちのほうが分かりやすいかも

英文法の話パート2、to doとsomething to doの見分け方と使い方についてなど

パート1: 英文法の話、時勢と動詞のニュアンスについてなど - 素人がプログラミングを勉強していたブログ

パート3: 英文法の話パート3、which,thatの使い方についてなど - 素人がプログラミングを勉強していたブログ

質問者
I should have taken some medicine to get over a cold.
これってto get over a coldが直前のmedicine(名詞)を修飾する形容詞句とも考えられるしtake(動詞)を修飾する副詞句とも考える事ができると思うんだけど、だとしたら文の意味って2つに分かれない?
形容詞句でとらえると(私は風邪を克服するための薬を飲むべきだった)
副詞句でとらえると(私は風邪を克服するために薬をのむべきだった)

javascripter
厳密にはそうだが
Medicine, to get over a cold の方の解釈が自然

javascripter
なぜかというと
I should have take some medicine for a cold 
みたいな言い方があるから
I should have が来た時点で、~to doで
To doのためにI should haveをするべきだったっていう
Because的なものが来ることを期待してるから
Toが


javascripter
I should have got a drink to hydrate myself earlier

In order to hydrate myself earlier, I should have taken a drink
俺にはこう分解して聞こえるのが自然な感じがする

javascripter
特に
To doのあとにmyselfとかがくるも
A drink to warm myself up 
みたいになるから
MyselfはIにかかってるから
I should have got a drink, to warm myself up、のほうがmyselfが主語からきてることが明らかだから
そっちにわけて読みたくなる、
結果的には意味は同じだから
目的がどこにかかってるのかによるねこれは

javascripter
I wanted some coffee to wake me up 
I wanted some coffee to wake up

wake me upしてくれるコーヒーが欲しかったのが前者

質問者
副詞句って文全体を修飾する場合は目的が文全体にかかるのかね

javascripter
Coffee が欲しくて、その理由がto wake upなのが後者
In order toだと
目的って意味が強くなる、修飾の意味が減る

さらに、明確にするときは
Toではなくforとかwhich, whichとかthatとかで修飾

あるいは
動名詞を先にもってきて形容詞にして


質問者
まあそれもありだけど
それだとmedicineを修飾することにならない?

javascripter
I wanted some strong coffee for staying awake 

さっきいったのはmedicineを修飾する
文全体の場合は
Toを最初に持ってきて文法的にはなすか、カンマおくか、in order toとかbecause とかをいれる

質問者
まあそうなるな

質問者
because(副詞節)か前置詞+名詞(副詞句)を入れると文全体、動詞、形容詞が修飾される

javascripter
I need some coffee. I need to wake up  

I need some coffee to wake up. 
I need some coffee as I need to wake up. 
I need some coffee for waking up. 
I need some coffee in order to wake up

javascripter
I need coffee as it wakes me up 
I need coffee that wakes me up 

質問者
I need some coffee for waking upってS V O for O?

javascripter
For doing = To do

質問者
あぁ

javascripter
てかどちらでも意味が変わらないからtoで使うんだと思う

質問者
あー現在形だと状態も動作も過去現在未来の習慣だからか

javascripter
いや、そこらへんはあんまり関係ないかな
何がどこまで修飾するのかってのは英語でも 文脈によることが多くて

質問者
I drink some coffee to wake up とかは

javascripter
To wake upはI drinkの目的になってる

javascripter
some coffee to wake upで切り出すと
wake up 誰っていう部分がぬけるから

javascripter
I wake upしたいわけだから to wake up はIのほうにつく


javascripter
Grab some coffee to wake yourself up!
こういう風な言い方の場合

javascripter
コーヒーを飲んで目を覚ませ! to wake yourself upは(You should ) grabにかかってるからto wake yourself upがcoffeeにかかる

質問者
Iにかかるとしたら名詞相当語句ってこと?

質問者
つまり不定詞の名詞的用法ってこと?

javascripter
用語はわからないけども

主語を常に意識して
wake upなんだとしたら 何がなにをwake upしてるのか
そう考えるとどこできれるのかだいたいわからないかな

javascripter
I drink coffee to wake up

coffee to wake upだけだと主語が存在しないから成立しないっしょ
Coffee to wake you upみたいなかたちにしないといけない

javascripter
I drink coffee, to wake up.の場合は
I drink coffee, in order for me to wake up => I drink coffee, because I want to I wake up 

主語が連続してIだからwake upを他動にかえなくてよくなる

質問者
to wake upがdrinkにかかってるんでしょ

javascripter
I drink, to wake up
短くするとこう

dishes to be cleaned 
dishes (that needs )to be cleaned

javascripter
some coffee (that makes SOMEONE to wake up)
このSOMEONEの不在さが
coffee to wake upで区切るときの不自然さ

質問者
I drink cofee to wake up も修飾されるものはcofeeとdrink

javascripter
コーヒー自体は立ち上がらないわけだから

javascripter
背後に主語があるはずなのに、名刺単位でそこで区切られると
主語の情報がきれる

javascripter
いや、to wake upはI drink coffee全体にかかってる

質問者
私は起きるためにコーヒーを飲む
私は起きるためのコーヒーを飲む
私はコーヒーを飲む起きるために

javascripter
becauseとかと同じで補足情報だから。

質問者
起きるために私はコーヒーを飲む

javascripter
I wake up to drink coffee

質問者
それだと名詞的用法になる

質問者
to drink cofeeは目的語

javascripter
I drink coffee to wake up
I drink coffee to clear my mind
I drink coffee to help wake up early

javascripter
ちょっと質問の内容見失いかけた
なにが聞きたいんだっけ

質問者
いや、形容詞句ともとらえられるし副詞句ともとらえられるからそれだと全く違う意味になるんじゃないかって思って

javascripter
全く違う意味になることはあるよ
ただ
I drink coffee to wake upの場合は
I drink something, and that something is coffee to wake up
に分解するとcoffee to wake up,になって 主語が消えるから分解できるパターンは
(I drink )coffee (to wake up)

しかありえないんじゃないのっていう

質問者
つまり名詞を修飾するってこと?

javascripter
toがSVの目的を示してる

javascripter
名刺を修飾していないとおもうよ

javascripter
In order to get ready for work, I drink coffee in the morning to wake (myself) up.

javascripter
これくらい長くなるとわかるとおもうけど
toが名詞を修飾しているというのはワンパターンにすぎなくて
分全体を修飾していることが多い

質問者
She became beautiful to enter a contest.

質問者
うーん

javascripter
She became beautiful because she wanted to enter a contest.

javascripter
これもto doが名詞を修飾していないよ

質問者
S V C to doはCとVと文全体かな

javascripter
to doは分全体とは関係ないから分けて考えたほうがいい

javascripter
なんか長めの文章ないの

質問者
なんかだして

質問者
He has gone to buy some cigarettes.

javascripter
I woke up at 7am to go to the gym before classes to make this a habit for my future self to be healthy

javascripter
I woke upが基本で
 To go to the gymがその理由
to make it(going to the gym) a habit. がI woke up to...からto go to the gymにかかってて
for my future self to be healthyは、to make this a habitにかかってる

ほとんどがbecauseで置き換えられるんだって
I woke up at 7am to go to the gym before classes, because I want to make a habit of going to the gym, because I want my future self to be healthy. 

質問者
(主節)I(代名詞・主語)woke up(自動詞)at7am(副詞句)to go to the gym(副詞句で動詞を修飾している)(従属節)because I(主語・代名詞)want(動詞) to make a habit of  going to the gym(不定詞の副詞的用法で動詞を修飾している),(従属節)I(主語・代名詞)want (動詞)(目的語)my future self (不定詞の形容詞的用法で直前の名詞selfを修飾している)to be healthy.

javascripter
I want to have some medicine for my sleeping issues
I want to have some medicine to sleep properly 
I want to have some sleeping pills
I want to have some medicine to fall myself into sleep faster


javascripter
どこが 薬局の人への説明で
どこが薬の説明なのか 意識して日本語に訳してみ

これは文脈からしてどれも全く意味がかわらないので
存在しないんだよな明確な訳が

javascripter
つまり 特定の場合を除いて toを使った結果、目的がどの部分にかかっているのかはwhichと同じで
どこに対してでもありうることがあるということで

javascripter
文脈からありえない可能性を排除していくしかない、厳密に書きたい場合はToですまさずにFor someone toとかin order for someone toとか, whichやthat、あるいは二文にするなどしてわけないといけない

質問者
for my sleeping issuesって形容詞句と副詞句で分かれるんじゃね。
形容詞句でみるとmedicineを修飾するから(私は不眠の薬を飲みたい)になって副詞句でみると(不眠のため私は薬を飲みたい)よく考えたらin orderも形容詞句の意味を持つから明確な訳がない。明確にするとしたら節で繋ぐのがいいのかね。 おやすみ。

javascripter
I want to have some medicine for my sleeping issues 

javascripter
だと
Myがあるから
目的はI want to haveにかかってると捉えるのが自然

javascripter
なぜかというとmedicine for my ...みたいに自分に限定されたmedicineが欲しいわけではないから

javascripter
逆にI want to have some medicine for sleeping issuesだと
medicine for sleeping issues
とかかっていると捉えるのが自然

javascripter
同様にto sleep properlyもI want to have some medicine全体にかかってる
Some sleeping pillsはまあそのままで
To fall myself...も文全体にかかってる


質問者
i should have taken 〜
薬を飲むべきだった(飲まなかったので後悔している)
風邪を治すための薬を飲むべきだった(薬は飲んだけれども違う薬を飲んで後悔していると考えられる)
ってなんのかな

javascripter
これは曖昧性があるな、どちらともとれるな。この場合は、文章からは区別できない
発音時に
I should had taken some medicine FOR A COLD, not for headaches 
みたいに
後者の場合にforを強調して発音すると後者に聞こえる
普通は前者

ただし、文章からは区別できない

質問者
ほう

英文法の話、時勢と動詞のニュアンスについてなど

忙しくてブログを全然更新していなかったが、最近、友達に英文法を教えているので、 役に立つかもしれないのでログを載せておく。

パート2: 英文法の話パート2、to doとsomething to doの見分け方と使い方についてなど - 素人がプログラミングを勉強していたブログ

パート3: 英文法の話パート3、which,thatの使い方についてなど - 素人がプログラミングを勉強していたブログ

質問者
Leaves turned red in fall.とLeaves got red in fall.って同じ?

javascripter
同じ。Turnedのほうがただ、
変わったという動作が少し強調されてる感じするがほぼ変わらないと思う

質問者
なるほどね

javascripter
I got 20 years old
I turned 20 years old

javascripter
これだとturnedのほうが
誕生日迎えたっぽさが強い感じするっしょ

質問者
ネイティブはどっちも使うん?

javascripter
使う
Turnのほうがでもいい

質問者
ネイティブらしく使うには洋書とか読んで覚える?
背景にあるロジックとかニュアンス的な部分

javascripter
そもそも
TOEIC600くらいはないと
読めないよたぶん

javascripter
ある程度点数あがってからはじめて色々まあまあ読めるようになって細かいニュアンスの違いがわかってくる

質問者
なるほどね

質問者
Leaves turned red in fall. it suddenly got dark. Leaves got red in fall. It suddenly turned dark. 同じ意味?

javascripter
二個目のleaves got red in fallが
Leaves turned red in fallより動作の強調具合が弱い以外同じ

javascripter
Suddenly got 
Suddenly turned
これ入ると同じ

javascripter
百円もらった
百円受け取った

意味は同じでも
受け取ったみたいに、意味が限定されてる言葉のほうがちょっとフォーマル寄りで意味が強い感じがするでしょ
そういう感じ

質問者
これ動詞の選択問題でどちらもsvcをとりどちらも〜なるって意味だけどどれが適切なのか分からなかったから

質問者
答えではturned red got darkだった

質問者
その程度の違いなのか

javascripter
その程度の違いだけど
Turnedにはもともとsuddenlyのニュアンスが含まれてるから

javascripter
Gotにsuddenlyをつけるほうが意味がダブらない

ただ急にという意味を強く込めてsuddenly turnedという言い方も普通にするから
まあ、どういう話なのかによるけどね

質問者
Everyone likes to hear the chimes.って文あってる?

質問者
みんなはそのチャイムの音を聞くのが好きですって訳されてるけど

javascripter
あってる
I'd like to
I would like to
これはわかるっしの
これが現在になっただけ

質問者
好きかどうかはチャイムを聞いてみないと分からんくね

javascripter
聞いてるから好きなんだろ
過去から未来全体だから現在で、
I like to do it 
I like doing it
どちらもありうる

javascripter
To hear the chimes is what everyone likes 
こういうことだよ

質問者
チャイムを聞くことが未来に向いてるけど、それまでに聞いたことがあって好きだと分かってるなら動名詞じゃないの

javascripter
I avoid taking to people. 
人と話すことを避けます
I try to avoid talking to people. 
人と話すことを避けようとしてます

javascripter
未来に向かっている動作が含まれている場合は
動名詞とは限らない
To doも普通にある

質問者
Everyone likes to hear the chimes.ってみんなはチャイムを聞くことが好きって話し手は言ってるけどto doは未来を表していてまだ聞く行為を行っていないのになんで好きって分かるの?って先生に聞いたらそれは間違ってるって言われた

javascripter
People vary in how they apply these phrases, but I reckon the general pattern is:

"I would like to do" is for hypothetical situations.

"I like doing" is for describing things that you actually do.

There's a slight difference in meaning between "I like to exercise" (I think it's the right thing to do) and "I like exercising" (I enjoy it).

That's how I see the difference, but I know some people have other ideas.

ネイティブの意見は
Like to doもlike doing もどちらも正しい

質問者
おっけ

javascripter
えっと
さっきから言ってるけどto doが未来を表してるから未完了だからダメっっていうのが違くて
好きっていうのは
そもそも
未完了で未来ってわけではなく
現在系だから

javascripter
To do「すること」「が好き」

っていう風に分解されてる

質問者
聞くことが好きって既に聞いたことあるから好きじゃないの

javascripter
すでに聞いたことがあり、そして
好きという状態が、過去、現在、未来にわたっているから

javascripter
全体でみて
Likeが現在である限り
Doingでもto doでも関係がない
そもそも時制をきめてるのはto doかdoingかではなく
Likeの部分だから

質問者
likeは状態動詞だね

javascripter
うん

質問者
like doing = 一般的、習慣的なこと、進行中のこと、体験済みのことなどに、一方
like to do= その場の特定の行為や未体験、未来の行為に用いると説明されて   います。

like doingはチャイムを聞いて体験済みで好き状態になってるんじゃないの

javascripter
うん

javascripter
いや

質問者
話し手はなんでチャイム未体験なのに好きだと判断したの

javascripter
そもそも
Everyone がto hear the chimes を体験してること自体が不自然なわけで

そもそも全員が聞いているということ自体が、強調というか仮定なわけで

javascripter
Everyone likes to hear the chimes because the chimes are so nice 

javascripter
こういう可能性もあるわけで

質問者
あー 話し手の仮定ってこと?

javascripter
仮定ってほど仮定ではなく事実のこともあるけど

質問者
でもそれだったら仮定文になるんじゃないの

javascripter
仮定ではなく強調ってことだよ

javascripter
俺は絶対に勝つ

javascripter
これは未来だけど
現在になってて強調されてるでしょ

質問者
まあ

質問者
everyone likes to hear the chimes(みんながそのチャイムを聞けば好きな状態になると確信している)ってこと?

javascripter
喋り方にもよるけど

javascripter
ぶっちゃlike doingと変わらないけど
まあ原理的にはそうといえばそう
はむずいけどこれ以上説明しようがない

質問者
あーたしかにlikeの説明で理解した

質問者
I go to the gym to do exercise. じゃあこれも現在形で過去現在未来において変わらぬ真理だから文としてはあってる?

javascripter
I go to the gym (in order )to do the exercise
この場合は目的だから、For doing the exerciseでもよくて

質問者
これだと、習慣的な動作どうしだからto doのところはdoingでもいいのか

javascripter
どちらでもいい
目的を表してる補足説明だから時制はI goの部分で表現されてる

質問者
現在形の動作動詞自体に習慣的な動作の意味が含まれてるから

javascripter
そう
I go to work everyday for my future

質問者
likeも過去現在未来においての状態だからそう考えるとto hearも理解できるな

javascripter
あくまで、時制は動詞部分にあるから

それ以外の部分は明確には時制は示されていない
To doが未来っぽいのもあくまで傾向なだけで
基本の時制は全て動詞にある
For my futureみたいに、途中で別の未来を表したいなら、そこで書く

To doが未来なのは
I go to the gymみたいに、
Toがもともと目的地みたいな、先のものを示すからで
逆にdoing は現在している動作だから
すでに行われている=過去にはじめて現在の動作
というふうに
そこからニュアンスの違いが生じてる

質問者
なるほど

javascripter
まあそんな感じ

質問者
To自体に目的の要素が含まれてることは知ってたけどto doの方ばかりに目がいってたわ

javascripter
言葉ってのはそもそも
辿っていくとシンプルなもとになってる発想と使い方があって
いろんなことを表現するために、そこから派生してるから

javascripter
よーく考えると背後にはまあまあロジックがある

一つのファイルでサーバーとHTMLファイルとコードを共有するやつ

ちょっと説明がし難いがindex.htmlというファイルがあったとして、ファイルプロトコル経由で

file://path/index.html

などのように直接ブラウザで起動すると、プロトコルの関係でファイルが読み込めなかったり、エラーになったりすることがよくある。サーバーをたててlocalhostなどから読み込めばいいのだが、微妙にセットアップが面倒臭い。下記コードをindex.htmlとして保存すると

node index.html

とするだけでブラウザが立ち上がり、index.html自身をルートとしてそのディレクトリ以下のファイルを配信する簡易サーバーが立ち上がる。具体的には、

http://localhost:port/index.html

のような形でアクセスできるようになる。コマンドを実行したディレクトリの絶対パスをもとにポートを自動生成しているので、すでにサーバーが起動済みの場合、

node index.html

は新規にサーバーを立ち上げずに、既存のlocalhost:port/index.htmlをブラウザで開く。 特殊な魔法を使っているので、HTMLとしてもJSとしてもvalidで、ブラウザでもnodeでもコードの一部を共有している。

<!--
(function () { /* -->

<!DOCTYPE html>
<html>
  <head>
    <title>Title</title>
    <meta charset="UTF-8">
  </head>
  <body>
    <p>Hello!</p>
  </body>
</html>

<script src="index.html"></script>
<!-- */

// Shared among server and client sides
function mapPort(s) {
  var hashCode = 0;
  for (var i = 0; i < s.length; i++) {
    hashCode += hashCode * 31 + s.charCodeAt(i);
  }
  return 49152 + hashCode % 16384;
}

// Only for browser

if (typeof window !== 'undefined') {

  if (location.protocol == 'file:') {
    var url = 'http://localhost:' + mapPort(location.pathname);
    var req = new XMLHttpRequest();
    req.open('GET', url, false);
    try {
      req.send(null);
      location.href = url;
    } catch (ex) {
      document.writeln("<p>Server is not running. Please run the server by node index.html</p>");
    }
  }
  return;
}

// Only for Node.js

// Running a simple server.

var http = require('http'),
  fs = require('fs'),
  child_process = require('child_process'),
  path = require('path');

var ROOT_DIR = path.dirname(process.argv[1]);

function openInBrowser(url) {
  return child_process.spawnSync('open', [url]);
}

var port = mapPort(process.argv[1]);

var server = http.createServer(function (req, res) {
  var fPath = path.join(ROOT_DIR, req.url,
    /\/$/.test(req.url) ? 'index.html' : ''),
    file = null;
  try {
    file = fs.readFileSync(fPath);
  } catch (e) {
    // NOOP
  }
  res.writeHead(file ? 200 : 404);
  res.end(file || "File Not Found");
}).listen({port: port }, function () {
  console.log('successfully established');
  openInBrowser('http://localhost:' + port);
}).on('error', function () {
  console.log('already open?');
  openInBrowser('http://localhost:' + port);
});


})(); // -->

DBにおけるウェブシステムのアカウントの権限管理とセキュリティについて

ユーザーと管理者アカウントがあるウェブサイトで、管理者がユーザーのパスワードをリセットできたりといった風に、アカウントによって権限が違う場合のセキュリティについての考察である。

/loginにアクセスし, emailとパスワードでuser/adminにアクセスできるとする。 (ログインページは1つであり、user/admin間のemailは重複してはいけない)

論理的構造を考えると account->user, account->admin といった階層構造になっているので、自然に設計すると accountテーブルにuser/admin情報をもたせて、

pragma foreign_keys=1;

CREATE TABLE account(
  id INTEGER PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  password TEXT NOT NULL,
  type INT NOT NULL,
  FOREIGN KEY(type) REFERENCES type(type)
);

CREATE TABLE type(
  type INTEGER NOT NULL PRIMARY KEY,
  type_name TEXT UNIQUE NOT NULL
);

INSERT INTO type VALUES
  (0, 'user'),
  (1, 'admin');

INSERT INTO account VALUES
  (0, 'user@example.com', 'pass1', 0),
  (1, 'admin@example.com', 'pass1', 1);

と書きたくなる(簡略のため生パスワードを使っているが、もちろん真似してはいけない)。これは、セキュリティの観点からは好ましくない。ログインページの実装を考えると、ログイン処理は

SELECT * FROM account WHERE email=$email AND password=$password;

になると思う。 $emailにSQL Injectionがあったとすると、$emailが

0 OR type=1 --

であった場合

SELECT * FROM account WHERE email=0 OR type=1 -- AND password=$password;

が実行され、管理者権限のアカウントが乗っ取られてしまう。 また、ログインに使用するemailアドレスを変更する機能を考えると

UPDATE account SET email=$email WHERE id=$id;

になるが、ここで、$emailにエスケープ漏れがありSQL Injectionがあるとする。$emailが

'a@example.com' type=1

であったとき、

UPDATE account SET email='a@example.com' type=1 WHERE id=$id

が実行されてしまい、ユーザーが管理者に昇格してしまう。これらは重大なセキュリティホールなので、SQL Injectionがあったとしても特権昇格ができないようなDB設計が好ましい。

具体的には、userとadminのテーブルを分けるのが好ましい。ただし、仕様上idとemailはuser/admin両方で一意であることを保証しなければならないので、完全に分けることは難しい。ここでは、id_tableを使った方法ともう一つの方法を考える(あまりSQLに詳しくないので、いい方法を知っている人がいたら教えてください)。

pragma foreign_keys=1;

CREATE TABLE id_email(
  id INTEGER NOT NULL,
  email TEXT NOT NULL UNIQUE,
  PRIMARY KEY(id, email)
);

CREATE TABLE user(
  id INTEGER NOT NULL UNIQUE PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  password TEXT NOT NULL,
  FOREIGN KEY(id, email) REFERENCES id_email(id, email)
);

CREATE TABLE admin(
  id INTEGER NOT NULL UNIQUE PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  password TEXT NOT NULL,
  FOREIGN KEY(id, email) REFERENCES id_email(id, email)
);

BEGIN;
INSERT OR ROLLBACK INTO id_email(0, 'user@example.com');
INSERT OR ROLLBACK INTO user(0, 'user@example.com', 'pass');
COMMIT;

emailから権限を判断する手段がないので、ログインはadmin, userの順に試行する。

上記ではidとemailのシステム内での一意性を保証するためにid_emailテーブルを定義したが、この方法もセキュリティ上よろしくない。

ユーザーがログインemailを変更するとき、userテーブルではなくuser/admin共通のid_email自体を参照しなければいけないので、

UPDATE id_email SET email=$new_email WHERE id=$id;

のようなコードを書くことになる。

この時、$new_emailでSQL Injectionが可能である場合、$new_emailが

'attacker@example.com' WHERE id=1 --

だとすると

UPDATE id_email SET email='attacker@example.com' WHERE id=1 --WHERE id=$id;

となり、管理者権限のアカウントのemailを攻撃者のものに書き換えられる可能性がある。書き換えが成功した場合、パスワードリセット機能によって管理者権限を奪われてしまう。

よって、id_emailを触るときには鬼のように気をつけるか、そもそもid_emailを使わないように定義するしかない。

id_emailを使わない方法についてだが、emailをuser/admin内で定義したい場合、テーブルにまたがった制約は外部キーや複合キーなどではうまく表現できないので、トリガーを使う(いい方法を知ってる人がいたら教えてください)。 また、システム内のidはaccountテーブルを使って一意性を保証しているが、アカウント作成時にidを取得する以外では使わないので特に問題はない。

pragma foreign_keys=1;

CREATE TABLE account(
  id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT
);

CREATE TABLE user(
  id INTEGER NOT NULL,
  email TEXT UNIQUE NOT NULL,
  password TEXT NOT NULL,
  PRIMARY KEY(id),
  FOREIGN KEY(id) REFERENCES account(id)
);

CREATE TABLE admin(
  id INTEGER NOT NULL,
  email TEXT UNIQUE NOT NULL,
  password TEXT NOT NULL,
  PRIMARY KEY(id),
  FOREIGN KEY(id) REFERENCES account(id)
);

-- user内でemailがUNIQUEであることは保証されているのでadminのみをチェックする
CREATE TRIGGER insert_user_email AFTER
  INSERT ON user
  FOR EACH ROW WHEN (SELECT email FROM admin WHERE email=new.email) IS NOT NULL
  BEGIN
    SELECT RAISE(ROLLBACK, 'email must be unique');
  END;

CREATE TRIGGER update_user_email AFTER
  UPDATE OF email ON user
  FOR EACH ROW WHEN (SELECT email FROM admin WHERE email=new.email) IS NOT NULL
  BEGIN
    SELECT RAISE(ROLLBACK, 'email must be unique');
  END;

-- 同様に、adminにおいてはuser内に同じemailが存在しないかをチェックする
CREATE TRIGGER insert_admin_email AFTER
  INSERT ON admin
  FOR EACH ROW WHEN (SELECT email FROM user WHERE email=new.email) IS NOT NULL
  BEGIN
    SELECT RAISE(ROLLBACK, 'email must be unique');
  END;

CREATE TRIGGER update_admin_email AFTER
  UPDATE OF email ON admin
  FOR EACH ROW WHEN (SELECT email FROM user WHERE email=new.email) IS NOT NULL
  BEGIN
    SELECT RAISE(ROLLBACK, 'email must be unique');
  END;

BEGIN;
INSERT OR ROLLBACK INTO account VALUES(null);
INSERT OR ROLLBACK INTO user VALUES (last_insert_rowid(), 'user1@example.com', 'pass');
COMMIT;

ログインするには、最初はadminとしてログインを試行して

SELECT * FROM admin where email=$email AND password=$password;

をして、失敗したらuserとして

SELECT * FROM user where email=$email AND password=$password;

で再試行する。ユーザのemailの更新は

UPDATE user SET email=$new_email WHERE id=$id;

なので、たとえSQL Injectionができても管理者権限を奪うことはできない(ただし、テーブルのアクセス権限を正しく設定している場合)。パスワードの変更などについても同様である。このように、権限の異なるアカウントは特権昇格攻撃を未然に防ぐためテーブルごと完全に分けたほうがよい。

さらに言うと、そもそも1つのログイン画面で複数の権限のアカウントの管理をすること自体、実装が複雑になるのでセキュリティ上好ましくない。

一般ユーザのログインは /login にして、 管理者のログインは /admin/login にし、idはadmin_id, user_idなど関連性がないことが明らかになるように名前をつけadmin/user間のemailの重複は無視し、userとadminを完全に分けるのが、安全な設計だと思われる。

「ORMを使ってまともに書いていたらSQL Injectionは起きないので関係ないし、親クラスに相当するものがあるほうが自然だよ」という考え方もあるが、最近「権限の異なるアカウントのテーブルは必ず分けないといけない」と断言されたので、いい機会なので考えてみた次第である。断言口調で書いたものの、絶対そうでなければならないかと言われると、うーんという感じではある。

おまけで一応書いておくが、生パスワードをDBに保存してはいけない。

hashed_password = password + salt; // saltが後ろである必要はない
for (i = 0; i < 1000; i++) {
  hashed_password = hash(hashed_password)
}

このようなニュアンスのものを使うべきである。詳しくは「パスワード ソルト」で検索してほしい。

実はあまりSQLに詳しくないので、他にidとemailの一意性を保証する上手い方法があるような気がしてならないので、いい方法があったらコメントで教えてください。

計算量とBig-O記法

プログラマであればアルゴリズムに関する話で、O(n)だとかO(log n)だとか、O(n2)だとか、そういった記号を目にすることはよくあると思う。 なんとなく、log n < n < n2の順に計算量が増加していくとかそういうことも知っていると思うが、計算量の増加とは何か説明しろと言われると、なかなか難しいと思う。この記事では高校数学レベルでわかるように考えていきたい。

まず、計算にかかる時間を、nを入力のサイズとしてf(n)を計算にかかる最大時間を返す関数とすると、計算量一般を、O(f(n))という、入力に対する計算時間の増加率として定義することができる(より厳密には、f:R+ -> R+ where R+=[0,∞)で、a>bであればf(a) >= f(b)であるとき)。

g(n) = n ^ 2

はn = 1に対して1を返す一方、より増加率の低い

h(n) = n + 100

は、n = 1に対して101を返すことに注意したい。言い換えると、関数の増加率の大きさと数字の大きさは違う。

では、どのように比較すればいいかというと、nが十分に大きくなると、増加量が大きい関数(n)>増加量が小さい関数(n)になるという性質を利用する。つまり、nを無限大に近づけて行ったときにおけるlim (n -> ∞)における大きさの違いを利用する。 ここで注意したいのは、正の極限値付近について

lim (n -> ∞) n + 100 = ∞
lim (n -> ∞) n ^ 2 = ∞

であることからわかるように、limによって導き出した∞は極限であって、大小の概念を含む一般の数値と違うことから、大小の比較には使えないということである。ただし、∞と∞の比較を避けることができれば、limを使っても、極値での大きさの比較はできる。つまり、

lim (n -> ∞) g(n) / h(n)

というように、比較したい二つの関数を分数の形にして∞自体を直接比較しなくて済むように変換する。

追記:コメント欄で指摘されたが、上記の定義だと、関数によっては振動して収束しない場合がある。limではなく、limsup/liminfを使って、上極限/下極限について考えると、これらも含めて定義できる。例えば、limsup( n -> ∞) |g(n) / h(n)| < ∞であればg(n) = O(h(n))、など。

この場合、lim(n -> ∞) g(n) / h(n)の値をkとしたとき (g(n)もh(n)も定義により常に正なので0/0は生じないことに注意)

追記2:最初O(f(n)) < O(g(n))と書いていたが、記号の使い方が不適切との指摘を受けたので訂正した。

  • kが正の無限大に発散するとき: O(f(n)) ⊊ O(g(n))
  • k∈Rでk != 0のとき: f(n) ∈ O(g(n))かつg(n) ∈ O(f(n))
  • k=0: O(g(n)) ⊊ O(h(n) となる。

先ほどの例の場合、

lim (n -> ∞) (n^2) / (n + 100) = ∞

となり、nは∞になるので、O(n2)の増加率のほうがO(n + 100)より大きいことがわかる。同様に lim (n -> ∞) (n + 100) / n = 1なのでO(n + 100)とO(n)が同一であることなどもわかる。

参考: ランダウの記号 - Wikipedia

promiseを順番に実行する

JavaScript の Promise を返す関数を直列で実行したいので Pinscher というライブラリを作ってみた。 - (define -ayalog '())について。 例えば、非同期に実行されるPromise p1, p2, p3があったとしてcallbackをp1, p2, p3の順番に実行したい場合、reduceが使える。

例1は、p1, p2, p3を並列に実行した上で、callbackの順序を保証したい場合。

まずテスト用に、一定時間まってからvalueでresolveする関数resolveLaterを定義しておく。

function resolveLater(value, ms) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(value);
    }, ms);
  });
}
var promises = [
  resolveLater('a', 1000),
  resolveLater('b', 1000),
  resolveLater('c', 2000),
  resolveLater('d', 0),
];

var callback = console.log.bind(console);

promises.reduce(function (current, next) {
  var p = current.then(function (v) {
    return next;
  });
  p.then(callback);
  return p;
}, Promise.resolve());

// a, b, c, d

このようになる。タスクそのものは並列に実行されるが、callbackは順番に実行される。

逆に、p1, p2, p3自体を順番に実行したい場合、つまり、あるタイミングで同時に動いてるpromiseが1つである必要がある場合。(ネットワークアクセスなどで、負荷をかけたくない場合などはこっちのパターンに近い実装をすることになる)

var argList = [
  ['a', 1000],
  ['b', 1000],
  ['c', 2000],
  ['d', 0]
];

var callback = console.log.bind(console);

argList.reduce(function (current, args) {
  return current.then(function () {
    return resolveLater.apply(null, args).then(function (v) { callback(v); });
  });
}, Promise.resolve());

非同期コードのテストについてだが、Jest | Painless JavaScript Unit Testingがおすすめで、jestを使うと、setTimeout系の関数自体を乗っ取って(実行順序を保証しながら)同期的に実行しながらsetTimeout.mock.callsに呼び出された時のmock関数の引数を記録していってくれるので、実際の非同期で不確実な動作に依存せずにテストしやすいコードを書ける。

ただ非同期のテストはどちらにしろ複雑になりがちで、ある程度のところで諦めてテストケースは書くがチェックは手動、という風にするのが現実的なのかもしれない。

追記

コメントでライブラリで解決していることを実現できていないと指摘されたので、その他の例について触れる。

まず、上記コードにはなかった、順番に実行したいpromiseがコード中で動的に追加される場合。 個々のタスク自体は並列に実行するが出力を順番にしたい場合

var callback = console.log.bind(console)
var task = Promise.resolve();

var identity = Object; // この場合function (a) { return a; }

task = task.then(identity.bind(null, resolveLater('a', 1000))).then(callback);

task = task.then(identity.bind(null, resolveLater('b', 1000))).then(callback);

task = task.then(identity.bind(null, resolveLater('c', 2000))).then(callback);

task = task.then(identity.bind(null, resolveLater('d', 0))).then(callback);

このようにreduceを手動で展開することで動的に追加できるようになる。

もう一つの例として、個々のタスクの実行も順番に(同時に一つしか動かさないように)したい場合

var callback = console.log.bind(console)
var task = Promise.resolve();

task = task.then(resolveLater.bind(null, 'a', 1000)).then(callback);

task = task.then(resolveLater.bind(null, 'b', 1000)).then(callback);

task = task.then(resolveLater.bind(null, 'c', 2000)).then(callback);

task = task.then(resolveLater.bind(null, 'c', 0)).then(callback);

このようになる。 Promiseの上にコントロールフローを実装するライブラリを使うより、このように直接promiseを組み合わせる手法を使ったほうがいいと思う理由は複数あって、まず、自由度の点。 例えばここに載せたコードでは、全体の状態がPromiseとして表現されていて取り出すことができる(reduceを直接使った例では、reduceが返すPromiseがそれにあたる)ので、複数のパターンを組み合わせることができる。 具体的には、最初のpromise数個をarrayとして入れておき、動的に実行したいものを後から追加したい場合。

var identity = Object; // この場合function (a) { return a; }
var callback = console.log.bind(console);

var task = [
  resolveLater('a', 1000),
  resolveLater('b', 1000),
  resolveLater('c', 2000),
  resolveLater('d', 0),
].reduce(function (current, next) {
  var p = current.then(function (v) {
    return next;
  });
  p.then(callback);
  return p;
}, Promise.resolve());

task = task.then(identity.bind(null, resolveLater('e', 1000))).then(callback);

このようにできる。

reduce相当の処理をしていないコードのもう一つの問題は、エラー処理。動的にタスクを追加する例では全体のエラーという概念は薄いかもしれないが、例えば、記事中のコントロールフローライブラリでは、すべての実行が終わった状態というものをPromiseとして取り出せない。よって、全体のどこかでエラーがあった場合にそれらをcatchする関数、というものが書けない。

全体の処理がPromiseとして表現されている場合は

task.then(null, function (err) { console.error(err); });

などを最後に追加することで、エラー処理が握りつぶされるわかりづらいエラーが防げる。

ES6のclassは巻き上げがされない

ちょっとハマったのでメモ。 例えば

alert(A);

function A() {
}

はできるが、

alert(A);
class A {

}

ができないのはなぜか。

class A {
}

class B extends A {

}

例えば、このようなコードの場合、hoisting(スコープ先端への巻き上げ)をしても問題ないが、 関数と違ってextendsはその場で評価しなければいけないことがある。 例えば、

var A = function () {

};
A.prototype.x = function () {
  return 1;
};

class B extends A {
}

alert(new B().x());

というコードの場合、BクラスはAを継承しなければいけないので、var Aで宣言されているものはhoistingされないのでclass宣言もその場で評価しないと、正常に継承することができない。

というわけで、

function A () {
}

class A {
}

は評価の順序が違い、class Aはvar A =とおなじような順序で評価されるわけである。

io.jsとES6とトランスパイラまわりの現状

最近いろいろな環境でES6まわりの環境が整いはじめたので、使っていくために下調べしておこうと思う。

ES6の実装状況であるが、ECMAScript 6 compatibility tableにまとめが載っている。

例えば、io.js (nodeのfork)のES6実装状況。stableなES6 featuresが元から有効になってる。

現在有効なのは、文字列テンプレート、for of、generators, Promise, Map, WeakMap, Setなど。

最新版をターゲットにするのであれば、上の機能は何も考えずにそのまま使うことができる。 このうち、テンプレート、for of, WeakMap以外はpolyfillが手に入るので、それらを使わなければPromise, Mapなどはfeature detectして実装されていない場合はmoduleを読み込むことで幅広いバージョンをサポートできる。

node.jsは--harmonyを有効にしなければジェネレータが使えないなど、node.jsはio.jsとはES6まわりの実装事情が違う。

io.jsでサポートされていない(classes)などの機能を使いたい、あるいは自分で書いているコードでサポートするバージョンのio.js / nodeでネイティブに実装されていない機能を使いたい場合は、トランスパイラを使う。 今一番勢いがあって機能が整ってるのはBabel.js (つい最近まで6to5という名前だった)で、クラスなどのES6の機能はもちろんsource mapsのサポートもあり、React、JSX、Flowなども使用することができる。

一般的なトランスパイラと同様で、

$ npm install --global babel
$ babel script.js

とするだけで使うことができるので、gulpなどでコンパイルするといい。

この場合、

dist/*.js
src/*.es6.js

のように、ES6のコードは.es6.jsとしておくとわかりやすくて、 npm publishするときは、 dist/側はhookでインストール時に生成されるようにしておくとgit logが汚れない。

source mapsについては、io.js / node側で有効にするには

require('source-map-support').install()

とすれば良い (参考: ES6 at PayPal )。

ReactとJSXを使いたい場合は、もともとreact-toolsはharmonyオプションがあるので、一部機能だけを使えればいい場合は

<script type="text/jsx;harmony=true">

などとして書くかreact-toolsでharmonyオプションを有効にすればいいのだが、Babel (6to5) もJSXをサポートしているので、より完全なES6実装が欲しい場合はBabelを使うとよい。

そんな感じで、タイトルと比べて内容がスカスカではあるが、ここ最近、トランスパイラが急に進化してきてES6が使える環境がいきなり整ってきたような雰囲気があるので、そろそろ使い始めてもいい頃だと思う。いつのまにかブログの更新が数ヶ月空いてしまったけど生きてるので、今年も頑張ってブログを書いていきたい。

JSライブラリ/フレームワークの良い、悪いメモ

※ただのメモで、未来志向なのであまり真に受けてはいけない。

良いっぽい

React.js

い/コンポネント志向/APIの設計がいい。JSXと他のトランスパイラの組み合わせという問題はある

Promise

ネイティブに入った、誰もが使ってる

TypeScript

ES6時代でも存在意義のある言語。TypeScript互換のFacebook Flowの動向に注目

Backbone.js

ModelとEventを使う/Viewは使わなくていい

Lodash

Underscore.jsをよくしたやつ

Gulp

Gruntより良いという意味で。browserifyまわりがうまく動かない問題があってnpm runのほうがいいという噂もあるがまあ良いに分類してもいい

EventEmitter

Custom EventはDOMにくっ付いてる感があるのでロジック志向の物にはEventEmitter使ったほうがいい

6to5

Traceurとか色々あるけどSource Mapsあってライブラリ依存が少なくてregenerator使ってるあたりが良い

co

generator/Promise時代のasync

JSHint

JS直接書くならこれがいる

Browserify

クライアントサイドもrequire使うべき

jQuery

UIベタ書きせずBetter DOMとして使う

悪いっぽい

AngularJS

フレームワークがでかい、設計がおかしい

Polymer

ダーティーハックの塊 コメントで指摘があったがハックが多いのはWebComponents(特にShadow DOM)のPolyfill実装のpaltform.jsで、これはpolyfillなのでいずれ消えるそうである。ただ、ヘビーなpolyfill実装に依存しているブラウザが多いので、現時点ではあまり勧められないのも事実だと思う。

Async.js

Promiseを使う

jQuery.Deferred

正しいPromiseを使う

AMD modules

CommonJSを使う

Prototype

だいぶ前に死んだ

jQuery UI / jQuery Mobile

もう役目は終えた

JSLint

JSHintの時代になった/どうでもいいことでエラーを出しすぎる

RequireJS

でかすぎる/AMDの時代は終わった/事前にコンパイルしてくっつけるスタイルが主流

Component

npm使って困ったらbowerで良い

判断しかねる

CoffeeScript

ES6+のclass / arrow function syntaxもろもろと相性が悪い/若干役目を終えつつある感が漂ってる

KnockoutJS

MVVMよりReactJSの設計のほうが綺麗

Meteor

ロックインが激しい/思ったより普及しなかった/まだ発展中

Grunt

Gulpの時代が来てる/gulpのプラグインも埋め込めるようになったから大は小を兼ねる的な使い方もある

Traceur

全体的に重くてコンパイル後がでかい/ES6カバー率は高い

ImmutableJS

見込みはあるけどAPIがまだまだ不安定/あまり使われてない