001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.access.spi;
015
016import ch.qos.logback.access.AccessConstants;
017import ch.qos.logback.access.pattern.AccessConverter;
018import ch.qos.logback.access.servlet.Util;
019import ch.qos.logback.core.Context;
020import ch.qos.logback.core.spi.SequenceNumberGenerator;
021
022import javax.servlet.http.Cookie;
023import javax.servlet.http.HttpServletRequest;
024import javax.servlet.http.HttpServletResponse;
025import javax.servlet.http.HttpSession;
026
027import java.io.Serializable;
028import java.util.ArrayList;
029import java.util.Enumeration;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033import java.util.TreeMap;
034import java.util.Vector;
035
036// Contributors:  Joern Huxhorn (see also bug #110)
037
038/**
039 * The Access module's internal representation of logging events. When the
040 * logging component instance is called in the container to log then a
041 * <code>AccessEvent</code> instance is created. This instance is passed around
042 * to the different logback components.
043 *
044 * @author Ceki G&uuml;lc&uuml;
045 * @author S&eacute;bastien Pennec
046 */
047public class AccessEvent implements Serializable, IAccessEvent {
048
049    private static final String[] NA_STRING_ARRAY = new String[] { NA };
050
051    private static final long serialVersionUID = 866718993618836343L;
052
053    private static final String EMPTY = "";
054
055    private transient final HttpServletRequest httpRequest;
056    private transient final HttpServletResponse httpResponse;
057
058    String queryString;
059    String requestURI;
060    String requestURL;
061    String remoteHost;
062    String remoteUser;
063    String remoteAddr;
064    String threadName;
065    String protocol;
066    String method;
067    String serverName;
068    String requestContent;
069    String responseContent;
070    String sessionID;
071    long elapsedTime;
072
073    Map<String, String> requestHeaderMap;
074    Map<String, String[]> requestParameterMap;
075    Map<String, String> responseHeaderMap;
076    Map<String, Object> attributeMap;
077
078    long contentLength = SENTINEL;
079    int statusCode = SENTINEL;
080    int localPort = SENTINEL;
081
082    transient ServerAdapter serverAdapter;
083
084    /**
085     * The number of milliseconds elapsed from 1/1/1970 until logging event was
086     * created.
087     */
088    private long timeStamp = 0;
089
090    private long sequenceNumber = 0;
091
092    public AccessEvent(Context context, HttpServletRequest httpRequest, HttpServletResponse httpResponse,
093            ServerAdapter adapter) {
094        this.httpRequest = httpRequest;
095        this.httpResponse = httpResponse;
096        this.timeStamp = System.currentTimeMillis();
097
098        SequenceNumberGenerator sng = context.getSequenceNumberGenerator();
099        if (sng != null) {
100            this.sequenceNumber = sng.nextSequenceNumber();
101        }
102        this.serverAdapter = adapter;
103        this.elapsedTime = calculateElapsedTime();
104    }
105
106    /**
107     * Returns the underlying HttpServletRequest. After serialization the returned
108     * value will be null.
109     *
110     * @return
111     */
112    @Override
113    public HttpServletRequest getRequest() {
114        return httpRequest;
115    }
116
117    /**
118     * Returns the underlying HttpServletResponse. After serialization the returned
119     * value will be null.
120     *
121     * @return
122     */
123    @Override
124    public HttpServletResponse getResponse() {
125        return httpResponse;
126    }
127
128    @Override
129    public long getTimeStamp() {
130        return timeStamp;
131    }
132
133    public void setTimeStamp(long timeStamp) {
134        if (this.timeStamp != 0) {
135            throw new IllegalStateException("timeStamp has been already set for this event.");
136        } else {
137            this.timeStamp = timeStamp;
138        }
139    }
140
141    public long getSequenceNumber() {
142        return sequenceNumber;
143    }
144
145    public void setSequenceNumber(long sequenceNumber) {
146        this.sequenceNumber = sequenceNumber;
147    }
148
149    /**
150     * @param threadName The threadName to set.
151     */
152    public void setThreadName(String threadName) {
153        this.threadName = threadName;
154    }
155
156    @Override
157    public String getThreadName() {
158        return threadName == null ? NA : threadName;
159    }
160
161    @Override
162    public String getRequestURI() {
163        if (requestURI == null) {
164            if (httpRequest != null) {
165                requestURI = httpRequest.getRequestURI();
166            } else {
167                requestURI = NA;
168            }
169        }
170        return requestURI;
171    }
172
173    @Override
174    public String getQueryString() {
175        if (queryString == null) {
176            if (httpRequest != null) {
177                StringBuilder buf = new StringBuilder();
178                final String qStr = httpRequest.getQueryString();
179                if (qStr != null) {
180                    buf.append(AccessConverter.QUESTION_CHAR);
181                    buf.append(qStr);
182                }
183                queryString = buf.toString();
184            } else {
185                queryString = NA;
186            }
187        }
188        return queryString;
189    }
190
191    /**
192     * The first line of the request.
193     */
194    @Override
195    public String getRequestURL() {
196        if (requestURL == null) {
197            if (httpRequest != null) {
198                StringBuilder buf = new StringBuilder();
199                buf.append(httpRequest.getMethod());
200                buf.append(AccessConverter.SPACE_CHAR);
201                buf.append(httpRequest.getRequestURI());
202                buf.append(getQueryString());
203                buf.append(AccessConverter.SPACE_CHAR);
204                buf.append(httpRequest.getProtocol());
205                requestURL = buf.toString();
206            } else {
207                requestURL = NA;
208            }
209        }
210        return requestURL;
211    }
212
213    @Override
214    public String getRemoteHost() {
215        if (remoteHost == null) {
216            if (httpRequest != null) {
217                // the underlying implementation of HttpServletRequest will
218                // determine if remote lookup will be performed
219                remoteHost = httpRequest.getRemoteHost();
220            } else {
221                remoteHost = NA;
222            }
223        }
224        return remoteHost;
225    }
226
227    @Override
228    public String getRemoteUser() {
229        if (remoteUser == null) {
230            if (httpRequest != null) {
231                remoteUser = httpRequest.getRemoteUser();
232            } else {
233                remoteUser = NA;
234            }
235        }
236        return remoteUser;
237    }
238
239    @Override
240    public String getProtocol() {
241        if (protocol == null) {
242            if (httpRequest != null) {
243                protocol = httpRequest.getProtocol();
244            } else {
245                protocol = NA;
246            }
247        }
248        return protocol;
249    }
250
251    @Override
252    public String getMethod() {
253        if (method == null) {
254            if (httpRequest != null) {
255                method = httpRequest.getMethod();
256            } else {
257                method = NA;
258            }
259        }
260        return method;
261    }
262
263    @Override
264    public String getSessionID() {
265        if (sessionID == null) {
266            if (httpRequest != null) {
267                final HttpSession session = httpRequest.getSession(false);
268                if (session != null) {
269                    sessionID = session.getId();
270                }
271            } else {
272                sessionID = NA;
273            }
274        }
275        return sessionID;
276    }
277
278    @Override
279    public String getServerName() {
280        if (serverName == null) {
281            if (httpRequest != null) {
282                serverName = httpRequest.getServerName();
283            } else {
284                serverName = NA;
285            }
286        }
287        return serverName;
288    }
289
290    @Override
291    public String getRemoteAddr() {
292        if (remoteAddr == null) {
293            if (httpRequest != null) {
294                remoteAddr = httpRequest.getRemoteAddr();
295            } else {
296                remoteAddr = NA;
297            }
298        }
299        return remoteAddr;
300    }
301
302    @Override
303    public String getRequestHeader(String key) {
304        String result = null;
305        key = key.toLowerCase();
306        if (requestHeaderMap == null) {
307            if (httpRequest != null) {
308                buildRequestHeaderMap();
309                result = requestHeaderMap.get(key);
310            }
311        } else {
312            result = requestHeaderMap.get(key);
313        }
314
315        if (result != null) {
316            return result;
317        } else {
318            return NA;
319        }
320    }
321
322    @Override
323    public Enumeration<String> getRequestHeaderNames() {
324        // post-serialization
325        if (httpRequest == null) {
326            Vector<String> list = new Vector<String>(getRequestHeaderMap().keySet());
327            return list.elements();
328        }
329        return httpRequest.getHeaderNames();
330    }
331
332    @Override
333    public Map<String, String> getRequestHeaderMap() {
334        if (requestHeaderMap == null) {
335            buildRequestHeaderMap();
336        }
337        return requestHeaderMap;
338    }
339
340    public void buildRequestHeaderMap() {
341        // according to RFC 2616 header names are case-insensitive
342        // latest versions of Tomcat return header names in lower-case
343        requestHeaderMap = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
344        Enumeration<String> e = httpRequest.getHeaderNames();
345        if (e == null) {
346            return;
347        }
348        while (e.hasMoreElements()) {
349            String key = e.nextElement();
350            requestHeaderMap.put(key, httpRequest.getHeader(key));
351        }
352    }
353
354    public void buildRequestParameterMap() {
355        requestParameterMap = new HashMap<String, String[]>();
356        try {
357            Enumeration<String> e = httpRequest.getParameterNames();
358            if (e == null) {
359                return;
360            }
361            while (e.hasMoreElements()) {
362                String key = e.nextElement();
363                requestParameterMap.put(key, httpRequest.getParameterValues(key));
364            }
365        } catch(Throwable t) {
366            // The use of HttpServletRequest.getParameterNames() can cause
367            // a READ of the Request body content.  This can fail with various
368            // Throwable failures depending on the state of the Request
369            // at the time this method is called.
370            // We don't want to fail the logging due to these types of requests
371            t.printStackTrace();
372        }
373    }
374
375    @Override
376    public Map<String, String[]> getRequestParameterMap() {
377        if (requestParameterMap == null) {
378            buildRequestParameterMap();
379        }
380        return requestParameterMap;
381    }
382
383    @Override
384    public String getAttribute(String key) {
385        Object value = null;
386        if (attributeMap != null) {
387            // Event was prepared for deferred processing so we have a copy of attribute map
388            // and must use that copy
389            value = attributeMap.get(key);
390        } else if (httpRequest != null) {
391            // We have original request so take attribute from it
392            value = httpRequest.getAttribute(key);
393        }
394
395        return value != null ? value.toString() : NA;
396    }
397
398    private void copyAttributeMap() {
399
400        if (httpRequest == null) {
401            return;
402        }
403
404        // attributeMap has been copied already. See also LOGBACK-1189
405        if (attributeMap != null) {
406            return;
407        }
408
409        attributeMap = new HashMap<String, Object>();
410
411        Enumeration<String> names = httpRequest.getAttributeNames();
412        while (names.hasMoreElements()) {
413            String name = names.nextElement();
414
415            Object value = httpRequest.getAttribute(name);
416            if (shouldCopyAttribute(name, value)) {
417                attributeMap.put(name, value);
418            }
419        }
420    }
421
422    private boolean shouldCopyAttribute(String name, Object value) {
423        if (AccessConstants.LB_INPUT_BUFFER.equals(name) || AccessConstants.LB_OUTPUT_BUFFER.equals(name)) {
424            // Do not copy attributes used by logback internally - these are available via
425            // other getters anyway
426            return false;
427        } else if (value == null) {
428            // No reasons to copy nulls - Map.get() will return null for missing keys and
429            // the list of attribute
430            // names is not available through IAccessEvent
431            return false;
432        } else {
433            // Only copy what is serializable
434            return value instanceof Serializable;
435        }
436    }
437
438    @Override
439    public String[] getRequestParameter(String key) {
440        String[] value = null;
441
442        if (requestParameterMap != null) {
443            value = requestParameterMap.get(key);
444        } else if (httpRequest != null) {
445            value = httpRequest.getParameterValues(key);
446        }
447
448        return (value != null) ? value : NA_STRING_ARRAY;
449    }
450
451    @Override
452    public String getCookie(String key) {
453
454        if (httpRequest != null) {
455            Cookie[] cookieArray = httpRequest.getCookies();
456            if (cookieArray == null) {
457                return NA;
458            }
459
460            for (Cookie cookie : cookieArray) {
461                if (key.equals(cookie.getName())) {
462                    return cookie.getValue();
463                }
464            }
465        }
466        return NA;
467    }
468
469    @Override
470    public long getContentLength() {
471        if (contentLength == SENTINEL) {
472            if (httpResponse != null) {
473                contentLength = serverAdapter.getContentLength();
474                return contentLength;
475            }
476        }
477        return contentLength;
478    }
479
480    public int getStatusCode() {
481        if (statusCode == SENTINEL) {
482            if (httpResponse != null) {
483                statusCode = serverAdapter.getStatusCode();
484            }
485        }
486        return statusCode;
487    }
488
489    public long getElapsedSeconds() {
490        return elapsedTime < 0 ? elapsedTime : elapsedTime / 1000;
491    }
492
493    public long getElapsedTime() {
494        return elapsedTime;
495    }
496
497    private long calculateElapsedTime() {
498        if (serverAdapter.getRequestTimestamp() < 0) {
499            return -1;
500        }
501        return getTimeStamp() - serverAdapter.getRequestTimestamp();
502    }
503
504    public String getRequestContent() {
505        if (requestContent != null) {
506            return requestContent;
507        }
508
509        if (Util.isFormUrlEncoded(httpRequest)) {
510            StringBuilder buf = new StringBuilder();
511
512            try {
513                Enumeration<String> pramEnumeration = httpRequest.getParameterNames();
514
515                // example: id=1234&user=cgu
516                // number=1233&x=1
517                int count = 0;
518                while (pramEnumeration.hasMoreElements()) {
519
520                    String key = pramEnumeration.nextElement();
521                    if (count++ != 0) {
522                        buf.append("&");
523                    }
524                    buf.append(key);
525                    buf.append("=");
526                    String val = httpRequest.getParameter(key);
527                    if (val != null) {
528                        buf.append(val);
529                    } else {
530                        buf.append("");
531                    }
532                }
533            } catch (Throwable t) {
534                // The use of HttpServletRequest.getParameterNames() and
535                // HttpServletRequest.getParameter(String) can cause
536                // a READ of the Request body content.  This can fail with various
537                // Throwable failures depending on the state of the Request
538                // at the time this method is called.
539                // We don't want to fail the logging due to these types of requests
540                t.printStackTrace();
541            }
542            requestContent = buf.toString();
543        } else {
544            // retrieve the byte array placed by TeeFilter
545            byte[] inputBuffer = (byte[]) httpRequest.getAttribute(AccessConstants.LB_INPUT_BUFFER);
546
547            if (inputBuffer != null) {
548                requestContent = new String(inputBuffer);
549            }
550
551            if (requestContent == null || requestContent.length() == 0) {
552                requestContent = EMPTY;
553            }
554        }
555
556        return requestContent;
557    }
558
559    public String getResponseContent() {
560        if (responseContent != null) {
561            return responseContent;
562        }
563
564        if (Util.isImageResponse(httpResponse)) {
565            responseContent = "[IMAGE CONTENTS SUPPRESSED]";
566        } else {
567
568            // retrieve the byte array previously placed by TeeFilter
569            byte[] outputBuffer = (byte[]) httpRequest.getAttribute(AccessConstants.LB_OUTPUT_BUFFER);
570
571            if (outputBuffer != null) {
572                responseContent = new String(outputBuffer);
573            }
574            if (responseContent == null || responseContent.length() == 0) {
575                responseContent = EMPTY;
576            }
577        }
578
579        return responseContent;
580    }
581
582    public int getLocalPort() {
583        if (localPort == SENTINEL) {
584            if (httpRequest != null) {
585                localPort = httpRequest.getLocalPort();
586            }
587
588        }
589        return localPort;
590    }
591
592    public ServerAdapter getServerAdapter() {
593        return serverAdapter;
594    }
595
596    public String getResponseHeader(String key) {
597        buildResponseHeaderMap();
598        return responseHeaderMap.get(key);
599    }
600
601    void buildResponseHeaderMap() {
602        if (responseHeaderMap == null) {
603            responseHeaderMap = serverAdapter.buildResponseHeaderMap();
604        }
605    }
606
607    public Map<String, String> getResponseHeaderMap() {
608        buildResponseHeaderMap();
609        return responseHeaderMap;
610    }
611
612    public List<String> getResponseHeaderNameList() {
613        buildResponseHeaderMap();
614        return new ArrayList<String>(responseHeaderMap.keySet());
615    }
616
617    public void prepareForDeferredProcessing() {
618        getRequestHeaderMap();
619        getRequestParameterMap();
620        getResponseHeaderMap();
621        getLocalPort();
622        getMethod();
623        getProtocol();
624        getRemoteAddr();
625        getRemoteHost();
626        getRemoteUser();
627        getRequestURI();
628        getRequestURL();
629        getServerName();
630        getTimeStamp();
631        getElapsedTime();
632
633        getStatusCode();
634        getContentLength();
635        getRequestContent();
636        getResponseContent();
637
638        copyAttributeMap();
639    }
640}