Strutsの「Auto growth collection limit（Collectionの要素数が256を超えるとエラーになる）の回避方法

# 問題の事象
StrutsではDOS攻撃対策として要素数が256件を超えるCollection型の項目をPOSTするとエラーが発生する。

```java:HelloAction.java
public class HelloAction extends ActionSupport {
    private List<String> addresses;

    public List<String> getAddresses() {
        return this.addresses;
    }

    public setAddresses(List<String> addresses) {
        this.addresses = addresses;
    }

    public String execute() {
        if (this.addresses.isEmpty()) {
            for (int i = 0; i < 300; i++) {
                this.addresses.add("Address" + i);
            }
        }
        return SUCCESS;
    }
}
```

```jsp:hello.jsp（抜粋）
<s:form action="hello" method="post">
    Address:<br/>
    <s:iterator value="addresses" status="status">
        <s:textfield name="addresses[%{#status.count - 1}]"/><br/>
    </s:iterator>
    <s:submit type="input" />
</s:form>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/2f7c240b-47db-2a52-c299-d5b87307ae54.png)

submitすると256個以降がエラーになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/ecda6928-a1e1-f1a2-a902-ff7fdd9c8eaa.png)
このチェックはcom.opensymphony.xwork2.ognl.accessor.XWorkListPropertyAccessorクラスで行われている。
```java:XWorkListPropertyAccessor.java（抜粋）
if (target instanceof List && name instanceof Number) {
   List list = (List)target;
   int listSize = list.size();
   count = ((Number)name).intValue();
   if (count > this.autoGrowCollectionLimit) {
      throw new OgnlException("Error auto growing collection size to " + count + " which limited to " + this.autoGrowCollectionLimit);
   }

   if (count >= listSize) {
      for(i = listSize; i <= count; ++i) {
         list.add((Object)null);
      }
   }
}
```

# 対策
考えられる対策方法を以下に記す
* 定数struts.ognl.autoGrowthCollectionLimitを変更する
* Type Conversionを使う
* Mapに変更する
* XWorkListPropertyAccessorの代わりにListPropertyAccessorを使用する

## 定数struts.ognl.autoGrowthCollectionLimitを変更する
[公式HP](https://struts.apache.org/core-developers/type-conversion "公式HP")に記載されているとおりCollectionの自動拡張の上限を変更する。一番簡単な方法と思うが、Listを使っている箇所すべてで制限が緩くなるのが欠点だと思う。
```xml:struts.xml
<constant name="struts.ognl.autoGrowthCollectionLimit" value="1024"/>
```

## Type Conversionを使う
Type Conversionを使えば特定の項目のみに限定して制限をなくすことができる。XWorkListPropertyAccessorクラスはType Conversionを使用した場合には上限を適用しないことを利用する。
List\<String>のように要素が書き込み不可の場合は適用できない。
```java:Address.java
// Listに格納するJavaBean
public class Address {
	private String id;
	private String address;

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}
}
```
```java:HelloAction.java
package org.demo;

public class HelloAction extends ActionSupport {
    private List<Address> addresses;

    public List<Address> getAddresses() {
        return this.addresses;
    }

    public setAddresses(List<Address> addresses) {
        this.addresses = addresses;
    }

    public String execute() {
        // 初期表示時に要素を300個にする
        if (this.addresses.isEmpty()) {
            for (int i = 0; i < 300; i++) {
                Address address = new Address();
                address.setId("id" + i);    // jspで丸括弧を使う書き方をする場合は必要
                this.addresses.add(address);
            }
        }
        return SUCCESS;
    }
}
```
```jsp:hello.jsp（抜粋）
<s:form action="hello" method="post">
    Address:<br/>
    <s:iterator value="addresses" status="status">
        <s:textfield name="addresses[%{#status.count - 1}].address"/><br/>
        <!-- ↓丸括弧を使う書き方も可能 -->
        <!-- <s:textfield name="addresses(%{'\\'id' + (#status.count - 1) + '\\''}).address"/><br/> -->
    </s:iterator>
    <s:submit type="input" />
</s:form>
```
以下はHelloAction.javaと同じフォルダに置くか、src/main/resources配下にHelloActionと同じパッケージ階層のフォルダを作成して配置する。
```properties:HelloAction-conversion.properties
KeyProperty_addresses=x　 ←デタラメでよい。丸括弧を使う書き方の場合はidのようなキーにする項目を指定する
Element_addresses=org.demo.Address
CreateIfNull_addresses=true
```
以下のような画面となり、submitしてもエラーはならない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132879/6879c82e-6a29-6904-fc4d-952b8eb44cd7.png)

## Mapに変更する
Collectionと違ってMapには上限が適用されないためMapにすることで解決する。
Stringでも使用できる。
```java:HelloAction.java
package org.demo;

public class HelloAction extends ActionSupport {
    private Map<Integer, String> addresses;

    public Map<Integer, String> getAddresses() {
        return this.addresses;
    }

    public setAddresses(Map<Integer, String> addresses) {
        this.addresses = addresses;
    }

    public String execute() {
        // 初期表示時に要素を300個にする
        if (this.addresses.isEmpty()) {
            for (int i = 0; i < 300; i++) {
                this.addresses.put(i, "");
            }
        }
        return SUCCESS;
    }
}
```
```jsp:hello.jsp（抜粋）
<s:form action="hello" method="post">
    Address:<br/>
    <s:iterator value="addresses" status="status">
        <s:textfield name="addresses[%{#status.count - 1}]"/><br/>
    </s:iterator>
    <s:submit type="input" />
</s:form>
```

## XWorkListPropertyAccessorの代わりにListPropertyAccessorを使用する
大げさかもしれないがOGNLが使用するAccessorを変更することで上限を適用しないようにする。
ArrayListを継承したカスタムリストを作成
```java:MyList.java
package org.demo;

import java.util.ArrayList;

public class MyList<T> extends ArrayList<T> {
}
```
カスタムリストに対しては上限チェックを行わないAccessorを使用する
```xml:struts.xml（抜粋）
<bean type="ognl.PropertyAccessor" name="org.demo.converter.MyList"
          class="ognl.ListPropertyAccessor"/>
```
```java:HelloAction.java
package org.demo;

public class HelloAction extends ActionSupport {
    private MyList<String> addresses = new MyList<>();

    public MyList<String> getAddresses() {
        return this.addresses;
    }

    public void setAddresses(MyList<String> addresses) {
        this.addresses = addresses;
    }

    public HelloAction() {
        // 自動的にリストを拡張してくれないのでコンストラクタで300個にする
        for (int i = 0; i < 300; i++) {
            this.addresses.add("");
        }
    }

    public String execute() {
        return SUCCESS;
    }
}
```
```jsp:hello.jsp（抜粋）
<s:form action="hello" method="post">
    Address:<br/>
    <s:iterator value="addresses" status="status">
        <s:textfield name="addresses[%{#status.count - 1}]"/><br/>
    </s:iterator>
    <s:submit type="input" />
</s:form>
```
リストを自動的に拡張してくれないのがいまいちだ。