ファイルのタイムスタンプを保持したまま、scpコマンドで転送する
いきさつ
サーバからファイルをダウンロードした後、
日付情報を用いてファイル処理をする必要があった。
コマンド
scpコマンドにpオプションを付けるだけ。
scp -rp remote_host:remote_dir local_dir
ちなみに
ファイル転送時に新しく作られるディレクトリについては、
どうしても転送時のタイムスタンプになるっぽい。
【OSSリーディング企画】BootstrapのJavaScriptを読み解く【その1】
いきさつ
Bootstrap、すごい便利。
htmlタグさえドキュメントどおりにおけば*1、
カルーセルは動くわモーダルは開くわアラートは出るわ、、、
いつも本当にお世話になっております。
ただ、、、ただね、
「なんで動くのか」について全然考えたことがなかったんですよ。
細かいところを調整しようと思った時に、
中がどうなっているのか分かっていないので、
手が完全に止まってしまう。。。
これじゃあ「ライブラリを使っている」のではなく、
「ライブラリに使われている」。そんな気がしてならない。。。
というわけで、OSSリーディングを始めました。
対象OSS
今回はBootstrapのモーダル周りについて読んでいきます。
読むファイルはこれ。
すでにビルドされている生のJavaScript。
どうせならJavaScriptも軽く復習したい。
そういう思いもあります。
OSSを読むにあたって大切なこと
「全部は読まない。読みたいとこだけ」
なんだか懐かしい響きですね。
OSSを、はじめから全部読もうと思ってはいけません。
なんせOSS(今回はBootstrap)は、
「1000人以上の人が」
「何年もかけて」
開発をしているライブラリです。
はじめから全部読むのはツライツライ。
今回のゴール
「何もしていないのに(JS書いていないのに)なんでボタンを押したらモーダルが開くのか」
に絞って読んでいきます。
モーダルを開く処理までは読みません。
「ボタンを押したら」という部分に着目し、
ボタンに対してclickイベントを登録している部分を読み解きます。
また、その途中で即時関数が使われているので、即時関数についても簡単に触れます。
読んでいきます
Modalモジュールが宣言されている部分
/** * -------------------------------------------------------------------------- * Bootstrap (v4.1.1): modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * -------------------------------------------------------------------------- */ var Modal = function ($$$1) { // いろいろな処理 }($);
だいたい1930行あたりにModalモジュール(モジュールという言い方が正しいのかどうかは微妙)が宣言されていますね。
Bootstrapでは様々な機能が使えますが、
これらはみなモジュールとして提供されています。
すごい乱暴に書くとこんな感じ。
var Modal = function ($$$1) { // いろいろな処理 }($); var Carousel = function ($$$1) { // いろいろな処理 }($); var Alert = function ($$$1) { // いろいろな処理 }($); exports.Alert = Alert; exports.Carousel = Carousel; exports.Modal = Modal; ...
各モジュールで使われる変数は関数の中で定義され、カプセル化されています。
それにより、他のモジュールで使われている変数とぶつかったりすることはありません。
かしこい(小並感)。
exportsの意味について
そして、
exports.Alert = Alert; exports.Carousel = Carousel; exports.Modal = Modal;
exportsにはwindow.bootstrapオブジェクトが入っているよ。*2
つまり、もっと分かりやすく書くとこんな感じ。
window.bootstrap.Alert = Alert; window.bootstrap.Carousel = Carousel; window.bootstrap.Modal = Modal;
なので、コンソール上でwindow.bootstrapすると、
これらのモジュール(正確にはコンストラクタ)にアクセスすることができるよ。
Modal用のボタンにクリックイベントをバインドしている部分
さて、Modalを開くためのボタンのhtmlを確認してみるよ。
<button type="button" data-toggle="modal" data-target="#exampleModal"> Launch demo modal </button>
これだけでいいらしいよ。ふざけてるね!
重要なのは、
data-toggle="modal"
という部分。
これを目印にして、
このdata属性を持っている要素にclickイベントをバインドしているよ。
こんな感じ!
// 2500行あたり $$$1(document).on('click', '[data-toggle="modal"]', function (event) { // クリックした時に起こるイベント }
え?
でもなんで勝手にclickイベントがバインドされるかって?
それは、この処理が即時関数の中で実行されているからだよ!
var Modal = function ($$$1) { // 即時関数の中 $$$1(document).on('click', '[data-toggle="modal"]', function (event) { // クリックした時に起こるイベント } }($);
一度JavaScriptが読み込まれると即時関数は勝手に実行されるので、
html上にdata-toggle="modal"さえ書いておけば、
勝手にclickイベントがバインドされるんだね!
即時関数の使い所
「一度しか実行されない処理」を即時関数で書くことが多いよ。
今回のように、
「特定の要素に対してイベントリスナを登録」
という処理は1度だけの実行でいいから、よく使われるよ。
即時関数のメリット
即時関数で処理を包むことにより、
変数のスコープが即時関数内に限定されるので、
グローバルスコープを汚さないよ!
巷のライブラリでも即時関数はよく見るよ。みてみてね!
まとめ
というわけで今回は、
モーダルを開くためのボタンに対してclickイベントが登録されている部分を確認してみました。
data-toggle='modal'さえ持っていればよいので、
ボタンだろうがlistだろうがdivだろうがなんでもモーダルが開けそうですね。
BootstrapのJavaScriptは、
定数がきちんと宣言されていたり、
jQueryからモーダルを操作できるようなAPIを提供していたり、
複数のモーダルを開くことができるような実装の仕方をしていたりと、
すごく勉強になる部分が多いので、ひきつづき読んできたいと思います。
*1:JavaScript書かなくてよいことも多い
*2:気になる人は1行目から読んでみよう。Boostrapがブラウザ上で実行される場合、exportsにはwindow.bootstrapオブジェクトが代入されているはずだ
つるのおんがえし〜「決して、この部屋をのぞかないで(Permission denied)」〜
この記事のゴール
この記事を読めば、
$ ls hoge d rwx-----x. 1 owner group 4096 Dec 22 15:48 hoge
この
rwx-----x
が読めるようになる(ハズ)です。
あらすじ
おじいさんが罠にかかっているつるを見つけました。
苦しんでいるつるがかわいそうだったので、おじいさんは助けてやりました。
その日の夜は大雪になり、とても寒い夜でした。
コン、コン。家の扉が叩かれる音がします。
夜、一人のおんながおじいさんの家にやってきました。
そのおんなは一晩泊めてほしいといいます。
やさしいおじいさんはおんなを家に招き入れることにしました。
家に招き入れた
おじいさんは手元の端末でおんなをlinux userに追加しました。
[root@ie ~]# useradd tsuru
おんながはたを折り始めた
おんなはいろいろ良くしてもらったお礼に、
はた(美しい布)をおりたいといいました。
おんな「はたをおりあげるまで、決してのぞかないでください」
[tsuru@ie ~]$ cd tsuru
といって、おんなはへやにはいっていきました。
おじいさんは好奇心に耐えられなかった
おじいさんは震える手で端末にこう打ち込みました。
[jiisan@ie ~]$ cd tsuru
しかし、
-zsh: cd: tsuru/: Permission denied
部屋には入れませんでした。
[jiisan@ie ~]$ ls tsuru drwx------. 2 tsuru tsuru 4096 3月 27 07:26 2018 tsuru
おじいさん「なるほど。。。こりゃ入れんわい」
おじいさんは納得しました。
なぜ、入れなかったのか
まず、おじいさんがuseraddしたことにより、
tsuruディレクトリが作成されました。
useraddしたときにできるこの部屋の所有者は「tsuru」になります。
ですので、こうなります。
この部屋は、作成された時から以下の要件をみたしています。
・つる[owner]は部屋を自由にする(見れる、いじれる、入れる)ことができる
・おじいさんとおばあさん[other]は入れない
つまり、こういうことですね。
もうすこしlinux風に書くとこんな感じです。
さらに、linuxのファイルは「所有者」だけでなく「所有グループ」をという情報を持っています。
これにより、
「つるの親族」というグループのユーザだけ部屋に入ることを許可できたりします。
useraddしたときにできる部屋の所有グループは、「tsuru」です。
これで完成です。
ちょっと見方を変えて、、、
ここでlsです!
「owner」ができること
「group」ができること
「other」ができること
をまとめたものを、パーミッションと言います。
おじいさんは強硬手段に出た
中でなにをやっているのか、気になって仕方がありません。
じいさんの秘蔵のこれくしょんが、
つるによって紐解かれていしまっているかも知れません。
3日悩んでおじいさんは決心しました。
「パーミッションを変えよう」
(つづく)
【Java言語で学ぶリファクタリング入門】クラスの抽出
いきさつ
昔書いたコードが悲しい感じだった&どこから手を付ければよいかわからない感じだったので、
リファクタリングの知識が欲しかった。
そしたら手元にこの本があった。
リファクタリング前
Playerクラス
class Player { private _currentMedia; private _musicData; private _videoData; public setMedia() { }; public play() { }; public loop() { }; public stop() { }; private playMusic() { }; private loopMusic() { }; private stopMusic() { }; private playVideo() { }; private loopVideo() { }; private stopVideo() { }; }
playerが責務を持ちすぎ。
- musicの再生・停止・ループ方法を知っている
- videoの再生・停止・ループ方法を知っている
なにがしんどいのか
新しいmediaを追加するたびにmethodを追加する必要がある。
playerが再生されるものに対しての知識を持ちすぎているので、密結合。
リファクタリング方法
クラスの抽出を行なう。
リファクタリング後
新たにMusicMediaとVideoMediaクラスを作った。
Mediaクラス
/** 共通インターフェース */ interface Playable { play(); loop(); stop(); } /**MusicMedia */ class MusicMedia implements Playable { public play() { }; public stop() { }; public loop() { }; } /** VideoMedia */ class VideoMedia implements Playable { public play() { }; public stop() { }; public loop() { }; }
Playerクラス
class Player { private _currentMedia: Playable; public setMedia(media: Playable) { this._currentMedia = media; } public play() { this._currentMedia.play(); } public stop() { this._currentMedia.stop(); } public loop() { this._currentMedia.loop(); } }
何がうれしいのか
新たにMediaを追加する時に、既存のコードに手を加えなくて良い(拡張に対して開いている)
既存のmediaの修正をする時に、クラス内で修正が収まる(修正に対して閉じている)
PlayerクラスはPlayableインターフェースを実装しているクラスなら何でも扱えるので、疎結合
【Node.js】Expressをinstallして起動(npm start)するまで
いきさつ
少し作りたいものがあったので、
Expressをinstallして起動してみる
手順
express-generatorをグローバルインストール
$ npm install express-generator -g
expressコマンドでプロジェクトを作成
$ express restapi
必要なnode_moduleをinstall
$ cd restapi; npm install
アプリケーションの起動
$ npm start > restapi@0.0.0 start /home/vagrant/dev/restapi > node ./bin/www
ポートにアクセス
ipアドレス:3000
にアクセスすると、
と表示され、起動が確認できる。
おまけ
ポート3000がLISTENになっているか見てみる。
$ netstat -antp tcp 0 0 :::3000 :::* LISTEN 22175/node
ポート3000をnodeプロセスがLISTENしている。
【CTF】サーバをハックする〜Simple Auth IIとSQLiteと〜
いきさつ
セキュリティの勉強も兼ねてCTFの問題を解いてみた。
サーバをハックするより前にソースコードが見えているので
ハックするような大したことはしていない。
問題
20ptなので超簡単ということだが。。。?
考えたこと
ログイン画面
適当なID&パスワードを入れ、ログインしてみる。
ソースコード
<?php function h($s) { return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); } if (!isset($_POST['id']) or !is_string($_POST['id'])) $_POST['id'] = ''; if (!isset($_POST['password']) or !is_string($_POST['password'])) $_POST['password'] = ''; $try = false; $ok = false; if ($_POST['id']!=='' or $_POST['password']!=='') { $try = true; $db = new PDO('sqlite:database.db'); $s = $db->prepare('SELECT * FROM user WHERE id=? AND password=?'); $s->execute(array($_POST['id'], $_POST['password'])); $ok = $s->fetch() !== false; }
postされたidとpasswordでuserテーブルを検索、
該当レコードが存在する場合はログイン成功ということらしい。
SQLインジェクション使える?→使えないよね
プリペアドステートメントを使用しているので、
execute内に渡された値はエスケープされる。
この時点で
やれることがなくなってしまった。。。
ググる
この記事をみると、
「ダウンロード」という言葉がある。
どういうこと?
SQLiteについて調べる
どうやらSQLiteのDB情報は、
****.dbというファイル形式で保存されるらしい。
そして、
<?php //... $db = new PDO('sqlite:database.db');
このsqliteの後で指定されているのはDBのファイルで、
相対パス指定されているということらしい。
DBファイルをダウンロード
sourceのurlのauth.phpの部分を、database.dbに変えるとDBファイルがダウンロードできる。
(普通じゃ考えられないけど)
SQLiteで読み込む
$ sqlite3 ~/Downloads/database.db sqlite>
適当なsqlを発行
phpソースコードから、userテーブルが有ることがわかっているので、
selectで全部持ってくる。
sqlite> select * from user; root|FLAG_****
でてきた。
まとめ
この手の問題を特には様々な知識が求められる。
今回で言えば、
SQLiteの接続方法
プリペアドステートメント
SQLインジェクション(使わなかったけど)
などなど。
なかなか勉強になった。
#Cでsocket通信をしてみる。ついでにnetstatでlistenしているポートを調べる。
C#でソケット通信
職場でC#を使うのと、
ソケット通信に興味があるのでちょっと練習してみた。
ソースコード
server側
using System; public class Server { public static void Main() { string ipString = "127.0.0.1"; System.Net.IPAddress ipAdd = System.Net.IPAddress.Parse(ipString); int port = 2001; System.Net.Sockets.TcpListener listener = new System.Net.Sockets.TcpListener(ipAdd, port); listener.Start(); Console.WriteLine("Listenを開始しました({0}:{1})。", ((System.Net.IPEndPoint)listener.LocalEndpoint).Address, ((System.Net.IPEndPoint)listener.LocalEndpoint).Port); // 接続要求があったら受け入れる System.Net.Sockets.TcpClient client = listener.AcceptTcpClient(); Console.WriteLine("クライアント({0}:{1})と接続しました。", ((System.Net.IPEndPoint)client.Client.RemoteEndPoint).Address, ((System.Net.IPEndPoint)client.Client.RemoteEndPoint).Port); // NetworkStreamを取得データの流れ System.Net.Sockets.NetworkStream ns = client.GetStream(); ns.ReadTimeout = 10000; ns.WriteTimeout = 10000; System.Text.Encoding enc = System.Text.Encoding.UTF8; bool disconnected = false; System.IO.MemoryStream ms = new System.IO.MemoryStream(); byte[] resBytes = new byte[256]; int resSize = 0; do { //データの一部を受信する resSize = ns.Read(resBytes, 0, resBytes.Length); //Readが0を返した時はクライアントが切断したと判断 if (resSize == 0) { disconnected = true; Console.WriteLine("クライアントが切断しました。"); break; } //受信したデータを蓄積する ms.Write(resBytes, 0, resSize); //まだ読み取れるデータがあるか、データの最後が\nでない時は、 // 受信を続ける } while (ns.DataAvailable || resBytes[resSize - 1] != '\n'); //受信したデータを文字列に変換 string resMsg = enc.GetString(ms.GetBuffer(), 0, (int)ms.Length); ms.Close(); resMsg = resMsg.TrimEnd('\n'); Console.WriteLine(resMsg); if (!disconnected) { //クライアントにデータを送信する //クライアントに送信する文字列を作成 string sendMsg = resMsg.Length.ToString(); //文字列をByte型配列に変換 byte[] sendBytes = enc.GetBytes(sendMsg + '\n'); //データを送信する ns.Write(sendBytes, 0, sendBytes.Length); Console.WriteLine(sendMsg); } ns.Close(); client.Close(); Console.WriteLine("クライアントとの接続を閉じました。"); listener.Stop(); Console.WriteLine("Listenerを閉じました。"); Console.ReadLine(); } }
clinent側
using System; public class Client { public static void Main() { Console.WriteLine("入力してください"); string sendMsg = Console.ReadLine(); if (sendMsg == null || sendMsg.Length == 0) { return; } string ipOrHost = "127.0.0.1"; int port = 2001; System.Net.Sockets.TcpClient tcp = new System.Net.Sockets.TcpClient(ipOrHost, port); Console.WriteLine("サーバー({0}:{1})と接続しました({2}:{3})。", ((System.Net.IPEndPoint)tcp.Client.RemoteEndPoint).Address, ((System.Net.IPEndPoint)tcp.Client.RemoteEndPoint).Port, ((System.Net.IPEndPoint)tcp.Client.LocalEndPoint).Address, ((System.Net.IPEndPoint)tcp.Client.LocalEndPoint).Port); System.Net.Sockets.NetworkStream ns = tcp.GetStream(); ns.ReadTimeout = 10000; ns.WriteTimeout = 10000; System.Text.Encoding enc = System.Text.Encoding.UTF8; byte[] sendBytes = enc.GetBytes(sendMsg + '\n'); ns.Write(sendBytes, 0, sendBytes.Length); Console.WriteLine(sendMsg); System.IO.MemoryStream ms = new System.IO.MemoryStream(); byte[] resBytes = new byte[256]; int resSize = 0; do { resSize = ns.Read(resBytes, 0, resBytes.Length); if (resSize == 0) { Console.WriteLine("サーバーが切断しました。"); break; } ms.Write(resBytes, 0, resSize); } while (ns.DataAvailable || resBytes[resSize - 1] != '\n'); string resMsg = enc.GetString(ms.GetBuffer(), 0, (int)ms.Length); ms.Close(); resMsg = resMsg.TrimEnd('\n'); Console.WriteLine(resMsg); ns.Close(); tcp.Close(); Console.WriteLine("切断しました。"); Console.ReadLine(); } }
コンパイル
mcs client.cs mcs server.cs
実行
サーバの実行
$ mono server.exe Listenを開始しました(127.0.0.1:2001)。
クライアントの実行
$ mono client.exe 入力してください hello world
サーバの反応
クライアント(127.0.0.1:32924)と接続しました。 hello world 11 クライアントとの接続を閉じました。 Listenerを閉じました。
まとめ
だからlocalhost上でポートさえ違えば、通信ができる。
今回は
・サーバ IP 127.0.0.1 ポート 2001
・クライアント IP 127.0.0.1 ポート 32924
で通信を行った。
なお、クライアントのポートは通信毎に自動で割り振られる。
netstatでポートがLISTENしているかどうかを調べてみる
$ netstat -anpt tcp 0 0 127.0.0.1:2001 0.0.0.0:* LISTEN 14145/mono
monoというプロセスが、 2001ポートを使って通信していることがわかる。
-aオプション
LISTEN状態も含むすべてのソケットを表示する。
-nオプション
IPアドレス・ポート番号をそのまま表示する。
これがない場合、ドメイン名として表示されたり、ssh・httpdとか表示されたりする。
-pオプション
通信しているプロセスを表示する。