<commit_before>/**
 * Copyright 2011-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.informantproject.plugin.servlet;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.informantproject.api.Optional;
import org.informantproject.api.PluginServices;
import org.informantproject.api.SpanDetail;
import org.informantproject.shaded.aspectj.lang.ProceedingJoinPoint;
import org.informantproject.shaded.aspectj.lang.annotation.AfterReturning;
import org.informantproject.shaded.aspectj.lang.annotation.Around;
import org.informantproject.shaded.aspectj.lang.annotation.Aspect;
import org.informantproject.shaded.aspectj.lang.annotation.Before;
import org.informantproject.shaded.aspectj.lang.annotation.Pointcut;
import org.informantproject.shaded.aspectj.lang.annotation.SuppressAjWarnings;

/**
 * Defines pointcuts and captures data on
 * {@link javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
 * and
 * {@link javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
 * calls.
 * 
 * By default only calls to the top-most Filter and to the top-most Servlet are captured.
 * "isWarnOnSpanOutsideTrace" core configuration property can be used to enable capturing of nested
 * Filters and nested Servlets as well.
 * 
 * This plugin is careful not to rely on request or session objects being thread-safe.
 * 
 * @author <NAME>
 * @since 0.5
 */
// TODO add support for async servlets (servlet 3.0)
@Aspect
@SuppressAjWarnings("adviceDidNotMatch")
public class ServletAspect {

    private static final ThreadLocal<ServletSpanDetail> topLevelServletSpanDetail =
            new ThreadLocal<ServletSpanDetail>();
    private static final PluginServices pluginServices = PluginServices
            .get("org.informantproject.plugins:servlet-plugin");

    @Pointcut("if()")
    public static boolean isPluginEnabled() {
        return pluginServices.isEnabled();
    }

    @Pointcut("if()")
    public static boolean inTrace() {
        return pluginServices.getRootSpanDetail() != null;
    }

    @Pointcut("execution(void javax.servlet.Filter.doFilter(javax.servlet.ServletRequest,"
            + " javax.servlet.ServletResponse, javax.servlet.FilterChain))")
    void filterPointcut() {}

    @Pointcut("execution(void javax.servlet.Servlet.service(javax.servlet.ServletRequest,"
            + " javax.servlet.ServletResponse)) || execution(void javax.servlet.http.HttpServlet"
            + ".do*(javax.servlet.http.HttpServletRequest,"
            + " javax.servlet.http.HttpServletResponse))")
    void servletPointcut() {}

    @Pointcut("(filterPointcut() || servletPointcut()) && !cflowbelow(filterPointcut())"
            + " && !cflowbelow(servletPointcut())")
    void topLevelPointcut() {}

    @Around("isPluginEnabled() && topLevelPointcut() && args(realRequest, ..)")
    public void httpRequestSpanMarker(ProceedingJoinPoint joinPoint, Object realRequest)
            throws Throwable {

        HttpServletRequest request = HttpServletRequest.from(realRequest);
        // capture more expensive data (request parameter map and session info) for the top
        // level filter/servlet pointcut
        // request parameter map is collected in afterReturningRequestGetParameterPointcut()
        // session info is collected here if the request already has a session
        ServletSpanDetail spanDetail;
        // passing "false" so it won't create a session if the request doesn't already have one
        HttpSession session = request.getSession(false);
        if (session == null) {
            spanDetail = new ServletSpanDetail(request.getMethod(), request.getRequestURI(),
                    Optional.absent(String.class), null, null);
        } else {
            Optional<String> sessionUsernameAttributePath = ServletPluginPropertyUtils
                    .getSessionUsernameAttributePath();
            Optional<String> username = Optional.absent();
            if (sessionUsernameAttributePath.isPresent()) {
                username = getSessionAttributeTextValue(session, sessionUsernameAttributePath
                        .get());
            }
            spanDetail = new ServletSpanDetail(request.getMethod(), request.getRequestURI(),
                    username, session.getId(), getSessionAttributes(session));
        }
        topLevelServletSpanDetail.set(spanDetail);
        try {
            pluginServices.executeRootSpan("http request", spanDetail, joinPoint);
        } finally {
            topLevelServletSpanDetail.set(null);
        }
    }

    /*
     * ================== Http Servlet Request Parameters ==================
     */

    @Pointcut("call(* javax.servlet.ServletRequest.getParameter*(..))")
    void requestGetParameterPointcut() {}

    @AfterReturning("isPluginEnabled() && inTrace() && requestGetParameterPointcut()"
            + " && !cflowbelow(requestGetParameterPointcut()) && target(realRequest)"
            + " && !within(org.informantproject.plugin.servlet.ServletAspect)")
    public void afterReturningRequestGetParameterPointcut(Object realRequest) {
        HttpServletRequest request = HttpServletRequest.from(realRequest);
        // only now is it safe to get parameters (if parameters are retrieved before this, it could
        // prevent a servlet from choosing to read the underlying stream instead of using the
        // getParameter* methods) see SRV.3.1.1 "When Parameters Are Available"
        ServletSpanDetail spanDetail = topLevelServletSpanDetail.get();
        if (spanDetail != null && !spanDetail.isRequestParameterMapCaptured()) {
            // this request is being traced and the request parameter map hasn't been captured yet
            spanDetail.captureRequestParameterMap(request.getParameterMap());
        }
    }

    /*
     * ================== Http Session Attributes ==================
     */

    @Pointcut("call(javax.servlet.http.HttpSession"
            + " javax.servlet.http.HttpServletRequest.getSession(..))")
    void requestGetSessionPointcut() {}

    @AfterReturning(pointcut = "isPluginEnabled() && inTrace() && requestGetSessionPointcut()"
            + " && !cflowbelow(requestGetSessionPointcut())", returning = "realSession")
    public void afterReturningRequestGetSession(Object realSession) {
        HttpSession session = HttpSession.from(realSession);
        // either getSession(), getSession(true) or getSession(false) has triggered this pointcut
        // after calls to the first two, a new session may have been created
        // (the third one could be ignored but is harmless)
        ServletSpanDetail spanDetail = topLevelServletSpanDetail.get();
        if (spanDetail != null && session != null && session.isNew()) {
            spanDetail.setSessionIdUpdatedValue(session.getId());
        }
    }
    @Pointcut("call(void javax.servlet.http.HttpSession.invalidate())")
    void sessionInvalidatePointcut() {}

    @Before("isPluginEnabled() && inTrace() && sessionInvalidatePointcut()"
            + " && !cflowbelow(sessionInvalidatePointcut()) && target(realSession)")
    public void beforeSessionInvalidatePointcut(Object realSession) {
        HttpSession session = HttpSession.from(realSession);
        ServletSpanDetail spanDetail = getRootServletSpanDetail(session);
        if (spanDetail != null) {
            spanDetail.setSessionIdUpdatedValue("");
        }
    }

    // TODO support deprecated HttpSession.putValue()

    @Pointcut("call(void javax.servlet.http.HttpSession.setAttribute(String, Object))")
    void sessionSetAttributePointcut() {}

    @AfterReturning("isPluginEnabled() && inTrace() && sessionSetAttributePointcut()"
            + " && !cflowbelow(sessionSetAttributePointcut()) && target(realSession)"
            + " && args(name, value)")
    public void afterReturningSessionSetAttributePointcut(Object realSession, String name,
            Object value) {

        HttpSession session = HttpSession.from(realSession);
        // name is non-null per HttpSession.setAttribute() javadoc, but value may be null (which per
        // the javadoc is the same as calling removeAttribute()
        ServletSpanDetail spanDetail = getRootServletSpanDetail(session);
        if (spanDetail != null) {
            updateUsernameIfApplicable(spanDetail, name, value, session);
            updateSessionAttributesIfApplicable(spanDetail, name, value, session);
        }
    }

    @Pointcut("call(void javax.servlet.http.HttpSession.removeAttribute(String))")
    void sessionRemoveAttributePointcut() {}

    @AfterReturning("isPluginEnabled() && inTrace() && sessionRemoveAttributePointcut()"
            + " && !cflowbelow(sessionRemoveAttributePointcut()) && target(realSession)"
            + " && args(name)")
    public void afterReturningSessionRemoveAttributePointcut(Object realSession, String name) {
        // calling HttpSession.setAttribute() with null value is the same as calling
        // removeAttribute(), per the setAttribute() javadoc
        afterReturningSessionSetAttributePointcut(realSession, name, null);
    }

    private void updateUsernameIfApplicable(ServletSpanDetail spanDetail, String name,
            Object value,
            HttpSession session) {

        if (value == null) {
            // if username value is set to null, don't clear it
            return;
        }
        Optional<String> sessionUsernameAttributePath = ServletPluginPropertyUtils
                .getSessionUsernameAttributePath();
        if (sessionUsernameAttributePath.isPresent()) {
            if (sessionUsernameAttributePath.get().equals(name)) {
                // it's unlikely, but possible, that toString() returns null
                spanDetail.setUsername(Optional.fromNullable(value.toString()));
            } else if (sessionUsernameAttributePath.get().startsWith(name + ".")) {<commit_msg>inTrace() isn't needed in pointcut anymore<commit_after>/**
 * Copyright 2011-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.informantproject.plugin.servlet;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.informantproject.api.Optional;
import org.informantproject.api.PluginServices;
import org.informantproject.api.SpanDetail;
import org.informantproject.shaded.aspectj.lang.ProceedingJoinPoint;
import org.informantproject.shaded.aspectj.lang.annotation.AfterReturning;
import org.informantproject.shaded.aspectj.lang.annotation.Around;
import org.informantproject.shaded.aspectj.lang.annotation.Aspect;
import org.informantproject.shaded.aspectj.lang.annotation.Before;
import org.informantproject.shaded.aspectj.lang.annotation.Pointcut;
import org.informantproject.shaded.aspectj.lang.annotation.SuppressAjWarnings;

/**
 * Defines pointcuts and captures data on
 * {@link javax.servlet.http.HttpServlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
 * and
 * {@link javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
 * calls.
 * 
 * By default only calls to the top-most Filter and to the top-most Servlet are captured.
 * "isWarnOnSpanOutsideTrace" core configuration property can be used to enable capturing of nested
 * Filters and nested Servlets as well.
 * 
 * This plugin is careful not to rely on request or session objects being thread-safe.
 * 
 * @author <NAME>
 * @since 0.5
 */
// TODO add support for async servlets (servlet 3.0)
@Aspect
@SuppressAjWarnings("adviceDidNotMatch")
public class ServletAspect {

    private static final ThreadLocal<ServletSpanDetail> topLevelServletSpanDetail =
            new ThreadLocal<ServletSpanDetail>();
    private static final PluginServices pluginServices = PluginServices
            .get("org.informantproject.plugins:servlet-plugin");

    @Pointcut("if()")
    public static boolean isPluginEnabled() {
        return pluginServices.isEnabled();
    }

    @Pointcut("execution(void javax.servlet.Filter.doFilter(javax.servlet.ServletRequest,"
            + " javax.servlet.ServletResponse, javax.servlet.FilterChain))")
    void filterPointcut() {}

    @Pointcut("execution(void javax.servlet.Servlet.service(javax.servlet.ServletRequest,"
            + " javax.servlet.ServletResponse)) || execution(void javax.servlet.http.HttpServlet"
            + ".do*(javax.servlet.http.HttpServletRequest,"
            + " javax.servlet.http.HttpServletResponse))")
    void servletPointcut() {}

    @Pointcut("(filterPointcut() || servletPointcut()) && !cflowbelow(filterPointcut())"
            + " && !cflowbelow(servletPointcut())")
    void topLevelPointcut() {}

    @Around("isPluginEnabled() && topLevelPointcut() && args(realRequest, ..)")
    public void httpRequestSpanMarker(ProceedingJoinPoint joinPoint, Object realRequest)
            throws Throwable {

        HttpServletRequest request = HttpServletRequest.from(realRequest);
        // capture more expensive data (request parameter map and session info) for the top
        // level filter/servlet pointcut
        // request parameter map is collected in afterReturningRequestGetParameterPointcut()
        // session info is collected here if the request already has a session
        ServletSpanDetail spanDetail;
        // passing "false" so it won't create a session if the request doesn't already have one
        HttpSession session = request.getSession(false);
        if (session == null) {
            spanDetail = new ServletSpanDetail(request.getMethod(), request.getRequestURI(),
                    Optional.absent(String.class), null, null);
        } else {
            Optional<String> sessionUsernameAttributePath = ServletPluginPropertyUtils
                    .getSessionUsernameAttributePath();
            Optional<String> username = Optional.absent();
            if (sessionUsernameAttributePath.isPresent()) {
                username = getSessionAttributeTextValue(session, sessionUsernameAttributePath
                        .get());
            }
            spanDetail = new ServletSpanDetail(request.getMethod(), request.getRequestURI(),
                    username, session.getId(), getSessionAttributes(session));
        }
        topLevelServletSpanDetail.set(spanDetail);
        try {
            pluginServices.executeRootSpan("http request", spanDetail, joinPoint);
        } finally {
            topLevelServletSpanDetail.set(null);
        }
    }

    /*
     * ================== Http Servlet Request Parameters ==================
     */

    @Pointcut("call(* javax.servlet.ServletRequest.getParameter*(..))")
    void requestGetParameterPointcut() {}

    @AfterReturning("isPluginEnabled() && requestGetParameterPointcut()"
            + " && !cflowbelow(requestGetParameterPointcut()) && target(realRequest)"
            + " && !within(org.informantproject.plugin.servlet.ServletAspect)")
    public void afterReturningRequestGetParameterPointcut(Object realRequest) {
        HttpServletRequest request = HttpServletRequest.from(realRequest);
        // only now is it safe to get parameters (if parameters are retrieved before this, it could
        // prevent a servlet from choosing to read the underlying stream instead of using the
        // getParameter* methods) see SRV.3.1.1 "When Parameters Are Available"
        ServletSpanDetail spanDetail = topLevelServletSpanDetail.get();
        if (spanDetail != null && !spanDetail.isRequestParameterMapCaptured()) {
            // this request is being traced and the request parameter map hasn't been captured yet
            spanDetail.captureRequestParameterMap(request.getParameterMap());
        }
    }

    /*
     * ================== Http Session Attributes ==================
     */

    @Pointcut("call(javax.servlet.http.HttpSession"
            + " javax.servlet.http.HttpServletRequest.getSession(..))")
    void requestGetSessionPointcut() {}

    @AfterReturning(pointcut = "isPluginEnabled() && requestGetSessionPointcut()"
            + " && !cflowbelow(requestGetSessionPointcut())", returning = "realSession")
    public void afterReturningRequestGetSession(Object realSession) {
        HttpSession session = HttpSession.from(realSession);
        // either getSession(), getSession(true) or getSession(false) has triggered this pointcut
        // after calls to the first two, a new session may have been created
        // (the third one could be ignored but is harmless)
        ServletSpanDetail spanDetail = topLevelServletSpanDetail.get();
        if (spanDetail != null && session != null && session.isNew()) {
            spanDetail.setSessionIdUpdatedValue(session.getId());
        }
    }
    @Pointcut("call(void javax.servlet.http.HttpSession.invalidate())")
    void sessionInvalidatePointcut() {}

    @Before("isPluginEnabled() && sessionInvalidatePointcut()"
            + " && !cflowbelow(sessionInvalidatePointcut()) && target(realSession)")
    public void beforeSessionInvalidatePointcut(Object realSession) {
        HttpSession session = HttpSession.from(realSession);
        ServletSpanDetail spanDetail = getRootServletSpanDetail(session);
        if (spanDetail != null) {
            spanDetail.setSessionIdUpdatedValue("");
        }
    }

    // TODO support deprecated HttpSession.putValue()

    @Pointcut("call(void javax.servlet.http.HttpSession.setAttribute(String, Object))")
    void sessionSetAttributePointcut() {}

    @AfterReturning("isPluginEnabled() && sessionSetAttributePointcut()"
            + " && !cflowbelow(sessionSetAttributePointcut()) && target(realSession)"
            + " && args(name, value)")
    public void afterReturningSessionSetAttributePointcut(Object realSession, String name,
            Object value) {

        HttpSession session = HttpSession.from(realSession);
        // name is non-null per HttpSession.setAttribute() javadoc, but value may be null (which per
        // the javadoc is the same as calling removeAttribute()
        ServletSpanDetail spanDetail = getRootServletSpanDetail(session);
        if (spanDetail != null) {
            updateUsernameIfApplicable(spanDetail, name, value, session);
            updateSessionAttributesIfApplicable(spanDetail, name, value, session);
        }
    }

    @Pointcut("call(void javax.servlet.http.HttpSession.removeAttribute(String))")
    void sessionRemoveAttributePointcut() {}

    @AfterReturning("isPluginEnabled() && sessionRemoveAttributePointcut()"
            + " && !cflowbelow(sessionRemoveAttributePointcut()) && target(realSession)"
            + " && args(name)")
    public void afterReturningSessionRemoveAttributePointcut(Object realSession, String name) {
        // calling HttpSession.setAttribute() with null value is the same as calling
        // removeAttribute(), per the setAttribute() javadoc
        afterReturningSessionSetAttributePointcut(realSession, name, null);
    }

    private void updateUsernameIfApplicable(ServletSpanDetail spanDetail, String name,
            Object value,
            HttpSession session) {

        if (value == null) {
            // if username value is set to null, don't clear it
            return;
        }
        Optional<String> sessionUsernameAttributePath = ServletPluginPropertyUtils
                .getSessionUsernameAttributePath();
        if (sessionUsernameAttributePath.isPresent()) {
            if (sessionUsernameAttributePath.get().equals(name)) {
                // it's unlikely, but possible, that toString() returns null
                spanDetail.setUsername(Optional.fromNullable(value.toString()));
            } else if (sessionUsernameAttributePath.get().startsWith(name + ".")) {
{'fraction_non_alphanumeric': 0.10606784519827998, 'fraction_numerical': 0.001815575728619207, 'mean_word_length': 3.4600468783294267, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '833887', 'n_tokens_mistral': 5339, 'n_tokens_neox': 5174, 'n_words': 1310}