我想,前三类外部数据都容易理解。第四类公开接口输出数据,不是内部数据吗?怎么变成需要考量的外部数据了?我们在[前面的章节](https://time.geekbang.org/column/article/87077)讨论过这个问题。
公开接口的输出数据,其实是把内部数据外部化了。如果输出数据是共享的可变量(比如没有深拷贝的集合和数组),那么外部的代码就可以通过修改输出数据,进而影响原接口的行为。这也算是一种意料之外的“输入”。
需要注意的是,公开接口的规范,要标明可变量的处理方式。要不然,调用者就不清楚可不可以修改可变量。
让调用者猜测公开接口的行为,会埋下兼容性的祸根。
比如下面的例子,就是两个Java核心类库的公开方法。这两个方法,对于传入、传出的可变量(数组)都做了拷贝,并且在接口规范里声明了变量拷贝。
```
package javax.net.ssl;
// snipped
public class SSLParameters {
private String[] applicationProtocols = new String[0];
// snipped
/**
* Returns a prioritized array of application-layer protocol names
* that can be negotiated over the SSL/TLS/DTLS protocols.
* <snipped>
* This method will return a new array each time it is invoked.
*
* @return a non-null, possibly zero-length array of application
* protocol {@code String}s. The array is ordered based
* on protocol preference, with {@code protocols[0]}
* being the most preferred.
* @see #setApplicationProtocols
* @since 9
*/
public String[] getApplicationProtocols() {
return applicationProtocols.clone();
}
/**
* Sets the prioritized array of application-layer protocol names
* that can be negotiated over the SSL/TLS/DTLS protocols.
* <snipped>
* @implSpec
* This method will make a copy of the {@code protocols} array.
* <snipped>
* @see #getApplicationProtocols
* @since 9
*/
public void setApplicationProtocols(String[] protocols) {
if (protocols == null) {
throw new IllegalArgumentException("protocols was null");
}
String[] tempProtocols = protocols.clone();
for (String p : tempProtocols) {
if (p == null || p.isEmpty()) {
throw new IllegalArgumentException(
"An element of protocols was null/empty");
}
}
applicationProtocols = tempProtocols;
}
}
```
从上面的例子中,我们也可以体会到,公开接口的编码要比内部接口的编码复杂得多。因为我们无法预料接口的使用者会怎么创造性地使用这些接口。公开接口的实现一般要慎重地考虑安全防护措施,这让公开接口的设计、规范和实现都变得很复杂。从这个意义上来说,我们也需要遵守在第二部分“经济的代码”里谈到的原则:[接口要简单直观](https://time.geekbang.org/column/article/82605)。
## 小结
通过对这个案例的讨论,我想和你分享下面两点个人看法。
**外部输入数据,需要检查数据的合法性;**
**公开接口的输入和输出数据,还要考虑可变量的传递带来的危害。**
## 一起来动手
外部数据的合法性问题,是信息安全里的一大类问题,也是安全攻击者经常利用的一类安全漏洞。
**区分内部数据、外部数据的依据,是数据的最原始来源,而不是数据在代码中的位置。**这一点让外部数据的识别变得有点艰难,特别是代码层数比较多的时候,我们可能没有办法识别一个传入参数,到底是内部数据还是外部数据。在这种情况下,我们需要采取比较保守的姿态,**无法识别来源的数据,不应该是可信任的数据。**
这一次的练习题,我们按照保守的姿态,来分析下面这段代码中的数据可信任性问题。
```
import java.util.HashMap;
import java.util.Map;
public class Solution {
/**
* Given an array of integers, return indices of the two numbers
* such that they add up to a specific target.
*/
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
}
```
欢迎你把你的看法写在留言区,我们一起来学习、思考、精进!
如果你觉得这篇文章有所帮助,欢迎点击“请朋友读”,把它分享给你的朋友或者同事。