Struts2 运行流程分析之doFilter
注意: 本文基于Struts2.2.1版本进行的代码分析
首先先大致看一下StrutsPrepareAndExecuteFilter.java的doFilter的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
// 设置request/response编码
prepare.setEncodingAndLocale(request, response);
// 设置Action上下文
prepare.createActionContext(request, response);
// 将当前Dispatcher加入线程管理,用于解决线程安全问题
prepare.assignDispatcherToThread();
// 排除拦截路径,具体在<constant name="struts.action.excludePattern" value=".*validcode.*,.*tohtml.*"/>中设置
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
chain.doFilter(request, response);
} else {
// 对request进行封装
request = prepare.wrapRequest(request);
// 根据Request的url获取映射的Action
ActionMapping mapping = prepare.findActionMapping(request, response, true);
// 找不到映射就认为是静态文件,继续下一个filter
if (mapping == null) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
// 执行action
execute.executeAction(request, response, mapping);
}
}
} finally {
prepare.cleanupRequest(request);
}
}
|
上面对dofilter进行了简单分析,下面我们进行详细分析,先整理出几个点:
- 设置request/response编码
- 设置Action上下文
- Dispatcher加入线程管理
- 排除除拦截路径
- 对request进行封装
- 根据request获取action映射
- 执行Action
0x01 设置request/response编码
首先看一下 prepare.setEncodingAndLocale(request, response); 的代码实现:
1
2
3
|
public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
dispatcher.prepare(request, response);
}
|
其实还是调用dispatcher对象, 我们继续看prepare的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
public void prepare(HttpServletRequest request, HttpServletResponse response) {
String encoding = null;
// 搜索代码defaultEncoding并没有被初始化设置
if (defaultEncoding != null) {
encoding = defaultEncoding;
}
Locale locale = null;
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
}
if (encoding != null) {
try {
// 设置编码
request.setCharacterEncoding(encoding);
} catch (Exception e) {
LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
}
}
// 设置response编码
if (locale != null) {
response.setLocale(locale);
}
// 该变量是根据struts.dispatcher.parametersWorkaround进行设置
// 对于某些Java EE服务器,不支持HttpServletRequest调用getParameterMap()方法,此时可以设置该属性值为true来解决该问题。
// 该属性的默认值是false。对于WebLogic、Orion和OC4J服务器,通常应该设置该属性为true。
if (paramsWorkaroundEnabled) {
request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
}
}
|
0x02 设置Action上下文
prepare.createActionContext(request, response); 代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
/**
*
* 创建action的context和初始化本地线程
*/
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
// 这个计数器是来干什么的???
Integer counter = 1;
// 计数器累加
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (oldCounter != null) {
counter = oldCounter + 1;
}
ActionContext oldContext = ActionContext.getContext();
if (oldContext != null) {
// detected existing context, so we are probably in a forward
ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
} else {
// 创建ValueStack对象,关于该对象之前我们分析过,
// ValueStack是XWork对OGNL的计算进行扩展的一个特殊的数据结构, 主要是对OGNL三要素中的Root对象进行扩展.
ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
// 首先看到的createContextMap是将Request对象转换成map上下文对象对象,具体我们下方分析.
// 并将这个context放进stack内,也就是放到root对象内
stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
// 初始化个Action上下文对象
ctx = new ActionContext(stack.getContext());
}
// 存放计数器
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
// 设置当前线程的操作上下文
ActionContext.setContext(ctx);
return ctx;
}
|
上面我们说到了createContextMap,我们看一下代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
/**
* Create a context map containing all the wrapped request objects
*
* @param request The servlet request
* @param response The servlet response
* @param mapping The action mapping
* @param context The servlet context
* @return A map of context objects
*/
public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
ActionMapping mapping, ServletContext context) {
// 下方注释写的非常清晰,也就是对reqeust对象进行map封装(包括request的参数、session、等等)最后创建个上下文。
// request map wrapping the http request objects
Map requestMap = new RequestMap(request);
// 下面注释写的都非常清晰,没有什么特殊关注点
// parameters map wrapping the http parameters. ActionMapping parameters are now handled and applied separately
Map params = new HashMap(request.getParameterMap());
// session map wrapping the http session
Map session = new SessionMap(request);
// application map wrapping the ServletContext
Map application = new ApplicationMap(context);
Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
if (mapping != null) {
extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
}
return extraContext;
}
/**
* Merge all application and servlet attributes into a single <tt>HashMap</tt> to represent the entire
* <tt>Action</tt> context.
*
* @param requestMap a Map of all request attributes.
* @param parameterMap a Map of all request parameters.
* @param sessionMap a Map of all session attributes.
* @param applicationMap a Map of all servlet context attributes.
* @param request the HttpServletRequest object.
* @param response the HttpServletResponse object.
* @param servletContext the ServletContextmapping object.
* @return a HashMap representing the <tt>Action</tt> context.
*/
public HashMap<String,Object> createContextMap(Map requestMap,
Map parameterMap,
Map sessionMap,
Map applicationMap,
HttpServletRequest request,
HttpServletResponse response,
ServletContext servletContext) {
HashMap<String,Object> extraContext = new HashMap<String,Object>();
extraContext.put(ActionContext.PARAMETERS, new HashMap(parameterMap));
extraContext.put(ActionContext.SESSION, sessionMap);
extraContext.put(ActionContext.APPLICATION, applicationMap);
Locale locale;
if (defaultLocale != null) {
locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
} else {
locale = request.getLocale();
}
extraContext.put(ActionContext.LOCALE, locale);
//extraContext.put(ActionContext.DEV_MODE, Boolean.valueOf(devMode));
extraContext.put(StrutsStatics.HTTP_REQUEST, request);
extraContext.put(StrutsStatics.HTTP_RESPONSE, response);
extraContext.put(StrutsStatics.SERVLET_CONTEXT, servletContext);
// helpers to get access to request/session/application scope
extraContext.put("request", requestMap);
extraContext.put("session", sessionMap);
extraContext.put("application", applicationMap);
extraContext.put("parameters", parameterMap);
AttributeMap attrMap = new AttributeMap(extraContext);
extraContext.put("attr", attrMap);
return extraContext;
}
|
代码很直观,我们在Struts2的模板里用到的session对象就是在这里初始化的,例如
1
|
<s:property value="#session.user.name">
|
0x03 Dispatcher加入线程管理
prepare.assignDispatcherToThread(); 代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/**
* 将调度程序分配给调度程序线程本地
*/
public void assignDispatcherToThread() {
Dispatcher.setInstance(dispatcher);
}
// Dispatcher.java
/**
* Provide a thread local instance.
*/
private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
/**
* Store the dispatcher instance for this thread.
*
* @param instance The instance
*/
public static void setInstance(Dispatcher instance) {
Dispatcher.instance.set(instance);
}
|
0x04 排除除拦截路径
prepare.isUrlExcluded(request, excludedPatterns) 实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/**
* Check whether the request matches a list of exclude patterns.
*
* @param request The request to check patterns against
* @param excludedPatterns list of patterns for exclusion
*
* @return <tt>true</tt> if the request URI matches one of the given patterns
*/
public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) {
if (excludedPatterns != null) {
String uri = getUri(request);
for ( Pattern pattern : excludedPatterns ) {
if (pattern.matcher(uri).matches()) {
return true;
}
}
}
return false;
}
|
也就是个正则match, 我们看一下excludedPatterns是在哪里初始化的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
// protected PrepareOperations prepare;
// protected ExecuteOperations execute;
protected List<Pattern> excludedPatterns = null;
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
try {
// FilterHostConfig config = new FilterHostConfig(filterConfig);
// init.initLogging(config);
// Dispatcher dispatcher = init.initDispatcher(config);
// init.initStaticContentLoader(config, dispatcher);
// prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
// execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
// postInit(dispatcher, filterConfig);
} finally {
// init.cleanup();
}
}
|
init.buildExcludedPatternsList(dispatcher);的实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/**
* Extract a list of patterns to exclude from request filtering
*
* @param dispatcher The dispatcher to check for exclude pattern configuration
*
* @return a List of Patterns for request to exclude if apply, or <tt>null</tt>
*
* @see org.apache.struts2.StrutsConstants#STRUTS_ACTION_EXCLUDE_PATTERN
*/
public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) {
// 设置排除拦截路径
return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN));
}
private List<Pattern> buildExcludedPatternsList( String patterns ) {
if (null != patterns && patterns.trim().length() != 0) {
List<Pattern> list = new ArrayList<Pattern>();
String[] tokens = patterns.split(",");
for ( String token : tokens ) {
list.add(Pattern.compile(token.trim()));
}
return Collections.unmodifiableList(list);
} else {
return null;
}
}
|
看到是通过StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN也就是struts.action.excludePattern配置的,如:
1
|
<constant name="struts.action.excludePattern" value="/res/.*,/css/.*,/images/.*,/js/.*,/services/.*" />
|
0x05 对request进行封装
request = prepare.wrapRequest(request); 代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/**
* Wraps the request with the Struts wrapper that handles multipart requests better
* @return The new request, if there is one
* @throws ServletException
*/
public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
HttpServletRequest request = oldRequest;
try {
// Wrap request first, just in case it is multipart/form-data
// parameters might not be accessible through before encoding (ww-1278)
request = dispatcher.wrapRequest(request, servletContext);
} catch (IOException e) {
String message = "Could not wrap servlet request with MultipartRequestWrapper!";
throw new ServletException(message, e);
}
return request;
}
|
看一下 request = dispatcher.wrapRequest(request, servletContext); 的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
/**
* Wrap and return the given request or return the original request object.
* </p>
* This method transparently handles multipart data as a wrapped class around the given request.
* Override this method to handle multipart requests in a special way or to handle other types of requests.
* Note, {@link org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper} is
* flexible - look first to that object before overriding this method to handle multipart data.
*
* @param request the HttpServletRequest object.
* @param servletContext Our ServletContext object
* @return a wrapped request or original request.
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper
* @throws java.io.IOException on any error.
*/
public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
// don't wrap more than once
if (request instanceof StrutsRequestWrapper) {
return request;
}
String content_type = request.getContentType();
// 如果request的header中content-type的value包含multipart/form-data
// 封装成MultiPartRequestWrapper对象
// 这部分代码太经典,s2-045安全漏洞就是从这里开始的
if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
MultiPartRequest mpr = null;
//check for alternate implementations of MultiPartRequest
Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);
if (multiNames != null) {
for (String multiName : multiNames) {
if (multiName.equals(multipartHandlerName)) {
mpr = getContainer().getInstance(MultiPartRequest.class, multiName);
}
}
}
if (mpr == null ) {
mpr = getContainer().getInstance(MultiPartRequest.class);
}
request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext));
} else {
// 封装成StrutsRequestWrapper
request = new StrutsRequestWrapper(request);
}
return request;
}
|
上面代码我们拆成两个部分进行分析:
1. 封装成MultiPartRequestWrapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
MultiPartRequest mpr = null;
//check for alternate implementations of MultiPartRequest
// 获取Struts-default.xml里的配置,也就是
// <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="struts" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default"/>
// <bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="jakarta" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default" />
Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);
if (multiNames != null) {
for (String multiName : multiNames) {
// 根据multipartHandlerName配置的multipart Name决定采用那个MultiPartRequest类的实现类
// 通过阅读代码,我们知道multipartHandlerName是由 <constant name="struts.multipart.handler" value="jakarta" />
// 也就是默认是jakarta,实现类是org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest, 再次怀念一下s2-045神洞
if (multiName.equals(multipartHandlerName)) {
// 获取org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest类的实例化
mpr = getContainer().getInstance(MultiPartRequest.class, multiName);
}
}
}
if (mpr == null ) {
mpr = getContainer().getInstance(MultiPartRequest.class);
}
// 这里的第三个参数是文件保存目录
// 下方我们看一下MultiPartRequestWrapper的大致实现
request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext));
} else {
....
}
// MultiPartRequestWrapper.java 实现
public class MultiPartRequestWrapper extends StrutsRequestWrapper {
protected static final Logger LOG = LoggerFactory.getLogger(MultiPartRequestWrapper.class);
Collection<String> errors;
MultiPartRequest multi;
/**
* Process file downloads and log any errors.
*
* @param request Our HttpServletRequest object
* @param saveDir Target directory for any files that we save
* @param multiPartRequest Our MultiPartRequest object
*/
public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir) {
super(request);
multi = multiPartRequest;
try {
// 用.JakartaMultiPartRequest类的parse方法解析reqeust对象,具体代码就不看了,也就是调用commons-fileupload组件进行上传文件
multi.parse(request, saveDir);
for (Object o : multi.getErrors()) {
String error = (String) o;
addError(error);
}
} catch (IOException e) {
addError("Cannot parse request: "+e.toString());
}
}
|
Content-Type为multipart/form-data的情况,我们已经分析完了,我们看一下非上传请求的正常http请求是怎么处理的吧.
2. 封装成StrutsRequestWrapper
1
2
3
4
5
6
7
8
9
|
if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
// .... 省略
} else {
// 封装成StrutsRequestWrapper
request = new StrutsRequestWrapper(request);
}
|
看一下StrutsRequestWrapper的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
public class StrutsRequestWrapper extends HttpServletRequestWrapper {
/**
* The constructor
* @param req The request
*/
public StrutsRequestWrapper(HttpServletRequest req) {
super(req);
}
/**
* Gets the object, looking in the value stack if not found
*
* @param s The attribute key
*/
public Object getAttribute(String s) {
if (s != null && s.startsWith("javax.servlet")) {
// don't bother with the standard javax.servlet attributes, we can short-circuit this
// see WW-953 and the forums post linked in that issue for more info
return super.getAttribute(s);
}
ActionContext ctx = ActionContext.getContext();
Object attribute = super.getAttribute(s);
if (ctx != null) {
// 如果原生的getAttribute获取不到数据,则从ValueStack中获取
if (attribute == null) {
boolean alreadyIn = false;
Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute");
if (b != null) {
alreadyIn = b.booleanValue();
}
// note: we don't let # come through or else a request for
// #attr.foo or #request.foo could cause an endless loop
if (!alreadyIn && s.indexOf("#") == -1) {
try {
// If not found, then try the ValueStack
ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);
ValueStack stack = ctx.getValueStack();
if (stack != null) {
attribute = stack.findValue(s);
}
} finally {
ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);
}
}
}
}
return attribute;
}
}
|
也就是对servlet的HttpServletRequestWrapper对象的实现,目的就是为了实现了getAttribute方法, 如果原生的getAttribute获取不到数据,则从ValueStack中获取。
0x06 根据request获取action映射
这种分析思路并不完美,容易让人阅读起来,思路中断,建议多看几遍
ActionMapping mapping = prepare.findActionMapping(request, response, true); 具体实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/**
* Finds and optionally creates an {@link ActionMapping}. It first looks in the current request to see if one
* has already been found, otherwise, it creates it and stores it in the request. No mapping will be created in the
* case of static resource requests or unidentifiable requests for other servlets, for example.
*/
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response) {
return findActionMapping(request, response, false);
}
private static final String STRUTS_ACTION_MAPPING_KEY = "struts.actionMapping";
/**
* Finds and optionally creates an {@link ActionMapping}. if forceLookup is false, it first looks in the current request to see if one
* has already been found, otherwise, it creates it and stores it in the request. No mapping will be created in the
* case of static resource requests or unidentifiable requests for other servlets, for example.
* @param forceLookup if true, the action mapping will be looked up from the ActionMapper instance, ignoring if there is one
* in the request or not
*/
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
try {
// 根据reqeust获取action映射,存放到Attribute,目的是为了性能优化
// 这里的ActionMapper.class的具体实现类是org.apache.struts2.dispatcher.mapper.DefaultActionMapper
// 下方我们分析一下DefaultActionMapper的getMapping实现
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
if (mapping != null) {
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
}
} catch (Exception ex) {
dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
}
}
return mapping;
}
|
分析一下DefaultActionMapper的getMapping实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
/*
* (non-Javadoc)
*
* @see org.apache.struts2.dispatcher.mapper.ActionMapper#getMapping(javax.servlet.http.HttpServletRequest)
*/
public ActionMapping getMapping(HttpServletRequest request,
ConfigurationManager configManager) {
ActionMapping mapping = new ActionMapping();
// 获取uri
// 例如我们访问的是http://localhost/index.action,那么uri就是/index.action
String uri = getUri(request);
// 这段代码的意思应该是为了处理这样的uri /index;xxxxx.action
// 具体可以看tomcat的uri处理
int indexOfSemicolon = uri.indexOf(";");
uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
// 删除扩展,如传递的是/index.action,最后保留的是index
uri = dropExtension(uri, mapping);
if (uri == null) {
return null;
}
// 根据uri解析name和Namespace,
parseNameAndNamespace(uri, mapping, configManager);
// 这个函数实现了Struts2的DynamicMethod功能,具体下方有详细分析
handleSpecialParameters(request, mapping);
if (mapping.getName() == null) {
return null;
}
// 这里实现了Struts2另外一种动态方法调用功能,具体下方有详细分析
parseActionName(mapping);
return mapping;
}
|
上方代码我们分为2个部分分析
1. parseNameAndNamespace方法分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
/**
* Parses the name and namespace from the uri
*
* @param uri The uri
* @param mapping The action mapping to populate
*/
protected void parseNameAndNamespace(String uri, ActionMapping mapping,
ConfigurationManager configManager) {
String namespace, name;
int lastSlash = uri.lastIndexOf("/");
if (lastSlash == -1) {
namespace = "";
name = uri;
} else if (lastSlash == 0) {
// ww-1046, assume it is the root namespace, it will fallback to
// default
// namespace anyway if not found in root namespace.
namespace = "/";
name = uri.substring(lastSlash + 1);
} else if (alwaysSelectFullNamespace) {
// Simply select the namespace as everything before the last slash
namespace = uri.substring(0, lastSlash);
name = uri.substring(lastSlash + 1);
} else {
// Try to find the namespace in those defined, defaulting to ""
// 获取配置信息
Configuration config = configManager.getConfiguration();
String prefix = uri.substring(0, lastSlash);
namespace = "";
boolean rootAvailable = false;
// Find the longest matching namespace, defaulting to the default
// 通过配置文件,根据uri获取到的namespace,去查找配置里的namespace
for (Object cfg : config.getPackageConfigs().values()) {
String ns = ((PackageConfig) cfg).getNamespace();
if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
if (ns.length() > namespace.length()) {
namespace = ns;
}
}
if ("/".equals(ns)) {
rootAvailable = true;
}
}
// 得到name
name = uri.substring(namespace.length() + 1);
// Still none found, use root namespace if found
if (rootAvailable && "".equals(namespace)) {
namespace = "/";
}
}
if (!allowSlashesInActionNames && name != null) {
int pos = name.lastIndexOf('/');
if (pos > -1 && pos < name.length() - 1) {
name = name.substring(pos + 1);
}
}
mapping.setNamespace(namespace);
mapping.setName(name);
}
|
这里我们不得不贴上我们的应用Struts.xml配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="true"/>
<constant name="struts.devMode" value="true" />
<package name="myPackage" extends="struts-default">
<default-action-ref name="index" />
<action name="index" class="com.jd.IndexAction">
<result>/WEB-INF/jsp/index.jsp</result>
</action>
</package>
</struts>
|
所以当我们访问/index.action的时候,parseNameAndNamespace方法处理过后的name是”index”,而namespace是”/“.
2. handleSpecialParameters方法分析
handleSpecialParameters(request, mapping); 代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
/**
* Special parameters, as described in the class-level comment, are searched
* for and handled.
*
*
* 搜索和处理特殊参数,如 class-level 注释中所述
*
* @param request The request
* @param mapping The action mapping
*/
public void handleSpecialParameters(HttpServletRequest request,
ActionMapping mapping) {
// handle special parameter prefixes.
Set<String> uniqueParameters = new HashSet<String>();
Map parameterMap = request.getParameterMap();
for (Iterator iterator = parameterMap.keySet().iterator(); iterator
.hasNext();) {
String key = (String) iterator.next();
// Strip off the image button location info, if found
// 翻译:剥离图像按钮位置信息(如果找到)
// 懵逼了,这是什么鬼? 可能是兼容某个struts2的image标签库的吧。。我猜的,总之也不是关键代码,不会造成什么影响
if (key.endsWith(".x") || key.endsWith(".y")) {
key = key.substring(0, key.length() - 2);
}
// Ensure a parameter doesn't get processed twice
// 参数去重
if (!uniqueParameters.contains(key)) {
// 这是是个骚操作,我们单独分析
ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key);
if (parameterAction != null) {
parameterAction.execute(key, mapping);
uniqueParameters.add(key);
break;
}
}
}
}
|
上面这个函数中有一段特殊代码,如下:
1
2
3
4
5
6
|
ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key);
if (parameterAction != null) {
parameterAction.execute(key, mapping);
uniqueParameters.add(key);
break;
}
|
首先我们看一下这个prefixTrie是什么鬼?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
public class DefaultActionMapper implements ActionMapper {
protected static final String METHOD_PREFIX = "method:";
protected static final String ACTION_PREFIX = "action:";
protected static final String REDIRECT_PREFIX = "redirect:";
protected static final String REDIRECT_ACTION_PREFIX = "redirectAction:";
protected boolean allowDynamicMethodCalls = true;
protected boolean allowSlashesInActionNames = false;
protected boolean alwaysSelectFullNamespace = false;
protected PrefixTrie prefixTrie = null;
public DefaultActionMapper() {
// 初始化prefixTrie
// 注意这里的key, 分别为method:、action:、redirect:
// 以及这些key对应的ParameterAction对象的实现
// 这里的allowDynamicMethodCalls变量是由配置文件中struts.enable.DynamicMethodInvocation设置,默认开启
prefixTrie = new PrefixTrie() {
{
put(METHOD_PREFIX, new ParameterAction() {
public void execute(String key, ActionMapping mapping) {
// 设置Action映射的方法为method:后的value
// 访问http://localhost/index.action?method:add,则
// 调用indexAction的add方法
if (allowDynamicMethodCalls) {
mapping.setMethod(key.substring(
METHOD_PREFIX.length()));
}
}
});
put(ACTION_PREFIX, new ParameterAction() {
public void execute(String key, ActionMapping mapping) {
// 这里和method有所不同
// 访问http://localhost/index.action?action:user!add , 则
// 调用userAction的add方法
String name = key.substring(ACTION_PREFIX.length());
if (allowDynamicMethodCalls) {
int bang = name.indexOf('!');
if (bang != -1) {
String method = name.substring(bang + 1);
mapping.setMethod(method);
name = name.substring(0, bang);
}
}
mapping.setName(name);
}
});
put(REDIRECT_PREFIX, new ParameterAction() {
// 这里不是方法的调用
// 访问 http://localhost/index.action?redirect:user , 则
// 重定向到user.jsp
public void execute(String key, ActionMapping mapping) {
ServletRedirectResult redirect = new ServletRedirectResult();
container.inject(redirect);
redirect.setLocation(key.substring(REDIRECT_PREFIX
.length()));
mapping.setResult(redirect);
}
});
put(REDIRECT_ACTION_PREFIX, new ParameterAction() {
// 这里的redirectAction: 和 redirect: 的区别是带不带后缀
public void execute(String key, ActionMapping mapping) {
String location = key.substring(REDIRECT_ACTION_PREFIX
.length());
ServletRedirectResult redirect = new ServletRedirectResult();
container.inject(redirect);
String extension = getDefaultExtension();
if (extension != null && extension.length() > 0) {
location += "." + extension;
}
redirect.setLocation(location);
mapping.setResult(redirect);
}
});
}
};
}
|
额…这是Struts2的奇葩设计,”动态方法调用“功能,通过struts.enable.DynamicMethodInvocation设置为true大概该功能, 这里简单介绍一下,具体功能使用google一下:
也就是当我们访问http://localhost/index.action?method:foo的时候,就会调用index这个Action中的foo方法, 具体阅读上方代码注释.
话说s2-16漏洞问题就是出在redirect:这个重定向功能, 具体参考[1]
TIPS: 这里的PrefixTrie值得研究一下,里面涉及到了Trie树的实现,具体参考[2]
3. parseActionName方法分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
protected ActionMapping parseActionName(ActionMapping mapping) {
if (mapping.getName() == null) {
return mapping;
}
if (allowDynamicMethodCalls) {
// handle "name!method" convention.
String name = mapping.getName();
int exclamation = name.lastIndexOf("!");
if (exclamation != -1) {
mapping.setName(name.substring(0, exclamation));
mapping.setMethod(name.substring(exclamation + 1));
}
}
return mapping;
}
|
从代码中可以看得到,也是一种Dynamic Method的调用方式,例如我们访问:http://locahost/index!add.action , 则调用了indexAction的add方法.
TIPS: 实在受不了为毛一个Dynamic Method功能用各种花样方式实现???
好了,我们已经分析完prepare.findActionMapping(request, response, true)的实现了,成功得到Action的映射关系了,下面开始最后一个环节,执行Action
0x07 执行Action
execute.executeAction(request, response, mapping); 的代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
/**
* Contains execution operations for filters
*/
public class ExecuteOperations {
/**
* Executes an action
* @throws ServletException
*/
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
dispatcher.serviceAction(request, response, servletContext, mapping);
}
// Dispatcher.java
public class Dispatcher {
/**
* Load Action class for mapping and invoke the appropriate Action method, or go directly to the Result.
* <p/>
* This method first creates the action context from the given parameters,
* and then loads an <tt>ActionProxy</tt> from the given action name and namespace.
* After that, the Action method is executed and output channels through the response object.
* Actions not found are sent back to the user via the {@link Dispatcher#sendError} method,
* using the 404 return code.
* All other errors are reported by throwing a ServletException.
*
* @param request the HttpServletRequest object
* @param response the HttpServletResponse object
* @param mapping the action mapping object
* @throws ServletException when an unknown error occurs (not a 404, but typically something that
* would end up as a 5xx by the servlet container)
* @param context Our ServletContext object
*/
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
ActionMapping mapping) throws ServletException {
// 上方分析过这个createContextMap,当时的mapping为空,现在已经拿到mapping,重新设置一下上下文
Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
boolean nullStack = stack == null;
if (nullStack) {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
stack = ctx.getValueStack();
}
}
if (stack != null) {
// 这里使用了OgnlValueStackFactory对象,该对象实例化了OgnlValueStack对象,并对OgnlValueStack对象进行初始化
// 之前我们对OgnlValueStack做过分析,OgnlValueStack其实就是对ValueStack接口的具体实现,用来对root对象进行ognl计算使用
// 具体可以翻看之前的文章 http://dean.csoio.com/posts/struts2-internals-readbook-note_06/
extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
}
String timerKey = "Handling request from Dispatcher";
try {
UtilTimerStack.push(timerKey);
String namespace = mapping.getNamespace();
String name = mapping.getName();
String method = mapping.getMethod();
Configuration config = configurationManager.getConfiguration();
// 这里的ActionProxyFactory的实现类是org.apache.struts2.impl.StrutsActionProxyFactory"
// StrutsActionProxyFactory的createActionProxy方法我们下方会做具体分析, 这里就认为返回了一个ActionProxy类,
// 具体实现类是org.apache.struts2.impl.StrutsActionProxyFactory
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
// 这里代码太关键,下方我们作为第二部分单独分析.
// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
// 调用org.apache.struts2.impl.StrutsActionProxyFactory的execute方法执行
proxy.execute();
}
// If there was a previous value stack then set it back onto the request
if (!nullStack) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
// WW-2874 Only log error if in devMode
if(devMode) {
String reqStr = request.getRequestURI();
if (request.getQueryString() != null) {
reqStr = reqStr + "?" + request.getQueryString();
}
LOG.error("Could not find action or result\n" + reqStr, e);
}
else {
LOG.warn("Could not find action or result", e);
}
sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
} catch (Exception e) {
sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
} finally {
UtilTimerStack.pop(timerKey);
}
}
|
上方我们做了简单分析,其中有多个疑惑,下方我们逐个详细分析.
1. StrutsActionProxyFactory类的createActionProxy方法分析
首先我们看一下createActionProxy方法的调用:
1
2
|
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
|
通过调用可以看到这个createActionProxy方法并未在StrutsActionProxyFactory中实现,而是在其父类DefaultActionProxyFactory中实现,我们看一下DefaultActionProxyFactory类的createActionProxy方法实现代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
// DefaultActionProxyFactory.java
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
// 将实例化的Action调用器存入容器
ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
container.inject(inv);
// 这里调用的是StrutsActionProxyFactory类的createActionProxy方法
return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
|
我们看一下 StrutsActionProxyFactory类的createActionProxy方法实现:
// 其实就是重写了DefaultActionProxyFactory的createActionProxy方法
public class StrutsActionProxyFactory extends DefaultActionProxyFactory {
@Override
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
StrutsActionProxy proxy = new StrutsActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
container.inject(proxy);
// 预处理StrutsActionProxy对象,下方我们看一下prepare方法的具体实现
proxy.prepare();
// 返回预处理完成的StrutsActionProxy对象
return proxy;
}
}
StrutsActionProxy类的prepare方法的具体实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public class StrutsActionProxy extends DefaultActionProxy {
private static final long serialVersionUID = -2434901249671934080L;
public StrutsActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName,
boolean executeResult, boolean cleanupContext) {
super(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
public String execute() throws Exception {
ActionContext previous = ActionContext.getContext();
ActionContext.setContext(invocation.getInvocationContext());
try {
return invocation.invoke();
} finally {
if (cleanupContext)
ActionContext.setContext(previous);
}
}
@Override
protected void prepare() {
super.prepare();
}
}
|
额欧~ 其实就是调用了DefaultActionProxy父类的prepare方法预处理,我们在回去看看了DefaultActionProxy父类的prepare方法实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
public class DefaultActionProxy implements ActionProxy, Serializable {
protected DefaultActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {
this.invocation = inv;
this.cleanupContext = cleanupContext;
if (LOG.isDebugEnabled()) {
LOG.debug("Creating an DefaultActionProxy for namespace " + namespace + " and action name " + actionName);
}
this.actionName = actionName;
this.namespace = namespace;
this.executeResult = executeResult;
this.method = methodName;
}
protected void prepare() {
String profileKey = "create DefaultActionProxy: ";
try {
UtilTimerStack.push(profileKey);
// 得到action的配置信息
config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName);
if (config == null && unknownHandlerManager.hasUnknownHandlers()) {
config = unknownHandlerManager.handleUnknownAction(namespace, actionName);
}
// 没有配置action就抛出异常
if (config == null) {
String message;
if ((namespace != null) && (namespace.trim().length() > 0)) {
message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_PACKAGE_ACTION_EXCEPTION, Locale.getDefault(), new String[]{
namespace, actionName
});
} else {
message = LocalizedTextUtil.findDefaultText(XWorkMessages.MISSING_ACTION_EXCEPTION, Locale.getDefault(), new String[]{
actionName
});
}
throw new ConfigurationException(message);
}
// 这里比较有意思,如果访问的action没有指定method,那么就调用action的execute方法
// this.method = "execute";
resolveMethod();
if (!config.isAllowedMethod(method)) {
throw new ConfigurationException("Invalid method: "+method+" for action "+actionName);
}
// 这里的invocation就是DefaultActionInvocation对象,调用DefaultActionInvocation的init方法
// 这里init做了太多的事情,里面最关键的是初始化了Struts2拦截器。
// 具体下方会贴出代码,简单看一下就行,就是个action执行前的初始化
invocation.init(this);
} finally {
UtilTimerStack.pop(profileKey);
}
}
|
DefaultActionInvocation对象的init方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
public void init(ActionProxy proxy) {
this.proxy = proxy;
Map<String, Object> contextMap = createContextMap();
// Setting this so that other classes, like object factories, can use the ActionProxy and other
// contextual information to operate
ActionContext actionContext = ActionContext.getContext();
if (actionContext != null) {
actionContext.setActionInvocation(this);
}
createAction(contextMap);
if (pushAction) {
stack.push(action);
contextMap.put("action", action);
}
invocationContext = new ActionContext(contextMap);
// 设置要调用的ActionName
invocationContext.setName(proxy.getActionName());
// get a new List so we don't get problems with the iterator if someone changes the list
// 初始化拦截器列表
List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();
}
|
Action代理类实例化已分析完成, 我们回去继续看Dispatcher的serviceAction的实现.
2. Action的执行
我们在回顾一下serviceAction的代码实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
// Dispatcher.java
public class Dispatcher {
// ... 省略n行代码
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
ActionMapping mapping) throws ServletException {
// ... 省略n行代码
try {
// ... 省略n行代码
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
// ... 省略n行代码
// result不为空,则调用result类的execute方法执行Action
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
// 调用org.apache.struts2.impl.StrutsActionProxyFactory的execute方法执行
proxy.execute();
}
// If there was a previous value stack then set it back onto the request
if (!nullStack) {
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
}
} catch (ConfigurationException e) {
// ... 省略n行代码
}
}
|
如上我们拆分为2个部分分析:
1. result.execute(proxy.getInvocation());
如果我们正常访问http://localhost/index.action,这里的result一定为空,只有我们在用Struts2动态方法调用的时候,才会设置result,下方贴上动态方法调用的部分代码,回顾一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
put(REDIRECT_PREFIX, new ParameterAction() {
public void execute(String key, ActionMapping mapping) {
ServletRedirectResult redirect = new ServletRedirectResult();
container.inject(redirect);
redirect.setLocation(key.substring(REDIRECT_PREFIX
.length()));
mapping.setResult(redirect);
}
});
put(REDIRECT_ACTION_PREFIX, new ParameterAction() {
public void execute(String key, ActionMapping mapping) {
String location = key.substring(REDIRECT_ACTION_PREFIX
.length());
ServletRedirectResult redirect = new ServletRedirectResult();
container.inject(redirect);
String extension = getDefaultExtension();
if (extension != null && extension.length() > 0) {
location += "." + extension;
}
redirect.setLocation(location);
mapping.setResult(redirect);
}
});
|
我们看一ServletRedirectResult类的execute方法实现:
1
2
3
4
5
6
7
8
|
public class ServletRedirectResult extends StrutsResultSupport implements ReflectionExceptionHandler {
public void execute(ActionInvocation invocation) throws Exception {
if (anchor != null) {
anchor = conditionalParse(anchor, invocation);
}
super.execute(invocation);
}
|
可以看到其实是调用其父类StrutsResultSupport的execute方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
public abstract class StrutsResultSupport implements Result, StrutsStatics {
public void execute(ActionInvocation invocation) throws Exception {
// 执行location得到string返回结果
lastFinalLocation = conditionalParse(location, invocation);
// 调用其子类ServletRedirectResult的doExecute
doExecute(lastFinalLocation, invocation);
}
protected String conditionalParse(String param, ActionInvocation invocation) {
if (parse && param != null && invocation != null) {
// 执行location得到string返回结果
// 通过OGNL表达式从invocation对象的stack中进行查找location
// 这里也就是s2-016漏洞出现的原因
// 这里重写了ParsedValueEvaluator的evaluate,目的是为了对stack.findValue(localtion)返回的string进行编码
// 关于stack.findValue之前有分析过,就是调用Ognl表达式
return TextParseUtil.translateVariables(param, invocation.getStack(),
new TextParseUtil.ParsedValueEvaluator() {
public Object evaluate(Object parsedValue) {
if (encode) {
if (parsedValue != null) {
try {
// use UTF-8 as this is the recommended encoding by W3C to
// avoid incompatibilities.
return URLEncoder.encode(parsedValue.toString(), "UTF-8");
}
catch(UnsupportedEncodingException e) {
LOG.warn("error while trying to encode ["+parsedValue+"]", e);
}
}
}
return parsedValue;
}
});
} else {
return param;
}
}
protected abstract void doExecute(String finalLocation, ActionInvocation invocation) throws Exception;
|
从上方代码我们得出执行location得到的stirng。
TIPS : 这里之所以用ognl进行查找,我猜想可能是加强一下重定向功能,但是拿localtion作为表达式。。。呵呵哒
我们继续看一下ServletRedirectResult类的doExecute方法对这个string都做了什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
/**
* Redirects to the location specified by calling {@link HttpServletResponse#sendRedirect(String)}.
*
* @param finalLocation the location to redirect to.
* @param invocation an encapsulation of the action execution state.
* @throws Exception if an error occurs when redirecting.
*/
protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
ActionContext ctx = invocation.getInvocationContext();
HttpServletRequest request = (HttpServletRequest) ctx.get(ServletActionContext.HTTP_REQUEST);
HttpServletResponse response = (HttpServletResponse) ctx.get(ServletActionContext.HTTP_RESPONSE);
// 这里确定是我们重定向的不是一个外部地址
if (isPathUrl(finalLocation)) {
// 加入redirect:的value前没有/, 则获取配置文件中的namespace,追加到location前方
if (!finalLocation.startsWith("/")) {
ActionMapping mapping = actionMapper.getMapping(request, Dispatcher.getInstance().getConfigurationManager());
String namespace = null;
if (mapping != null) {
namespace = mapping.getNamespace();
}
if ((namespace != null) && (namespace.length() > 0) && (!"/".equals(namespace))) {
finalLocation = namespace + "/" + finalLocation;
} else {
finalLocation = "/" + finalLocation;
}
}
// if the URL's are relative to the servlet context, append the servlet context path
// 将Request的上下文路径也追加到location前方
if (prependServletContext && (request.getContextPath() != null) && (request.getContextPath().length() > 0)) {
finalLocation = request.getContextPath() + finalLocation;
}
// 获取配置文件中Result标签里param标签的配置,进行解析参数
ResultConfig resultConfig = invocation.getProxy().getConfig().getResults().get(invocation.getResultCode());
if (resultConfig != null ) {
Map resultConfigParams = resultConfig.getParams();
for (Iterator i = resultConfigParams.entrySet().iterator(); i.hasNext();) {
Map.Entry e = (Map.Entry) i.next();
if (!getProhibitedResultParams().contains(e.getKey())) {
// 解析参数
// <result name="success" type="redirect">
// <param name="location">foo.jsp</param>
// <param name="parse">false</param><!--不解析OGNL-->
// </result>
// 又调用conditionalParse解析
requestParameters.put(e.getKey().toString(), e.getValue() == null ? "" : conditionalParse(e.getValue().toString(), invocation));
String potentialValue = e.getValue() == null ? "" : conditionalParse(e.getValue().toString(), invocation);
if (!supressEmptyParameters || ((potentialValue != null) && (potentialValue.length() > 0))) {
requestParameters.put(e.getKey().toString(), potentialValue);
}
}
}
}
// 将localtion和参数合并
StringBuilder tmpLocation = new StringBuilder(finalLocation);
UrlHelper.buildParametersString(requestParameters, tmpLocation, "&");
// add the anchor
if (anchor != null) {
tmpLocation.append('#').append(anchor);
}
// 对要重定向的location进行编码
finalLocation = response.encodeRedirectURL(tmpLocation.toString());
}
if (LOG.isDebugEnabled()) {
LOG.debug("Redirecting to finalLocation " + finalLocation);
}
// 开始重定向
sendRedirect(response, finalLocation);
}
// 写入response
protected void sendRedirect(HttpServletResponse response, String finalLocation) throws IOException {
if (SC_FOUND == statusCode) {
response.sendRedirect(finalLocation);
} else {
response.setStatus(statusCode);
response.setHeader("Location", finalLocation);
response.getWriter().write(finalLocation);
response.getWriter().close();
}
}
|
result执行,分析结束.
2. proxy.execute();
终于要大结局了,来,我们看一下 StrutsActionProxy.execute方法的实现,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class StrutsActionProxy extends DefaultActionProxy {
private static final long serialVersionUID = -2434901249671934080L;
public StrutsActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName,
boolean executeResult, boolean cleanupContext) {
super(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
public String execute() throws Exception {
ActionContext previous = ActionContext.getContext();
ActionContext.setContext(invocation.getInvocationContext());
try {
// 这里我们调用的是DefaultActionInvocation的invoke方法
return invocation.invoke();
} finally {
if (cleanupContext)
ActionContext.setContext(previous);
}
}
@Override
protected void prepare() {
super.prepare();
}
}
|
DefaultActionInvocation的invoke方法的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
public class DefaultActionInvocation implements ActionInvocation {
public String invoke() throws Exception {
String profileKey = "invoke: ";
try {
UtilTimerStack.push(profileKey);
if (executed) {
throw new IllegalStateException("Action has already executed");
}
// 遍历Struts2的拦截器,并调用每个拦截器的intercept方法,返回每个拦截器的resultCode
// 由于拦截器太多了,我们这里就不分析了
if (interceptors.hasNext()) {
final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
String interceptorMsg = "interceptor: " + interceptor.getName();
UtilTimerStack.push(interceptorMsg);
try {
resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
}
finally {
UtilTimerStack.pop(interceptorMsg);
}
} else {
// 拦截器调用完后,才是我们用户自定义action的调用,下方我们好好分析一下这个方法
resultCode = invokeActionOnly();
}
// this is needed because the result will be executed, then control will return to the Interceptor, which will
// return above and flow through again
if (!executed) {
if (preResultListeners != null) {
for (Object preResultListener : preResultListeners) {
PreResultListener listener = (PreResultListener) preResultListener;
String _profileKey = "preResultListener: ";
try {
UtilTimerStack.push(_profileKey);
listener.beforeResult(this, resultCode);
}
finally {
UtilTimerStack.pop(_profileKey);
}
}
}
// 当执行完了用户的indexAction,开始对result进行处理
if (proxy.getExecuteResult()) {
executeResult();
}
executed = true;
}
return resultCode;
}
finally {
UtilTimerStack.pop(profileKey);
}
}
|
我们看一下invokeActionOnly的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
public String invokeActionOnly() throws Exception {
return invokeAction(getAction(), proxy.getConfig());
}
public Object getAction() {
// 拿到我们的indexAction对象
return action;
}
protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
// 得到indexAction的执行方法名
String methodName = proxy.getMethod();
if (LOG.isDebugEnabled()) {
LOG.debug("Executing action method = " + actionConfig.getMethodName());
}
String timerKey = "invokeAction: " + proxy.getActionName();
try {
UtilTimerStack.push(timerKey);
boolean methodCalled = false;
Object methodResult = null;
Method method = null;
try {
// 得到method对象
method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY);
} catch (NoSuchMethodException e) {
// hmm -- OK, try doXxx instead
try {
String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
// 额。。看懂了吧? 这又是一个风骚的操作,带有do的方法名
method = getAction().getClass().getMethod(altMethodName, EMPTY_CLASS_ARRAY);
} catch (NoSuchMethodException e1) {
// well, give the unknown handler a shot
if (unknownHandlerManager.hasUnknownHandlers()) {
try {
methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
methodCalled = true;
} catch (NoSuchMethodException e2) {
// throw the original one
throw e;
}
} else {
throw e;
}
}
}
if (!methodCalled) {
// 用java的反射,调用indexAction的方法,得到result结果
methodResult = method.invoke(action, new Object[0]);
}
// 这里扩展了返回结果的类型,具体可以看struts-default.xml中的配置, 好多好多result扩展类型
if (methodResult instanceof Result) {
this.explicitResult = (Result) methodResult;
// Wire the result automatically
container.inject(explicitResult);
return null;
} else {
// 返回普通的string结果
return (String) methodResult;
}
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
} catch (InvocationTargetException e) {
// We try to return the source exception.
Throwable t = e.getTargetException();
if (actionEventListener != null) {
String result = actionEventListener.handleException(t, getStack());
if (result != null) {
return result;
}
}
if (t instanceof Exception) {
throw (Exception) t;
} else {
throw e;
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
|
好了,分析完了action的执行,我们看一下结果处理,回顾一下
1
2
3
4
5
6
7
8
|
resultCode = invokeActionOnly();
// 省略n行代码
if (proxy.getExecuteResult()) {
executeResult();
}
|
看一下executeResult的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
/**
* Uses getResult to get the final Result and executes it
*
* @throws ConfigurationException If not result can be found with the returned code
*/
private void executeResult() throws Exception {
// 创建ServletDispchterResult对象,
// 通过配置文件获取result配置信息,查找到location
// 设置ServletDispchterResult对象的location为配置文件获得location
result = createResult();
String timerKey = "executeResult: " + getResultCode();
try {
UtilTimerStack.push(timerKey);
if (result != null) {
// 执行result
// 最终还是调用的ServletDispchterResult的doExecute方法
// 下方我们看一下这个方法的具体实现
result.execute(this);
} else if (resultCode != null && !Action.NONE.equals(resultCode)) {
throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()
+ " and result " + getResultCode(), proxy.getConfig());
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("No result returned for action " + getAction().getClass().getName() + " at " + proxy.getConfig().getLocation());
}
}
} finally {
UtilTimerStack.pop(timerKey);
}
}
|
ServletDispchterResult的doExecute方法的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
// 加入我们的配置文件里的location是:/WEB-INF/jsp/index.jsp
public void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
if (LOG.isDebugEnabled()) {
LOG.debug("Forwarding to location " + finalLocation);
}
PageContext pageContext = ServletActionContext.getPageContext();
if (pageContext != null) {
pageContext.include(finalLocation);
} else {
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
// 将配置文件中的location传递给系统Servlet的getRequestDispatcher方法
// 关于Servlet的getRequestDispatcher方法,参考[3]
// 这里仅是构造dispatcher,(这是java的servlet的编程, 非struts2)
RequestDispatcher dispatcher = request.getRequestDispatcher(finalLocation);
//add parameters passed on the location to #parameters
// see WW-2120
if (invocation != null && finalLocation != null && finalLocation.length() > 0
&& finalLocation.indexOf("?") > 0) {
String queryString = finalLocation.substring(finalLocation.indexOf("?") + 1);
Map parameters = (Map) invocation.getInvocationContext().getContextMap().get("parameters");
Map queryParams = UrlHelper.parseQueryString(queryString, true);
if (queryParams != null && !queryParams.isEmpty())
parameters.putAll(queryParams);
}
// if the view doesn't exist, let's do a 404
if (dispatcher == null) {
response.sendError(404, "result '" + finalLocation + "' not found");
return;
}
//if we are inside an action tag, we always need to do an include
Boolean insideActionTag = (Boolean) ObjectUtils.defaultIfNull(request.getAttribute(StrutsStatics.STRUTS_ACTION_TAG_INVOCATION), Boolean.FALSE);
// If we're included, then include the view
// Otherwise do forward
// This allow the page to, for example, set content type
if (!insideActionTag && !response.isCommitted() && (request.getAttribute("javax.servlet.include.servlet_path") == null)) {
request.setAttribute("struts.view_uri", finalLocation);
request.setAttribute("struts.request_uri", request.getRequestURI());
// 转发到 /WEB-INF/jsp/index.jsp ,由jsp进行相应操作
dispatcher.forward(request, response);
} else {
dispatcher.include(request, response);
}
}
}
|
目前我们已经分析完成Action的执行和Result处理的所有流程,最后可以看到Result结果处理使用的是Servlet的API,具体可以参考[4].
下一篇我们分析一下Struts2的标签库
参考
- S2-016远程代码执行漏洞分析
- struts2实现的简单的Trie树
- getRequestDispatcher 和sendRedirect区别及路径问题
- Servlet中关于RequestDispatcher的原理