1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework. Copyright (C) 1999-2015, QOS.ch. All rights
3    * reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under either the terms of the Eclipse Public License
6    * v1.0 as published by the Eclipse Foundation
7    *
8    * or (per the licensee's choosing)
9    *
10   * under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation.
11   */
12  package ch.qos.logback.access.common.spi;
13  
14  import ch.qos.logback.access.common.AccessConstants;
15  import ch.qos.logback.access.common.pattern.AccessConverter;
16  import ch.qos.logback.access.common.servlet.Util;
17  import ch.qos.logback.core.Context;
18  import ch.qos.logback.core.spi.SequenceNumberGenerator;
19  
20  import jakarta.servlet.http.Cookie;
21  import jakarta.servlet.http.HttpServletRequest;
22  import jakarta.servlet.http.HttpServletResponse;
23  import jakarta.servlet.http.HttpSession;
24  
25  import java.io.Serializable;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Enumeration;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.TreeMap;
33  import java.util.Vector;
34  
35  // Contributors:  Joern Huxhorn (see also bug #110)
36  
37  /**
38   * <p>This class is the implementation of the {@link IAccessEvent} interface.</p>
39   *
40   * <p>Much of the processing done by the logack-access project revolves around this class.</p>
41   *
42   * @author Ceki G&uuml;lc&uuml;
43   * @author S&eacute;bastien Pennec
44   */
45  public class AccessEvent implements Serializable, IAccessEvent {
46  
47      private static final String[] NA_STRING_ARRAY = new String[] { NA };
48  
49      private static final long serialVersionUID = 866718993618836343L;
50  
51      private static final String EMPTY = "";
52  
53      private transient final HttpServletRequest httpRequest;
54      private transient final HttpServletResponse httpResponse;
55  
56      String queryString;
57      String requestURI;
58      String requestURL;
59      String remoteHost;
60      String remoteUser;
61      String remoteAddr;
62      String threadName;
63      String protocol;
64      String method;
65      String serverName;
66      String requestContent;
67      String responseContent;
68      String sessionID;
69      long elapsedTime;
70  
71      Map<String, String> requestHeaderMap;
72      Map<String, String[]> requestParameterMap;
73      Map<String, String> responseHeaderMap;
74      Map<String, Object> attributeMap;
75  
76      List<Cookie> cookieList;
77  
78      long contentLength = SENTINEL;
79      int statusCode = SENTINEL;
80      int localPort = SENTINEL;
81  
82      transient ServerAdapter serverAdapter;
83  
84      /**
85       * The number of milliseconds elapsed from 1/1/1970 until logging event was created.
86       */
87      private long timeStamp = 0;
88  
89      private long sequenceNumber = 0;
90  
91      public AccessEvent(Context context, HttpServletRequest httpRequest, HttpServletResponse httpResponse,
92              ServerAdapter adapter) {
93          this.httpRequest = httpRequest;
94          this.httpResponse = httpResponse;
95          this.timeStamp = System.currentTimeMillis();
96  
97          SequenceNumberGenerator sng = context.getSequenceNumberGenerator();
98          if (sng != null) {
99              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 }