ダークソウル初心者メモ

ダークソウルの(下手くそであるところの)自分用メモ。

フリーズについて

自分は20時間くらいプレイして、オフライン状態ではまだフリーズなし。一度だけフリーズしたときは、オフラインを選択していたのに何故かいつのまにかオンラインになっていた。一応PS3のオプションの「自動的にサインインする」のチェックを外してみてからは、勝手にオンラインになることもないし、フリーズもない。

キック/ジャンプ斬りのコツ

左スティックを既に倒している状態でR1/R2を押すと普通の攻撃になってしまい出ない。左スティックを倒していない状態から、左スティックを倒すのとR1/R2ボタン押下を同時に行う。気持ちR1/R2ボタンを早めに押すほうが良い気がする。まずは静止した状態で練習すること。

安全策

強そうな敵がいたら、その場では戦わず、敵をクリア済みの方、篝火に近いほうにおびき出してから戦うこと。強そうな敵が出てくるその場で死んでしまうと、回収しに行ってまた死ぬ可能性大。

ボス戦

ほとんどのボスは盾なんか構えてても攻撃を防ぎきれない(もしくは下手くそには防げる攻撃と防げない攻撃を判断できない)ので、最初からローリングで避けることだけを考えて両手持ちで戦った方が早く終わる。

注ぎ火の罠

初期状態では1回(エスト瓶が10回になる火の強さ)までしかできず、2回目を行おうとすると「秘儀が必要」とか言われる。「火継ぎの祭祀場」のものは最初から注ぎ火済み状態。「人間性が2個溜まったから祭祀場の火を更にパワーアップするぜ!」と思って無駄に生身になった後絶望しないように注意。

北の不死院のアイテム交換

アイテムを置くときは、きちんと鳥の巣の上に置くこと。手前の狭いところにゴミクズを捨ててセーブ・ロードしたら交換アイテム無しに普通に消えていた。

JDK7 では、Windowsでもファイルのアトミックな上書きリネームができる

JDK7になって、以前はできなかった Windows上でのファイルのアトミックな上書きリネームができるようになっていてうれしい。

ソース

package example.jdk7;

import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

public class FilesMoveTest {
  public static void main(String[] args) throws Exception {
    FileSystem fs = FileSystems.getDefault();
    Path source = fs.getPath(args[0]);
    Path target = fs.getPath(args[1]);
    System.out.printf("from [%s] to [%s]\n", source, target);
    Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
  }
}

ATOMIC_MOVE 指定時には他のオプションは全て無視される、とドキュメントにあるので、REPLACE_EXISITING は指定していない。

ドキュメントには、ATOMIC_MOVE 指定時に移動先が上書きされるか移動に失敗するかは実装依存とあるけど、一応 Windows の場合は ATOMIC_MOVE 指定時には内部で MoveFileEx(移動元, 移動先, 1) が呼び出されていて、第三引数のフラグに MOVEFILE_REPLACE_EXISTING を示す 1 が指定されているので*1、移動先にファイルが存在すると上書きしてくれる(ロックされていなければ)。

確認

> java example.jdk7.FilesMoveTest C:\temp\test1.txt C:\temp\test2.txt

from [C:\temp\test1.txt] to [C:\temp\test2.txt]

C:\temp を覗くと、test2.txt のみが存在していて、正しく「上書きリネーム」できていることがわかる。

test2.txt がロックされている場合、

from [C:\temp\test1.txt] to [C:\temp\test2.txt]
Exception in thread "main" java.nio.file.AccessDeniedException: C:\temp\test1.txt -> C:\temp\test2.txt
	at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:83)
	at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
	at sun.nio.fs.WindowsFileCopy.move(WindowsFileCopy.java:301)
	at sun.nio.fs.WindowsFileSystemProvider.move(WindowsFileSystemProvider.java:286)
	at java.nio.file.Files.move(Files.java:1339)
	at example.jdk7.FilesMoveTest.main(FilesMoveTest.java:15)

当然、移動に失敗。

別ドライブに移動しようとした場合、

> java example.jdk7.FilesMoveTest C:\temp\test1.txt D:\temp\test2.txt

from [C:\temp\test1.txt] to [D:\temp\test2.txt]
Exception in thread "main" java.nio.file.AtomicMoveNotSupportedException: C:\temp\test1.txt -> D:\temp\test2.txt: ファイルを別のディスク ドライブに移動できません。

	at sun.nio.fs.WindowsFileCopy.move(WindowsFileCopy.java:296)
	at sun.nio.fs.WindowsFileSystemProvider.move(WindowsFileSystemProvider.java:286)
	at java.nio.file.Files.move(Files.java:1339)
	at example.jdk7.FilesMoveTest.main(FilesMoveTest.java:21)

別ドライブの場合は「アトミックな移動」はできないので(コピー扱いのため)例外が発生する。

Pleiades All In One 版の Eclipse 3.7.0 Indigo (JREあり) で何故かプロキシ認証に失敗する件

インターネット接続するのに認証つきプロキシが必要な環境で Pleiades All In One 版の Eclipse 3.7.0 Indigo (JREあり)を入れてみたら、きちんとプロキシ設定をしているのに

HTTP プロキシー認証が必要です

とか言われて「新規ソフトウェアの追加」とか「Eclipseマーケットプレイス」が全く動かない。この辺 の設定を行ってみてもだめ。検索してもこういう事例は見つからず困ったが、きちんと動いている Eclipse 3.6 との設定の差異を見てみたらなんとか動かすことができた。

とりあえずの解決法:別途JDKをインストールして、Pleiades 同梱のJREではなく、そのJDKを使うように eclipse.ini に以下のように指定する。

-vm
C:/Program Files/Java/jdk1.6.0_26/bin/javaw.exe

こうすると、何故か普通に動くようになった。

理由はよくわからないし、Pleiades固有の問題なのかEclipse自体の問題なのか、Pleiades同梱のJRE固有の問題なのかJRE自体が悪いのか、3.7からなのか3.6でも同じ問題があったのか、何もわからないが、とりあえず検証する時間がないのでここまで。

(2011/08/19追記)いったん接続できるようになったら、上記の設定を消しても接続できるようになった。わけがわからない…。

感想: 体系的に学ぶ 安全なWebアプリケーションの作り方

読んでます。

まだ途中だけど、主にJava/Tomcatユーザとしての備忘メモ。ただし、セキュリティの専門家でも何でもないので間違いは大いにあると思われます。

HttpOnly 属性の指定(p.103)

Tomcat では、Tomcat7以降(Servlet 3.0以降)なら単純に web.xml か web-fragment.xml

<session-config>
  <cookie-config>
    <http-only>true</http-only>
  </cookie-config>
</session-config>

と書けばOK。Tomcat6の場合は、context.xml や server.xml の Context 要素に

<Context useHttpOnly="true" ...>

のように useHttpOnly 属性を設定する*1

URL埋め込みの防止 (p.171)

URL Rewriting を使わないようにする対策として、Javaについては

J2EEの場合はセッションIDをURL埋め込み(J2EEではURLリライティングと呼ばれます)にするには、HttpServletResponseインタフェースのencodieURLメソッドまたはencodeRedirectURLメソッドを用いて明示的にURLを書き換える必要があるので、これらによる処理を記述しなければ、URL埋め込みのセッションIDとなることはありません。

とあるが、JSTLのc:urlやStrutsの類似のタグ等、多くのフレームワークの内部で encodeURL などが使われているため*2、実際には回避は難しいような気がする。

encodeURL を使わざるを得ない場合の対策としては、Servlet 3.0 以降なら HttpOnly の時と同様、web.xml か web-fragment.xml

<session-config>
  <tracking-mode>COOKIE</tracking-mode>
</session-config>

でOK。Servlet 2.5以前の場合は標準の設定は用意されておらず、Tomcat6でもできないので*3、個人的には以下のようなフィルタ(書き方は Servlet 3.0だけどやり方は2.5でも同じ)を適用してごまかしていた。

package example;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

@WebFilter("/*")
public class URLRewritingFilter implements Filter {

    @Override
    public void destroy() {
        // do nothing
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (response instanceof HttpServletResponse) {
            response = new HttpServletResponseWrapper((HttpServletResponse)response) {
                @Override
                public String encodeURL(String url) {
                    // 書き換えないで、無理やりそのまま返す
                    // 初期化パラメータでオンオフ切り替えられると良いかも
                    return url;
                }
            };
        }
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig fConfig) throws ServletException {
        // do nothing
    }
}

ログイン前のセッションIDの固定化攻撃の対策(p.182)

ログイン前にセッション変数を使っていると、セッションIDの固定化攻撃に完全に対策することは困難です。

とあるのだけど、理由がよくわからない(わかりました。tetz42さんのコメント参照)。例えば Spring Security (2.x の時代だけど)では、session-fixation-protection を有効にすると、ログイン時に

  1. 新しいセッションを作る
  2. ログイン前のセッションから、新しいセッションにセッション変数をコピーする
  3. ログイン前のセッションを破棄する

としていて、セッション固定化を回避すると同時に、ログイン前のセッションも引き継げるようになっていたんだけど、これだとまずいんだろうか?

  1. 攻撃者がショッピングカートに注文を入れる。Orderオブジェクトのリストがセッションに入る
  2. セッション固定化攻撃で、被害者が攻撃者のセッションIDを引き継いでログインする
  3. セッションIDは変更され、攻撃者はログインできない
  4. しかし、「Orderオブジェクトのリスト」は(deep copyされていない限り)共通のインスタンスなので、攻撃者が変更前のセッションから内容を変更できる可能性がある

とか?*4 でも、これも、セッション変数のコピー後に、古いセッションを破棄していれば問題ないような気がするんだけど、どうなんだろう。

攻撃者の作成したセッション変数を引き継ぐ可能性があること自体がセキュリティリスクである、ということなのかな。

セッションCookieにSecure属性をつける(p.215)

Apache Tomcatの場合は、HTTPS接続されたリクエストに対して、セッションIDのクッキーには自動的にセキュア属性が設定されます。

とあるが、SSLアクセラレータやWebサーバをリバースプロキシとしている場合など、Tomcatにリクエストが来た時点では HTTP になってしまっていてSecure属性が付かないケースがあるので、この仕様に頼るのはやめた方が良いと思う。

対策としては、Servlet 3.0 以降なら、例によって web.xml か web-fragment.xml

<session-config>
  <cookie-config>
    <secure>true</secure>
  </cookie-config>
</session-config>

で、かならずSecure属性が付くようになる。Tomcat6以前の場合は、server.xml などの Connector 要素に secure 属性を true に指定する*5

<!-- HTTP -->
<Connector port="8080" protocol="HTTP/1.1" secure="true" ...
<!-- AJP -->
<Connector port="8009" protocol="AJP/1.3" secure="true" ...

これで、HttpServletRequest#isSecure() が常に true を返すようになり、HTTPSでリクエストが来た場合と同じ動作にすることができる。

まとめ

セキュリティも地味に強化された Servlet 3.0 を使いましょう。

*1:Tomcat7以降でも使える http://tomcat.apache.org/tomcat-7.0-doc/config/context.html#Common_Attributes

*2:フレームワークが勝手にURL書き換えをやめてしまうと逆に書き換えが不可能になるので、これは正しい。設定できない仕様や実装の問題

*3:WebLogicではできたような気もする

*4:インスタンス固定化攻撃とでも言うのだろうか

*5:これも、Tomcat7でも使える。http://tomcat.apache.org/tomcat-7.0-doc/config/http.html#Common_Attributes

Javaの文字列置換で、関数を使って動的に置換文字列を作成する

JavaScriptのreplace関数は

// 各数字を2倍に置換する
"12345".replace(/\d/g, function(str) {
  return parseInt(str, 10) * 2;
});
// => "246810"

のように、関数を使って置換文字列を作ることができるのがうれしい。これをJavaでもできないだろうか。

まず、Matcherクラス に用意されている機能を使った簡単な実装から。

package example;

import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class RegexUtils {
    public static interface Replacement {
        String replace(MatchResult result);
    }

    public static String replaceAll(
            CharSequence cseq, Pattern pattern, Replacement replacement) {
        Matcher matcher = pattern.matcher(cseq);
        StringBuffer sb = new StringBuffer();

        while (matcher.find()) {
            String replaced = replacement.replace(matcher.toMatchResult());
            matcher.appendReplacement(sb, Matcher.quoteReplacement(replaced));
        }
        matcher.appendTail(sb);

        return sb.toString();
    }

    private RegexUtils() {}
}

使用方法

RegexUtils.replaceAll("12345", Pattern.compile("\\d"),
                new RegexUtils.Replacement() {
                    @Override
                    public String replace(MatchResult result) {
                        int digit = Integer.valueOf(result.group());
                        return String.valueOf(digit * 2);
                    }
                });

// => "246810"

appendReplacement()/appendTail() は、まさにこういった「置換文字列をその場で作成する」ためにある機能のようだ。

ただ、いまどき StringBuffer は無いだろうというのと、appendReplacement() には $1 をキャプチャした文字列に置換するような余計な機能がついておりそれを打ち消すために quoteReplacement() するとか、なんか無駄な感じなので、やっぱり完全に自前で実装してみる。あとついでに、一度もマッチしない場合には余計なオブジェクトを生成しないようにもしてみる*1

// 改良版
public static String replaceAll(
        CharSequence cseq, Pattern pattern, Replacement replacement) {
    Matcher matcher = pattern.matcher(cseq);

    if (matcher.find()) {
        StringBuilder sb = new StringBuilder();

        int previousEnd = 0;
        do {
            sb.append(cseq.subSequence(previousEnd, matcher.start()));
            sb.append(replacement.replace(matcher.toMatchResult()));
            previousEnd = matcher.end();
        } while (matcher.find());
        sb.append(cseq.subSequence(previousEnd, cseq.length()));

        return sb.toString();
    }

    return cseq.toString();
}

*1:Matcher#replaceAll の実装と似たような感じ

Enum と データベースの「コード値」の相互変換

データベースで「コード値」的なものを使うことはよくある。たとえば、以下のような顧客データベースがあって、「ランク」は 1 が通常、2 がVIPを意味する、だとか。

ID名前ランク
101山田 奈緒1
102上田 次郎2

Javaにはこういうのを表現するのにぴったりな 列挙型 という仕組みがあり、たとえば上の「ランク」は以下のように表現できる。

CustomerRank.java

public enum CustomerRank {
    NORMAL(1, "通常"),
    VIP(2, "VIP"),
    ;

    private final Integer code;
    private final String name;

    CustomerRank(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    public Integer getCode() {
        return code;
    }

    public String getName() {
        return name;
    }
}

これで、アプリケーションコード上で 1 とか 2 とかのコード値を直接触らなくても良くなるし、コードに属する振る舞いを定義しやすくなるし(例えば、割引ロジック DiscountPolicy を作成して、上の VIP と NORMAL に割り当てるとか)、とてもわかりやすくなる。

ただ、このままだとデータベースから読んできたコード値を enum に直すのが面倒なので、以下のような汎用的な仕組みを作る。

Encodable.java

import java.io.Serializable;

public interface Encodable<T extends Serializable> {
    T encode();
}

Decoder.java

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

public class Decoder<K extends Serializable, V extends Encodable<K>> {
    private Map<K, V> map;

    private Decoder(V[] values) {
        map = new HashMap<K, V>(values.length);

        for (V value : values) {
            V old = map.put(value.encode(), value);

            // コード値の重複はサポートしない
            if (old != null) {
                throw new IllegalArgumentException("duplicated code: " + value);
            }
        }
    }

    public V decode(K code) {
        return map.get(code);
    }

    // 型引数の指定を省略するため
    public static <K1 extends Serializable, V1 extends Encodable<K1>>
    Decoder<K1, V1> create(V1[] values) {
        return new Decoder<K1, V1>(values);
    }
}

改造版の CustomerRank.java

public enum CustomerRank implements Encodable<Integer> {
    NORMAL(1, "通常"),
    VIP(2, "VIP"),
    ;

    private final Integer code;
    private final String name;
    private static final Decoder<Integer, CustomerRank> decoder =
        Decoder.create(values());

    CustomerRank(Integer code, String name) {
        this.code = code;
        this.name = name;
    }

    public static CustomerRank decode(Integer code) {
        return decoder.decode(code);
    }

    @Override
    public Integer encode() {
        return code;
    }

    public String getName() {
        return name;
    }
}

このような仕組みを用意しておくと、以下のように簡単に変換できるようになるし、

// コード値 ⇒ enum
CustomerRank rank = CustomerRank.decode(1);

// enum ⇒ コード値
Ingeger code = rank.encode();

他の enum にも最低限の労力で同様の encode/decode メカニズムを追加できる。

public enum Prefecture implements Encodable<String> {
  HOKKAIDO("01", "北海道"),
  ...
  OKINAWA("47", "沖縄"),
  ;

  private final String code;
  private final String name;
  private final Decoder<String, Prefecture> decoder =
      Decoder.create(values());

  Prefecture(String code, String name) {
    this.code = code;
    this.name = name;
  }

  public static Prefecture decode(String code) {
    return decoder.decode(code);
  }

  @Override
  public String encode() {
    return code;
  }
}

enum の name() 値(CustomerRank での "NORMAL" や "VIP")をそのまま永続化するよりも、このように永続化用の内部コード値を別に持ったほうが良いと思う。DB上の表現をアプリ開発者が完全に制御できるとは限らないし、変数名を変えられないのは不自由だし。

また、上のように汎用的な変換の仕組みを作っておけば、ORマッパに自動変換させることもでき、さらに楽だ。例えば Hibernate の場合*1http://pastebin.com/f39d77565 にあるようなコードを書き、hbmファイルに

<class name="example.Customer" table="CUSTOMERS">
...
  <property name="rank">
    <type name="at.molindo.util.hibernate.EnumUserType">
      <param name="enumClass">example.CustomerRank</param>
      <param name="identifierMethod">encode</param>
      <param name="valueOfMethod">decode</param>
    </type>
  </property>

のように定義しておけば、DBと読み書きする時に勝手に相互変換してくれて、アプリケーション側では完全に enum のみで「コード」を扱うことができる。

*1:4年くらい触ってないので知識が古い可能性あり。今はもっと簡単にできたりするかも?

メソッドチェインと継承

Javaでメソッドチェインと継承を組み合わせて使ったとき、↓こういうのが困る。

public class Foo {
  public Foo foo() {
    return this;
  }
}

public class Bar extends Foo {
  public Bar bar() {
    return this;
  }
}

// 文法エラー
// foo() は Foo を返すので、bar() が呼べない
new Bar().foo().bar();

以下のように物凄く汚らしい感じで作ればそれなりに動作するんだけど、他にまともなやり方はないのだろうか。

public class Foo<T extends Foo<?>> {
    @SuppressWarnings("unchecked")
    protected T getThis() {
        return (T) this;
    }

    public static Foo<?> getInstance() {
        return new Foo<Foo<?>>();
    }

    public T foo() {
        return getThis();
    }
}

public class Bar<T extends Bar<?>> extends Foo<T> {

    public static Bar<?> getInstance() {
        return new Bar<Bar<?>>();
    }

    public T bar() {
        return getThis();
    }
}

public class Baz<T extends Baz<?>> extends Bar<T> {
    // 以下同じような感じ
}

// OK
Bar.getInstance().foo().bar();

// OK
new Bar<Bar<?>>().foo().bar();

// これはだめ
new Bar().foo().bar();