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